Merge branch 'master' into help-command

pull/997/head
Omnikar 3 years ago
commit c039c9b6fd
No known key found for this signature in database
GPG Key ID: 7DE6694CDA7885ED

4
.gitmodules vendored

@ -178,6 +178,10 @@
path = helix-syntax/languages/tree-sitter-git-diff path = helix-syntax/languages/tree-sitter-git-diff
url = https://github.com/the-mikedavis/tree-sitter-git-diff.git url = https://github.com/the-mikedavis/tree-sitter-git-diff.git
shallow = true shallow = true
[submodule "helix-syntax/languages/tree-sitter-tablegen"]
path = helix-syntax/languages/tree-sitter-tablegen
url = https://github.com/Flakebi/tree-sitter-tablegen
shallow = true
[submodule "helix-syntax/languages/tree-sitter-git-rebase"] [submodule "helix-syntax/languages/tree-sitter-git-rebase"]
path = helix-syntax/languages/tree-sitter-git-rebase path = helix-syntax/languages/tree-sitter-git-rebase
url = https://github.com/the-mikedavis/tree-sitter-git-rebase.git url = https://github.com/the-mikedavis/tree-sitter-git-rebase.git

@ -40,6 +40,7 @@
| rust | ✓ | ✓ | ✓ | `rust-analyzer` | | rust | ✓ | ✓ | ✓ | `rust-analyzer` |
| scala | ✓ | | ✓ | `metals` | | scala | ✓ | | ✓ | `metals` |
| svelte | ✓ | | ✓ | `svelteserver` | | svelte | ✓ | | ✓ | `svelteserver` |
| tablegen | ✓ | ✓ | ✓ | |
| toml | ✓ | | | | | toml | ✓ | | | |
| tsq | ✓ | | | | | tsq | ✓ | | | |
| tsx | ✓ | | | `typescript-language-server` | | tsx | ✓ | | | `typescript-language-server` |

