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:
parent
b1c114bf6d
commit
3d7d0fecca
|
@ -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`>>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
10
src/scope.cc
10
src/scope.cc
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user