Introduce "local" scope in evaluate-commands

When using `eval` a new scope named 'local' gets pushed for the
whole evaluation, this makes it possible to temporarily set
an option/hook/alias...

Local scopes nest so nested evals do work as expected.

Remove the now trivial with-option command
This commit is contained in:
Maxime Coste 2024-04-11 11:37:18 +10:00
parent b1c114bf6d
commit 3d7d0fecca
16 changed files with 99 additions and 36 deletions

View File

@ -64,3 +64,8 @@ are then restored when the keys have been executed: */*, *"*, *|*, *^*,
*-with-hooks*:: *-with-hooks*::
Execute keys and trigger existing hooks. Execute keys and trigger existing hooks.
(See <<hooks#,`:doc hooks`>>) (See <<hooks#,`:doc hooks`>>)
== Local scope in *evaluate-commands*
When using *evaluate-commands* a new scope, named `local` is inserted.
See <<scopes#,`:doc scopes`>>

View File

@ -36,15 +36,25 @@ Scopes are named as follows:
*global*:: *global*::
global context linked to the instance of Kakoune global context linked to the instance of Kakoune
*local*::
A local scope is inserted by each *evaluate-commands* invocations
for its duration. Nested *evaluate-commands* each inject a new
local scope whose parent is the previous local scope.
A local scope is intended for temporarily overwriting some scoped
value, such as an option or an alias.
The following order of priority applies to the above scopes: The following order of priority applies to the above scopes:
-------------------------- -----------------------------------
window ]> buffer ]> global local ]> window ]> buffer ]> global
-------------------------- -----------------------------------
The above priority line implies that objects can have individual values that The above priority line implies that objects can have individual values
will be resolved first in the *window* scope (highest priority), then in that will be resolved first in the *local* scope (if it exists), then the
the *buffer* scope, and finally in the *global* scope (lowest priority). *window* scope, then in the *buffer* scope, and finally in the *global*
scope.
Normally, the *buffer* scope keyword means the scope associated with the Normally, the *buffer* scope keyword means the scope associated with the
currently active buffer, but it's possible to specify any existing buffer by currently active buffer, but it's possible to specify any existing buffer by

View File

@ -48,7 +48,7 @@ define-command terminal -params 1.. -docstring %{
Example usage: Example usage:
terminal sh terminal sh
with-option windowing_placement horizontal terminal sh evaluate-commands %{ set local windowing_placement horizontal; terminal sh }
See also the 'new' command. See also the 'new' command.
} %{ } %{
@ -56,29 +56,6 @@ define-command terminal -params 1.. -docstring %{
} }
complete-command terminal shell complete-command terminal shell
# TODO Move this?
define-command with-option -params 3.. -docstring %{
with-option <option_name> <new_value> <command> [<arguments>]: evaluate a command with a modified option
} %{
evaluate-commands -save-regs s %{
evaluate-commands set-register s %exp{%%opt{%arg{1}}}
set-option current %arg{1} %arg{2}
try %{
evaluate-commands %sh{
shift 2
for arg
do
printf "'%s' " "$(printf %s "$arg" | sed "s/'/''/g")"
done
}
} catch %{
set-option current %arg{1} %reg{s}
fail "with-option: %val{error}"
}
set-option current %arg{1} %reg{s}
}
}
hook -group windowing global KakBegin .* %{ hook -group windowing global KakBegin .* %{
evaluate-commands %sh{ evaluate-commands %sh{
set -- ${kak_opt_windowing_modules} set -- ${kak_opt_windowing_modules}

View File

@ -12,6 +12,9 @@ class AliasRegistry : public SafeCountable
{ {
public: public:
AliasRegistry(AliasRegistry& parent) : SafeCountable{}, m_parent(&parent) {} AliasRegistry(AliasRegistry& parent) : SafeCountable{}, m_parent(&parent) {}
void reparent(AliasRegistry& parent) { m_parent = &parent; }
void add_alias(String alias, String command); void add_alias(String alias, String command);
void remove_alias(StringView alias); void remove_alias(StringView alias);
StringView operator[](StringView alias) const; StringView operator[](StringView alias) const;

View File

@ -48,6 +48,24 @@ namespace Kakoune
extern const char* version; extern const char* version;
struct LocalScope : Scope
{
LocalScope(Context& context)
: Scope(context.scope()), m_context{context}
{
m_context.m_local_scopes.push_back(this);
}
~LocalScope()
{
kak_assert(not m_context.m_local_scopes.empty() and m_context.m_local_scopes.back() == this);
m_context.m_local_scopes.pop_back();
}
private:
Context& m_context;
};
namespace namespace
{ {
@ -215,21 +233,21 @@ const ParameterDesc double_params{ {}, ParameterDesc::Flags::None, 2, 2 };
static Completions complete_scope(const Context&, CompletionFlags, static Completions complete_scope(const Context&, CompletionFlags,
StringView prefix, ByteCount cursor_pos) StringView prefix, ByteCount cursor_pos)
{ {
static constexpr StringView scopes[] = { "global", "buffer", "window", }; static constexpr StringView scopes[] = { "global", "buffer", "window", "local"};
return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) }; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };
} }
static Completions complete_scope_including_current(const Context&, CompletionFlags, static Completions complete_scope_including_current(const Context&, CompletionFlags,
StringView prefix, ByteCount cursor_pos) StringView prefix, ByteCount cursor_pos)
{ {
static constexpr StringView scopes[] = { "global", "buffer", "window", "current" }; static constexpr StringView scopes[] = { "global", "buffer", "window", "local", "current" };
return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) }; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };
} }
static Completions complete_scope_no_global(const Context&, CompletionFlags, static Completions complete_scope_no_global(const Context&, CompletionFlags,
StringView prefix, ByteCount cursor_pos) StringView prefix, ByteCount cursor_pos)
{ {
static constexpr StringView scopes[] = { "buffer", "window", "current" }; static constexpr StringView scopes[] = { "buffer", "window", "local", "current" };
return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) }; return { 0_byte, cursor_pos, complete(prefix, cursor_pos, scopes) };
} }
@ -395,6 +413,8 @@ Scope* get_scope_ifp(StringView scope, const Context& context)
return &context.buffer(); return &context.buffer();
else if (prefix_match("window", scope)) else if (prefix_match("window", scope))
return &context.window(); return &context.window();
else if (prefix_match("local", scope))
return context.local_scope();
else if (prefix_match(scope, "buffer=")) else if (prefix_match(scope, "buffer="))
return &BufferManager::instance().get_buffer(scope.substr(7_byte)); return &BufferManager::instance().get_buffer(scope.substr(7_byte));
return nullptr; return nullptr;
@ -2168,6 +2188,7 @@ const CommandDesc execute_keys_cmd = {
} }
}; };
const CommandDesc evaluate_commands_cmd = { const CommandDesc evaluate_commands_cmd = {
"evaluate-commands", "evaluate-commands",
"eval", "eval",
@ -2186,6 +2207,7 @@ const CommandDesc evaluate_commands_cmd = {
const bool no_hooks = context.hooks_disabled() or parser.get_switch("no-hooks"); const bool no_hooks = context.hooks_disabled() or parser.get_switch("no-hooks");
ScopedSetBool disable_hooks(context.hooks_disabled(), no_hooks); ScopedSetBool disable_hooks(context.hooks_disabled(), no_hooks);
LocalScope local_scope{context};
if (parser.get_switch("verbatim")) if (parser.get_switch("verbatim"))
CommandManager::instance().execute_single_command(parser | gather<Vector<String>>(), context, shell_context); CommandManager::instance().execute_single_command(parser | gather<Vector<String>>(), context, shell_context);
else else

View File

@ -51,8 +51,10 @@ Client& Context::client() const
return *m_client; return *m_client;
} }
Scope& Context::scope() const Scope& Context::scope(bool allow_local) const
{ {
if (allow_local and not m_local_scopes.empty())
return *m_local_scopes.back();
if (has_window()) if (has_window())
return window(); return window();
if (has_buffer()) if (has_buffer())
@ -70,6 +72,8 @@ void Context::set_window(Window& window)
{ {
kak_assert(&window.buffer() == &buffer()); kak_assert(&window.buffer() == &buffer());
m_window.reset(&window); m_window.reset(&window);
if (not m_local_scopes.empty())
m_local_scopes.front()->reparent(window);
} }
void Context::print_status(DisplayLine status) const void Context::print_status(DisplayLine status) const
@ -314,6 +318,8 @@ void Context::change_buffer(Buffer& buffer, Optional<FunctionRef<void()>> set_se
ScopedSelectionEdition selection_edition{*this}; ScopedSelectionEdition selection_edition{*this};
selections_write_only() = SelectionList{buffer, Selection{}}; selections_write_only() = SelectionList{buffer, Selection{}};
} }
if (not m_local_scopes.empty())
m_local_scopes.front()->reparent(buffer);
} }
if (has_input_handler()) if (has_input_handler())

View File

@ -42,6 +42,8 @@ private:
using LastSelectFunc = std::function<void (Context&)>; using LastSelectFunc = std::function<void (Context&)>;
struct LocalScope;
// A Context is used to access non singleton objects for various services // A Context is used to access non singleton objects for various services
// in commands. // in commands.
// //
@ -97,7 +99,10 @@ public:
void set_client(Client& client); void set_client(Client& client);
void set_window(Window& window); void set_window(Window& window);
Scope& scope() const; friend struct LocalScope;
Scope& scope(bool allow_local = true) const;
Scope* local_scope() const { return m_local_scopes.empty() ? nullptr : m_local_scopes.back(); }
OptionManager& options() const { return scope().options(); } OptionManager& options() const { return scope().options(); }
HookManager& hooks() const { return scope().hooks(); } HookManager& hooks() const { return scope().hooks(); }
@ -155,6 +160,7 @@ private:
SafePtr<InputHandler> m_input_handler; SafePtr<InputHandler> m_input_handler;
SafePtr<Window> m_window; SafePtr<Window> m_window;
SafePtr<Client> m_client; SafePtr<Client> m_client;
std::vector<Scope*> m_local_scopes;
class SelectionHistory { class SelectionHistory {
public: public:

View File

@ -24,6 +24,8 @@ class FaceRegistry : public SafeCountable
public: public:
FaceRegistry(FaceRegistry& parent) : SafeCountable{}, m_parent(&parent) {} FaceRegistry(FaceRegistry& parent) : SafeCountable{}, m_parent(&parent) {}
void reparent(FaceRegistry& parent) { m_parent = &parent; }
Face operator[](StringView facedesc) const; Face operator[](StringView facedesc) const;
Face operator[](const FaceSpec& facespec) const; Face operator[](const FaceSpec& facespec) const;
void add_face(StringView name, StringView facedesc, bool override = false); void add_face(StringView name, StringView facedesc, bool override = false);

View File

@ -43,6 +43,8 @@ class Highlighters : public SafeCountable
public: public:
Highlighters(Highlighters& parent) : SafeCountable{}, m_parent{&parent}, m_group{HighlightPass::All} {} Highlighters(Highlighters& parent) : SafeCountable{}, m_parent{&parent}, m_group{HighlightPass::All} {}
void reparent(Highlighters& parent) { m_parent = &parent; }
HighlighterGroup& group() { return m_group; } HighlighterGroup& group() { return m_group; }
const HighlighterGroup& group() const { return m_group; } const HighlighterGroup& group() const { return m_group; }

View File

@ -123,6 +123,8 @@ public:
HookManager(HookManager& parent); HookManager(HookManager& parent);
~HookManager(); ~HookManager();
void reparent(HookManager& parent) { m_parent = &parent; }
void add_hook(Hook hook, String group, HookFlags flags, void add_hook(Hook hook, String group, HookFlags flags,
Regex filter, String commands, Context& context); Regex filter, String commands, Context& context);
void remove_hooks(const Regex& regex); void remove_hooks(const Regex& regex);

View File

@ -403,7 +403,10 @@ InsertCompletion complete_line(const SelectionList& sels,
} }
InsertCompleter::InsertCompleter(Context& context) InsertCompleter::InsertCompleter(Context& context)
: m_context(context), m_options(context.options()), m_faces(context.faces()) : m_context(context),
// local scopes might go away before completion ends, make sure to register on a long lived one
m_options(context.scope(false).options()),
m_faces(context.scope(false).faces())
{ {
m_options.register_watcher(*this); m_options.register_watcher(*this);
} }

View File

@ -31,6 +31,8 @@ class KeymapManager
public: public:
KeymapManager(KeymapManager& parent) : m_parent(&parent) {} KeymapManager(KeymapManager& parent) : m_parent(&parent) {}
void reparent(KeymapManager& parent) { m_parent = &parent; }
using KeyList = Vector<Key, MemoryDomain::Mapping>; using KeyList = Vector<Key, MemoryDomain::Mapping>;
void map_key(Key key, KeymapMode mode, KeyList mapping, String docstring); void map_key(Key key, KeymapMode mode, KeyList mapping, String docstring);
void unmap_key(Key key, KeymapMode mode); void unmap_key(Key key, KeymapMode mode);

View File

@ -28,6 +28,15 @@ OptionManager::~OptionManager()
kak_assert(m_watchers.empty()); kak_assert(m_watchers.empty());
} }
void OptionManager::reparent(OptionManager& parent)
{
if (m_parent)
m_parent->unregister_watcher(*this);
m_parent = &parent;
parent.register_watcher(*this);
}
void OptionManager::register_watcher(OptionManagerWatcher& watcher) const void OptionManager::register_watcher(OptionManagerWatcher& watcher) const
{ {
kak_assert(not contains(m_watchers, &watcher)); kak_assert(not contains(m_watchers, &watcher));

View File

@ -89,6 +89,8 @@ public:
OptionManager(OptionManager& parent); OptionManager(OptionManager& parent);
~OptionManager(); ~OptionManager();
void reparent(OptionManager& parent);
Option& operator[] (StringView name); Option& operator[] (StringView name);
const Option& operator[] (StringView name) const; const Option& operator[] (StringView name) const;
Option& get_local_option(StringView name); Option& get_local_option(StringView name);

View File

@ -4,6 +4,16 @@
namespace Kakoune namespace Kakoune
{ {
void Scope::reparent(Scope& parent)
{
m_options.reparent(parent.m_options);
m_hooks.reparent(parent.m_hooks);
m_keymaps.reparent(parent.m_keymaps);
m_aliases.reparent(parent.m_aliases);
m_faces.reparent(parent.m_faces);
m_highlighters.reparent(parent.m_highlighters);
}
GlobalScope::GlobalScope() GlobalScope::GlobalScope()
: m_option_registry(m_options) : m_option_registry(m_options)
{ {

View File

@ -36,6 +36,8 @@ public:
Highlighters& highlighters() { return m_highlighters; } Highlighters& highlighters() { return m_highlighters; }
const Highlighters& highlighters() const { return m_highlighters; } const Highlighters& highlighters() const { return m_highlighters; }
void reparent(Scope& parent);
private: private:
friend class GlobalScope; friend class GlobalScope;
Scope() = default; Scope() = default;