@ -20,6 +20,7 @@
| `:quit-all`, `:qa` | Close all views. | | `:quit-all`, `:qa` | Close all views. |
| `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). | | `:quit-all!`, `:qa!` | Close all views forcefully (ignoring unsaved changes). |
| `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). | | `:cquit`, `:cq` | Quit with exit code (default 1). Accepts an optional integer exit code (:cq 2). |
| `:cquit!`, `:cq!` | Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2). |
| `:theme` | Change the editor theme. | | `:theme` | Change the editor theme. |
| `:clipboard-yank` | Yank main selection into system clipboard. | | `:clipboard-yank` | Yank main selection into system clipboard. |
| `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. | | `:clipboard-yank-join` | Yank joined selections into system clipboard. A separator can be provided as first argument. Default value is newline. |

@ -1,6 +1,5 @@
use crate::{ use crate::{
chars::{char_is_line_ending, char_is_whitespace}, chars::{char_is_line_ending, char_is_whitespace},
find_first_non_whitespace_char,
syntax::{IndentQuery, LanguageConfiguration, Syntax}, syntax::{IndentQuery, LanguageConfiguration, Syntax},
tree_sitter::Node, tree_sitter::Node,
Rope, RopeSlice, Rope, RopeSlice,
@ -174,8 +173,7 @@ pub fn auto_detect_indent_style(document_text: &Rope) -> Option<IndentStyle> {
/// To determine indentation of a newly inserted line, figure out the indentation at the last col /// To determine indentation of a newly inserted line, figure out the indentation at the last col
/// of the previous line. /// of the previous line.
#[allow(dead_code)] pub fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
fn indent_level_for_line(line: RopeSlice, tab_width: usize) -> usize {
let mut len = 0; let mut len = 0;
for ch in line.chars() { for ch in line.chars() {
match ch { match ch {
@ -210,10 +208,15 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
Some(node) Some(node)
} }
fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize { /// Calculate the indentation at a given treesitter node.
// NOTE: can't use contains() on query because of comparing Vec<String> and &str /// If newline is false, then any "indent" nodes on the line are ignored ("outdent" still applies).
// https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains /// This is because the indentation is only increased starting at the second line of the node.
fn calculate_indentation(
query: &IndentQuery,
node: Option<Node>,
line: usize,
newline: bool,
) -> usize {
let mut increment: isize = 0; let mut increment: isize = 0;
let mut node = match node { let mut node = match node {
@ -221,68 +224,43 @@ fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool)
None => return 0, None => return 0,
}; };
let mut prev_start = node.start_position().row; let mut current_line = line;
let mut consider_indent = newline;
let mut increment_from_line: isize = 0;
// if we're calculating indentation for a brand new line then the current node will become the loop {
// parent node. We need to take it's indentation level into account too.
let node_kind = node.kind(); let node_kind = node.kind();
if newline && query.indent.contains(node_kind) { let start = node.start_position().row;
increment += 1; if current_line != start {
} // Indent/dedent by at most one per line:
while let Some(parent) = node.parent() {
let parent_kind = parent.kind();
let start = parent.start_position().row;
// detect deeply nested indents in the same line
// .map(|a| { <-- ({ is two scopes // .map(|a| { <-- ({ is two scopes
// let len = 1; <-- indents one level // let len = 1; <-- indents one level
// }) <-- }) is two scopes // }) <-- }) is two scopes
let starts_same_line = start == prev_start; if consider_indent || increment_from_line < 0 {
increment += increment_from_line.signum();
if query.outdent.contains(node.kind()) && !starts_same_line {
// we outdent by skipping the rules for the current level and jumping up
// node = parent;
increment -= 1;
// continue;
} }
increment_from_line = 0;
if query.indent.contains(parent_kind) // && not_first_or_last_sibling current_line = start;
&& !starts_same_line consider_indent = true;
{
// println!("is_scope {}", parent_kind);
prev_start = start;
increment += 1
} }
// if last_scope && increment > 0 && ...{ ignore } if query.outdent.contains(node_kind) {
increment_from_line -= 1;
}
if query.indent.contains(node_kind) {
increment_from_line += 1;
}
if let Some(parent) = node.parent() {
node = parent; node = parent;
} else {
break;
} }
increment.max(0) as usize
} }
if consider_indent || increment_from_line < 0 {
#[allow(dead_code)] increment += increment_from_line.signum();
fn suggested_indent_for_line( }
language_config: &LanguageConfiguration, increment.max(0) as usize
syntax: Option<&Syntax>,
text: RopeSlice,
line_num: usize,
_tab_width: usize,
) -> usize {
if let Some(start) = find_first_non_whitespace_char(text.line(line_num)) {
return suggested_indent_for_pos(
Some(language_config),
syntax,
text,
start + text.line_to_char(line_num),
false,
);
};
// if the line is blank, indent should be zero
0
} }
// TODO: two usecases: if we are triggering this for a new, blank line: // TODO: two usecases: if we are triggering this for a new, blank line:
@ -293,23 +271,20 @@ pub fn suggested_indent_for_pos(
syntax: Option<&Syntax>, syntax: Option<&Syntax>,
text: RopeSlice, text: RopeSlice,
pos: usize, pos: usize,
line: usize,
new_line: bool, new_line: bool,
) -> usize { ) -> Option<usize> {
if let (Some(query), Some(syntax)) = ( if let (Some(query), Some(syntax)) = (
language_config.and_then(|config| config.indent_query()), language_config.and_then(|config| config.indent_query()),
syntax, syntax,
) { ) {
let byte_start = text.char_to_byte(pos); let byte_start = text.char_to_byte(pos);
let node = get_highest_syntax_node_at_bytepos(syntax, byte_start); let node = get_highest_syntax_node_at_bytepos(syntax, byte_start);
// let config = load indentation query config from Syntax(should contain language_config)
// TODO: special case for comments // TODO: special case for comments
// TODO: if preserve_leading_whitespace // TODO: if preserve_leading_whitespace
calculate_indentation(query, node, new_line) Some(calculate_indentation(query, node, line, new_line))
} else { } else {
// TODO: heuristics for non-tree sitter grammars None
0
} }
} }
@ -484,14 +459,23 @@ where
for i in 0..doc.len_lines() { for i in 0..doc.len_lines() {
let line = text.line(i); let line = text.line(i);
if let Some(pos) = crate::find_first_non_whitespace_char(line) {
let indent = indent_level_for_line(line, tab_width); let indent = indent_level_for_line(line, tab_width);
assert_eq!( assert_eq!(
suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width), suggested_indent_for_pos(
indent, Some(&language_config),
"line {}: {}", Some(&syntax),
text,
text.line_to_char(i) + pos,
i,
false
),
Some(indent),
"line {}: \"{}\"",
i, i,
line line
); );
} }
} }
} }
}

@ -66,39 +66,26 @@ pub mod util {
pos: lsp::Position, pos: lsp::Position,
offset_encoding: OffsetEncoding, offset_encoding: OffsetEncoding,
) -> Option<usize> { ) -> Option<usize> {
let max_line = doc.lines().count().saturating_sub(1);
let pos_line = pos.line as usize; let pos_line = pos.line as usize;
let pos_line = if pos_line > max_line { if pos_line > doc.len_lines() - 1 {
return None; return None;
} else { }
pos_line
};
match offset_encoding { match offset_encoding {
OffsetEncoding::Utf8 => { OffsetEncoding::Utf8 => {
let max_char = doc
.line_to_char(max_line)
.checked_add(doc.line(max_line).len_chars())?;
let line = doc.line_to_char(pos_line); let line = doc.line_to_char(pos_line);
let pos = line.checked_add(pos.character as usize)?; let pos = line.checked_add(pos.character as usize)?;
if pos <= max_char { if pos <= doc.len_chars() {
Some(pos) Some(pos)
} else { } else {
None None
} }
} }
OffsetEncoding::Utf16 => { OffsetEncoding::Utf16 => {
let max_char = doc
.line_to_char(max_line)
.checked_add(doc.line(max_line).len_chars())?;
let max_cu = doc.char_to_utf16_cu(max_char);
let line = doc.line_to_char(pos_line); let line = doc.line_to_char(pos_line);
let line_start = doc.char_to_utf16_cu(line); let line_start = doc.char_to_utf16_cu(line);
let pos = line_start.checked_add(pos.character as usize)?; let pos = line_start.checked_add(pos.character as usize)?;
if pos <= max_cu { doc.try_utf16_cu_to_char(pos).ok()
Some(doc.utf16_cu_to_char(pos))
} else {
None
}
} }
} }
} }

@ -0,0 +1 @@
Subproject commit 568dd8a937347175fd58db83d4c4cdaeb6069bd2

@ -1282,6 +1282,8 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
.max(view.offset.row + scrolloff) .max(view.offset.row + scrolloff)
.min(last_line.saturating_sub(scrolloff)); .min(last_line.saturating_sub(scrolloff));
// If cursor needs moving, replace primary selection
if line != cursor.row {
let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end
let anchor = if doc.mode == Mode::Select { let anchor = if doc.mode == Mode::Select {
@ -1290,8 +1292,13 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
head head
}; };
// TODO: only manipulate main selection // replace primary selection with an empty selection at cursor pos
doc.set_selection(view.id, Selection::single(anchor, head)); let prim_sel = Range::new(anchor, head);
let mut sel = doc.selection(view.id).clone();
let idx = sel.primary_index();
sel = sel.replace(idx, prim_sel);
doc.set_selection(view.id, sel);
}
} }
fn page_up(cx: &mut Context) { fn page_up(cx: &mut Context) {
@ -1545,7 +1552,7 @@ fn searcher(cx: &mut Context, direction: Direction) {
let reg = cx.register.unwrap_or('/'); let reg = cx.register.unwrap_or('/');
let scrolloff = cx.editor.config.scrolloff; let scrolloff = cx.editor.config.scrolloff;
let (_, doc) = current!(cx.editor); let doc = doc!(cx.editor);
// TODO: could probably share with select_on_matches? // TODO: could probably share with select_on_matches?
@ -2046,7 +2053,7 @@ pub mod cmd {
fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> { fn write_impl(cx: &mut compositor::Context, path: Option<&Cow<str>>) -> anyhow::Result<()> {
let jobs = &mut cx.jobs; let jobs = &mut cx.jobs;
let (_, doc) = current!(cx.editor); let doc = doc_mut!(cx.editor);
if let Some(ref path) = path { if let Some(ref path) = path {
doc.set_path(Some(path.as_ref().as_ref())) doc.set_path(Some(path.as_ref().as_ref()))
@ -2099,8 +2106,7 @@ pub mod cmd {
_args: &[Cow<str>], _args: &[Cow<str>],
_event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let (_, doc) = current!(cx.editor); let doc = doc!(cx.editor);
if let Some(format) = doc.format() { if let Some(format) = doc.format() {
let callback = let callback =
make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format); make_format_callback(doc.id(), doc.version(), Modified::LeaveModified, format);
@ -2323,12 +2329,7 @@ pub mod cmd {
write_all_impl(cx, args, event, true, true) write_all_impl(cx, args, event, true, true)
} }
fn quit_all_impl( fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
editor: &mut Editor,
_args: &[Cow<str>],
_event: PromptEvent,
force: bool,
) -> anyhow::Result<()> {
if !force { if !force {
buffers_remaining_impl(editor)?; buffers_remaining_impl(editor)?;
} }
@ -2344,18 +2345,18 @@ pub mod cmd {
fn quit_all( fn quit_all(
cx: &mut compositor::Context, cx: &mut compositor::Context,
args: &[Cow<str>], _args: &[Cow<str>],
event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
quit_all_impl(cx.editor, args, event, false) quit_all_impl(cx.editor, false)
} }
fn force_quit_all( fn force_quit_all(
cx: &mut compositor::Context, cx: &mut compositor::Context,
args: &[Cow<str>], _args: &[Cow<str>],
event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
quit_all_impl(cx.editor, args, event, true) quit_all_impl(cx.editor, true)
} }
fn cquit( fn cquit(
@ -2369,12 +2370,21 @@ pub mod cmd {
.unwrap_or(1); .unwrap_or(1);
cx.editor.exit_code = exit_code; cx.editor.exit_code = exit_code;
let views: Vec<_> = cx.editor.tree.views().map(|(view, _)| view.id).collect(); quit_all_impl(cx.editor, false)
for view_id in views {
cx.editor.close(view_id);
} }
Ok(()) fn force_cquit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
let exit_code = args
.first()
.and_then(|code| code.parse::<i32>().ok())
.unwrap_or(1);
cx.editor.exit_code = exit_code;
quit_all_impl(cx.editor, true)
} }
fn theme( fn theme(
@ -2409,7 +2419,7 @@ pub mod cmd {
args: &[Cow<str>], args: &[Cow<str>],
_event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let (_, doc) = current!(cx.editor); let doc = doc!(cx.editor);
let default_sep = Cow::Borrowed(doc.line_ending.as_str()); let default_sep = Cow::Borrowed(doc.line_ending.as_str());
let separator = args.first().unwrap_or(&default_sep); let separator = args.first().unwrap_or(&default_sep);
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard) yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Clipboard)
@ -2428,7 +2438,7 @@ pub mod cmd {
args: &[Cow<str>], args: &[Cow<str>],
_event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let (_, doc) = current!(cx.editor); let doc = doc!(cx.editor);
let default_sep = Cow::Borrowed(doc.line_ending.as_str()); let default_sep = Cow::Borrowed(doc.line_ending.as_str());
let separator = args.first().unwrap_or(&default_sep); let separator = args.first().unwrap_or(&default_sep);
yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection) yank_joined_to_clipboard_impl(cx.editor, separator, ClipboardType::Selection)
@ -2555,7 +2565,7 @@ pub mod cmd {
args: &[Cow<str>], args: &[Cow<str>],
_event: PromptEvent, _event: PromptEvent,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let (_, doc) = current!(cx.editor); let doc = doc_mut!(cx.editor);
if let Some(label) = args.first() { if let Some(label) = args.first() {
doc.set_encoding(label) doc.set_encoding(label)
} else { } else {
@ -3017,6 +3027,13 @@ pub mod cmd {
fun: cquit, fun: cquit,
completer: None, completer: None,
}, },
TypableCommand {
name: "cquit!",
aliases: &["cq!"],
doc: "Quit with exit code (default 1) forcefully (ignoring unsaved changes). Accepts an optional integer exit code (:cq! 2).",
fun: force_cquit,
completer: None,
},
TypableCommand { TypableCommand {
name: "theme", name: "theme",
aliases: &[], aliases: &[],
@ -3393,7 +3410,7 @@ fn symbol_picker(cx: &mut Context) {
nested_to_flat(list, file, child); nested_to_flat(list, file, child);
} }
} }
let (_, doc) = current!(cx.editor); let doc = doc!(cx.editor);
let language_server = match doc.language_server() { let language_server = match doc.language_server() {
Some(language_server) => language_server, Some(language_server) => language_server,
@ -3414,7 +3431,7 @@ fn symbol_picker(cx: &mut Context) {
let symbols = match symbols { let symbols = match symbols {
lsp::DocumentSymbolResponse::Flat(symbols) => symbols, lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
lsp::DocumentSymbolResponse::Nested(symbols) => { lsp::DocumentSymbolResponse::Nested(symbols) => {
let (_view, doc) = current!(editor); let doc = doc!(editor);
let mut flat_symbols = Vec::new(); let mut flat_symbols = Vec::new();
for symbol in symbols { for symbol in symbols {
nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol) nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
@ -3456,17 +3473,15 @@ fn symbol_picker(cx: &mut Context) {
} }
fn workspace_symbol_picker(cx: &mut Context) { fn workspace_symbol_picker(cx: &mut Context) {
let (_, doc) = current!(cx.editor); let doc = doc!(cx.editor);
let current_path = doc.path().cloned();
let language_server = match doc.language_server() { let language_server = match doc.language_server() {
Some(language_server) => language_server, Some(language_server) => language_server,
None => return, None => return,
}; };
let offset_encoding = language_server.offset_encoding(); let offset_encoding = language_server.offset_encoding();
let future = language_server.workspace_symbols("".to_string()); let future = language_server.workspace_symbols("".to_string());
let current_path = doc_mut!(cx.editor).path().cloned();
cx.callback( cx.callback(
future, future,
move |_editor: &mut Editor, move |_editor: &mut Editor,
@ -3576,8 +3591,7 @@ pub fn code_action(cx: &mut Context) {
} }
pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) { pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
let (_view, doc) = current!(editor); let doc = doc!(editor);
let language_server = match doc.language_server() { let language_server = match doc.language_server() {
Some(language_server) => language_server, Some(language_server) => language_server,
None => return, None => return,
@ -3839,22 +3853,22 @@ fn open(cx: &mut Context, open: Open) {
let mut offs = 0; let mut offs = 0;
let mut transaction = Transaction::change_by_selection(contents, selection, |range| { let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
let line = range.cursor_line(text); let cursor_line = range.cursor_line(text);
let line = match open { let new_line = match open {
// adjust position to the end of the line (next line - 1) // adjust position to the end of the line (next line - 1)
Open::Below => line + 1, Open::Below => cursor_line + 1,
// adjust position to the end of the previous line (current line - 1) // adjust position to the end of the previous line (current line - 1)
Open::Above => line, Open::Above => cursor_line,
}; };
// Index to insert newlines after, as well as the char width // Index to insert newlines after, as well as the char width
// to use to compensate for those inserted newlines. // to use to compensate for those inserted newlines.
let (line_end_index, line_end_offset_width) = if line == 0 { let (line_end_index, line_end_offset_width) = if new_line == 0 {
(0, 0) (0, 0)
} else { } else {
( (
line_end_char_index(&doc.text().slice(..), line.saturating_sub(1)), line_end_char_index(&doc.text().slice(..), new_line.saturating_sub(1)),
doc.line_ending.len_chars(), doc.line_ending.len_chars(),
) )
}; };
@ -3865,8 +3879,10 @@ fn open(cx: &mut Context, open: Open) {
doc.syntax(), doc.syntax(),
text, text,
line_end_index, line_end_index,
new_line.saturating_sub(1),
true, true,
); )
.unwrap_or_else(|| indent::indent_level_for_line(text.line(cursor_line), doc.tab_width()));
let indent = doc.indent_unit().repeat(indent_level); let indent = doc.indent_unit().repeat(indent_level);
let indent_len = indent.len(); let indent_len = indent.len();
let mut text = String::with_capacity(1 + indent_len); let mut text = String::with_capacity(1 + indent_len);
@ -4331,27 +4347,21 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
} }
fn goto_first_diag(cx: &mut Context) { fn goto_first_diag(cx: &mut Context) {
let editor = &mut cx.editor; let doc = doc!(cx.editor);
let (_, doc) = current!(editor);
let pos = match doc.diagnostics().first() { let pos = match doc.diagnostics().first() {
Some(diag) => diag.range.start, Some(diag) => diag.range.start,
None => return, None => return,
}; };
goto_pos(cx.editor, pos);
goto_pos(editor, pos);
} }
fn goto_last_diag(cx: &mut Context) { fn goto_last_diag(cx: &mut Context) {
let editor = &mut cx.editor; let doc = doc!(cx.editor);
let (_, doc) = current!(editor);
let pos = match doc.diagnostics().last() { let pos = match doc.diagnostics().last() {
Some(diag) => diag.range.start, Some(diag) => diag.range.start,
None => return, None => return,
}; };
goto_pos(cx.editor, pos);
goto_pos(editor, pos);
} }
fn goto_next_diag(cx: &mut Context) { fn goto_next_diag(cx: &mut Context) {
@ -4607,48 +4617,48 @@ pub mod insert {
}; };
let curr = contents.get_char(pos).unwrap_or(' '); let curr = contents.get_char(pos).unwrap_or(' ');
// TODO: offset range.head by 1? when calculating? let current_line = text.char_to_line(pos);
let indent_level = indent::suggested_indent_for_pos( let indent_level = indent::suggested_indent_for_pos(
doc.language_config(), doc.language_config(),
doc.syntax(), doc.syntax(),
text, text,
pos.saturating_sub(1), pos,
current_line,
true, true,
); )
.unwrap_or_else(|| {
indent::indent_level_for_line(text.line(current_line), doc.tab_width())
});
let indent = doc.indent_unit().repeat(indent_level); let indent = doc.indent_unit().repeat(indent_level);
let mut text = String::with_capacity(1 + indent.len()); let mut text = String::new();
// If we are between pairs (such as brackets), we want to insert an additional line which is indented one level more and place the cursor there
let new_head_pos = if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
let inner_indent = doc.indent_unit().repeat(indent_level + 1);
text.reserve_exact(2 + indent.len() + inner_indent.len());
text.push_str(doc.line_ending.as_str());
text.push_str(&inner_indent);
let new_head_pos = pos + offs + text.chars().count();
text.push_str(doc.line_ending.as_str()); text.push_str(doc.line_ending.as_str());
text.push_str(&indent); text.push_str(&indent);
new_head_pos
let head = pos + offs + text.chars().count();
// TODO: range replace or extend
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
// can be used with cx.mode to do replace or extend on most changes
ranges.push(Range::new(
if range.is_empty() {
head
} else { } else {
range.anchor + offs text.reserve_exact(1 + indent.len());
},
head,
));
// if between a bracket pair
if helix_core::auto_pairs::PAIRS.contains(&(prev, curr)) {
// another newline, indent the end bracket one level less
let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
text.push_str(doc.line_ending.as_str()); text.push_str(doc.line_ending.as_str());
text.push_str(&indent); text.push_str(&indent);
} pos + offs + text.chars().count()
};
// TODO: range replace or extend
// range.replace(|range| range.is_empty(), head); -> fn extend if cond true, new head pos
// can be used with cx.mode to do replace or extend on most changes
ranges.push(Range::new(new_head_pos, new_head_pos));
offs += text.chars().count(); offs += text.chars().count();
(pos, pos, Some(text.into())) (pos, pos, Some(text.into()))
}); });
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index())); transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
//
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
} }
@ -5416,7 +5426,7 @@ pub fn completion(cx: &mut Context) {
move |editor: &mut Editor, move |editor: &mut Editor,
compositor: &mut Compositor, compositor: &mut Compositor,
response: Option<lsp::CompletionResponse>| { response: Option<lsp::CompletionResponse>| {
let (_, doc) = current!(editor); let doc = doc!(editor);
if doc.mode() != Mode::Insert { if doc.mode() != Mode::Insert {
// we're not in insert mode anymore // we're not in insert mode anymore
return; return;

@ -781,8 +781,9 @@ impl EditorView {
pub fn clear_completion(&mut self, editor: &mut Editor) { pub fn clear_completion(&mut self, editor: &mut Editor) {
self.completion = None; self.completion = None;
// Clear any savepoints // Clear any savepoints
let (_, doc) = current!(editor); let doc = doc_mut!(editor);
doc.savepoint = None; doc.savepoint = None;
editor.clear_idle_timer(); // don't retrigger editor.clear_idle_timer(); // don't retrigger
} }
@ -940,14 +941,18 @@ impl EditorView {
} }
impl Component for EditorView { impl Component for EditorView {
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { fn handle_event(
let mut cxt = commands::Context { &mut self,
editor: cx.editor, event: Event,
context: &mut crate::compositor::Context,
) -> EventResult {
let mut cx = commands::Context {
editor: context.editor,
count: None, count: None,
register: None, register: None,
callback: None, callback: None,
on_next_key_callback: None, on_next_key_callback: None,
jobs: cx.jobs, jobs: context.jobs,
}; };
match event { match event {
@ -957,18 +962,19 @@ impl Component for EditorView {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(key) => { Event::Key(key) => {
cxt.editor.reset_idle_timer(); cx.editor.reset_idle_timer();
let mut key = KeyEvent::from(key); let mut key = KeyEvent::from(key);
canonicalize_key(&mut key); canonicalize_key(&mut key);
// clear status // clear status
cxt.editor.status_msg = None; cx.editor.status_msg = None;
let (_, doc) = current!(cxt.editor); let doc = doc!(cx.editor);
let mode = doc.mode(); let mode = doc.mode();
if let Some(on_next_key) = self.on_next_key.take() { if let Some(on_next_key) = self.on_next_key.take() {
// if there's a command waiting input, do that first // if there's a command waiting input, do that first
on_next_key(&mut cxt, key); on_next_key(&mut cx, key);
} else { } else {
match mode { match mode {
Mode::Insert => { Mode::Insert => {
@ -980,8 +986,8 @@ impl Component for EditorView {
if let Some(completion) = &mut self.completion { if let Some(completion) = &mut self.completion {
// use a fake context here // use a fake context here
let mut cx = Context { let mut cx = Context {
editor: cxt.editor, editor: cx.editor,
jobs: cxt.jobs, jobs: cx.jobs,
scroll: None, scroll: None,
}; };
let res = completion.handle_event(event, &mut cx); let res = completion.handle_event(event, &mut cx);
@ -991,40 +997,40 @@ impl Component for EditorView {
if callback.is_some() { if callback.is_some() {
// assume close_fn // assume close_fn
self.clear_completion(cxt.editor); self.clear_completion(cx.editor);
} }
} }
} }
// if completion didn't take the event, we pass it onto commands // if completion didn't take the event, we pass it onto commands
if !consumed { if !consumed {
self.insert_mode(&mut cxt, key); self.insert_mode(&mut cx, key);
// lastly we recalculate completion // lastly we recalculate completion
if let Some(completion) = &mut self.completion { if let Some(completion) = &mut self.completion {
completion.update(&mut cxt); completion.update(&mut cx);
if completion.is_empty() { if completion.is_empty() {
self.clear_completion(cxt.editor); self.clear_completion(cx.editor);
} }
} }
} }
} }
mode => self.command_mode(mode, &mut cxt, key), mode => self.command_mode(mode, &mut cx, key),
} }
} }
self.on_next_key = cxt.on_next_key_callback.take(); self.on_next_key = cx.on_next_key_callback.take();
// appease borrowck // appease borrowck
let callback = cxt.callback.take(); let callback = cx.callback.take();
// if the command consumed the last view, skip the render. // if the command consumed the last view, skip the render.
// on the next loop cycle the Application will then terminate. // on the next loop cycle the Application will then terminate.
if cxt.editor.should_close() { if cx.editor.should_close() {
return EventResult::Ignored; return EventResult::Ignored;
} }
let (view, doc) = current!(cxt.editor); let (view, doc) = current!(cx.editor);
view.ensure_cursor_in_view(doc, cxt.editor.config.scrolloff); view.ensure_cursor_in_view(doc, cx.editor.config.scrolloff);
// mode transitions // mode transitions
match (mode, doc.mode()) { match (mode, doc.mode()) {
@ -1053,7 +1059,7 @@ impl Component for EditorView {
EventResult::Consumed(callback) EventResult::Consumed(callback)
} }
Event::Mouse(event) => self.handle_mouse_event(event, &mut cxt), Event::Mouse(event) => self.handle_mouse_event(event, &mut cx),
} }
} }

@ -436,6 +436,15 @@ file-types = ["ll"]
comment-token = ";" comment-token = ";"
indent = { tab-width = 2, unit = " " } indent = { tab-width = 2, unit = " " }
[[language]]
name = "tablegen"
scope = "source.tablegen"
roots = []
file-types = ["td"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
injection-regex = "tablegen"
[[language]] [[language]]
name = "markdown" name = "markdown"
scope = "source.md" scope = "source.md"

@ -1,6 +1,7 @@
(fenced_code_block (fenced_code_block
(info_string) @injection.language (info_string) @injection.language
(code_fence_content) @injection.content) (code_fence_content) @injection.content
(#set! injection.include-children))
((html_block) @injection.content ((html_block) @injection.content
(#set! injection.language "html")) (#set! injection.language "html"))

@ -9,6 +9,7 @@ indent = [
"field_initializer_list", "field_initializer_list",
"struct_pattern", "struct_pattern",
"tuple_pattern", "tuple_pattern",
"unit_expression",
"enum_variant_list", "enum_variant_list",
"call_expression", "call_expression",
"binary_expression", "binary_expression",

@ -0,0 +1,90 @@
[
(comment)
(multiline_comment)
] @comment
[
"("
")"
"["
"]"
"{"
"}"
"<"
">"
] @punctuation.bracket
[
","
";"
"."
] @punctuation.delimiter
[
"#"
"-"
"..."
":"
] @operator
[
"="
"!cond"
(operator_keyword)
] @function
[
"true"
"false"
] @constant.builtin.boolean
[
"?"
] @constant.builtin
(var) @variable
(template_arg (identifier) @variable.parameter)
(_ argument: (value (identifier) @variable.parameter))
(type) @type
"code" @type.builtin
(number) @constant.numeric.integer
[
(string_string)
(code_string)
] @string
(preprocessor) @keyword.directive
[
"class"
"field"
"let"
"defvar"
"def"
"defset"
"defvar"
"assert"
] @keyword
[
"let"
"in"
"foreach"
"if"
"then"
"else"
] @keyword.operator
"include" @keyword.control.import
[
"multiclass"
"defm"
] @namespace
(ERROR) @error

@ -0,0 +1,7 @@
indent = [
"statement",
]
outdent = [
"}",
]

@ -0,0 +1,2 @@
([ (comment) (multiline_comment)] @injection.content
(#set! injection.language "comment"))

@ -0,0 +1,7 @@
(class
body: (_) @class.inside) @class.around
(multiclass
body: (_) @class.inside) @class.around
(_ argument: _ @parameter.inside)

@ -53,3 +53,6 @@
"error" = "#dc597f" "error" = "#dc597f"
"info" = "#59dcb7" "info" = "#59dcb7"
"hint" = "#59c0dc" "hint" = "#59c0dc"
# make diagnostic underlined, to distinguish with selection text.
diagnostic = { modifiers = ["underlined"] }

@ -12,7 +12,7 @@
"type" = "yellow" "type" = "yellow"
"constant" = "purple" "constant" = "purple"
"constant.numeric" = "purple" "constant.numeric" = "purple"
"string" = "grey2" "string" = "green"
"comment" = "grey0" "comment" = "grey0"
"variable" = "fg" "variable" = "fg"
"variable.builtin" = "blue" "variable.builtin" = "blue"

@ -0,0 +1,90 @@
# Everforest (Dark Hard)
# Author: CptPotato
# Original Author:
# URL: https://github.com/sainnhe/everforest
# Filename: autoload/everforest.vim
# Author: sainnhe
# Email: sainnhe@gmail.com
# License: MIT License
"constant.character.escape" = "orange"
"type" = "yellow"
"constant" = "purple"
"constant.numeric" = "purple"
"string" = "green"
"comment" = "grey0"
"variable" = "fg"
"variable.builtin" = "blue"
"variable.parameter" = "fg"
"variable.other.member" = "fg"
"label" = "aqua"
"punctuation" = "grey2"
"punctuation.delimiter" = "grey2"
"punctuation.bracket" = "fg"
"keyword" = "red"
"operator" = "orange"
"function" = "green"
"function.builtin" = "blue"
"function.macro" = "aqua"
"tag" = "yellow"
"namespace" = "aqua"
"attribute" = "aqua"
"constructor" = "yellow"
"module" = "blue"
"special" = "orange"
"diff.plus" = "green"
"diff.delta" = "orange"
"diff.minus" = "red"
"ui.background" = { bg = "bg0" }
"ui.cursor" = { fg = "bg0", bg = "fg" }
"ui.cursor.match" = { fg = "orange", bg = "bg_yellow" }
"ui.cursor.insert" = { fg = "bg0", bg = "grey1" }
"ui.cursor.select" = { fg = "bg0", bg = "blue" }
"ui.linenr" = "grey0"
"ui.linenr.selected" = "fg"
"ui.statusline" = { fg = "grey2", bg = "bg2" }
"ui.statusline.inactive" = { fg = "grey0", bg = "bg1" }
"ui.popup" = { fg = "grey2", bg = "bg1" }
"ui.window" = { fg = "grey2", bg = "bg1" }
"ui.help" = { fg = "fg", bg = "bg1" }
"ui.text" = "fg"
"ui.text.focus" = "fg"
"ui.menu" = { fg = "fg", bg = "bg2" }
"ui.menu.selected" = { fg = "bg0", bg = "green" }
"ui.selection" = { bg = "bg3" }
"hint" = "blue"
"info" = "aqua"
"warning" = "yellow"
"error" = "red"
"diagnostic" = { modifiers = ["underlined"] }
[palette]
bg0 = "#fff9e8"
bg1 = "#f7f4e0"
bg2 = "#f0eed9"
bg3 = "#e9e8d2"
bg4 = "#e1ddcb"
bg5 = "#bec5b2"
bg_visual = "#edf0cd"
bg_red = "#fce5dc"
bg_green = "#f1f3d4"
bg_blue = "#eaf2eb"
bg_yellow = "#fbefd0"
fg = "#5c6a72"
red = "#f85552"
orange = "#f57d26"
yellow = "#dfa000"
green = "#8da101"
aqua = "#35a77c"
blue = "#3a94c5"
purple = "#df69ba"
grey0 = "#a6b0a0"
grey1 = "#939f91"
grey2 = "#829181"
Loading…
Cancel
Save