Merge branch 'feat/editor-ide' into ide/editing

This commit is contained in:
l0ng-ai
2026-09-28 17:16:58 +08:00
6 changed files with 91 additions and 4 deletions
+17 -4
View File
@@ -3255,15 +3255,28 @@ impl Tty7App {
}
});
let cursor: Option<SharedString> = active.map(|f| {
let pos = f.input.read(cx).cursor_position();
t_fmt(
let input = f.input.read(cx);
let pos = input.cursor_position();
let ln_col = t_fmt(
L10nKey::EditorLnCol,
&[
("line", &(pos.line + 1).to_string()),
("column", &(pos.character + 1).to_string()),
],
)
.into()
);
// Multi-cursor: the position is the primary caret's, the count
// says there are more.
match input.selection_count() {
1 => ln_col.into(),
n => {
let n = n.to_string();
format!(
"{ln_col} {}",
t_fmt(L10nKey::EditorSelections, &[("n", &n)])
)
.into()
}
}
});
let wrap: Option<bool> = active.map(|f| f.wrap);
let is_markdown = active.is_some_and(|f| f.language() == "markdown");
+1
View File
@@ -1179,6 +1179,7 @@ pub fn translate_en(key: L10nKey) -> &'static str {
L10nKey::EditorNoFileOpen => "No file open",
L10nKey::EditorBackToTerminal => "Back to Terminal (Esc)",
L10nKey::EditorLnCol => "Ln {line}, Col {column}",
L10nKey::EditorSelections => "({n} selections)",
L10nKey::EditorEdit => "Edit",
L10nKey::EditorPreview => "Preview",
L10nKey::EditorWrapOn => "Wrap: on",
+1
View File
@@ -1227,6 +1227,7 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
L10nKey::EditorNoFileOpen => "開かれているファイルはありません",
L10nKey::EditorBackToTerminal => "ターミナルに戻る (Esc)",
L10nKey::EditorLnCol => "行 {line}, 列 {column}",
L10nKey::EditorSelections => "({n} 個の選択範囲)",
L10nKey::EditorEdit => "編集",
L10nKey::EditorPreview => "プレビュー",
L10nKey::EditorWrapOn => "折り返し: オン",
+1
View File
@@ -917,6 +917,7 @@ l10n_keys! {
EditorNoFileOpen,
EditorBackToTerminal,
EditorLnCol,
EditorSelections,
EditorEdit,
EditorPreview,
EditorWrapOn,
+1
View File
@@ -1099,6 +1099,7 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
L10nKey::EditorNoFileOpen => "没有打开的文件",
L10nKey::EditorBackToTerminal => "返回终端 (Esc)",
L10nKey::EditorLnCol => "行 {line},列 {column}",
L10nKey::EditorSelections => "({n} 个选区)",
L10nKey::EditorEdit => "编辑",
L10nKey::EditorPreview => "预览",
L10nKey::EditorWrapOn => "自动换行:开",
+70
View File
@@ -70,6 +70,28 @@ fn fixed_bindings() -> Vec<KeyBinding> {
let search = Some(crate::ui::search::KEY_CONTEXT);
bindings.push(KeyBinding::new("tab", SearchNextTab, search));
bindings.push(KeyBinding::new("shift-tab", SearchPrevTab, search));
// The code editor's multi-cursor chords, laid down again after tty7's
// own table. gpui ranks a context-free binding as deep as the focused
// context, and breaks the tie by whichever was added last — so the copies
// gpui-component installs lose ⌘D to `SplitRight` and ⌘⌥↑/↓ to
// `FocusPaneUp`/`Down`, whose handlers sit on the workspace root and take
// the key before the editor is asked. Re-added here they win inside an
// `Input`; only the code editor handles them, so in any other text field
// gpui falls through to the pane binding next in line.
{
use gpui_component::input::{
AddCursorAbove, AddCursorBelow, SelectAllOccurrences, SelectNextOccurrence,
};
let input = Some("Input");
bindings.push(KeyBinding::new("secondary-d", SelectNextOccurrence, input));
bindings.push(KeyBinding::new(
"secondary-shift-l",
SelectAllOccurrences,
input,
));
bindings.push(KeyBinding::new("secondary-alt-up", AddCursorAbove, input));
bindings.push(KeyBinding::new("secondary-alt-down", AddCursorBelow, input));
}
bindings
}
@@ -2833,4 +2855,52 @@ mod gpui_tests {
assert_eq!(backspace(cx), inherited, "nor may a rebind");
});
}
/// The code editor's multi-cursor chords sit on keys tty7 ships for panes
/// (⌘D split, ⌘⌥↑/↓ focus). `fixed_bindings` re-adds them after the
/// pane table so they rank first inside an `Input`; any other text field
/// has no handler for them, and gpui falls through to the pane binding
/// listed after. A terminal never sees them.
#[gpui::test]
fn editor_multi_cursor_chords_outrank_the_pane_bindings(cx: &mut TestAppContext) {
use gpui::Action as _;
use gpui_component::input::{
AddCursorAbove, AddCursorBelow, SelectAllOccurrences, SelectNextOccurrence,
};
cx.update(|cx| {
running_on_json(cx, "{}");
let in_input = |keys: &str| -> Vec<&'static str> {
let input = [Keystroke::parse(keys).expect("the keystroke parses")];
let context = [
gpui::KeyContext::parse("Workspace").expect("the context parses"),
gpui::KeyContext::parse("Input").expect("the context parses"),
];
cx.key_bindings()
.borrow()
.bindings_for_input(&input, &context)
.0
.iter()
.map(|b| b.action().name())
.collect()
};
let cases = [
("secondary-d", SelectNextOccurrence::name_for_type()),
("secondary-shift-l", SelectAllOccurrences::name_for_type()),
("secondary-alt-up", AddCursorAbove::name_for_type()),
("secondary-alt-down", AddCursorBelow::name_for_type()),
];
for (keys, editor_action) in cases {
let hits = in_input(keys);
assert_eq!(hits.first(), Some(&editor_action), "{keys} in the editor");
}
if cfg!(target_os = "macos") {
assert!(in_input("secondary-d").contains(&SplitRight::name_for_type()));
assert_eq!(
fired(cx, "secondary-d").first(),
Some(&SplitRight::name_for_type()),
"a terminal still splits on ⌘D"
);
}
});
}
}