From 57c14d4a9315aecc1be0c8f268e7dd3d541c1480 Mon Sep 17 00:00:00 2001 From: Martin Junghanns Date: Tue, 23 Nov 2021 22:26:55 -0800 Subject: Add `:` and `:goto ` commands (#1128) * Add typable `goto` command * Support `:` on prompt * Rename function according to convention * Directly call into goto_line_number function--- helix-term/src/commands.rs | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) (limited to 'helix-term') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a7179c30..2c556281 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2460,6 +2460,22 @@ mod cmd { Ok(()) } + pub(super) fn goto_line_number( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let line = args[0].parse::()?; + + goto_line_impl(&mut cx.editor, NonZeroUsize::new(line)); + + let (view, doc) = current!(cx.editor); + + view.ensure_cursor_in_view(doc, line); + + Ok(()) + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -2741,6 +2757,13 @@ mod cmd { fun: tutor, completer: None, }, + TypableCommand { + name: "goto", + aliases: &["g"], + doc: "Go to line number.", + fun: goto_line_number, + completer: None, + } ]; pub static COMMANDS: Lazy> = Lazy::new(|| { @@ -2803,6 +2826,15 @@ fn command_mode(cx: &mut Context) { return; } + // If command is numeric, interpret as line number and go there. + if parts.len() == 1 && parts[0].parse::().ok().is_some() { + if let Err(e) = cmd::goto_line_number(cx, &parts[0..], event) { + cx.editor.set_error(format!("{}", e)); + } + return; + } + + // Handle typable commands if let Some(cmd) = cmd::COMMANDS.get(parts[0]) { if let Err(e) = (cmd.fun)(cx, &parts[1..], event) { cx.editor.set_error(format!("{}", e)); @@ -3436,10 +3468,14 @@ fn push_jump(editor: &mut Editor) { } fn goto_line(cx: &mut Context) { - if let Some(count) = cx.count { - push_jump(cx.editor); + goto_line_impl(&mut cx.editor, cx.count) +} - let (view, doc) = current!(cx.editor); +fn goto_line_impl(editor: &mut Editor, count: Option) { + if let Some(count) = count { + push_jump(editor); + + let (view, doc) = current!(editor); let max_line = if doc.text().line(doc.text().len_lines() - 1).len_chars() == 0 { // If the last line is blank, don't jump to it. doc.text().len_lines().saturating_sub(2) -- cgit v1.2.3-70-g09d2 From 72f606ee19a20eca7901ac1647e8efa58ea4f8b8 Mon Sep 17 00:00:00 2001 From: ath3 Date: Wed, 24 Nov 2021 08:46:40 +0100 Subject: Implement no-yank delete/change (#1099) --- book/src/keymap.md | 72 ++++++++++++++++++++++++---------------------- helix-term/src/commands.rs | 64 +++++++++++++++++++++++++++-------------- helix-term/src/keymap.rs | 4 +-- 3 files changed, 81 insertions(+), 59 deletions(-) (limited to 'helix-term') diff --git a/book/src/keymap.md b/book/src/keymap.md index fbe77267..4d542800 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -45,44 +45,46 @@ ### Changes -| Key | Description | Command | -| ----- | ----------- | ------- | -| `r` | Replace with a character | `replace` | -| `R` | Replace with yanked text | `replace_with_yanked` | -| `~` | Switch case of the selected text | `switch_case` | -| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | -| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | -| `i` | Insert before selection | `insert_mode` | -| `a` | Insert after selection (append) | `append_mode` | -| `I` | Insert at the start of the line | `prepend_to_line` | -| `A` | Insert at the end of the line | `append_to_line` | -| `o` | Open new line below selection | `open_below` | -| `O` | Open new line above selection | `open_above` | -| `.` | Repeat last change | N/A | -| `u` | Undo change | `undo` | -| `U` | Redo change | `redo` | -| `Alt-u` | Move backward in history | `earlier` | -| `Alt-U` | Move forward in history | `later` | -| `y` | Yank selection | `yank` | -| `p` | Paste after selection | `paste_after` | -| `P` | Paste before selection | `paste_before` | -| `"` `` | Select a register to yank to or paste from | `select_register` | -| `>` | Indent selection | `indent` | -| `<` | Unindent selection | `unindent` | -| `=` | Format selection (**LSP**) | `format_selections` | -| `d` | Delete selection | `delete_selection` | -| `c` | Change selection (delete and enter insert mode) | `change_selection` | -| `Ctrl-a` | Increment object (number) under cursor | `increment` | -| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | +| Key | Description | Command | +| ----- | ----------- | ------- | +| `r` | Replace with a character | `replace` | +| `R` | Replace with yanked text | `replace_with_yanked` | +| `~` | Switch case of the selected text | `switch_case` | +| `` ` `` | Set the selected text to lower case | `switch_to_lowercase` | +| `` Alt-` `` | Set the selected text to upper case | `switch_to_uppercase` | +| `i` | Insert before selection | `insert_mode` | +| `a` | Insert after selection (append) | `append_mode` | +| `I` | Insert at the start of the line | `prepend_to_line` | +| `A` | Insert at the end of the line | `append_to_line` | +| `o` | Open new line below selection | `open_below` | +| `O` | Open new line above selection | `open_above` | +| `.` | Repeat last change | N/A | +| `u` | Undo change | `undo` | +| `U` | Redo change | `redo` | +| `Alt-u` | Move backward in history | `earlier` | +| `Alt-U` | Move forward in history | `later` | +| `y` | Yank selection | `yank` | +| `p` | Paste after selection | `paste_after` | +| `P` | Paste before selection | `paste_before` | +| `"` `` | Select a register to yank to or paste from | `select_register` | +| `>` | Indent selection | `indent` | +| `<` | Unindent selection | `unindent` | +| `=` | Format selection (**LSP**) | `format_selections` | +| `d` | Delete selection | `delete_selection` | +| `Alt-d` | Delete selection, without yanking | `delete_selection_noyank` | +| `c` | Change selection (delete and enter insert mode) | `change_selection` | +| `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | +| `Ctrl-a` | Increment object (number) under cursor | `increment` | +| `Ctrl-x` | Decrement object (number) under cursor | `decrement` | #### Shell -| Key | Description | Command | -| ------ | ----------- | ------- | -| | | Pipe each selection through shell command, replacing with output | `shell_pipe` | -| A-| | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | -| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | -| `A-!` | Run shell command, appending output after each selection | `shell_append_output` | +| Key | Description | Command | +| ------ | ----------- | ------- | +| | | Pipe each selection through shell command, replacing with output | `shell_pipe` | +| Alt-| | Pipe each selection into shell command, ignoring output | `shell_pipe_to` | +| `!` | Run shell command, inserting output before each selection | `shell_insert_output` | +| `Alt-!` | Run shell command, appending output after each selection | `shell_append_output` | ### Selection manipulation diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 2c556281..b5b6ac6d 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -9,7 +9,6 @@ use helix_core::{ numbers::NumberIncrementor, object, pos_at_coords, regex::{self, Regex, RegexBuilder}, - register::Register, search, selection, surround, textobject, unicode::width::UnicodeWidthChar, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, @@ -232,7 +231,9 @@ impl Command { extend_line, "Select current line, if already selected, extend to next line", extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)", delete_selection, "Delete selection", + delete_selection_noyank, "Delete selection, without yanking", change_selection, "Change selection (delete and enter insert mode)", + change_selection_noyank, "Change selection (delete and enter insert mode, without yanking)", collapse_selection, "Collapse selection onto a single cursor", flip_selections, "Flip selection cursor and anchor", insert_mode, "Insert before selection", @@ -1693,19 +1694,42 @@ fn extend_to_line_bounds(cx: &mut Context) { ); } -fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId) { +enum Operation { + Delete, + Change, +} + +fn delete_selection_impl(cx: &mut Context, op: Operation) { + let (view, doc) = current!(cx.editor); + let text = doc.text().slice(..); - let selection = doc.selection(view_id); + let selection = doc.selection(view.id); - // first yank the selection - let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); - reg.write(values); + if cx.register != Some('_') { + // first yank the selection + let values: Vec = selection.fragments(text).map(Cow::into_owned).collect(); + let reg_name = cx.register.unwrap_or('"'); + let registers = &mut cx.editor.registers; + let reg = registers.get_mut(reg_name); + reg.write(values); + }; // then delete let transaction = Transaction::change_by_selection(doc.text(), selection, |range| { (range.from(), range.to(), None) }); - doc.apply(&transaction, view_id); + doc.apply(&transaction, view.id); + + match op { + Operation::Delete => { + doc.append_changes_to_history(view.id); + // exit select mode, if currently in select mode + exit_select_mode(cx); + } + Operation::Change => { + enter_insert_mode(doc); + } + } } #[inline] @@ -1720,25 +1744,21 @@ fn delete_selection_insert_mode(doc: &mut Document, view: &View, selection: &Sel } fn delete_selection(cx: &mut Context) { - let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - let reg = registers.get_mut(reg_name); - delete_selection_impl(reg, doc, view.id); - - doc.append_changes_to_history(view.id); + delete_selection_impl(cx, Operation::Delete); +} - // exit select mode, if currently in select mode - exit_select_mode(cx); +fn delete_selection_noyank(cx: &mut Context) { + cx.register = Some('_'); + delete_selection_impl(cx, Operation::Delete); } fn change_selection(cx: &mut Context) { - let reg_name = cx.register.unwrap_or('"'); - let (view, doc) = current!(cx.editor); - let registers = &mut cx.editor.registers; - let reg = registers.get_mut(reg_name); - delete_selection_impl(reg, doc, view.id); - enter_insert_mode(doc); + delete_selection_impl(cx, Operation::Change); +} + +fn change_selection_noyank(cx: &mut Context) { + cx.register = Some('_'); + delete_selection_impl(cx, Operation::Change); } fn collapse_selection(cx: &mut Context) { diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 7f6d0c6b..0062e636 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -537,9 +537,9 @@ impl Default for Keymaps { "O" => open_above, "d" => delete_selection, - // TODO: also delete without yanking + "A-d" => delete_selection_noyank, "c" => change_selection, - // TODO: also change delete without yanking + "A-c" => change_selection_noyank, "C" => copy_selection_on_next_line, "A-C" => copy_selection_on_prev_line, -- cgit v1.2.3-70-g09d2 From e8f800a141af11b4f2f4c838cd4fd1f8aa7fa971 Mon Sep 17 00:00:00 2001 From: Thanabodee Charoenpiriyakij Date: Thu, 25 Nov 2021 09:02:51 +0700 Subject: Do not crash when run goto command without line number (#1160) * Do not crash when run goto command without line number Report an error when running goto command without entering a line number. Fixes #1159 * Use is_empty() instead check len zero--- helix-term/src/commands.rs | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'helix-term') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index b5b6ac6d..ff8d7a4f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2485,6 +2485,10 @@ mod cmd { args: &[&str], _event: PromptEvent, ) -> anyhow::Result<()> { + if args.is_empty() { + bail!("Line number required"); + } + let line = args[0].parse::()?; goto_line_impl(&mut cx.editor, NonZeroUsize::new(line)); -- cgit v1.2.3-70-g09d2 From 6e62c3de47ff14df394c1c17fec103c134aa045c Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 26 Nov 2021 18:26:22 +0900 Subject: Simplify some code in editor.rs --- helix-term/src/application.rs | 2 +- helix-view/src/editor.rs | 54 ++++++++++++++++++------------------------- 2 files changed, 24 insertions(+), 32 deletions(-) (limited to 'helix-term') diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index a795a56e..90330751 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -265,7 +265,7 @@ impl Application { use crate::commands::{insert::idle_completion, Context}; use helix_view::document::Mode; - if doc_mut!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion { + if doc!(self.editor).mode != Mode::Insert || !self.config.editor.auto_completion { return; } let editor_view = self diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 725dc1b8..b93d8126 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -19,7 +19,7 @@ use std::{ use tokio::time::{sleep, Duration, Instant, Sleep}; -use anyhow::Error; +use anyhow::{bail, Context, Error}; pub use helix_core::diagnostic::Severity; pub use helix_core::register::Registers; @@ -188,8 +188,8 @@ pub enum Action { impl Editor { pub fn new( mut area: Rect, - themes: Arc, - config_loader: Arc, + theme_loader: Arc, + syn_loader: Arc, config: Config, ) -> Self { let language_servers = helix_lsp::Registry::new(); @@ -199,15 +199,14 @@ impl Editor { Self { tree: Tree::new(area), - // Safety: 1 is non-zero next_document_id: DocumentId::default(), documents: BTreeMap::new(), count: None, selected_register: None, - theme: themes.default(), + theme: theme_loader.default(), language_servers, - syn_loader: config_loader, - theme_loader: themes, + syn_loader, + theme_loader, registers: Registers::default(), clipboard_provider: get_clipboard_provider(), status_msg: None, @@ -264,7 +263,6 @@ impl Editor { } pub fn set_theme_from_name(&mut self, theme: &str) -> anyhow::Result<()> { - use anyhow::Context; let theme = self .theme_loader .load(theme.as_ref()) @@ -343,23 +341,22 @@ impl Editor { } Action::Load => { let view_id = view!(self).id; - if let Some(doc) = self.document_mut(id) { - if doc.selections().is_empty() { - doc.selections.insert(view_id, Selection::point(0)); - } + let doc = self.documents.get_mut(&id).unwrap(); + if doc.selections().is_empty() { + doc.selections.insert(view_id, Selection::point(0)); } return; } - Action::HorizontalSplit => { - let view = View::new(id); - let view_id = self.tree.split(view, Layout::Horizontal); - // initialize selection for view - let doc = self.documents.get_mut(&id).unwrap(); - doc.selections.insert(view_id, Selection::point(0)); - } - Action::VerticalSplit => { + Action::HorizontalSplit | Action::VerticalSplit => { let view = View::new(id); - let view_id = self.tree.split(view, Layout::Vertical); + let view_id = self.tree.split( + view, + match action { + Action::HorizontalSplit => Layout::Horizontal, + Action::VerticalSplit => Layout::Vertical, + _ => unreachable!(), + }, + ); // initialize selection for view let doc = self.documents.get_mut(&id).unwrap(); doc.selections.insert(view_id, Selection::point(0)); @@ -397,11 +394,7 @@ impl Editor { pub fn open(&mut self, path: PathBuf, action: Action) -> Result { let path = helix_core::path::get_canonicalized_path(&path)?; - - let id = self - .documents() - .find(|doc| doc.path() == Some(&path)) - .map(|doc| doc.id); + let id = self.document_by_path(&path).map(|doc| doc.id); let id = if let Some(id) = id { id @@ -463,11 +456,11 @@ impl Editor { pub fn close_document(&mut self, doc_id: DocumentId, force: bool) -> anyhow::Result<()> { let doc = match self.documents.get(&doc_id) { Some(doc) => doc, - None => anyhow::bail!("document does not exist"), + None => bail!("document does not exist"), }; if !force && doc.is_modified() { - anyhow::bail!( + bail!( "buffer {:?} is modified", doc.relative_path() .map(|path| path.to_string_lossy().to_string()) @@ -500,7 +493,7 @@ impl Editor { // If the document we removed was visible in all views, we will have no more views. We don't // want to close the editor just for a simple buffer close, so we need to create a new view // containing either an existing document, or a brand new document. - if self.tree.views().peekable().peek().is_none() { + if self.tree.views().next().is_none() { let doc_id = self .documents .iter() @@ -585,8 +578,7 @@ impl Editor { } pub fn cursor(&self) -> (Option, CursorKind) { - let view = view!(self); - let doc = &self.documents[&view.doc]; + let (view, doc) = current_ref!(self); let cursor = doc .selection(view.id) .primary() -- cgit v1.2.3-70-g09d2 From 103b5125e4bc51965d98a38cdbeed2151ed816fa Mon Sep 17 00:00:00 2001 From: RustyStriker Date: Sun, 28 Nov 2021 03:19:54 +0200 Subject: Detect filetype on :write (#1141) fixes #1136 * removed a log::info * removed temp.rs * cargo clippy no longer complains * new get_lang_server function * get_lang_server is now launch_language_server * launch_lang_server will now close the previous one * better code readability * remove resfresh_ls(and a wrong comment)--- helix-term/src/commands.rs | 7 +++- helix-view/src/editor.rs | 79 ++++++++++++++++++++++++++++------------------ 2 files changed, 54 insertions(+), 32 deletions(-) (limited to 'helix-term') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ff8d7a4f..8e57ef30 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -1907,7 +1907,7 @@ mod cmd { let jobs = &mut cx.jobs; let (_, doc) = current!(cx.editor); - if let Some(path) = path { + if let Some(ref path) = path { doc.set_path(Some(path.as_ref())) .context("invalid filepath")?; } @@ -1927,6 +1927,11 @@ mod cmd { }); let future = doc.format_and_save(fmt); cx.jobs.add(Job::new(future).wait_before_exiting()); + + if path.is_some() { + let id = doc.id(); + let _ = cx.editor.refresh_language_server(id); + } Ok(()) } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index b93d8126..77cea783 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -271,6 +271,53 @@ impl Editor { Ok(()) } + /// Refreshes the language server for a given document + pub fn refresh_language_server(&mut self, doc_id: DocumentId) -> Option<()> { + let doc = self.documents.get_mut(&doc_id)?; + doc.detect_language(Some(&self.theme), &self.syn_loader); + Self::launch_language_server(&mut self.language_servers, doc) + } + + /// Launch a language server for a given document + fn launch_language_server(ls: &mut helix_lsp::Registry, doc: &mut Document) -> Option<()> { + // try to find a language server based on the language name + let language_server = doc.language.as_ref().and_then(|language| { + ls.get(language) + .map_err(|e| { + log::error!( + "Failed to initialize the LSP for `{}` {{ {} }}", + language.scope(), + e + ) + }) + .ok() + }); + if let Some(language_server) = language_server { + // only spawn a new lang server if the servers aren't the same + if Some(language_server.id()) != doc.language_server().map(|server| server.id()) { + if let Some(language_server) = doc.language_server() { + tokio::spawn(language_server.text_document_did_close(doc.identifier())); + } + let language_id = doc + .language() + .and_then(|s| s.split('.').last()) // source.rust + .map(ToOwned::to_owned) + .unwrap_or_default(); + + // TODO: this now races with on_init code if the init happens too quickly + tokio::spawn(language_server.text_document_did_open( + doc.url().unwrap(), + doc.version(), + doc.text(), + language_id, + )); + + doc.set_language_server(Some(language_server)); + } + } + Some(()) + } + fn _refresh(&mut self) { for (view, _) in self.tree.views_mut() { let doc = &self.documents[&view.doc]; @@ -401,37 +448,7 @@ impl Editor { } else { let mut doc = Document::open(&path, None, Some(&self.theme), Some(&self.syn_loader))?; - // try to find a language server based on the language name - let language_server = doc.language.as_ref().and_then(|language| { - self.language_servers - .get(language) - .map_err(|e| { - log::error!( - "Failed to initialize the LSP for `{}` {{ {} }}", - language.scope(), - e - ) - }) - .ok() - }); - - if let Some(language_server) = language_server { - let language_id = doc - .language() - .and_then(|s| s.split('.').last()) // source.rust - .map(ToOwned::to_owned) - .unwrap_or_default(); - - // TODO: this now races with on_init code if the init happens too quickly - tokio::spawn(language_server.text_document_did_open( - doc.url().unwrap(), - doc.version(), - doc.text(), - language_id, - )); - - doc.set_language_server(Some(language_server)); - } + let _ = Self::launch_language_server(&mut self.language_servers, &mut doc); self.new_document(doc) }; -- cgit v1.2.3-70-g09d2 From 4f9390a435a44578bcedf1da63ea1fad92185afc Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 29 Nov 2021 09:53:29 +0800 Subject: gf as goto_file (#1102) * goto_file * support goto_file under current cursor * add C-w f/F * sync space w with window mode * Update helix-term/src/commands.rs Co-authored-by: Blaž Hrastnik --- book/src/keymap.md | 3 +++ helix-term/src/commands.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ helix-term/src/keymap.rs | 5 +++++ 3 files changed, 54 insertions(+) (limited to 'helix-term') diff --git a/book/src/keymap.md b/book/src/keymap.md index 5e861cfb..865a700b 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -160,6 +160,7 @@ Jumps to various locations. | ----- | ----------- | ------- | | `g` | Go to the start of the file | `goto_file_start` | | `e` | Go to the end of the file | `goto_last_line` | +| `f` | Go to files in the selection | `goto_file` | | `h` | Go to the start of the line | `goto_line_start` | | `l` | Go to the end of the line | `goto_line_end` | | `s` | Go to first non-whitespace character of the line | `goto_first_nonwhitespace` | @@ -202,6 +203,8 @@ This layer is similar to vim keybindings as kakoune does not support window. | `v`, `Ctrl-v` | Vertical right split | `vsplit` | | `s`, `Ctrl-s` | Horizontal bottom split | `hsplit` | | `h`, `Ctrl-h`, `left` | Move to left split | `jump_view_left` | +| `f` | Go to files in the selection in horizontal splits | `goto_file` | +| `F` | Go to files in the selection in vertical splits | `goto_file` | | `j`, `Ctrl-j`, `down` | Move to split below | `jump_view_down` | | `k`, `Ctrl-k`, `up` | Move to split above | `jump_view_up` | | `l`, `Ctrl-l`, `right` | Move to right split | `jump_view_right` | diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 8e57ef30..aafeb476 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -259,6 +259,9 @@ impl Command { goto_implementation, "Goto implementation", goto_file_start, "Goto file start/line", goto_file_end, "Goto file end", + goto_file, "Goto files in the selection", + goto_file_hsplit, "Goto files in the selection in horizontal splits", + goto_file_vsplit, "Goto files in the selection in vertical splits", goto_reference, "Goto references", goto_window_top, "Goto window top", goto_window_middle, "Goto window middle", @@ -835,6 +838,49 @@ fn goto_file_end(cx: &mut Context) { doc.set_selection(view.id, selection); } +fn goto_file(cx: &mut Context) { + goto_file_impl(cx, Action::Replace); +} + +fn goto_file_hsplit(cx: &mut Context) { + goto_file_impl(cx, Action::HorizontalSplit); +} + +fn goto_file_vsplit(cx: &mut Context) { + goto_file_impl(cx, Action::VerticalSplit); +} + +fn goto_file_impl(cx: &mut Context, action: Action) { + let (view, doc) = current_ref!(cx.editor); + let text = doc.text(); + let selections = doc.selection(view.id); + let mut paths: Vec<_> = selections + .iter() + .map(|r| text.slice(r.from()..r.to()).to_string()) + .collect(); + let primary = selections.primary(); + if selections.len() == 1 && primary.to() - primary.from() == 1 { + let current_word = movement::move_next_long_word_start( + text.slice(..), + movement::move_prev_long_word_start(text.slice(..), primary, 1), + 1, + ); + paths.clear(); + paths.push( + text.slice(current_word.from()..current_word.to()) + .to_string(), + ); + } + for sel in paths { + let p = sel.trim(); + if !p.is_empty() { + if let Err(e) = cx.editor.open(PathBuf::from(p), action) { + cx.editor.set_error(format!("Open file failed: {:?}", e)); + } + } + } +} + fn extend_word_impl(cx: &mut Context, extend_fn: F) where F: Fn(RopeSlice, Range, usize) -> Range, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 0062e636..06639dcd 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -512,6 +512,7 @@ impl Default for Keymaps { "g" => { "Goto" "g" => goto_file_start, "e" => goto_last_line, + "f" => goto_file, "h" => goto_line_start, "l" => goto_line_end, "s" => goto_first_nonwhitespace, @@ -622,6 +623,8 @@ impl Default for Keymaps { "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, @@ -650,6 +653,8 @@ impl Default for Keymaps { "C-w" | "w" => rotate_view, "C-s" | "s" => hsplit, "C-v" | "v" => vsplit, + "f" => goto_file_hsplit, + "F" => goto_file_vsplit, "C-q" | "q" => wclose, "C-o" | "o" => wonly, "C-h" | "h" | "left" => jump_view_left, -- cgit v1.2.3-70-g09d2 From 42fde95223a62bded340a1737e8be50ef94af4af Mon Sep 17 00:00:00 2001 From: Bob Date: Mon, 29 Nov 2021 09:58:21 +0800 Subject: Accept count for goto_window (#1033) * accept count for goto_window also fix view is not fullfilled issue * fix fulfilled mispell * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * Update helix-term/src/commands.rs Co-authored-by: Ivan Tham * fix merge issue * revert line computation logic Co-authored-by: Ivan Tham --- helix-term/src/commands.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'helix-term') diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index aafeb476..b4cc9ae9 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -733,10 +733,12 @@ fn align_fragment_to_width(fragment: &str, width: usize, align_style: usize) -> } fn goto_window(cx: &mut Context, align: Align) { + let count = cx.count() - 1; let (view, doc) = current!(cx.editor); let height = view.inner_area().height as usize; + // respect user given count if any // - 1 so we have at least one gap in the middle. // a height of 6 with padding of 3 on each side will keep shifting the view back and forth // as we type @@ -745,11 +747,12 @@ fn goto_window(cx: &mut Context, align: Align) { let last_line = view.last_line(doc); let line = match align { - Align::Top => (view.offset.row + scrolloff), - Align::Center => (view.offset.row + (height / 2)), - Align::Bottom => last_line.saturating_sub(scrolloff), + Align::Top => (view.offset.row + scrolloff + count), + Align::Center => (view.offset.row + ((last_line - view.offset.row) / 2)), + Align::Bottom => last_line.saturating_sub(scrolloff + count), } - .min(last_line.saturating_sub(scrolloff)); + .min(last_line.saturating_sub(scrolloff)) + .max(view.offset.row + scrolloff); let pos = doc.text().line_to_char(line); -- cgit v1.2.3-70-g09d2 From 30171416cb5b801086da69566a82462fca16ea14 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 24 Sep 2021 10:29:41 +0900 Subject: Gutter functions --- helix-core/src/diagnostic.rs | 4 +- helix-term/src/ui/editor.rs | 150 ++++++++++++++++++++++++++----------------- helix-view/src/editor.rs | 2 +- 3 files changed, 94 insertions(+), 62 deletions(-) (limited to 'helix-term') diff --git a/helix-core/src/diagnostic.rs b/helix-core/src/diagnostic.rs index ad1ba16a..4fcf51c9 100644 --- a/helix-core/src/diagnostic.rs +++ b/helix-core/src/diagnostic.rs @@ -1,7 +1,7 @@ //! LSP diagnostic utility types. /// Describes the severity level of a [`Diagnostic`]. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Severity { Error, Warning, @@ -17,7 +17,7 @@ pub struct Range { } /// Corresponds to [`lsp_types::Diagnostic`](https://docs.rs/lsp-types/0.91.0/lsp_types/struct.Diagnostic.html) -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Diagnostic { pub range: Range, pub line: usize, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 27d33d22..8c4ea9cc 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -17,7 +17,7 @@ use helix_core::{ }; use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, - editor::LineNumber, + editor::{Config, LineNumber}, graphics::{CursorKind, Modifier, Rect, Style}, info::Info, input::KeyEvent, @@ -412,22 +412,6 @@ impl EditorView { let text = doc.text().slice(..); let last_line = view.last_line(doc); - let linenr = theme.get("ui.linenr"); - let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); - - let warning = theme.get("warning"); - let error = theme.get("error"); - let info = theme.get("info"); - let hint = theme.get("hint"); - - // Whether to draw the line number for the last line of the - // document or not. We only draw it if it's not an empty line. - let draw_last = text.line_to_byte(last_line) < text.len_bytes(); - - let current_line = doc - .text() - .char_to_line(doc.selection(view.id).primary().cursor(text)); - // it's used inside an iterator so the collect isn't needless: // https://github.com/rust-lang/rust-clippy/issues/6164 #[allow(clippy::needless_collect)] @@ -437,51 +421,99 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { - use helix_core::diagnostic::Severity; - if let Some(diagnostic) = doc.diagnostics().iter().find(|d| d.line == line) { - surface.set_stringn( - viewport.x, - viewport.y + i as u16, - "●", - 1, - match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }, - ); - } + fn diagnostic( + doc: &Document, + _view: &View, + theme: &Theme, + _config: &Config, + _is_focused: bool, + _width: usize, + ) -> GutterFn { + let warning = theme.get("warning"); + let error = theme.get("error"); + let info = theme.get("info"); + let hint = theme.get("hint"); + let diagnostics = doc.diagnostics().to_vec(); // TODO + + Box::new(move |line: usize, _selected: bool| { + use helix_core::diagnostic::Severity; + if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { + return Some(( + "●".to_string(), + match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }, + )); + } + None + }) + } - let selected = cursors.contains(&line); + fn line_number( + doc: &Document, + view: &View, + theme: &Theme, + config: &Config, + is_focused: bool, + width: usize, + ) -> GutterFn { + let text = doc.text().slice(..); + let last_line = view.last_line(doc); + // Whether to draw the line number for the last line of the + // document or not. We only draw it if it's not an empty line. + let draw_last = text.line_to_byte(last_line) < text.len_bytes(); - let text = if line == last_line && !draw_last { - " ~".into() - } else { - let line = match config.line_number { - LineNumber::Absolute => line + 1, - LineNumber::Relative => { - if current_line == line { - line + 1 - } else { - abs_diff(current_line, line) - } - } - }; - format!("{:>5}", line) - }; - surface.set_stringn( - viewport.x + 1, - viewport.y + i as u16, - text, - 5, - if selected && is_focused { - linenr_select + let linenr = theme.get("ui.linenr"); + let linenr_select: Style = theme.try_get("ui.linenr.selected").unwrap_or(linenr); + + let current_line = doc + .text() + .char_to_line(doc.selection(view.id).primary().cursor(text)); + + let config = config.line_number; + + Box::new(move |line: usize, selected: bool| { + if line == last_line && !draw_last { + Some((format!("{:>1$}", '~', width), linenr)) } else { - linenr - }, - ); + let line = match config { + LineNumber::Absolute => line + 1, + LineNumber::Relative => { + if current_line == line { + line + 1 + } else { + abs_diff(current_line, line) + } + } + }; + let style = if selected && is_focused { + linenr_select + } else { + linenr + }; + Some((format!("{:>1$}", line, width), style)) + } + }) + } + + type GutterFn = Box Option<(String, Style)>>; + type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn; + let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; + + let mut offset = 0; + for (constructor, width) in gutters { + let gutter = constructor(doc, view, theme, config, is_focused, *width); + for (i, line) in (view.offset.row..(last_line + 1)).enumerate() { + let selected = cursors.contains(&line); + + if let Some((text, style)) = gutter(line, selected) { + surface.set_stringn(viewport.x + offset, viewport.y + i as u16, text, 5, style); + } + } + offset += *width as u16; } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 77cea783..d5913a51 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -106,7 +106,7 @@ pub struct Config { pub file_picker: FilePickerConfig, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum LineNumber { /// Show absolute line number -- cgit v1.2.3-70-g09d2 From c71c9f69e21a80c5c9c744bddbde7d5041da99a5 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Thu, 7 Oct 2021 10:26:28 +0900 Subject: TODO --- helix-term/src/ui/markdown.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'helix-term') diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index 649703b5..ca8303dd 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -228,6 +228,7 @@ impl Component for Markdown { return None; } let contents = parse(&self.contents, None, &self.config_loader); + // TODO: account for tab width let max_text_width = (viewport.0 - padding).min(120); let mut text_width = 0; let mut height = padding; -- cgit v1.2.3-70-g09d2 From ba45db84d4b49913836a949472366a30a620e67b Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 22 Nov 2021 16:45:52 +0900 Subject: Tie the GutterFn lifetime to the doc so we can avoid cloning data --- helix-term/src/ui/editor.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'helix-term') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 8c4ea9cc..2cc212ea 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -421,19 +421,19 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); - fn diagnostic( - doc: &Document, + fn diagnostic<'doc>( + doc: &'doc Document, _view: &View, theme: &Theme, _config: &Config, _is_focused: bool, _width: usize, - ) -> GutterFn { + ) -> GutterFn<'doc> { let warning = theme.get("warning"); let error = theme.get("error"); let info = theme.get("info"); let hint = theme.get("hint"); - let diagnostics = doc.diagnostics().to_vec(); // TODO + let diagnostics = doc.diagnostics(); Box::new(move |line: usize, _selected: bool| { use helix_core::diagnostic::Severity; @@ -452,14 +452,14 @@ impl EditorView { }) } - fn line_number( - doc: &Document, + fn line_number<'doc>( + doc: &'doc Document, view: &View, theme: &Theme, config: &Config, is_focused: bool, width: usize, - ) -> GutterFn { + ) -> GutterFn<'doc> { let text = doc.text().slice(..); let last_line = view.last_line(doc); // Whether to draw the line number for the last line of the @@ -499,8 +499,9 @@ impl EditorView { }) } - type GutterFn = Box Option<(String, Style)>>; - type Gutter = fn(&Document, &View, &Theme, &Config, bool, usize) -> GutterFn; + type GutterFn<'doc> = Box Option<(String, Style)> + 'doc>; + type Gutter = + for<'doc> fn(&'doc Document, &View, &Theme, &Config, bool, usize) -> GutterFn<'doc>; let gutters: &[(Gutter, usize)] = &[(diagnostic, 1), (line_number, 5)]; let mut offset = 0; -- cgit v1.2.3-70-g09d2 From 27c1a84f053d1282ed09d64ec737a46f55685d85 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 22 Nov 2021 17:02:46 +0900 Subject: Reuse a text buffer for each gutter line --- helix-term/src/ui/editor.rs | 46 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) (limited to 'helix-term') diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 2cc212ea..82fb8fbf 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -421,6 +421,8 @@ impl EditorView { .map(|range| range.cursor_line(text)) .collect(); + use std::fmt::Write; + fn diagnostic<'doc>( doc: &'doc Document, _view: &View, @@ -435,18 +437,16 @@ impl EditorView { let hint = theme.get("hint"); let diagnostics = doc.diagnostics(); - Box::new(move |line: usize, _selected: bool| { + Box::new(move |line: usize, _selected: bool, out: &mut String| { use helix_core::diagnostic::Severity; if let Some(diagnostic) = diagnostics.iter().find(|d| d.line == line) { - return Some(( - "●".to_string(), - match diagnostic.severity { - Some(Severity::Error) => error, - Some(Severity::Warning) | None => warning, - Some(Severity::Info) => info, - Some(Severity::Hint) => hint, - }, - )); + write!(out, "●").unwrap(); + return Some(match diagnostic.severity { + Some(Severity::Error) => error, + Some(Severity::Warning) | None => warning, + Some(Severity::Info) => info, + Some(Severity::Hint) => hint, + }); } None }) @@ -475,9 +475,10 @@ impl EditorView { let config = config.line_number; - Box::new(move |line: usize, selected: bool| { + Box::new(move |line: usize, selected: bool, out: &mut String| { if line == last_line && !draw_last { - Some((format!("{:>1$}", '~', width), linenr)) + write!(out, "{:>1$}", '~', width).unwrap(); + Some(linenr) } else { let line = match config { LineNumber::Absolute => line + 1, @@ -494,25 +495,38 @@ impl EditorView { } else { linenr }; - Some((format!("{:>1$}", line, width), style)) + write!(out, "{:>1$}", line, width).unwrap(); + Some(style) } }) } - type GutterFn<'doc> = Box Option<(String, Style)> + 'doc>; + type GutterFn<'doc> = Box Option