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*::
|
||||
Execute keys and trigger existing 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 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:
|
||||
|
||||
--------------------------
|
||||
window ]> buffer ]> global
|
||||
--------------------------
|
||||
-----------------------------------
|
||||
local ]> window ]> buffer ]> global
|
||||
-----------------------------------
|
||||
|
||||
The above priority line implies that objects can have individual values that
|
||||
will be resolved first in the *window* scope (highest priority), then in
|
||||
the *buffer* scope, and finally in the *global* scope (lowest priority).
|
||||
The above priority line implies that objects can have individual values
|
||||
that will be resolved first in the *local* scope (if it exists), then the
|
||||
*window* scope, then in the *buffer* scope, and finally in the *global*
|
||||
scope.
|
||||
|
||||
Normally, the *buffer* scope keyword means the scope associated with the
|
||||
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:
|
||||
|
||||
terminal sh
|
||||
with-option windowing_placement horizontal terminal sh
|
||||
evaluate-commands %{ set local windowing_placement horizontal; terminal sh }
|
||||
|
||||
See also the 'new' command.
|
||||
} %{
|
||||
|
@ -56,29 +56,6 @@ define-command terminal -params 1.. -docstring %{
|
|||
}
|
||||
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 .* %{
|
||||
evaluate-commands %sh{
|
||||
set -- ${kak_opt_windowing_modules}
|
||||
|
|
|
@ -12,6 +12,9 @@ class AliasRegistry : public SafeCountable
|
|||
{
|
||||
public:
|
||||
AliasRegistry(AliasRegistry& parent) : SafeCountable{}, m_parent(&parent) {}
|
||||
|
||||
void reparent(AliasRegistry& parent) { m_parent = &parent; }
|
||||
|
||||
void add_alias(String alias, String command);
|
||||
void remove_alias(StringView alias);
|
||||
StringView operator[](StringView alias) const;
|
||||
|
|
|
@ -48,6 +48,24 @@ namespace Kakoune
|
|||
|
||||
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
|
||||
{
|
||||
|
||||
|
@ -215,21 +233,21 @@ const ParameterDesc double_params{ {}, ParameterDesc::Flags::None, 2, 2 };
|
|||
static Completions complete_scope(const Context&, CompletionFlags,
|
||||
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) };
|
||||
}
|
||||
|
||||
static Completions complete_scope_including_current(const Context&, CompletionFlags,
|
||||
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) };
|
||||
}
|
||||
|
||||
static Completions complete_scope_no_global(const Context&, CompletionFlags,
|
||||
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) };
|
||||
}
|
||||
|
||||
|
@ -395,6 +413,8 @@ Scope* get_scope_ifp(StringView scope, const Context& context)
|
|||
return &context.buffer();
|
||||
else if (prefix_match("window", scope))
|
||||
return &context.window();
|
||||
else if (prefix_match("local", scope))
|
||||
return context.local_scope();
|
||||
else if (prefix_match(scope, "buffer="))
|
||||
return &BufferManager::instance().get_buffer(scope.substr(7_byte));
|
||||
return nullptr;
|
||||
|
@ -2168,6 +2188,7 @@ const CommandDesc execute_keys_cmd = {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
const CommandDesc evaluate_commands_cmd = {
|
||||
"evaluate-commands",
|
||||
"eval",
|
||||
|
@ -2186,6 +2207,7 @@ const CommandDesc evaluate_commands_cmd = {
|
|||
const bool no_hooks = context.hooks_disabled() or parser.get_switch("no-hooks");
|
||||
ScopedSetBool disable_hooks(context.hooks_disabled(), no_hooks);
|
||||
|
||||
LocalScope local_scope{context};
|
||||
if (parser.get_switch("verbatim"))
|
||||
CommandManager::instance().execute_single_command(parser | gather<Vector<String>>(), context, shell_context);
|
||||
else
|
||||
|
|
|
@ -51,8 +51,10 @@ Client& Context::client() const
|
|||
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())
|
||||
return window();
|
||||
if (has_buffer())
|
||||
|
@ -70,6 +72,8 @@ void Context::set_window(Window& window)
|
|||
{
|
||||
kak_assert(&window.buffer() == &buffer());
|
||||
m_window.reset(&window);
|
||||
if (not m_local_scopes.empty())
|
||||
m_local_scopes.front()->reparent(window);
|
||||
}
|
||||
|
||||
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};
|
||||
selections_write_only() = SelectionList{buffer, Selection{}};
|
||||
}
|
||||
if (not m_local_scopes.empty())
|
||||
m_local_scopes.front()->reparent(buffer);
|
||||
}
|
||||
|
||||
if (has_input_handler())
|
||||
|
|
|
@ -42,6 +42,8 @@ private:
|
|||
|
||||
using LastSelectFunc = std::function<void (Context&)>;
|
||||
|
||||
struct LocalScope;
|
||||
|
||||
// A Context is used to access non singleton objects for various services
|
||||
// in commands.
|
||||
//
|
||||
|
@ -97,7 +99,10 @@ public:
|
|||
void set_client(Client& client);
|
||||
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(); }
|
||||
HookManager& hooks() const { return scope().hooks(); }
|
||||
|
@ -155,6 +160,7 @@ private:
|
|||
SafePtr<InputHandler> m_input_handler;
|
||||
SafePtr<Window> m_window;
|
||||
SafePtr<Client> m_client;
|
||||
std::vector<Scope*> m_local_scopes;
|
||||
|
||||
class SelectionHistory {
|
||||
public:
|
||||
|
|
|
@ -24,6 +24,8 @@ class FaceRegistry : public SafeCountable
|
|||
public:
|
||||
FaceRegistry(FaceRegistry& parent) : SafeCountable{}, m_parent(&parent) {}
|
||||
|
||||
void reparent(FaceRegistry& parent) { m_parent = &parent; }
|
||||
|
||||
Face operator[](StringView facedesc) const;
|
||||
Face operator[](const FaceSpec& facespec) const;
|
||||
void add_face(StringView name, StringView facedesc, bool override = false);
|
||||
|
|
|
@ -43,6 +43,8 @@ class Highlighters : public SafeCountable
|
|||
public:
|
||||
Highlighters(Highlighters& parent) : SafeCountable{}, m_parent{&parent}, m_group{HighlightPass::All} {}
|
||||
|
||||
void reparent(Highlighters& parent) { m_parent = &parent; }
|
||||
|
||||
HighlighterGroup& group() { return m_group; }
|
||||
const HighlighterGroup& group() const { return m_group; }
|
||||
|
||||
|
|
|
@ -123,6 +123,8 @@ public:
|
|||
HookManager(HookManager& parent);
|
||||
~HookManager();
|
||||
|
||||
void reparent(HookManager& parent) { m_parent = &parent; }
|
||||
|
||||
void add_hook(Hook hook, String group, HookFlags flags,
|
||||
Regex filter, String commands, Context& context);
|
||||
void remove_hooks(const Regex& regex);
|
||||
|
|
|
@ -403,7 +403,10 @@ InsertCompletion complete_line(const SelectionList& sels,
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ class KeymapManager
|
|||
public:
|
||||
KeymapManager(KeymapManager& parent) : m_parent(&parent) {}
|
||||
|
||||
void reparent(KeymapManager& parent) { m_parent = &parent; }
|
||||
|
||||
using KeyList = Vector<Key, MemoryDomain::Mapping>;
|
||||
void map_key(Key key, KeymapMode mode, KeyList mapping, String docstring);
|
||||
void unmap_key(Key key, KeymapMode mode);
|
||||
|
|
|
@ -28,6 +28,15 @@ OptionManager::~OptionManager()
|
|||
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
|
||||
{
|
||||
kak_assert(not contains(m_watchers, &watcher));
|
||||
|
|
|
@ -89,6 +89,8 @@ public:
|
|||
OptionManager(OptionManager& parent);
|
||||
~OptionManager();
|
||||
|
||||
void reparent(OptionManager& parent);
|
||||
|
||||
Option& operator[] (StringView name);
|
||||
const Option& operator[] (StringView name) const;
|
||||
Option& get_local_option(StringView name);
|
||||
|
|
10
src/scope.cc
10
src/scope.cc
|
@ -4,6 +4,16 @@
|
|||
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()
|
||||
: m_option_registry(m_options)
|
||||
{
|
||||
|
|
|
@ -36,6 +36,8 @@ public:
|
|||
Highlighters& highlighters() { return m_highlighters; }
|
||||
const Highlighters& highlighters() const { return m_highlighters; }
|
||||
|
||||
void reparent(Scope& parent);
|
||||
|
||||
private:
|
||||
friend class GlobalScope;
|
||||
Scope() = default;
|
||||
|
|
Loading…
Reference in New Issue
Block a user