From: Kelly Dean <kelly@prtime.org>
To: Stefan Monnier <monnier@IRO.UMontreal.CA>
Cc: emacs-devel@gnu.org
Subject: [PATCH] (Updated) Run hook when variable is set
Date: Fri, 13 Feb 2015 23:08:21 +0000 [thread overview]
Message-ID: <MBg5mY7yfO03nBuk9R0OKNqMDmUD1YQ8MssrosC5NOv@local> (raw)
In-Reply-To: <jwvd25fdjh4.fsf-monnier+emacs@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 4470 bytes --]
Stefan Monnier wrote:
> We don't want to install this in emacs-24, so only the trunk (aka
> "master") code is important in this respect.
Understood. Last time I included the 24.4 patch just to ensure you could reproduce my benchmark results. (Due to some logistical issues, I can't run trunk on a system that has stable performance, so I can't reliably benchmark it.)
>> +typedef enum
>> + {
>> + Dyn_Unbind = -1,
>> + Dyn_Current = 0,
>> + Dyn_Bind = 1,
>> + Dyn_Skip = 2,
>> + Dyn_Global = 3
>> + } Dyn_Bind_Direction;
>
> In which sense is this a "direction"?
Originally I had just the first three values, and it was a direction in the sense of movement up or down the dynamic-binding stack. Later I discovered that I needed the last two values too. I've changed it to a more appropriate name.
> That's a good idea, to circumvent the question of how to not-trigger the
> hooked-p check recursively when the hook function calls the setter (tho
> the question partly remains, in case the hook function *accidentally*
> sets one of the hooked variables).
The docstring for symbol-setter-function says lexical binding is required for the hook function, which means its local variables won't trigger varhook. If the hook function does set a dynamic variable that's hooked, and has no terminating condition for the recursion, you'll immediately find out when it exceeds max-lisp-eval-depth. So, don't do that. ;-)
> It does mean that the hooks can't redirect the assignment elsewhere, but
> maybe it's a good thing anyway.
They still can. Just temporarily unhook the destination variable before setting it. But yes, it would be easy to introduce bugs by doing that, so I guess the documentation should discourage it.
>> + if (shadowed) env = Qdyn_local;
>> + else if (buf_local) env = Qbuf_local;
>> + else env = Qglobal;
>
> Why does the hook need to know about those different cases?
So the user can notice, during debugging, if setq is setting the symbol in a different environment than the one he intended, e.g. due to an unexpected buffer-local variable or due to a missing buffer-local variable, or due to an unexpected dynamic binding of a global variable in code that calls code that uses setq with the assumption that the global (not a dynamic local) variable will be set. Also, this enables detailed profiling of globals vs. buffer-locals vs. dynamic bindings. And it lets your hook function filter out the cases you want to ignore, e.g. if you only want to watch global settings, not buffer-local or let-local.
>> +DEFUN ("symbol-setter-function", Fsymbol_setter_function, Ssymbol_setter_function, 4, 4, 0,
>
> Hmm, no symbol-setter-function should be a variable (holding
> a function), modified via add-function/remove-function.
I don't see why; the only difference is using add-function with an unquoted variable name vs. using advice-add with a quoted function name. It also makes the hook run a bit slower. And it results in the help page for the function exposing the advice mechanism's internal representation of the advice, rather than cleanly showing e.g. ⌜:before advice: `mywatcher'⌝. That seems like a bad idea.
But anyway I changed it to what you want, IIUC. I hope I misunderstood.
> Also the docstring should not recommend :override (which should be
> a rather rare case, the more useful cases are probably :before
> and :around)
The docstring already says to use :before if you just need to watch variables, but not block or override attempts to set them. And :around is the same as :override in this case, since all the standard function does is return newval, which is overridden by either :around or :override; the standard function doesn't do any processing. Unless maybe you want to have multiple pieces of advice wrapped around each other, all blocking/overriding attempts to set variables?
Anyway I changed the docstring to recommend :around instead of :override.
I also made the other changes you wanted. And I cleaned up the patch a bit, consolidating set_internal and set_internal_1, so the extra name isn't needed anymore. (This is just a cosmetic change; it doesn't affect the compiled code, since one was just a wrapper for the other and both were inlined.) And I fixed a bug: it was reporting the wrong environment for setq-default if you do:
(setq-local foo 'bar)
(let ((foo 'baz)) (setq-default foo 'biz))
Updated patch attached.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: varhook-advice-1.patch --]
[-- Type: text/x-diff, Size: 31656 bytes --]
--- src/lisp.h
+++ src/lisp.h
@@ -305,6 +305,17 @@
#endif
+/* These are the masks for the constant_or_hooked field of Lisp_Symbol.
+ Bit 0 stores the constant field. Bit 1 stores the hooked field. */
+#define SYMBOL_CONSTANT_MASK 1
+#define SYMBOL_HOOKED_MASK 2
+
+# define SYM_CONSTANT_P(sym) (((sym)->constant_or_hooked) \
+ & SYMBOL_CONSTANT_MASK)
+# define SYM_HOOKED_P(sym) (((sym)->constant_or_hooked) \
+ & SYMBOL_HOOKED_MASK)
+
+
/* Some operations are so commonly executed that they are implemented
as macros, not functions, because otherwise runtime performance would
suffer too much when compiling with GCC without optimization.
@@ -359,7 +370,7 @@
#define lisp_h_NILP(x) EQ (x, Qnil)
#define lisp_h_SET_SYMBOL_VAL(sym, v) \
(eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value = (v))
-#define lisp_h_SYMBOL_CONSTANT_P(sym) (XSYMBOL (sym)->constant)
+#define lisp_h_SYMBOL_CONSTANT_P(sym) (SYM_CONSTANT_P (XSYMBOL (sym)))
#define lisp_h_SYMBOL_VAL(sym) \
(eassert ((sym)->redirect == SYMBOL_PLAINVAL), (sym)->val.value)
#define lisp_h_SYMBOLP(x) (XTYPE (x) == Lisp_Symbol)
@@ -1597,10 +1608,13 @@
3 : it's a forwarding variable, the value is in `forward'. */
ENUM_BF (symbol_redirect) redirect : 3;
- /* Non-zero means symbol is constant, i.e. changing its value
- should signal an error. If the value is 3, then the var
- can be changed, but only by `defconst'. */
- unsigned constant : 2;
+ /* When masked with SYMBOL_CONSTANT_MASK, non-zero means symbol is
+ constant, i.e. changing its value should signal an error.
+ When masked with SYMBOL_HOOKED_MASK, non-zero means setting
+ symbol will run varhook. These two fields are combined into one
+ in order to optimize the fast path of unhooked non-constants by
+ having only one conditional branch for that case. */
+ unsigned constant_or_hooked : 2;
/* Interned state of the symbol. This is an enumerator from
enum symbol_interned. */
@@ -3391,6 +3405,14 @@
EXFUN (Fbyteorder, 0) ATTRIBUTE_CONST;
/* Defined in data.c. */
+typedef enum
+ { /* See set_internal for a description of these values */
+ Dyn_Unbind = -1,
+ Dyn_Current = 0,
+ Dyn_Bind = 1,
+ Dyn_Skip = 2,
+ Dyn_Global = 3
+ } Dyn_Bind_Env;
extern Lisp_Object indirect_function (Lisp_Object);
extern Lisp_Object find_symbol_value (Lisp_Object);
enum Arith_Comparison {
@@ -3438,7 +3460,16 @@
Lisp_Object);
extern _Noreturn Lisp_Object wrong_type_argument (Lisp_Object, Lisp_Object);
extern Lisp_Object do_symval_forwarding (union Lisp_Fwd *);
-extern void set_internal (Lisp_Object, Lisp_Object, Lisp_Object, bool);
+extern Lisp_Object run_varhook (struct Lisp_Symbol*, bool, Dyn_Bind_Env,
+ Lisp_Object, Lisp_Object);
+extern void set_internal_with_varhook (Lisp_Object, Lisp_Object,
+ Lisp_Object, bool,
+ Dyn_Bind_Env, struct Lisp_Symbol *);
+extern void set_internal_localized_or_forwarded (Lisp_Object, Lisp_Object,
+ Lisp_Object, bool,
+ Dyn_Bind_Env,
+ struct Lisp_Symbol *);
+extern void set_default_internal (Lisp_Object, Lisp_Object, Dyn_Bind_Env);
extern void syms_of_data (void);
extern void swap_in_global_binding (struct Lisp_Symbol *);
@@ -4595,6 +4627,65 @@
return false;
}
+/* Store the value NEWVAL into SYMBOL.
+ If buffer/frame-locality is an issue, WHERE specifies which context to use.
+ (nil stands for the current buffer/frame).
+
+ If BINDFLAG is false, then if this symbol is supposed to become
+ local in every buffer where it is set, then we make it local.
+ If BINDFLAG is true, we don't do that.
+
+ ENV indicates the dynamic environment for this function call, i.e. whether
+ this call is due to a variable binding (Dyn_Bind), an unbinding (Dyn_Unbind),
+ or neither (Dyn_Current). As special cases, a value of Dyn_Skip is a flag
+ to disable run_varhook so that varhooks aren't run during backtraces, and
+ a value of Dyn_Global is a flag indicating that this function call is due
+ to set_default, which allows run_varhook to distinguish beween the global
+ and the dyn-local binding. */
+
+INLINE void
+set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
+ bool bindflag, Dyn_Bind_Env env)
+{
+ struct Lisp_Symbol *sym;
+
+ /* If restoring in a dead buffer, do nothing. */
+ /* if (BUFFERP (where) && NILP (XBUFFER (where)->name))
+ return; */
+
+ CHECK_SYMBOL (symbol);
+ sym = XSYMBOL (symbol);
+ if (sym->constant_or_hooked)
+ {
+ if (SYM_HOOKED_P (sym))
+ {
+ set_internal_with_varhook (symbol, newval, where, bindflag, env, sym);
+ return;
+ }
+ if (NILP (Fkeywordp (symbol))
+ || !EQ (newval, Fsymbol_value (symbol)))
+ xsignal1 (Qsetting_constant, symbol);
+ else
+ /* Allow setting keywords to their own value. */
+ return;
+ }
+
+ start:
+ switch (sym->redirect)
+ {
+ case SYMBOL_PLAINVAL: SET_SYMBOL_VAL (sym, newval); return;
+ case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
+ default: set_internal_localized_or_forwarded
+ (symbol, newval, where, bindflag, env, sym);
+ }
+}
+
+#define MAYBE_RUN_VARHOOK(result, sym, buf_local, env, oldval, newval) \
+ { \
+ if (SYM_HOOKED_P (sym)) \
+ (result) = run_varhook (sym, buf_local, env, oldval, newval); \
+ }
+
INLINE_HEADER_END
#endif /* EMACS_LISP_H */
--- src/eval.c
+++ src/eval.c
@@ -267,7 +268,7 @@
max_lisp_eval_depth = XINT (XCDR (data));
}
-static void grow_specpdl (void);
+static inline void grow_specpdl (void);
/* Call the Lisp debugger, giving it argument ARG. */
@@ -601,6 +602,63 @@
return quoted;
}
+DEFUN ("void-p", Fvoid_p, Svoid_p, 1, UNEVALLED, 0,
+ doc: /* Return t if ARG has no value.
+If ARG is a non-lexical variable, this is equivalent to
+(not (boundp (quote ARG))).
+
+Unlike `boundp', this function can also test a lexical variable.
+
+See also `void'.
+usage: (void-p ARG) */)
+ (Lisp_Object args)
+{
+ register Lisp_Object val;
+ struct gcpro gcpro1;
+ GCPRO1 (args);
+ if (CONSP (XCDR (args)))
+ xsignal2 (Qwrong_number_of_arguments, Qvoid_p, Flength (args));
+
+ val = XCAR (args);
+
+ if (SYMBOLP (val))
+ { /* This block is derived from the first block of eval_sub */
+ Lisp_Object lex_binding
+ = !NILP (Vinternal_interpreter_environment)
+ ? Fassq (val, Vinternal_interpreter_environment)
+ : Qnil;
+ if (CONSP (lex_binding))
+ val = XCDR (lex_binding);
+ else
+ val = find_symbol_value (val); /* Avoid signaling error if unbound */
+ }
+ else
+ val = eval_sub (val);
+
+ val = EQ (val, Qunbound) ? Qt : Qnil;
+ UNGCPRO;
+ return val;
+}
+
+
+DEFUN ("void", Fvoid, Svoid, 0, 0, 0,
+ doc: /* Return nothing.
+This is the only built-in Elisp function that does not return a value.
+Returning the result of this function enables any other function
+to avoid returning a value.
+
+Setting a variable to the result of this function will unbind the variable.
+For example, (setq foo (void)) is equivalent to (makunbound 'foo), if
+foo is a non-lexical variable.
+
+Unlike `makunbound', this function can also be used to unbind a
+lexical variable.
+
+See also `void-p'. */)
+ ()
+{
+ return Qunbound;
+}
DEFUN ("defvaralias", Fdefvaralias, Sdefvaralias, 2, 3, 0,
doc: /* Make NEW-ALIAS a variable alias for symbol BASE-VARIABLE.
@@ -620,7 +678,7 @@
sym = XSYMBOL (new_alias);
- if (sym->constant)
+ if (SYM_CONSTANT_P (sym))
/* Not sure why, but why not? */
error ("Cannot make a constant an alias");
@@ -637,7 +695,7 @@
so that old-code that affects n_a before the aliasing is setup
still works. */
if (NILP (Fboundp (base_variable)))
- set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1);
+ set_internal (base_variable, find_symbol_value (new_alias), Qnil, 1, Dyn_Current);
{
union specbinding *p;
@@ -652,7 +710,7 @@
XSYMBOL (base_variable)->declared_special = 1;
sym->redirect = SYMBOL_VARALIAS;
SET_SYMBOL_ALIAS (sym, XSYMBOL (base_variable));
- sym->constant = SYMBOL_CONSTANT_P (base_variable);
+ sym->constant_or_hooked = SYMBOL_CONSTANT_P (base_variable);
LOADHIST_ATTACH (new_alias);
/* Even if docstring is nil: remove old docstring. */
Fput (new_alias, Qvariable_documentation, docstring);
@@ -2007,7 +2065,7 @@
never-used entry just before the bottom of the stack; sometimes its
address is taken. */
-static void
+static inline void
grow_specpdl (void)
{
specpdl_ptr++;
@@ -3132,8 +3190,6 @@
start:
switch (sym->redirect)
{
- case SYMBOL_VARALIAS:
- sym = indirect_variable (sym); XSETSYMBOL (symbol, sym); goto start;
case SYMBOL_PLAINVAL:
/* The most common case is that of a non-constant symbol with a
trivial value. Make that as fast as we can. */
@@ -3141,11 +3197,15 @@
specpdl_ptr->let.symbol = symbol;
specpdl_ptr->let.old_value = SYMBOL_VAL (sym);
grow_specpdl ();
- if (!sym->constant)
- SET_SYMBOL_VAL (sym, value);
+ if (!sym->constant_or_hooked) SET_SYMBOL_VAL (sym, value);
+ else if (SYM_HOOKED_P (sym))
+ SET_SYMBOL_VAL (sym, run_varhook
+ (sym, false, Dyn_Bind, sym->val.value, value));
else
- set_internal (symbol, value, Qnil, 1);
+ set_internal (symbol, value, Qnil, 1, Dyn_Bind);
break;
+ case SYMBOL_VARALIAS:
+ sym = indirect_variable (sym); XSETSYMBOL (symbol, sym); goto start;
case SYMBOL_LOCALIZED:
if (SYMBOL_BLV (sym)->frame_local)
error ("Frame-local vars cannot be let-bound");
@@ -3176,7 +3236,7 @@
{
specpdl_ptr->let.kind = SPECPDL_LET_DEFAULT;
grow_specpdl ();
- Fset_default (symbol, value);
+ set_default_internal (symbol, value, Dyn_Bind);
return;
}
}
@@ -3184,7 +3244,7 @@
specpdl_ptr->let.kind = SPECPDL_LET;
grow_specpdl ();
- set_internal (symbol, value, Qnil, 1);
+ set_internal (symbol, value, Qnil, 1, Dyn_Bind);
break;
}
default: emacs_abort ();
@@ -3319,7 +3379,9 @@
struct Lisp_Symbol *sym = XSYMBOL (specpdl_symbol (specpdl_ptr));
if (sym->redirect == SYMBOL_PLAINVAL)
{
- SET_SYMBOL_VAL (sym, specpdl_old_value (specpdl_ptr));
+ Lisp_Object oldval = specpdl_old_value (specpdl_ptr);
+ MAYBE_RUN_VARHOOK (oldval, sym, false, Dyn_Unbind, sym->val.value, oldval);
+ SET_SYMBOL_VAL (sym, oldval);
break;
}
else
@@ -3329,8 +3391,8 @@
}
}
case SPECPDL_LET_DEFAULT:
- Fset_default (specpdl_symbol (specpdl_ptr),
- specpdl_old_value (specpdl_ptr));
+ set_default_internal (specpdl_symbol (specpdl_ptr),
+ specpdl_old_value (specpdl_ptr), Dyn_Unbind);
break;
case SPECPDL_LET_LOCAL:
{
@@ -3342,7 +3404,7 @@
/* If this was a local binding, reset the value in the appropriate
buffer, but only if that buffer's binding still exists. */
if (!NILP (Flocal_variable_p (symbol, where)))
- set_internal (symbol, old_value, where, 1);
+ set_internal (symbol, old_value, where, 1, Dyn_Unbind);
}
break;
}
@@ -3537,7 +3599,7 @@
Lisp_Object sym = specpdl_symbol (tmp);
Lisp_Object old_value = specpdl_old_value (tmp);
set_specpdl_old_value (tmp, Fdefault_value (sym));
- Fset_default (sym, old_value);
+ set_default_internal (sym, old_value, Dyn_Skip);
}
break;
case SPECPDL_LET_LOCAL:
@@ -3553,7 +3615,7 @@
{
set_specpdl_old_value
(tmp, Fbuffer_local_value (symbol, where));
- set_internal (symbol, old_value, where, 1);
+ set_internal (symbol, old_value, where, 1, Dyn_Skip);
}
}
break;
@@ -3754,6 +3816,11 @@
DEFSYM (Qinhibit_debugger, "inhibit-debugger");
DEFSYM (Qmacro, "macro");
DEFSYM (Qdeclare, "declare");
+ DEFSYM (Qvoid_p, "void-p");
+ DEFSYM (Qsym, "sym");
+ DEFSYM (Qenv, "env");
+ DEFSYM (Qoldval, "oldval");
+ DEFSYM (Qnewval, "newval");
/* Note that the process handling also uses Qexit, but we don't want
to staticpro it twice, so we just do it here. */
@@ -3828,6 +3895,67 @@
still determine whether to handle the particular condition. */);
Vdebug_on_signal = Qnil;
+ DEFVAR_LISP ("symbol-setter-function", Vsymbol_setter_function,
+ doc: /* This function is called whenever a hooked variable is set.
+It takes four arguments: SYMBOL, ENV, OLDVAL, NEWVAL. By default, it just
+returns NEWVAL unchanged.
+
+SYMBOL is the symbol being set. ENV is the environment is which it's being
+set. OLDVAL is the current value. NEWVAL is the new value to which the
+setter, i.e. the caller of a function such as `setq', is attempting to set
+the variable. The actual new value to which the variable will be set is the
+return value of this function, which is NEWVAL if this function does not
+have advice that overrides it.
+
+The possible values of ENV are these symbols, with these meanings:
+global: The global environment.
+buf-local: The setter's buffer-local environment.
+dyn-local: The innermost dynamic environment in which SYMBOL is bound.
+dyn-bind: A new dynamic environment, such as creatable using `let'.
+dyn-unbind: The next-outer dynamic environment in which SYMBOL is still bound,
+or the buffer-local environment if SYMBOL is not bound in any dynamic
+environment, or the global environment is SYMBOL is not in the buffer-local
+environment, unshadowed due to destruction of the setter's current
+dynamic environment, such as due to exit of a `let' form.
+
+To watch hooked variables, advise this function using `add-function' with
+:before as the WHERE argument.
+
+To watch hooked variables and optionally override the attempts to set them,
+advise this function with advice that overrides the return value, such as
+by using :override or (preferably) :around as the WHERE argument.
+
+At the time the definition of your advice function is evaluated,
+`lexical-binding' must be t, i.e. your advice must be a closure (even if
+its lexical environment is empty).
+
+If you use overriding advice, your advice must return the value to which to
+set the variable. To avoid overriding the setter's attempt to set the variable
+to NEWVAL, return NEWVAL. To block the attempt, and leave the variable
+unchanged, return OLDVAL. If ENV is dyn-bind or dyn-unbind, you can block
+the change of value, but you can't prevent the corresponding creation or
+destruction of a dynamic environment. Therefore, blocking when ENV is
+dyn-bind will set SYMBOL in the new environment to its value in the outer
+environment, and blocking when ENV is dyn-unbind will set SYMBOL in the
+outer environment to its value in the environment being destroyed.
+
+If the variable is currently void, OLDVAL will be void. If the setter
+is attempting to unbind the variable, NEWVAL will be void. Test for this
+using `void-p'. If you use overriding advice, OLDVAL is void, and you return
+it, the variable will remain void. If NEWVAL is void, and you return it, the
+setter's attempt to unbind the variable succeeds. If neither is void, you
+can still unbind the variable by returning the result of the function `void'.
+
+Don't set the variable in your advice. Instead, if your advice needs
+to set the variable, use `add-function' with overriding advice.
+
+To hook all variables of a symbol, use `symbol-hook'. To unhook them,
+use `symbol-unhook'. If you only want to watch or override some variables
+of a symbol, then filter according to ENV, and if you use overriding advice,
+simply return NEWVAL for the ones you don't want to process. */);
+ Vsymbol_setter_function =
+ list4 (Qclosure, list1 (Qt), list4 (Qsym, Qenv, Qoldval, Qnewval), Qnewval);
+
/* When lexical binding is being used,
Vinternal_interpreter_environment is non-nil, and contains an alist
of lexically-bound variable, or (t), indicating an empty
@@ -3902,4 +4030,6 @@
defsubr (&Sbacktrace__locals);
defsubr (&Sspecial_variable_p);
defsubr (&Sfunctionp);
+ defsubr (&Svoid);
+ defsubr (&Svoid_p);
}
--- src/data.c
+++ src/data.c
@@ -612,6 +613,20 @@
\f
/* Extract and set components of symbols. */
+DEFUN ("symbol-hooked-p", Fsymbol_hooked_p, Ssymbol_hooked_p, 1, 1, 0,
+ doc: /* Return t if SYMBOL is hooked.
+To hook and unhook it, use `symbol-hook' and `symbol-unhook'.
+When hooked, setting SYMBOL will run `symbol-setter-function'. */)
+ (register Lisp_Object symbol)
+{
+ struct Lisp_Symbol *sym;
+ CHECK_SYMBOL (symbol);
+ sym = XSYMBOL (symbol);
+ while (sym->redirect == SYMBOL_VARALIAS)
+ sym = indirect_variable (sym);
+ return SYM_HOOKED_P (sym) ? Qt : Qnil;
+}
+
DEFUN ("boundp", Fboundp, Sboundp, 1, 1, 0,
doc: /* Return t if SYMBOL's value is not void.
Note that if `lexical-binding' is in effect, this refers to the
@@ -661,6 +676,46 @@
return NILP (XSYMBOL (symbol)->function) ? Qnil : Qt;
}
+DEFUN ("symbol-hook", Fsymbol_hook, Ssymbol_hook, 1, 1, 0,
+ doc: /* Hook SYMBOL.
+When hooked, setting it will run `symbol-setter-function'.
+To unhook it, use `symbol-unhook'.
+To test whether it's hooked, use `symbol-hooked-p'.
+Return SYMBOL. */)
+ (register Lisp_Object symbol)
+{
+ struct Lisp_Symbol *sym;
+ CHECK_SYMBOL (symbol);
+ sym = XSYMBOL (symbol);
+ sym->constant_or_hooked |= SYMBOL_HOOKED_MASK;
+ while (sym->redirect == SYMBOL_VARALIAS)
+ {
+ sym = indirect_variable (sym);
+ sym->constant_or_hooked |= SYMBOL_HOOKED_MASK;
+ }
+ return symbol;
+}
+
+DEFUN ("symbol-unhook", Fsymbol_unhook, Ssymbol_unhook, 1, 1, 0,
+ doc: /* Unhook SYMBOL.
+When unhooked, setting it will not run `symbol-setter-function'.
+To hook it, use `symbol-hook'.
+To test whether it's hooked, use `symbol-hooked-p'.
+Return SYMBOL. */)
+ (register Lisp_Object symbol)
+{
+ struct Lisp_Symbol *sym;
+ CHECK_SYMBOL (symbol);
+ sym = XSYMBOL (symbol);
+ sym->constant_or_hooked &= (SYMBOL_HOOKED_MASK ^ -1);
+ while (sym->redirect == SYMBOL_VARALIAS)
+ {
+ sym = indirect_variable (sym);
+ sym->constant_or_hooked &= (SYMBOL_HOOKED_MASK ^ -1);
+ }
+ return symbol;
+}
+
DEFUN ("makunbound", Fmakunbound, Smakunbound, 1, 1, 0,
doc: /* Make SYMBOL's value be void.
Return SYMBOL. */)
@@ -1137,8 +1192,8 @@
start:
switch (sym->redirect)
{
- case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
case SYMBOL_PLAINVAL: return SYMBOL_VAL (sym);
+ case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
case SYMBOL_LOCALIZED:
{
struct Lisp_Buffer_Local_Value *blv = SYMBOL_BLV (sym);
@@ -1167,54 +1222,97 @@
xsignal1 (Qvoid_variable, symbol);
}
+/* For the symbol S being set, run symbol-setter-function with these arguments:
+ 0. S
+ 1. A symbol indicating the environment in which S is being set.
+ 2. The current value of S in that environment.
+ 3. The value to which the setter is attempting to set the variable.
+
+ Return the result of symbol-setter-function. The variable will be set
+ (by code that calls run_varhook) to that result, overriding the value to
+ which the setter is attempting to set the variable. */
+
+Lisp_Object
+run_varhook (struct Lisp_Symbol* sym, bool buf_local, Dyn_Bind_Env rawenv,
+ Lisp_Object oldval, Lisp_Object newval)
+{
+ Lisp_Object symbol;
+ Lisp_Object env;
+ if (rawenv == Dyn_Skip) /* From backtrace_eval_unrewind */
+ return newval;
+ XSETSYMBOL (symbol, sym);
+ switch (rawenv) /* Resolve Dyn_Current and disambiguate Dyn_Global */
+ {
+ case Dyn_Current:
+ {
+ bool shadowed = (buf_local ? let_shadows_buffer_binding_p (sym)
+ : let_shadows_global_binding_p (symbol));
+ if (shadowed) env = Qdyn_local;
+ else if (buf_local) env = Qbuf_local;
+ else env = Qglobal;
+ break;
+ }
+ case Dyn_Global:
+ {
+ /* let_shadows_buffer_binding_p doesn't disambiguate this case */
+ if (let_shadows_global_binding_p (symbol) &&
+ NILP (Flocal_variable_p (symbol, Qnil)))
+ env = Qdyn_local;
+ else env = Qglobal;
+ break;
+ }
+ case Dyn_Bind: env = Qdyn_bind; break;
+ case Dyn_Unbind: env = Qdyn_unbind; break;
+ default: emacs_abort ();
+ }
+ return call4 (Vsymbol_setter_function, symbol, env, oldval, newval);
+}
+
DEFUN ("set", Fset, Sset, 2, 2, 0,
doc: /* Set SYMBOL's value to NEWVAL, and return NEWVAL. */)
(register Lisp_Object symbol, Lisp_Object newval)
{
- set_internal (symbol, newval, Qnil, 0);
+ set_internal (symbol, newval, Qnil, 0, Dyn_Current);
return newval;
}
-/* Store the value NEWVAL into SYMBOL.
- If buffer/frame-locality is an issue, WHERE specifies which context to use.
- (nil stands for the current buffer/frame).
-
- If BINDFLAG is false, then if this symbol is supposed to become
- local in every buffer where it is set, then we make it local.
- If BINDFLAG is true, we don't do that. */
+/* set_internal is in lisp.h due to being inlined. */
+
+/* Split from set_internal just to avoid an extra conditional branch on the fast
+ path for non-hooked variables. */
void
-set_internal (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
- bool bindflag)
+set_internal_with_varhook (Lisp_Object symbol, Lisp_Object newval, Lisp_Object where,
+ bool bindflag, Dyn_Bind_Env env, struct Lisp_Symbol *sym)
{
- bool voide = EQ (newval, Qunbound);
- struct Lisp_Symbol *sym;
- Lisp_Object tem1;
-
- /* If restoring in a dead buffer, do nothing. */
- /* if (BUFFERP (where) && NILP (XBUFFER (where)->name))
- return; */
-
- CHECK_SYMBOL (symbol);
- if (SYMBOL_CONSTANT_P (symbol))
+ start:
+ switch (sym->redirect)
{
- if (NILP (Fkeywordp (symbol))
- || !EQ (newval, Fsymbol_value (symbol)))
- xsignal1 (Qsetting_constant, symbol);
- else
- /* Allow setting keywords to their own value. */
+ case SYMBOL_PLAINVAL:
+ {
+ SET_SYMBOL_VAL (sym, run_varhook (sym, false, env, sym->val.value, newval));
return;
}
+ case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
+ default: set_internal_localized_or_forwarded (symbol, newval, where, bindflag, env, sym);
+ }
+}
- sym = XSYMBOL (symbol);
+/* Split from set_internal to avoid code duplication, because both set_internal and
+ set_internal_with_varhook must call this function. */
- start:
+void
+set_internal_localized_or_forwarded (Lisp_Object symbol, Lisp_Object newval,
+ Lisp_Object where, bool bindflag,
+ Dyn_Bind_Env env, struct Lisp_Symbol *sym)
+{
+ bool voide;
+ Lisp_Object tem1;
switch (sym->redirect)
{
- case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
- case SYMBOL_PLAINVAL: SET_SYMBOL_VAL (sym , newval); return;
case SYMBOL_LOCALIZED:
{
+ bool buf_local = true;
struct Lisp_Buffer_Local_Value *blv = SYMBOL_BLV (sym);
if (NILP (where))
{
@@ -1258,6 +1356,7 @@
indicating that we're seeing the default value.
Likewise if the variable has been let-bound
in the current buffer. */
+ buf_local = false;
if (bindflag || !blv->local_if_set
|| let_shadows_buffer_binding_p (sym))
{
@@ -1285,6 +1384,9 @@
set_blv_valcell (blv, tem1);
}
+ MAYBE_RUN_VARHOOK (newval, sym, buf_local, env, blv_value (blv), newval);
+ voide = EQ (newval, Qunbound);
+
/* Store the new value in the cons cell. */
set_blv_value (blv, newval);
@@ -1316,6 +1418,11 @@
SET_PER_BUFFER_VALUE_P (buf, idx, 1);
}
+ MAYBE_RUN_VARHOOK (newval, sym,
+ (XFWDTYPE (innercontents)) == Lisp_Fwd_Buffer_Obj,
+ env, do_symval_forwarding (innercontents), newval);
+ voide = EQ (newval, Qunbound);
+
if (voide)
{ /* If storing void (making the symbol void), forward only through
buffer-local indicator, not through Lisp_Objfwd, etc. */
@@ -1347,8 +1454,8 @@
start:
switch (sym->redirect)
{
- case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
case SYMBOL_PLAINVAL: return SYMBOL_VAL (sym);
+ case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
case SYMBOL_LOCALIZED:
{
/* If var is set up for a buffer that lacks a local value for it,
@@ -1413,6 +1520,17 @@
for this variable. */)
(Lisp_Object symbol, Lisp_Object value)
{
+ set_default_internal (symbol, value, Dyn_Global);
+ return value;
+}
+
+/* Like Fset_default, but with ENV argument. See set_internal for
+ a description of this argument. */
+
+void
+set_default_internal (Lisp_Object symbol, Lisp_Object value,
+ Dyn_Bind_Env env)
+{
struct Lisp_Symbol *sym;
CHECK_SYMBOL (symbol);
@@ -1423,26 +1541,32 @@
xsignal1 (Qsetting_constant, symbol);
else
/* Allow setting keywords to their own value. */
- return value;
+ return;
}
sym = XSYMBOL (symbol);
start:
switch (sym->redirect)
{
+ case SYMBOL_PLAINVAL:
+ {
+ set_internal (symbol, value, Qnil, false, env);
+ return;
+ }
case SYMBOL_VARALIAS: sym = indirect_variable (sym); goto start;
- case SYMBOL_PLAINVAL: return Fset (symbol, value);
case SYMBOL_LOCALIZED:
{
struct Lisp_Buffer_Local_Value *blv = SYMBOL_BLV (sym);
+ MAYBE_RUN_VARHOOK (value, sym, false, env, XCDR (blv->defcell), value);
+
/* Store new value into the DEFAULT-VALUE slot. */
XSETCDR (blv->defcell, value);
/* If the default binding is now loaded, set the REALVALUE slot too. */
if (blv->fwd && EQ (blv->defcell, blv->valcell))
store_symval_forwarding (blv->fwd, value, NULL);
- return value;
+ return;
}
case SYMBOL_FORWARDED:
{
@@ -1456,6 +1580,8 @@
int offset = XBUFFER_OBJFWD (valcontents)->offset;
int idx = PER_BUFFER_IDX (offset);
+ MAYBE_RUN_VARHOOK (value, sym, false, env, per_buffer_default (offset), value);
+
set_per_buffer_default (offset, value);
/* If this variable is not always local in all buffers,
@@ -1468,10 +1594,13 @@
if (!PER_BUFFER_VALUE_P (b, idx))
set_per_buffer_value (b, offset, value);
}
- return value;
+ return;
}
else
- return Fset (symbol, value);
+ {
+ set_internal (symbol, value, Qnil, false, env);
+ return;
+ }
}
default: emacs_abort ();
}
@@ -1599,7 +1728,7 @@
default: emacs_abort ();
}
- if (sym->constant)
+ if (SYM_CONSTANT_P (sym))
error ("Symbol %s may not be buffer-local", SDATA (SYMBOL_NAME (variable)));
if (!blv)
@@ -1672,7 +1801,7 @@
default: emacs_abort ();
}
- if (sym->constant)
+ if (SYM_CONSTANT_P (sym))
error ("Symbol %s may not be buffer-local",
SDATA (SYMBOL_NAME (variable)));
@@ -1861,7 +1990,7 @@
default: emacs_abort ();
}
- if (sym->constant)
+ if (SYM_CONSTANT_P (sym))
error ("Symbol %s may not be frame-local", SDATA (SYMBOL_NAME (variable)));
blv = make_blv (sym, forwarded, valcontents);
@@ -3470,6 +3599,12 @@
DEFSYM (Qad_advice_info, "ad-advice-info");
DEFSYM (Qad_activate_internal, "ad-activate-internal");
+ DEFSYM (Qglobal, "global");
+ DEFSYM (Qbuf_local, "buf-local");
+ DEFSYM (Qdyn_local, "dyn-local");
+ DEFSYM (Qdyn_bind, "dyn-bind");
+ DEFSYM (Qdyn_unbind, "dyn-unbind");
+
error_tail = pure_cons (Qerror, Qnil);
/* ERROR is used as a signaler for random errors for which nothing else is
@@ -3609,8 +3744,11 @@
defsubr (&Sindirect_function);
defsubr (&Ssymbol_plist);
defsubr (&Ssymbol_name);
+ defsubr (&Ssymbol_hook);
+ defsubr (&Ssymbol_unhook);
defsubr (&Smakunbound);
defsubr (&Sfmakunbound);
+ defsubr (&Ssymbol_hooked_p);
defsubr (&Sboundp);
defsubr (&Sfboundp);
defsubr (&Sfset);
@@ -3678,10 +3816,10 @@
DEFVAR_LISP ("most-positive-fixnum", Vmost_positive_fixnum,
doc: /* The largest value that is representable in a Lisp integer. */);
Vmost_positive_fixnum = make_number (MOST_POSITIVE_FIXNUM);
- XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant = 1;
+ XSYMBOL (intern_c_string ("most-positive-fixnum"))->constant_or_hooked = 1;
DEFVAR_LISP ("most-negative-fixnum", Vmost_negative_fixnum,
doc: /* The smallest value that is representable in a Lisp integer. */);
Vmost_negative_fixnum = make_number (MOST_NEGATIVE_FIXNUM);
- XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant = 1;
+ XSYMBOL (intern_c_string ("most-negative-fixnum"))->constant_or_hooked = 1;
}
--- src/alloc.c
+++ src/alloc.c
@@ -3390,7 +3390,7 @@
set_symbol_next (val, NULL);
p->gcmarkbit = false;
p->interned = SYMBOL_UNINTERNED;
- p->constant = 0;
+ p->constant_or_hooked = 0;
p->declared_special = false;
p->pinned = false;
consing_since_gc += sizeof (struct Lisp_Symbol);
--- src/lread.c
+++ src/lread.c
@@ -3821,7 +3821,7 @@
if ((SREF (string, 0) == ':')
&& EQ (obarray, initial_obarray))
{
- XSYMBOL (sym)->constant = 1;
+ XSYMBOL (sym)->constant_or_hooked = 1;
XSYMBOL (sym)->redirect = SYMBOL_PLAINVAL;
SET_SYMBOL_VAL (XSYMBOL (sym), sym);
}
@@ -4042,7 +4042,7 @@
set_symbol_function (Qunbound, Qnil);
set_symbol_plist (Qunbound, Qnil);
SET_SYMBOL_VAL (XSYMBOL (Qnil), Qnil);
- XSYMBOL (Qnil)->constant = 1;
+ XSYMBOL (Qnil)->constant_or_hooked = 1;
XSYMBOL (Qnil)->declared_special = true;
set_symbol_plist (Qnil, Qnil);
set_symbol_function (Qnil, Qnil);
@@ -4050,7 +4050,7 @@
Qt = intern_c_string ("t");
SET_SYMBOL_VAL (XSYMBOL (Qt), Qt);
- XSYMBOL (Qt)->constant = 1;
+ XSYMBOL (Qt)->constant_or_hooked = 1;
XSYMBOL (Qt)->declared_special = true;
/* Qt is correct even if CANNOT_DUMP. loadup.el will set to nil at end. */
--- src/buffer.c
+++ src/buffer.c
@@ -5753,7 +5753,7 @@
This variable is buffer-local but you cannot set it directly;
use the function `set-buffer-multibyte' to change a buffer's representation.
See also Info node `(elisp)Text Representations'. */);
- XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant = 1;
+ XSYMBOL (intern_c_string ("enable-multibyte-characters"))->constant_or_hooked = 1;
DEFVAR_PER_BUFFER ("buffer-file-coding-system",
&BVAR (current_buffer, buffer_file_coding_system), Qnil,
--- src/bytecode.c
+++ src/bytecode.c
@@ -840,7 +840,7 @@
else
{
BEFORE_POTENTIAL_GC ();
- set_internal (sym, val, Qnil, 0);
+ set_internal (sym, val, Qnil, 0, Dyn_Current);
AFTER_POTENTIAL_GC ();
}
}
--- src/font.c
+++ src/font.c
@@ -5197,19 +5197,19 @@
[NUMERIC-VALUE SYMBOLIC-NAME ALIAS-NAME ...]
NUMERIC-VALUE is an integer, and SYMBOLIC-NAME and ALIAS-NAME are symbols. */);
Vfont_weight_table = BUILD_STYLE_TABLE (weight_table);
- XSYMBOL (intern_c_string ("font-weight-table"))->constant = 1;
+ XSYMBOL (intern_c_string ("font-weight-table"))->constant_or_hooked = 1;
DEFVAR_LISP_NOPRO ("font-slant-table", Vfont_slant_table,
doc: /* Vector of font slant symbols vs the corresponding numeric values.
See `font-weight-table' for the format of the vector. */);
Vfont_slant_table = BUILD_STYLE_TABLE (slant_table);
- XSYMBOL (intern_c_string ("font-slant-table"))->constant = 1;
+ XSYMBOL (intern_c_string ("font-slant-table"))->constant_or_hooked = 1;
DEFVAR_LISP_NOPRO ("font-width-table", Vfont_width_table,
doc: /* Alist of font width symbols vs the corresponding numeric values.
See `font-weight-table' for the format of the vector. */);
Vfont_width_table = BUILD_STYLE_TABLE (width_table);
- XSYMBOL (intern_c_string ("font-width-table"))->constant = 1;
+ XSYMBOL (intern_c_string ("font-width-table"))->constant_or_hooked = 1;
staticpro (&font_style_table);
font_style_table = make_uninit_vector (3);
next prev parent reply other threads:[~2015-02-13 23:08 UTC|newest]
Thread overview: 110+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-04-20 2:54 Proposal to change cursor appearance to indicate region activation Kelly Dean
2013-04-20 7:23 ` Drew Adams
2015-01-22 5:38 ` [PATCH] " Kelly Dean
2015-01-22 14:25 ` Stefan Monnier
2015-01-23 3:08 ` [PATCH] " Kelly Dean
2015-01-23 4:55 ` Stefan Monnier
2015-01-23 11:07 ` Kelly Dean
2015-01-23 17:49 ` Drew Adams
2015-01-24 3:06 ` Kelly Dean
2015-01-24 4:52 ` Stefan Monnier
2015-01-24 9:22 ` Kelly Dean
2015-01-25 14:29 ` Stefan Monnier
2015-01-28 9:15 ` [PATCH] Run hook when variable is set Kelly Dean
2015-01-28 9:23 ` [PATCH] Proposal to change cursor appearance to indicate region activation Kelly Dean
2015-01-28 11:24 ` David Kastrup
2015-01-28 12:13 ` David Kastrup
2015-01-29 10:46 ` Kelly Dean
2015-01-29 11:16 ` David Kastrup
2015-01-30 7:20 ` Kelly Dean
2015-01-30 9:19 ` David Kastrup
2015-01-30 10:05 ` Kelly Dean
2015-01-30 10:12 ` David Kastrup
2015-01-30 9:43 ` Kelly Dean
2015-01-28 19:25 ` [PATCH] Run hook when variable is set Stefan Monnier
2015-01-29 8:20 ` Kelly Dean
2015-01-29 8:28 ` Lars Ingebrigtsen
2015-01-29 14:58 ` Stefan Monnier
2015-01-30 7:34 ` Kelly Dean
2015-01-30 15:55 ` Stefan Monnier
2015-01-31 9:18 ` Kelly Dean
2015-01-31 20:48 ` Stefan Monnier
2015-02-02 5:40 ` Kelly Dean
2015-02-02 15:57 ` Stefan Monnier
2015-02-03 19:56 ` Kelly Dean
2015-02-03 22:49 ` Stefan Monnier
2015-02-05 3:10 ` [PATCH] (Updated) " Kelly Dean
2015-02-05 13:57 ` Stefan Monnier
2015-02-06 5:34 ` Kelly Dean
2015-02-06 14:42 ` Stefan Monnier
2015-02-07 12:27 ` Kelly Dean
2015-02-07 15:09 ` Stefan Monnier
2015-02-09 3:24 ` Kelly Dean
2015-02-12 19:58 ` Stefan Monnier
2015-02-13 23:08 ` Kelly Dean [this message]
2015-02-14 0:55 ` Stefan Monnier
2015-02-14 22:19 ` Kelly Dean
2015-02-15 20:25 ` Stefan Monnier
2015-02-17 2:22 ` Kelly Dean
2015-02-17 23:07 ` Richard Stallman
2015-02-18 3:19 ` The purpose of makunbound (Was: Run hook when variable is set) Kelly Dean
2015-02-18 5:48 ` The purpose of makunbound Stefan Monnier
2015-02-18 8:51 ` Kelly Dean
2015-02-18 14:34 ` Stefan Monnier
2015-02-18 18:53 ` Kelly Dean
2015-02-18 22:42 ` Stefan Monnier
2015-02-19 10:36 ` Kelly Dean
2015-02-22 0:18 ` Kelly Dean
2015-02-19 10:45 ` Kelly Dean
2015-02-19 13:33 ` Stefan Monnier
2015-02-19 23:51 ` Kelly Dean
2015-02-20 1:59 ` Stefan Monnier
2015-02-20 9:35 ` Kelly Dean
2015-02-20 16:55 ` Stefan Monnier
2015-02-20 2:58 ` Stephen J. Turnbull
2015-02-20 0:56 ` Richard Stallman
2015-02-20 9:02 ` Kelly Dean
2015-02-20 15:41 ` Richard Stallman
2015-02-21 5:45 ` Stephen J. Turnbull
2015-02-22 0:32 ` Kelly Dean
2015-02-22 8:45 ` Andreas Schwab
2015-02-18 5:15 ` [PATCH] (Updated) Run hook when variable is set Kelly Dean
2015-02-18 22:37 ` Stefan Monnier
2015-02-18 22:37 ` Stefan Monnier
2015-02-19 10:35 ` Kelly Dean
2015-02-19 13:30 ` Stefan Monnier
2015-02-20 6:48 ` Kelly Dean
2015-02-20 19:29 ` Stefan Monnier
2015-02-21 14:18 ` Kelly Dean
2015-02-21 20:51 ` Stefan Monnier
2015-02-22 0:32 ` Kelly Dean
2015-02-22 10:40 ` Stephen J. Turnbull
2015-02-22 21:35 ` Stefan Monnier
2015-02-23 3:09 ` Kelly Dean
2015-02-23 4:19 ` Stefan Monnier
2015-02-20 20:27 ` Proposal for debugging/testing option Kelly Dean
2015-02-24 16:28 ` Stefan Monnier
2015-02-14 20:37 ` [PATCH] (Updated) Run hook when variable is set Johan Bockgård
2015-02-15 19:36 ` Stefan Monnier
2015-02-15 19:53 ` Patches: inline vs. attachment, compressed vs. uncompressed. [was: Run hook when variable is set] Alan Mackenzie
2015-02-06 9:55 ` [PATCH] (Updated) Run hook when variable is set Kelly Dean
2015-01-30 23:29 ` [PATCH] " Richard Stallman
2015-01-31 9:23 ` Kelly Dean
2015-01-31 23:16 ` Richard Stallman
2015-02-02 5:41 ` Kelly Dean
2015-02-01 2:04 ` Alexis
2015-02-01 4:05 ` Stefan Monnier
2015-02-01 8:58 ` David Kastrup
2015-01-29 16:06 ` Eli Zaretskii
2015-01-30 7:14 ` Kelly Dean
2015-01-30 9:08 ` Eli Zaretskii
2015-01-23 20:34 ` [PATCH] Proposal to change cursor appearance to indicate region activation Stefan Monnier
2015-01-24 0:25 ` Kelly Dean
2015-01-23 10:01 ` Tassilo Horn
2015-01-23 17:49 ` Drew Adams
2015-01-23 10:06 ` Eli Zaretskii
2015-01-23 11:40 ` Kelly Dean
2015-01-23 11:56 ` Eli Zaretskii
2015-01-22 5:41 ` Kelly Dean
2013-11-23 13:34 ` Stefan Monnier
2013-11-23 20:25 ` Drew Adams
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=MBg5mY7yfO03nBuk9R0OKNqMDmUD1YQ8MssrosC5NOv@local \
--to=kelly@prtime.org \
--cc=emacs-devel@gnu.org \
--cc=monnier@IRO.UMontreal.CA \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).