Skip to content

Commit

Permalink
feat: smart_tab
Browse files Browse the repository at this point in the history
Implement `smart_tab`, which optionally makes the tab key run the
`move_parent_node_start` command when the cursor has non- whitespace to
its left.
  • Loading branch information
dead10ck committed Aug 1, 2023
1 parent 3d61e07 commit 03af146
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 10 deletions.
8 changes: 8 additions & 0 deletions book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,11 @@ max-wrap = 25 # increase value to reduce forced mid-word wrapping
max-indent-retain = 0
wrap-indicator = "" # set wrap-indicator to "" to hide it
```

### `[editor.smart-tab]` Section


| Key | Description | Default |
|------------|-------------|---------|
| `enable` | If set to true, then when the cursor is in a position with non-whitespace to its left, instead of inserting a tab, it will run `move_parent_node_end`. If there is only whitespace to the left, then it inserts a tab as normal. With the default bindings, to explicitly insert a tab character, press Shift-tab. | `true` |
| `supersede-menu` | Normally, when a menu is on screen, such as when auto complete is triggered, the tab key is bound to cycling through the items. This means when menus are on screen, one cannot use the tab key to trigger the `smart-tab` command. If this option is set to true, the `smart-tab` command always takes precedence, which means one cannot use the tab key to cycle through menu items. One of the other bindings must be used instead, such as arrow keys or `C-n`/`C-p`. | `false` |
9 changes: 3 additions & 6 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ use crate::{
};

use log::{debug, error, warn};
use std::{
collections::btree_map::Entry,
io::{stdin, stdout},
path::Path,
sync::Arc,
};
#[cfg(not(feature = "integration"))]
use std::io::stdout;
use std::{collections::btree_map::Entry, io::stdin, path::Path, sync::Arc};

use anyhow::{Context, Error};

Expand Down
38 changes: 36 additions & 2 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ impl MappableCommand {
extend_to_line_end, "Extend to line end",
extend_to_line_end_newline, "Extend to line end",
signature_help, "Show signature help",
smart_tab, "Insert tab if all cursors have all whitespace to their left; otherwise, run a separate command.",
insert_tab, "Insert tab char",
insert_newline, "Insert newline char",
delete_char_backward, "Delete previous char",
Expand Down Expand Up @@ -2521,6 +2522,10 @@ fn insert_mode(cx: &mut Context) {
.transform(|range| Range::new(range.to(), range.from()));

doc.set_selection(view.id, selection);

// [TODO] temporary workaround until we're not using the idle timer to
// trigger auto completions any more
cx.editor.clear_idle_timer();
}

// inserts at the end of each selection
Expand Down Expand Up @@ -3444,6 +3449,7 @@ pub mod insert {
}

use helix_core::auto_pairs;
use helix_view::editor::SmartTabConfig;

pub fn insert_char(cx: &mut Context, c: char) {
let (view, doc) = current_ref!(cx.editor);
Expand All @@ -3469,6 +3475,31 @@ pub mod insert {
}
}

pub fn smart_tab(cx: &mut Context) {
let (view, doc) = current_ref!(cx.editor);
let view_id = view.id;

if matches!(
cx.editor.config().smart_tab,
Some(SmartTabConfig { enable: true, .. })
) {
let cursors_after_whitespace = doc.selection(view_id).ranges().iter().all(|range| {
let cursor = range.cursor(doc.text().slice(..));
let current_line_num = doc.text().char_to_line(cursor);
let current_line_start = doc.text().line_to_char(current_line_num);
let left = doc.text().slice(current_line_start..cursor);
left.chars().all(|c| c.is_whitespace())
});

if !cursors_after_whitespace {
move_parent_node_end(cx);
return;
}
}

insert_tab(cx);
}

pub fn insert_tab(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
// TODO: round out to nearest indentation level (for example a line with 3 spaces should
Expand Down Expand Up @@ -4626,11 +4657,14 @@ fn move_node_bound_impl(cx: &mut Context, dir: Direction, movement: Movement) {
);

doc.set_selection(view.id, selection);

// [TODO] temporary workaround until we're not using the idle timer to
// trigger auto completions any more
editor.clear_idle_timer();
}
};

motion(cx.editor);
cx.editor.last_motion = Some(Motion(Box::new(motion)));
cx.editor.apply_motion(motion);
}

pub fn move_parent_node_end(cx: &mut Context) {
Expand Down
1 change: 1 addition & 0 deletions helix-term/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ impl Config {
)?,
}
}

// these are just two io errors return the one for the global config
(Err(err), Err(_)) => return Err(err),
};
Expand Down
3 changes: 2 additions & 1 deletion helix-term/src/keymap/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ pub fn default() -> HashMap<Mode, KeyTrie> {
"C-h" | "backspace" | "S-backspace" => delete_char_backward,
"C-d" | "del" => delete_char_forward,
"C-j" | "ret" => insert_newline,
"tab" => insert_tab,
"tab" => smart_tab,
"S-tab" => insert_tab,

"up" => move_visual_line_up,
"down" => move_visual_line_down,
Expand Down
17 changes: 16 additions & 1 deletion helix-term/src/ui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub use tui::widgets::{Cell, Row};
use fuzzy_matcher::skim::SkimMatcherV2 as Matcher;
use fuzzy_matcher::FuzzyMatcher;

use helix_view::{graphics::Rect, Editor};
use helix_view::{editor::SmartTabConfig, graphics::Rect, Editor};
use tui::layout::Constraint;

pub trait Item {
Expand Down Expand Up @@ -247,6 +247,21 @@ impl<T: Item + 'static> Component for Menu<T> {
compositor.pop();
}));

// Ignore tab key when supertab is turned on in order not to interfere
// with it. (Is there a better way to do this?)
if (event == key!(Tab) || event == shift!(Tab))
&& cx.editor.config().auto_completion
&& matches!(
cx.editor.config().smart_tab,
Some(SmartTabConfig {
enable: true,
supersede_menu: true,
})
)
{
return EventResult::Ignored(None);
}

match event {
// esc or ctrl-c aborts the completion and closes the menu
key!(Esc) | ctrl!('c') => {
Expand Down
Loading

0 comments on commit 03af146

Please sign in to comment.