gutter and keys

This commit is contained in:
2026-10-31 11:20:28 +03:00
parent afd7600150
commit 16e88117d5
14 changed files with 1844 additions and 558 deletions
+378 -80
View File
@@ -36,12 +36,12 @@ fn lua_lsp_api_lists_no_servers_and_validates_config() {
std::fs::write(
&script,
r#"
assert(#app:lsp_servers() == 0)
assert(app:stop_lsp("missing") == false)
assert(app:restart_lsp("missing") == false)
assert(#app.editor:lsp_servers() == 0)
assert(app.editor:stop_lsp("missing") == false)
assert(app.editor:restart_lsp("missing") == false)
-- Без cmd сервер не запускается, а ошибка объясняет, чего не хватает.
local ok, err = pcall(function() app:run_lsp({ name = "broken" }) end)
local ok, err = pcall(function() app.editor:run_lsp({ name = "broken" }) end)
assert(not ok)
assert(tostring(err):find("cmd", 1, true), tostring(err))
"#,
@@ -127,7 +127,7 @@ fn api_lists_tabs_and_closes_them_by_number() {
let workspace = Rc::new(RefCell::new(Workspace::default()));
let (api, _keymap) = install_api(workspace.clone());
let tabs = api.tabs();
let tabs = api.editor().tabs();
assert_eq!(
tabs.iter()
.map(|tab| (tab.index, tab.title.as_str()))
@@ -136,13 +136,13 @@ fn api_lists_tabs_and_closes_them_by_number() {
);
// Несуществующий номер ничего не закрывает.
assert!(!api.close_tab(99));
assert_eq!(api.tabs().len(), 1);
assert!(!api.editor().close_tab(99));
assert_eq!(api.editor().tabs().len(), 1);
api.open_tab();
assert_eq!(api.tabs().len(), 2);
assert!(api.close_tab(1));
assert_eq!(api.tabs().len(), 1);
api.editor().open_tab();
assert_eq!(api.editor().tabs().len(), 2);
assert!(api.editor().close_tab(1));
assert_eq!(api.editor().tabs().len(), 1);
assert_eq!(workspace.borrow().documents().len(), 1);
}
@@ -152,26 +152,26 @@ fn api_sets_and_clears_tab_icons() {
let (api, _keymap) = install_api(workspace);
// По умолчанию у всех вкладок иконка file.
assert_eq!(api.tabs()[0].icon, "file");
assert_eq!(api.tab_icon(0).as_deref(), Some("file"));
assert_eq!(api.editor().tabs()[0].icon, "file");
assert_eq!(api.editor().tab_icon(0).as_deref(), Some("file"));
assert!(api.set_tab_icon(0, "file-code"));
assert_eq!(api.tabs()[0].icon, "file-code");
assert_eq!(api.tab_icon(0).as_deref(), Some("file-code"));
assert!(api.editor().set_tab_icon(0, "file-code"));
assert_eq!(api.editor().tabs()[0].icon, "file-code");
assert_eq!(api.editor().tab_icon(0).as_deref(), Some("file-code"));
// Имя нормализуется, а вкладка возвращает канонический вариант.
assert!(api.set_tab_icon(0, "icons/book-open.svg"));
assert_eq!(api.tab_icon(0).as_deref(), Some("book-open"));
assert!(api.editor().set_tab_icon(0, "icons/book-open.svg"));
assert_eq!(api.editor().tab_icon(0).as_deref(), Some("book-open"));
// Неизвестная иконка и несуществующая вкладка ничего не меняют.
assert!(!api.set_tab_icon(0, "definitely-not-an-icon"));
assert_eq!(api.tab_icon(0).as_deref(), Some("book-open"));
assert!(!api.set_tab_icon(99, "file"));
assert!(!api.editor().set_tab_icon(0, "definitely-not-an-icon"));
assert_eq!(api.editor().tab_icon(0).as_deref(), Some("book-open"));
assert!(!api.editor().set_tab_icon(99, "file"));
assert!(api.clear_tab_icon(0));
assert_eq!(api.tab_icon(0).as_deref(), Some("file"));
assert!(!api.clear_tab_icon(99));
assert_eq!(api.tab_icon(99), None);
assert!(api.editor().clear_tab_icon(0));
assert_eq!(api.editor().tab_icon(0).as_deref(), Some("file"));
assert!(!api.editor().clear_tab_icon(99));
assert_eq!(api.editor().tab_icon(99), None);
}
#[test]
@@ -184,14 +184,14 @@ fn lua_script_can_set_tab_icons() {
std::fs::write(
&script,
r#"
assert(app:tab_icon(0) == "file")
assert(app:set_tab_icon(0, "book-open"))
assert(app:tab_icon(0) == "book-open")
app:open_tab()
assert(app:tabs()[2].icon == "file")
assert(not app:set_tab_icon(0, "no-such-icon"))
assert(app:clear_tab_icon(0))
assert(app:tab_icon(0) == "file")
assert(app.editor:tab_icon(0) == "file")
assert(app.editor:set_tab_icon(0, "book-open"))
assert(app.editor:tab_icon(0) == "book-open")
app.editor:open_tab()
assert(app.editor:tabs()[2].icon == "file")
assert(not app.editor:set_tab_icon(0, "no-such-icon"))
assert(app.editor:clear_tab_icon(0))
assert(app.editor:tab_icon(0) == "file")
"#,
)
.unwrap();
@@ -203,6 +203,87 @@ fn lua_script_can_set_tab_icons() {
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn api_sets_and_clears_gutter_icons_per_document() {
let workspace = Rc::new(RefCell::new(Workspace::default()));
let (api, _keymap) = install_api(workspace);
assert_eq!(api.editor().gutter_icon(0), None);
assert!(api.editor().set_gutter_icon(2, "circle", None));
assert_eq!(api.editor().gutter_icon(2).as_deref(), Some("circle"));
assert_eq!(api.editor().gutter_color(2), None);
// Неизвестная иконка и некорректный цвет ничего не меняют.
assert!(
!api.editor()
.set_gutter_icon(3, "definitely-not-an-icon", None)
);
assert!(
!api.editor()
.set_gutter_icon(3, "circle", Some("not-a-color"))
);
assert_eq!(api.editor().gutter_icon(3), None);
assert!(api.editor().set_gutter_icon(2, "play", Some("#ff0000")));
assert_eq!(api.editor().gutter_icon(2).as_deref(), Some("play"));
assert_eq!(api.editor().gutter_color(2).as_deref(), Some("#ff0000"));
// Иконки принадлежат вкладке, а не приложению.
api.editor().open_tab();
assert_eq!(api.editor().gutter_icon(2), None);
assert!(api.editor().set_gutter_icon(0, "circle", None));
api.editor().set_active_tab(0);
assert_eq!(api.editor().gutter_icon(2).as_deref(), Some("play"));
assert!(api.editor().clear_gutter_icon(2));
assert!(!api.editor().clear_gutter_icon(2));
assert_eq!(api.editor().gutter_icon(2), None);
api.editor().clear_gutter();
assert_eq!(api.editor().gutter_icon(0), None);
}
#[test]
fn lua_script_can_set_gutter_icons() {
let workspace = Rc::new(RefCell::new(Workspace::default()));
let (api, _keymap) = install_api(workspace);
let dir = temp_script_dir("gutter-icons");
let script = dir.join("gutter.lua");
std::fs::write(
&script,
r##"
assert(app.editor:gutter_icon(0) == nil)
assert(app.editor:set_gutter_icon(4, "circle"))
assert(app.editor:gutter_icon(4) == "circle")
assert(app.editor:gutter_color(4) == nil)
assert(app.editor:set_gutter_icon(4, "play", "#00ff00"))
assert(app.editor:gutter_icon(4) == "play")
assert(app.editor:gutter_color(4) == "#00ff00")
assert(not app.editor:set_gutter_icon(1, "no-such-icon"))
assert(not app.editor:set_gutter_icon(1, "circle", "oops"))
assert(app.editor:gutter_icon(1) == nil)
assert(app.editor:clear_gutter_icon(4))
assert(not app.editor:clear_gutter_icon(4))
assert(app.editor:gutter_icon(4) == nil)
app.editor:set_gutter_icon(0, "circle")
app.editor:clear_gutter()
assert(app.editor:gutter_icon(0) == nil)
"##,
)
.unwrap();
let engine = ScriptEngine::new(api).expect("движок Lua");
engine.run_file(&script).expect("скрипт gutter");
drop(engine);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn api_save_writes_active_tab_to_file_named_after_it() {
// Тест пишет во временный файл, а не в каталог проекта.
@@ -223,7 +304,7 @@ fn api_save_writes_active_tab_to_file_named_after_it() {
assert!(workspace.borrow().active().is_dirty());
let (api, _keymap) = install_api(workspace.clone());
api.save().expect("сохранение должно удаться");
api.editor().save().expect("сохранение должно удаться");
assert_eq!(
std::fs::read_to_string(&path).expect("файл должен существовать"),
@@ -239,19 +320,19 @@ fn api_switches_tabs_forward_and_back_in_a_cycle() {
let workspace = Rc::new(RefCell::new(Workspace::default()));
let (api, _keymap) = install_api(workspace.clone());
api.open_tab();
api.set_active_tab(0);
assert_eq!(api.active_tab_index(), 0);
api.editor().open_tab();
api.editor().set_active_tab(0);
assert_eq!(api.editor().active_tab_index(), 0);
assert_eq!(api.next_tab(), 1);
assert_eq!(api.active_tab_index(), 1);
assert_eq!(api.editor().next_tab(), 1);
assert_eq!(api.editor().active_tab_index(), 1);
// С последней вкладки следующая — первая.
assert_eq!(api.next_tab(), 0);
assert_eq!(api.editor().next_tab(), 0);
// С первой вкладки предыдущая — последняя.
assert_eq!(api.previous_tab(), 1);
assert_eq!(api.previous_tab(), 0);
assert_eq!(api.editor().previous_tab(), 1);
assert_eq!(api.editor().previous_tab(), 0);
assert_eq!(workspace.borrow().active_index(), 0);
}
@@ -572,7 +653,7 @@ fn lua_diagnostics_handler_receives_every_server_and_file() {
std::fs::write(
&script,
r#"
app:on_lsp_diagnostics(0, function(servers)
app.editor:on_lsp_diagnostics(0, function(servers)
assert(#servers == 1, #servers)
local entry = servers[1]
assert(entry.server == "test")
@@ -589,7 +670,7 @@ fn lua_diagnostics_handler_receives_every_server_and_file() {
assert(first.message == "плохо")
assert(entry.diagnostics[2].severity_name == "warning")
app:open_tab()
app.editor:open_tab()
end)
"#,
)
@@ -597,7 +678,10 @@ fn lua_diagnostics_handler_receives_every_server_and_file() {
let engine = ScriptEngine::new(api.clone()).expect("движок Lua");
engine.run_file(&script).expect("регистрация обработчика");
assert_eq!(api.diagnostics_delay(), Some(std::time::Duration::ZERO));
assert_eq!(
api.editor().diagnostics_delay(),
Some(std::time::Duration::ZERO)
);
let uri = crate::lsp::path_to_uri(Path::new("/tmp/qode-diagnostics.go"));
let diagnostics = vec![
@@ -623,7 +707,7 @@ fn lua_diagnostics_handler_receives_every_server_and_file() {
assert_eq!(report[0].path.as_deref(), Some("/tmp/qode-diagnostics.go"));
assert_eq!(report[0].diagnostics.len(), 2);
assert!(api.emit_diagnostics(report));
assert!(api.editor().emit_diagnostics(report));
assert_eq!(
workspace.borrow().documents().len(),
2,
@@ -696,13 +780,13 @@ fn lua_script_can_include_files_and_control_the_app() {
let main = dir.join("main.lua");
std::fs::write(
&helper,
"function lua_config() app:set_active_text(\"from lua\") end\n",
"function lua_config() app.editor:set_active_text(\"from lua\") end\n",
)
.expect("helper.lua");
std::fs::write(
&main,
format!(
"include({:?})\nlua_config()\napp:open_tab()\napp:set_language(1, \"python\")\n",
"include({:?})\nlua_config()\napp.editor:open_tab()\napp.editor:set_language(1, \"python\")\n",
helper.to_string_lossy()
),
)
@@ -720,12 +804,12 @@ fn lua_script_can_include_files_and_control_the_app() {
assert_eq!(
state.active_index(),
1,
"app:open_tab() не переключил вкладку"
"app.editor:open_tab() не переключил вкладку"
);
assert_eq!(
state.documents()[1].language,
Some(Language::Python),
"app:set_language() не включил подсветку из Lua"
"app.editor:set_language() не включил подсветку из Lua"
);
let _ = std::fs::remove_dir_all(&dir);
@@ -740,7 +824,7 @@ fn api_opens_file_from_disk_and_detects_language() {
let path = dir.join("main.go");
std::fs::write(&path, "package main\n").unwrap();
let index = api.open(&path).expect("файл открыт");
let index = api.editor().open(&path).expect("файл открыт");
{
let state = workspace.borrow();
assert_eq!(index, state.active_index());
@@ -752,7 +836,7 @@ fn api_opens_file_from_disk_and_detects_language() {
// Повторное открытие чистого файла переиспользует вкладку.
let tabs = workspace.borrow().documents().len();
assert_eq!(api.open(&path).unwrap(), index);
assert_eq!(api.editor().open(&path).unwrap(), index);
assert_eq!(workspace.borrow().documents().len(), tabs);
let _ = std::fs::remove_dir_all(&dir);
@@ -770,8 +854,14 @@ fn lua_open_controls_focus_and_cursor() {
// Без второго аргумента фокус остаётся прежним.
let plain = dir.join("plain.lua");
std::fs::write(&plain, format!("app:open({:?})\n", path.to_string_lossy())).unwrap();
engine.run_file(&plain).expect("app:open без позиции");
std::fs::write(
&plain,
format!("app.editor:open({:?})\n", path.to_string_lossy()),
)
.unwrap();
engine
.run_file(&plain)
.expect("app.editor:open без позиции");
assert_eq!(
workspace.borrow_mut().take_focus_after_sync(),
Some(FocusAfterSync::Keep)
@@ -781,10 +871,12 @@ fn lua_open_controls_focus_and_cursor() {
let focus = dir.join("focus.lua");
std::fs::write(
&focus,
format!("app:open({:?}, {{}})\n", path.to_string_lossy()),
format!("app.editor:open({:?}, {{}})\n", path.to_string_lossy()),
)
.unwrap();
engine.run_file(&focus).expect("app:open с пустой таблицей");
engine
.run_file(&focus)
.expect("app.editor:open с пустой таблицей");
assert_eq!(
workspace.borrow_mut().take_focus_after_sync(),
Some(FocusAfterSync::Editor(None))
@@ -794,10 +886,10 @@ fn lua_open_controls_focus_and_cursor() {
let at = dir.join("at.lua");
std::fs::write(
&at,
format!("app:open({:?}, {{2, 5}})\n", path.to_string_lossy()),
format!("app.editor:open({:?}, {{2, 5}})\n", path.to_string_lossy()),
)
.unwrap();
engine.run_file(&at).expect("app:open с позицией");
engine.run_file(&at).expect("app.editor:open с позицией");
assert_eq!(
workspace.borrow_mut().take_focus_after_sync(),
Some(FocusAfterSync::Editor(Some(19)))
@@ -815,7 +907,7 @@ fn api_open_missing_file_creates_an_empty_buffer() {
let dir = temp_script_dir("open-missing");
let path = dir.join("new.go");
let index = api.open(&path).expect("новый буфер");
let index = api.editor().open(&path).expect("новый буфер");
let state = workspace.borrow();
assert_eq!(index, state.active_index());
assert_eq!(state.active().name, path.to_string_lossy());
@@ -839,14 +931,14 @@ fn lua_app_open_opens_a_file_and_returns_its_tab() {
std::fs::write(
&script,
format!(
"local index = app:open({:?})\nassert(index == app:active_tab())\n",
"local index = app.editor:open({:?})\nassert(index == app.editor:active_tab())\n",
path.to_string_lossy()
),
)
.unwrap();
let engine = ScriptEngine::new_in(api, &dir).expect("движок Lua");
engine.run_file(&script).expect("app:open из Lua");
engine.run_file(&script).expect("app.editor:open из Lua");
let state = workspace.borrow();
assert_eq!(state.active().name, path.to_string_lossy());
@@ -866,7 +958,7 @@ fn lua_engine_loads_init_from_a_custom_config_dir() {
let dir = temp_script_dir("config-dir");
std::fs::write(
dir.join("helper.lua"),
"function config_value() app:set_active_text(\"custom config\") end\n",
"function config_value() app.editor:set_active_text(\"custom config\") end\n",
)
.expect("helper.lua");
std::fs::write(
@@ -895,11 +987,11 @@ fn lua_key_bindings_match_press_release_and_modifiers() {
std::fs::write(
&script,
r#"
app:map_key("a", function() app:set_active_text("a pressed") end)
app:map_key("!1", function() app:set_active_text("1 released") end)
app:map_key("<S-c>", function() app:set_active_text("shift c") end)
app:map_key("<C-z>", function() app:set_active_text("ctrl z") end)
app:map_key("<A-!r>", function() app:set_active_text("alt r released") end)
app:map_key("", "a", function() app.editor:set_active_text("a pressed") end)
app:map_key("", "!1", function() app.editor:set_active_text("1 released") end)
app:map_key("", "<S-c>", function() app.editor:set_active_text("shift c") end)
app:map_key("", "<C-z>", function() app.editor:set_active_text("ctrl z") end)
app:map_key("", "<A-!r>", function() app.editor:set_active_text("alt r released") end)
"#,
)
.expect("keys.lua");
@@ -997,11 +1089,11 @@ fn lua_keymap_accepts_command_names_and_nil() {
std::fs::write(
&script,
r#"
app:map_key("<C-n>", "next_tab")
app:map_key("", "<C-n>", "next_tab")
app:map_completion_key("<C-n>", "completion_next")
local ok, err = pcall(function()
app:map_key("<C-x>", "no_such_command")
app:map_key("", "<C-x>", "no_such_command")
end)
assert(not ok)
assert(tostring(err):find("доступны", 1, true), tostring(err))
@@ -1042,7 +1134,7 @@ fn lua_keymap_accepts_command_names_and_nil() {
// nil удаляет привязку, не оставляя заглушку.
std::fs::write(
&script,
"app:map_key(\"<C-n>\", nil)\napp:map_completion_key(\"<C-n>\", nil)\n",
"app:map_key(\"\", \"<C-n>\", nil)\napp:map_completion_key(\"<C-n>\", nil)\n",
)
.expect("keys.lua");
engine.run_file(&script).expect("привязки должны удалиться");
@@ -1106,22 +1198,22 @@ fn highlighting_is_off_by_default_and_enabled_through_api() {
let workspace = Rc::new(RefCell::new(Workspace::default()));
let (api, _keymap) = install_api(workspace.clone());
assert_eq!(api.language(0), None);
assert_eq!(api.editor().language(0), None);
assert_eq!(workspace.borrow().documents()[0].language, None);
assert!(api.set_language(0, "rs"));
assert_eq!(api.language(0), Some("rust".to_owned()));
assert!(api.editor().set_language(0, "rs"));
assert_eq!(api.editor().language(0), Some("rust".to_owned()));
assert_eq!(
workspace.borrow().documents()[0].language,
Some(Language::Rust)
);
// Неизвестный язык и несуществующая вкладка.
assert!(!api.set_language(0, "cobol"));
assert!(!api.set_language(99, "rust"));
assert!(!api.editor().set_language(0, "cobol"));
assert!(!api.editor().set_language(99, "rust"));
assert!(api.clear_language(0));
assert_eq!(api.language(0), None);
assert!(api.editor().clear_language(0));
assert_eq!(api.editor().language(0), None);
}
#[test]
@@ -1144,8 +1236,13 @@ fn keymap_bindings_are_parsed_and_replaced() {
.load("return function() end")
.eval::<mlua::Function>()
.unwrap();
keymap.set(KeyContext::Global, plain.clone(), KeyHandler::Lua(first));
keymap.set(KeyContext::Global, plain, KeyHandler::Lua(second));
keymap.set(
KeyContext::Global,
"",
plain.clone(),
KeyHandler::Lua(first),
);
keymap.set(KeyContext::Global, "", plain, KeyHandler::Lua(second));
let stroke = Keystroke {
modifiers: Modifiers::default(),
@@ -1175,7 +1272,7 @@ fn lua_key_bindings_receive_keys_while_editor_is_focused(cx: &mut TestAppContext
let script = dir.join("keys.lua");
std::fs::write(
&script,
r#"app:map_key("<A-r>", function() app:set_active_text("alt r pressed") end)"#,
r#"app:map_key("", "<A-r>", function() app.editor:set_active_text("alt r pressed") end)"#,
)
.expect("keys.lua");
let engine = ScriptEngine::new(AppApi::global()).expect("движок Lua");
@@ -1199,6 +1296,178 @@ fn lua_key_bindings_receive_keys_while_editor_is_focused(cx: &mut TestAppContext
let _ = std::fs::remove_dir_all(&dir);
}
#[gpui_kit::test]
fn editor_and_block_keymaps_depend_on_focus(cx: &mut TestAppContext) {
cx.update(gpui_kit::init);
let (app, visual) = cx.add_window_view(MyApp::new);
let dir = temp_script_dir("focus-keys");
let script = dir.join("keys.lua");
std::fs::write(
&script,
r#"
app:map_key("", "<A-g>", function() app.editor:set_gutter_icon(0, "circle") end)
app.editor:map_key("", "<A-r>", function() app.editor:set_gutter_icon(0, "play") end)
local tree = app.left_panel:add_tree()
tree:append({ { id = "a", label = "a" } }, "")
tree:map_key("", "<A-t>", function() app.editor:set_gutter_icon(0, "file-code") end)
app.left_panel:open()
"#,
)
.expect("keys.lua");
let engine = ScriptEngine::new(AppApi::global()).expect("движок Lua");
engine.run_file(&script).expect("привязки");
let tree = {
let panel = AppApi::global().left_panel_handle();
let panel = panel.borrow();
let PanelBlock::Tree { tree, .. } = &panel.blocks()[0];
tree.clone()
};
let icon = || AppApi::global().editor().gutter_icon(0);
// Фокус в редакторе: работают глобальные и редакторские привязки.
visual.update(|window, cx| {
app.update(cx, |this, cx| {
this.editor_handle()
.update(cx, |state, cx| state.focus(window, cx));
});
});
visual.run_until_parked();
visual.simulate_keystrokes("alt-r");
visual.run_until_parked();
assert_eq!(icon().as_deref(), Some("play"));
// Привязка блока при фокусе в редакторе не срабатывает.
visual.simulate_keystrokes("alt-t");
visual.run_until_parked();
assert_eq!(icon().as_deref(), Some("play"));
visual.simulate_keystrokes("alt-g");
visual.run_until_parked();
assert_eq!(icon().as_deref(), Some("circle"));
// Фокус на дереве: работают глобальные и привязки блока, а не редактора.
tree.borrow_mut().focus("a");
visual.update(|_window, cx| {
app.update(cx, |_, cx| cx.notify());
});
visual.run_until_parked();
visual.simulate_keystrokes("alt-t");
visual.run_until_parked();
assert_eq!(icon().as_deref(), Some("file-code"));
visual.simulate_keystrokes("alt-r");
visual.run_until_parked();
assert_eq!(icon().as_deref(), Some("file-code"));
visual.simulate_keystrokes("alt-g");
visual.run_until_parked();
assert_eq!(icon().as_deref(), Some("circle"));
drop(engine);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn lua_key_modes_are_per_object_and_dynamic() {
let workspace = Rc::new(RefCell::new(Workspace::default()));
let (api, keymap) = install_api(workspace.clone());
let dir = temp_script_dir("key-modes");
let script = dir.join("modes.lua");
std::fs::write(
&script,
r#"
assert(app:mode() == "-")
app:set_mode("i")
assert(app:mode() == "i")
app:map_key("-", "<A-a>", function() app.editor:set_active_text("default") end)
app:map_key("i", "<A-b>", function() app.editor:set_active_text("insert") end)
app:map_key("", "<A-c>", function() app.editor:set_active_text("all") end)
assert(app.editor:mode() == "-")
app.editor:set_mode("n")
assert(app.editor:mode() == "n")
app.editor:map_key("n", "<A-d>", function() app.editor:set_active_text("editor n") end)
app.editor:map_key("i", "<A-e>", function() app.editor:set_active_text("editor i") end)
local tree = app.left_panel:add_tree()
assert(tree:mode() == "-")
tree:set_mode("v")
assert(tree:mode() == "v")
tree:map_key("v", "<A-f>", function() app.editor:set_active_text("tree v") end)
"#,
)
.expect("modes.lua");
let engine = ScriptEngine::new(api.clone()).expect("движок Lua");
engine.run_file(&script).expect("режимы");
let tree_keymap = {
let panel = api.left_panel_handle();
let panel = panel.borrow();
let PanelBlock::Tree { tree, .. } = &panel.blocks()[0];
tree.borrow().keymap_handle()
};
let editor_keymap = api.editor_keymap_handle();
let alt = Modifiers {
alt: true,
..Default::default()
};
let fire = |keymap: &Rc<RefCell<KeyMap>>, key: &str| {
let stroke = Keystroke {
modifiers: alt,
key: key.to_owned(),
key_char: None,
};
for handler in keymap
.borrow()
.handlers_for(KeyContext::Global, &stroke, true)
{
if let KeyHandler::Lua(callback) = handler {
callback.call::<()>(()).expect("Lua-обработчик");
}
}
};
let text = || workspace.borrow().active().content.clone();
// Глобальная карта в режиме i: "-" не действует, "i" и "" — действуют.
fire(&keymap, "a");
assert_eq!(text(), "");
fire(&keymap, "b");
assert_eq!(text(), "insert");
fire(&keymap, "c");
assert_eq!(text(), "all");
// Смена режима не пересоздаёт привязки.
keymap.borrow_mut().set_mode("n").unwrap();
fire(&keymap, "b");
assert_eq!(text(), "all");
// Привязка "-" действует только в режиме по умолчанию.
fire(&keymap, "a");
assert_eq!(text(), "all");
keymap.borrow_mut().set_mode("-").unwrap();
fire(&keymap, "a");
assert_eq!(text(), "default");
// У редактора и блока свои режимы и карты.
fire(&editor_keymap, "d");
assert_eq!(text(), "editor n");
fire(&editor_keymap, "e");
assert_eq!(text(), "editor n");
fire(&tree_keymap, "f");
assert_eq!(text(), "tree v");
drop(engine);
let _ = std::fs::remove_dir_all(&dir);
}
#[gpui_kit::test]
fn opened_panels_render_without_panicking(cx: &mut TestAppContext) {
cx.update(gpui_kit::init);
@@ -1265,6 +1534,35 @@ fn toolbar_items_render_from_api(cx: &mut TestAppContext) {
assert_eq!(toolbar.borrow().right().len(), 1);
}
#[gpui_kit::test]
fn gutter_icons_render_from_api(cx: &mut TestAppContext) {
cx.update(gpui_kit::init);
let (app, visual) = cx.add_window_view(MyApp::new);
let api = AppApi::global();
assert!(api.editor().set_gutter_icon(0, "circle", Some("#ff0000")));
assert!(api.editor().set_gutter_icon(5, "play", None));
visual.update(|_window, cx| {
app.update(cx, |_, cx| cx.notify());
});
visual.run_until_parked();
assert_eq!(
AppApi::global().editor().gutter_icon(0).as_deref(),
Some("circle")
);
assert_eq!(
AppApi::global().editor().gutter_color(0).as_deref(),
Some("#ff0000")
);
assert_eq!(
AppApi::global().editor().gutter_icon(5).as_deref(),
Some("play")
);
}
#[gpui_kit::test]
fn tree_block_renders_with_nested_nodes(cx: &mut TestAppContext) {
cx.update(gpui_kit::init);