mirror of
https://github.com/helix-editor/helix.git
synced 2025-10-06 00:13:28 +02:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f1dc25a774 | ||
|
4f335fabc8 | ||
|
f366b97bce | ||
|
9c24f1ec0e | ||
|
f99a683991 | ||
|
9edae7e1f8 | ||
|
51d1d43289 | ||
|
5a245b83a0 | ||
|
2100f5a2c0 | ||
|
8f6f329057 | ||
|
8949347e2c | ||
|
54de768915 | ||
|
6e03019a2c | ||
|
31d41080ed | ||
|
5e6b46e7c5 | ||
|
354b822d21 | ||
|
fae2127a11 | ||
|
0e5b421646 | ||
|
4a9d1163e0 |
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -291,6 +291,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smallvec",
|
||||
"tendril",
|
||||
"toml",
|
||||
"tree-sitter",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
@@ -753,18 +754,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.125"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.125"
|
||||
version = "1.0.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
|
||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -920,9 +921,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.5.0"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
|
||||
checksum = "bd3076b5c8cc18138b8f8814895c11eb4de37114a5d127bafdc5e55798ceef37"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
@@ -940,9 +941,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
|
||||
checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -951,9 +952,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0"
|
||||
checksum = "f8864d706fdb3cc0843a49647ac892720dac98a6eeb818b77190592cf4994066"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
|
@@ -24,7 +24,7 @@ It's a terminal-based editor first, but I'd like to explore a custom renderer
|
||||
|
||||
# Installation
|
||||
|
||||
Note: Only the Rust syntax has indentation definitions at the moment.
|
||||
Note: Only Rust and Golang have indentation definitions at the moment.
|
||||
|
||||
We provide packaging for various distributions, but here's a quick method to
|
||||
build from source.
|
||||
|
5
TODO.md
5
TODO.md
@@ -1,5 +1,4 @@
|
||||
- Refactor tree-sitter-highlight to work like the atom one, recomputing partial tree updates.
|
||||
- syntax errors highlight query
|
||||
|
||||
------
|
||||
|
||||
@@ -19,15 +18,11 @@
|
||||
- [ ] document.on_type provider triggers
|
||||
- [ ] completion isIncomplete support
|
||||
|
||||
- [ ] extract indentation calculation queries so we can support other languages.
|
||||
|
||||
1
|
||||
- [ ] :format/:fmt that formats the buffer
|
||||
- [ ] respect view fullscreen flag
|
||||
- [ ] Implement marks (superset of Selection/Range)
|
||||
|
||||
- [ ] nixos packaging
|
||||
- [ ] CI binary builds
|
||||
|
||||
- [ ] = for auto indent line/selection
|
||||
- [ ] :x for closing buffers
|
||||
|
@@ -17,7 +17,7 @@
|
||||
| f | find next char |
|
||||
| T | find 'till previous char |
|
||||
| F | find previous char |
|
||||
| 0 | move to the start of the line |
|
||||
| ^ | move to the start of the line |
|
||||
| $ | move to the end of the line |
|
||||
| m | Jump to matching bracket |
|
||||
| PageUp | Move page up |
|
||||
|
@@ -22,5 +22,6 @@ once_cell = "1.4"
|
||||
regex = "1"
|
||||
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "0.5"
|
||||
|
||||
etcetera = "0.3"
|
||||
|
@@ -65,14 +65,20 @@ fn handle_open(
|
||||
) -> Transaction {
|
||||
let mut ranges = SmallVec::with_capacity(selection.len());
|
||||
|
||||
let mut offs = 0;
|
||||
|
||||
let mut transaction = Transaction::change_by_selection(doc, selection, |range| {
|
||||
let pos = range.head;
|
||||
let next = next_char(doc, pos);
|
||||
|
||||
let head = pos + open.len_utf8();
|
||||
let head = pos + offs + open.len_utf8();
|
||||
// if selection, retain anchor, if cursor, move over
|
||||
ranges.push(Range::new(
|
||||
if range.is_empty() { head } else { range.anchor },
|
||||
if range.is_empty() {
|
||||
head
|
||||
} else {
|
||||
range.anchor + offs
|
||||
},
|
||||
head,
|
||||
));
|
||||
|
||||
@@ -88,6 +94,8 @@ fn handle_open(
|
||||
pair.push_char(open);
|
||||
pair.push_char(close);
|
||||
|
||||
offs += 2;
|
||||
|
||||
(pos, pos, Some(pair))
|
||||
}
|
||||
}
|
||||
@@ -99,14 +107,20 @@ fn handle_open(
|
||||
fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> Transaction {
|
||||
let mut ranges = SmallVec::with_capacity(selection.len());
|
||||
|
||||
let mut offs = 0;
|
||||
|
||||
let mut transaction = Transaction::change_by_selection(doc, selection, |range| {
|
||||
let pos = range.head;
|
||||
let next = next_char(doc, pos);
|
||||
|
||||
let head = pos + close.len_utf8();
|
||||
let head = pos + offs + close.len_utf8();
|
||||
// if selection, retain anchor, if cursor, move over
|
||||
ranges.push(Range::new(
|
||||
if range.is_empty() { head } else { range.anchor },
|
||||
if range.is_empty() {
|
||||
head
|
||||
} else {
|
||||
range.anchor + offs
|
||||
},
|
||||
head,
|
||||
));
|
||||
|
||||
@@ -114,6 +128,8 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
|
||||
// return transaction that moves past close
|
||||
(pos, pos, None) // no-op
|
||||
} else {
|
||||
offs += close.len_utf8();
|
||||
|
||||
// TODO: else return (use default handler that inserts close)
|
||||
(pos, pos, Some(Tendril::from_char(close)))
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
find_first_non_whitespace_char,
|
||||
syntax::Syntax,
|
||||
syntax::{IndentQuery, LanguageConfiguration, Syntax},
|
||||
tree_sitter::{Node, Tree},
|
||||
Rope, RopeSlice,
|
||||
};
|
||||
@@ -43,41 +43,12 @@ fn get_highest_syntax_node_at_bytepos(syntax: &Syntax, pos: usize) -> Option<Nod
|
||||
Some(node)
|
||||
}
|
||||
|
||||
fn calculate_indentation(node: Option<Node>, newline: bool) -> usize {
|
||||
fn calculate_indentation(query: &IndentQuery, node: Option<Node>, newline: bool) -> usize {
|
||||
// NOTE: can't use contains() on query because of comparing Vec<String> and &str
|
||||
// https://doc.rust-lang.org/std/vec/struct.Vec.html#method.contains
|
||||
|
||||
let mut increment: i32 = 0;
|
||||
|
||||
// Hardcoded for rust for now
|
||||
let indent_scopes = &[
|
||||
"while_expression",
|
||||
"for_expression",
|
||||
"loop_expression",
|
||||
"if_expression",
|
||||
"if_let_expression",
|
||||
// "match_expression",
|
||||
// "match_arm",
|
||||
"tuple_expression",
|
||||
"array_expression",
|
||||
// indent_except_first_scopes
|
||||
"use_list",
|
||||
"block",
|
||||
"match_block",
|
||||
"arguments",
|
||||
"parameters",
|
||||
"declaration_list",
|
||||
"field_declaration_list",
|
||||
"field_initializer_list",
|
||||
"struct_pattern",
|
||||
"tuple_pattern",
|
||||
"enum_variant_list",
|
||||
// "function_item",
|
||||
// "closure_expression",
|
||||
"binary_expression",
|
||||
"field_expression",
|
||||
"where_clause",
|
||||
];
|
||||
|
||||
let outdent = &["where", "}", "]", ")"];
|
||||
|
||||
let mut node = match node {
|
||||
Some(node) => node,
|
||||
None => return 0,
|
||||
@@ -88,7 +59,7 @@ fn calculate_indentation(node: Option<Node>, newline: bool) -> usize {
|
||||
// if we're calculating indentation for a brand new line then the current node will become the
|
||||
// parent node. We need to take it's indentation level into account too.
|
||||
let node_kind = node.kind();
|
||||
if newline && indent_scopes.contains(&node_kind) {
|
||||
if newline && query.indent.contains(node_kind) {
|
||||
increment += 1;
|
||||
}
|
||||
|
||||
@@ -102,14 +73,14 @@ fn calculate_indentation(node: Option<Node>, newline: bool) -> usize {
|
||||
// }) <-- }) is two scopes
|
||||
let starts_same_line = start == prev_start;
|
||||
|
||||
if outdent.contains(&node.kind()) && !starts_same_line {
|
||||
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;
|
||||
}
|
||||
|
||||
if indent_scopes.contains(&parent_kind) // && not_first_or_last_sibling
|
||||
if query.indent.contains(parent_kind) // && not_first_or_last_sibling
|
||||
&& !starts_same_line
|
||||
{
|
||||
// println!("is_scope {}", parent_kind);
|
||||
@@ -128,6 +99,7 @@ fn calculate_indentation(node: Option<Node>, newline: bool) -> usize {
|
||||
}
|
||||
|
||||
fn suggested_indent_for_line(
|
||||
language_config: &LanguageConfiguration,
|
||||
syntax: Option<&Syntax>,
|
||||
text: RopeSlice,
|
||||
line_num: usize,
|
||||
@@ -137,7 +109,7 @@ fn suggested_indent_for_line(
|
||||
let current = indent_level_for_line(line, tab_width);
|
||||
|
||||
if let Some(start) = find_first_non_whitespace_char(text, line_num) {
|
||||
return suggested_indent_for_pos(syntax, text, start, false);
|
||||
return suggested_indent_for_pos(Some(language_config), syntax, text, start, false);
|
||||
};
|
||||
|
||||
// if the line is blank, indent should be zero
|
||||
@@ -148,18 +120,24 @@ fn suggested_indent_for_line(
|
||||
// - it should return 0 when mass indenting stuff
|
||||
// - it should look up the wrapper node and count it too when we press o/O
|
||||
pub fn suggested_indent_for_pos(
|
||||
language_config: Option<&LanguageConfiguration>,
|
||||
syntax: Option<&Syntax>,
|
||||
text: RopeSlice,
|
||||
pos: usize,
|
||||
new_line: bool,
|
||||
) -> usize {
|
||||
if let Some(syntax) = syntax {
|
||||
if let (Some(query), Some(syntax)) = (
|
||||
language_config.and_then(|config| config.indent_query()),
|
||||
syntax,
|
||||
) {
|
||||
let byte_start = text.char_to_byte(pos);
|
||||
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: if preserve_leading_whitespace
|
||||
calculate_indentation(node, new_line)
|
||||
calculate_indentation(query, node, new_line)
|
||||
} else {
|
||||
// TODO: heuristics for non-tree sitter grammars
|
||||
0
|
||||
@@ -286,6 +264,7 @@ where
|
||||
tab_width: 4,
|
||||
unit: String::from(" "),
|
||||
}),
|
||||
indent_query: OnceCell::new(),
|
||||
}],
|
||||
});
|
||||
|
||||
@@ -304,7 +283,7 @@ where
|
||||
let line = text.line(i);
|
||||
let indent = indent_level_for_line(line, tab_width);
|
||||
assert_eq!(
|
||||
suggested_indent_for_line(Some(&syntax), text, i, tab_width),
|
||||
suggested_indent_for_line(&language_config, Some(&syntax), text, i, tab_width),
|
||||
indent,
|
||||
"line {}: {}",
|
||||
i,
|
||||
|
@@ -4,7 +4,7 @@ pub use helix_syntax::{get_language, get_language_name, Lang};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
@@ -41,6 +41,9 @@ pub struct LanguageConfiguration {
|
||||
pub language_server: Option<LanguageServerConfiguration>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub indent: Option<IndentationConfiguration>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub(crate) indent_query: OnceCell<Option<IndentQuery>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -59,6 +62,17 @@ pub struct IndentationConfiguration {
|
||||
pub unit: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct IndentQuery {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub indent: HashSet<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "HashSet::is_empty")]
|
||||
pub outdent: HashSet<String>,
|
||||
}
|
||||
|
||||
fn read_query(language: &str, filename: &str) -> String {
|
||||
static INHERITS_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()]+)\s*").unwrap());
|
||||
@@ -127,6 +141,20 @@ impl LanguageConfiguration {
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn indent_query(&self) -> Option<&IndentQuery> {
|
||||
self.indent_query
|
||||
.get_or_init(|| {
|
||||
let language = get_language_name(self.language_id).to_ascii_lowercase();
|
||||
|
||||
let root = crate::runtime_dir();
|
||||
let path = root.join("queries").join(language).join("indents.toml");
|
||||
|
||||
let toml = std::fs::read(&path).ok()?;
|
||||
toml::from_slice(&toml).ok()
|
||||
})
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> &str {
|
||||
&self.scope
|
||||
}
|
||||
|
@@ -37,7 +37,6 @@ use once_cell::sync::Lazy;
|
||||
pub struct Context<'a> {
|
||||
pub count: usize,
|
||||
pub editor: &'a mut Editor,
|
||||
pub view_id: ViewId,
|
||||
|
||||
pub callback: Option<crate::compositor::Callback>,
|
||||
pub on_next_key_callback: Option<Box<dyn FnOnce(&mut Context, KeyEvent)>>,
|
||||
@@ -187,37 +186,31 @@ pub fn move_line_down(cx: &mut Context) {
|
||||
|
||||
pub fn move_line_end(cx: &mut Context) {
|
||||
let (view, doc) = cx.current();
|
||||
let lines = selection_lines(doc.text(), doc.selection(view.id));
|
||||
|
||||
let positions = lines
|
||||
.into_iter()
|
||||
.map(|index| {
|
||||
// adjust all positions to the end of the line.
|
||||
let selection = doc.selection(view.id).transform(|range| {
|
||||
let text = doc.text();
|
||||
let line = text.char_to_line(range.head);
|
||||
|
||||
// Line end is pos at the start of next line - 1
|
||||
// subtract another 1 because the line ends with \n
|
||||
doc.text().line_to_char(index + 1).saturating_sub(2)
|
||||
})
|
||||
.map(|pos| Range::new(pos, pos));
|
||||
|
||||
let selection = Selection::new(positions.collect(), 0);
|
||||
// Line end is pos at the start of next line - 1
|
||||
// subtract another 1 because the line ends with \n
|
||||
let pos = text.line_to_char(line + 1).saturating_sub(2);
|
||||
Range::new(pos, pos)
|
||||
});
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
|
||||
pub fn move_line_start(cx: &mut Context) {
|
||||
let (view, doc) = cx.current();
|
||||
let lines = selection_lines(doc.text(), doc.selection(view.id));
|
||||
|
||||
let positions = lines
|
||||
.into_iter()
|
||||
.map(|index| {
|
||||
// adjust all positions to the start of the line.
|
||||
doc.text().line_to_char(index)
|
||||
})
|
||||
.map(|pos| Range::new(pos, pos));
|
||||
let selection = doc.selection(view.id).transform(|range| {
|
||||
let text = doc.text();
|
||||
let line = text.char_to_line(range.head);
|
||||
|
||||
let selection = Selection::new(positions.collect(), 0);
|
||||
// adjust to start of the line
|
||||
let pos = text.line_to_char(line);
|
||||
Range::new(pos, pos)
|
||||
});
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
@@ -640,6 +633,11 @@ fn _search(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, e
|
||||
let start = text.byte_to_char(mat.start());
|
||||
let end = text.byte_to_char(mat.end());
|
||||
|
||||
if end == 0 {
|
||||
// skip empty matches that don't make sense
|
||||
return;
|
||||
}
|
||||
|
||||
let head = end - 1;
|
||||
|
||||
let selection = if extend {
|
||||
@@ -656,7 +654,7 @@ fn _search(doc: &mut Document, view: &mut View, contents: &str, regex: &Regex, e
|
||||
|
||||
// TODO: use one function for search vs extend
|
||||
pub fn search(cx: &mut Context) {
|
||||
let doc = cx.doc();
|
||||
let (view, doc) = cx.current();
|
||||
|
||||
// TODO: could probably share with select_on_matches?
|
||||
|
||||
@@ -664,7 +662,7 @@ pub fn search(cx: &mut Context) {
|
||||
// feed chunks into the regex yet
|
||||
let contents = doc.text().slice(..).to_string();
|
||||
|
||||
let view_id = cx.view_id;
|
||||
let view_id = view.id;
|
||||
let prompt = ui::regex_prompt(cx, "search:".to_string(), move |view, doc, regex| {
|
||||
let text = doc.text();
|
||||
let start = doc.selection(view.id).cursor();
|
||||
@@ -824,6 +822,17 @@ pub fn append_mode(cx: &mut Context) {
|
||||
graphemes::next_grapheme_boundary(text, range.to()), // to() + next char
|
||||
)
|
||||
});
|
||||
|
||||
let end = text.len_chars();
|
||||
|
||||
if selection.iter().any(|range| range.head == end) {
|
||||
let transaction = Transaction::change(
|
||||
doc.text(),
|
||||
std::array::IntoIter::new([(end, end, Some(Tendril::from_char('\n')))]),
|
||||
);
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
|
||||
doc.set_selection(view.id, selection);
|
||||
}
|
||||
|
||||
@@ -912,6 +921,7 @@ mod cmd {
|
||||
|
||||
if let Ok(transaction) = transaction {
|
||||
doc.apply(&transaction, view.id);
|
||||
doc.append_changes_to_history(view.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1107,19 +1117,6 @@ pub fn buffer_picker(cx: &mut Context) {
|
||||
cx.push_layer(Box::new(picker));
|
||||
}
|
||||
|
||||
// calculate line numbers for each selection range
|
||||
fn selection_lines(doc: &Rope, selection: &Selection) -> Vec<usize> {
|
||||
let mut lines = selection
|
||||
.iter()
|
||||
.map(|range| doc.char_to_line(range.head))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
lines.sort_unstable(); // sorting by usize so _unstable is preferred
|
||||
lines.dedup();
|
||||
|
||||
lines
|
||||
}
|
||||
|
||||
// I inserts at the start of each line with a selection
|
||||
pub fn prepend_to_line(cx: &mut Context) {
|
||||
move_line_start(cx);
|
||||
@@ -1129,15 +1126,14 @@ pub fn prepend_to_line(cx: &mut Context) {
|
||||
|
||||
// A inserts at the end of each line with a selection
|
||||
pub fn append_to_line(cx: &mut Context) {
|
||||
move_line_end(cx);
|
||||
|
||||
let (view, doc) = cx.current();
|
||||
enter_insert_mode(doc);
|
||||
|
||||
// offset by another 1 char since move_line_end will position on the last char, we want to
|
||||
// append past that
|
||||
let selection = doc.selection(view.id).transform(|range| {
|
||||
let pos = range.head + 1;
|
||||
let text = doc.text();
|
||||
let line = text.char_to_line(range.head);
|
||||
// we can't use line_to_char(line + 1) - 2 because the last line might not contain \n
|
||||
let pos = (text.line_to_char(line) + text.line(line).len_chars()).saturating_sub(1);
|
||||
Range::new(pos, pos)
|
||||
});
|
||||
doc.set_selection(view.id, selection);
|
||||
@@ -1154,13 +1150,15 @@ fn open(cx: &mut Context, open: Open) {
|
||||
enter_insert_mode(doc);
|
||||
|
||||
let text = doc.text().slice(..);
|
||||
let lines = selection_lines(doc.text(), doc.selection(view.id));
|
||||
let selection = doc.selection(view.id);
|
||||
|
||||
let mut ranges = SmallVec::with_capacity(lines.len());
|
||||
let mut ranges = SmallVec::with_capacity(selection.len());
|
||||
|
||||
let changes: Vec<Change> = selection
|
||||
.iter()
|
||||
.map(|range| {
|
||||
let line = text.char_to_line(range.head);
|
||||
|
||||
let changes: Vec<Change> = lines
|
||||
.into_iter()
|
||||
.map(|line| {
|
||||
let line = match open {
|
||||
// adjust position to the end of the line (next line - 1)
|
||||
Open::Below => line + 1,
|
||||
@@ -1171,7 +1169,13 @@ fn open(cx: &mut Context, open: Open) {
|
||||
let index = doc.text().line_to_char(line).saturating_sub(1);
|
||||
|
||||
// TODO: share logic with insert_newline for indentation
|
||||
let indent_level = indent::suggested_indent_for_pos(doc.syntax(), text, index, true);
|
||||
let indent_level = indent::suggested_indent_for_pos(
|
||||
doc.language_config(),
|
||||
doc.syntax(),
|
||||
text,
|
||||
index,
|
||||
true,
|
||||
);
|
||||
let indent = doc.indent_unit().repeat(indent_level);
|
||||
let mut text = String::with_capacity(1 + indent.len());
|
||||
text.push('\n');
|
||||
@@ -1638,6 +1642,9 @@ pub mod insert {
|
||||
let selection = doc.selection(view.id);
|
||||
let mut ranges = SmallVec::with_capacity(selection.len());
|
||||
|
||||
// TODO: this is annoying, but we need to do it to properly calculate pos after edits
|
||||
let mut offs = 0;
|
||||
|
||||
let mut transaction = Transaction::change_by_selection(contents, selection, |range| {
|
||||
let pos = range.head;
|
||||
|
||||
@@ -1649,17 +1656,26 @@ pub mod insert {
|
||||
let curr = contents.char(pos);
|
||||
|
||||
// TODO: offset range.head by 1? when calculating?
|
||||
let indent_level =
|
||||
indent::suggested_indent_for_pos(doc.syntax(), text, pos.saturating_sub(1), true);
|
||||
let indent_level = indent::suggested_indent_for_pos(
|
||||
doc.language_config(),
|
||||
doc.syntax(),
|
||||
text,
|
||||
pos.saturating_sub(1),
|
||||
true,
|
||||
);
|
||||
let indent = doc.indent_unit().repeat(indent_level);
|
||||
let mut text = String::with_capacity(1 + indent.len());
|
||||
text.push('\n');
|
||||
text.push_str(&indent);
|
||||
|
||||
let head = pos + text.len();
|
||||
let head = pos + offs + text.len();
|
||||
|
||||
ranges.push(Range::new(
|
||||
if range.is_empty() { head } else { range.anchor },
|
||||
if range.is_empty() {
|
||||
head
|
||||
} else {
|
||||
range.anchor + offs
|
||||
},
|
||||
head,
|
||||
));
|
||||
|
||||
@@ -1669,11 +1685,11 @@ pub mod insert {
|
||||
let indent = doc.indent_unit().repeat(indent_level.saturating_sub(1));
|
||||
text.push('\n');
|
||||
text.push_str(&indent);
|
||||
|
||||
(pos, pos, Some(text.into()))
|
||||
} else {
|
||||
(pos, pos, Some(text.into()))
|
||||
}
|
||||
|
||||
offs += text.len();
|
||||
|
||||
(pos, pos, Some(text.into()))
|
||||
});
|
||||
|
||||
transaction = transaction.with_selection(Selection::new(ranges, selection.primary_index()));
|
||||
@@ -1721,12 +1737,12 @@ pub mod insert {
|
||||
// storing it?
|
||||
|
||||
pub fn undo(cx: &mut Context) {
|
||||
let view_id = cx.view_id;
|
||||
let view_id = cx.view().id;
|
||||
cx.doc().undo(view_id);
|
||||
}
|
||||
|
||||
pub fn redo(cx: &mut Context) {
|
||||
let view_id = cx.view_id;
|
||||
let view_id = cx.view().id;
|
||||
cx.doc().redo(view_id);
|
||||
}
|
||||
|
||||
@@ -1839,11 +1855,12 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
|
||||
}
|
||||
|
||||
pub fn indent(cx: &mut Context) {
|
||||
let count = cx.count;
|
||||
let (view, doc) = cx.current();
|
||||
let lines = get_lines(doc, view.id);
|
||||
|
||||
// Indent by one level
|
||||
let indent = Tendril::from(doc.indent_unit());
|
||||
let indent = Tendril::from(doc.indent_unit().repeat(count));
|
||||
|
||||
let transaction = Transaction::change(
|
||||
doc.text(),
|
||||
@@ -1857,14 +1874,17 @@ pub fn indent(cx: &mut Context) {
|
||||
}
|
||||
|
||||
pub fn unindent(cx: &mut Context) {
|
||||
let count = cx.count;
|
||||
let (view, doc) = cx.current();
|
||||
let lines = get_lines(doc, view.id);
|
||||
let mut changes = Vec::with_capacity(lines.len());
|
||||
let tab_width = doc.tab_width();
|
||||
let indent_width = count * tab_width;
|
||||
|
||||
for line_idx in lines {
|
||||
let line = doc.text().line(line_idx);
|
||||
let mut width = 0;
|
||||
let mut pos = 0;
|
||||
|
||||
for ch in line.chars() {
|
||||
match ch {
|
||||
@@ -1873,14 +1893,17 @@ pub fn unindent(cx: &mut Context) {
|
||||
_ => break,
|
||||
}
|
||||
|
||||
if width >= tab_width {
|
||||
pos += 1;
|
||||
|
||||
if width >= indent_width {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if width > 0 {
|
||||
// now delete from start to first non-blank
|
||||
if pos > 0 {
|
||||
let start = doc.text().line_to_char(line_idx);
|
||||
changes.push((start, start + width, None))
|
||||
changes.push((start, start + pos, None))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2254,8 +2277,9 @@ pub fn space_mode(cx: &mut Context) {
|
||||
tokio::spawn(doc.save());
|
||||
}
|
||||
'c' => {
|
||||
let view_id = cx.view().id;
|
||||
// close current split
|
||||
cx.editor.close(cx.view_id, /* close_buffer */ false);
|
||||
cx.editor.close(view_id, /* close_buffer */ false);
|
||||
}
|
||||
// ' ' => toggle_alternate_buffer(cx),
|
||||
// TODO: temporary since space mode took it's old key
|
||||
|
@@ -145,7 +145,7 @@ pub fn default() -> Keymaps {
|
||||
//
|
||||
key!('r') => commands::replace,
|
||||
|
||||
key!('0') => commands::move_line_start,
|
||||
key!('^') => commands::move_line_start,
|
||||
key!('$') => commands::move_line_end,
|
||||
|
||||
key!('w') => commands::move_next_word_start,
|
||||
|
@@ -82,16 +82,6 @@ impl Completion {
|
||||
// and we insert at position.
|
||||
};
|
||||
|
||||
// TODO: merge edit with additional_text_edits
|
||||
if let Some(additional_edits) = &item.additional_text_edits {
|
||||
if !additional_edits.is_empty() {
|
||||
unimplemented!(
|
||||
"completion: additional_text_edits: {:?}",
|
||||
additional_edits
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if more text was entered, remove it
|
||||
let cursor = doc.selection(view.id).cursor();
|
||||
if trigger_offset < cursor {
|
||||
@@ -109,6 +99,19 @@ impl Completion {
|
||||
offset_encoding, // TODO: should probably transcode in Client
|
||||
);
|
||||
doc.apply(&transaction, view.id);
|
||||
|
||||
// TODO: merge edit with additional_text_edits
|
||||
if let Some(additional_edits) = &item.additional_text_edits {
|
||||
// gopls uses this to add extra imports
|
||||
if !additional_edits.is_empty() {
|
||||
let transaction = util::generate_transaction_from_edits(
|
||||
doc.text(),
|
||||
additional_edits.clone(),
|
||||
offset_encoding, // TODO: should probably transcode in Client
|
||||
);
|
||||
doc.apply(&transaction, view.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
@@ -530,7 +530,6 @@ impl Component for EditorView {
|
||||
let mode = doc.mode();
|
||||
|
||||
let mut cxt = commands::Context {
|
||||
view_id: view.id,
|
||||
editor: &mut cx.editor,
|
||||
count: 1,
|
||||
callback: None,
|
||||
|
@@ -85,7 +85,7 @@ pub fn file_picker(root: PathBuf) -> Picker<PathBuf> {
|
||||
Err(_err) => None,
|
||||
});
|
||||
|
||||
const MAX: usize = 1024;
|
||||
const MAX: usize = 2048;
|
||||
|
||||
Picker::new(
|
||||
files.take(MAX).collect(),
|
||||
|
@@ -328,6 +328,11 @@ impl Document {
|
||||
.map(|language| language.scope.as_str())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn language_config(&self) -> Option<&LanguageConfiguration> {
|
||||
self.language.as_deref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Current document version, incremented at each change.
|
||||
pub fn version(&self) -> i32 {
|
||||
|
@@ -81,11 +81,18 @@ impl Editor {
|
||||
view.jumps.push(jump);
|
||||
view.doc = id;
|
||||
view.first_line = 0;
|
||||
let view_id = view.id;
|
||||
|
||||
let (view, doc) = self.current();
|
||||
|
||||
// initialize selection for view
|
||||
let doc = &mut self.documents[id];
|
||||
doc.selections.insert(view_id, Selection::point(0));
|
||||
let selection = doc
|
||||
.selections
|
||||
.entry(view.id)
|
||||
.or_insert_with(|| Selection::point(0));
|
||||
// TODO: reuse align_view
|
||||
let pos = selection.cursor();
|
||||
let line = doc.text().char_to_line(pos);
|
||||
view.first_line = line.saturating_sub(view.area.height as usize / 2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
22
runtime/queries/go/indents.toml
Normal file
22
runtime/queries/go/indents.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
indent = [
|
||||
"import_declaration",
|
||||
"const_declaration",
|
||||
"var_declaration",
|
||||
"type_declaration",
|
||||
"function_declaration",
|
||||
"method_declaration",
|
||||
"composite_literal",
|
||||
"func_literal",
|
||||
"literal_value",
|
||||
"expression_case",
|
||||
"default_case",
|
||||
"argument_list",
|
||||
"block"
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"case",
|
||||
"}",
|
||||
"]",
|
||||
")"
|
||||
]
|
@@ -1,10 +1,28 @@
|
||||
; Identifier conventions
|
||||
|
||||
|
||||
; Assume all-caps names are constants
|
||||
((identifier) @constant
|
||||
(#match? @constant "^[A-Z][A-Z\\d_]+$'"))
|
||||
|
||||
; Assume other uppercase names are enum constructors
|
||||
((identifier) @constructor
|
||||
(#match? @constructor "^[A-Z]"))
|
||||
|
||||
; Assume that uppercase names in paths are types
|
||||
(mod_item
|
||||
name: (identifier) @namespace)
|
||||
(scoped_identifier
|
||||
path: (identifier) @namespace)
|
||||
(scoped_identifier
|
||||
(scoped_identifier
|
||||
name: (identifier) @namespace))
|
||||
(scoped_type_identifier
|
||||
path: (identifier) @namespace)
|
||||
(scoped_type_identifier
|
||||
(scoped_identifier
|
||||
name: (identifier) @namespace))
|
||||
|
||||
((scoped_identifier
|
||||
path: (identifier) @type)
|
||||
(#match? @type "^[A-Z]"))
|
||||
@@ -13,9 +31,15 @@
|
||||
name: (identifier) @type))
|
||||
(#match? @type "^[A-Z]"))
|
||||
|
||||
; Assume other uppercase names are enum constructors
|
||||
((identifier) @constructor
|
||||
(#match? @constructor "^[A-Z]"))
|
||||
; Namespaces
|
||||
|
||||
(crate) @namespace
|
||||
(scoped_use_list
|
||||
path: (identifier) @namespace)
|
||||
(scoped_use_list
|
||||
path: (scoped_identifier
|
||||
(identifier) @namespace))
|
||||
(use_list (scoped_identifier (identifier) @namespace . (_)))
|
||||
|
||||
; Function calls
|
||||
|
||||
@@ -38,9 +62,19 @@
|
||||
function: (field_expression
|
||||
field: (field_identifier) @function.method))
|
||||
|
||||
; (macro_invocation
|
||||
; macro: (identifier) @function.macro
|
||||
; "!" @function.macro)
|
||||
(macro_invocation
|
||||
macro: (identifier) @function.macro
|
||||
"!" @function.macro)
|
||||
macro: (identifier) @function.macro)
|
||||
(macro_invocation
|
||||
macro: (scoped_identifier
|
||||
(identifier) @function.macro .))
|
||||
|
||||
; (metavariable) @variable
|
||||
(metavariable) @function.macro
|
||||
|
||||
"$" @function.macro
|
||||
|
||||
; Function definitions
|
||||
|
||||
@@ -73,6 +107,7 @@
|
||||
";" @punctuation.delimiter
|
||||
|
||||
(parameter (identifier) @variable.parameter)
|
||||
(closure_parameters (_) @variable.parameter)
|
||||
|
||||
(lifetime (identifier) @label)
|
||||
|
||||
@@ -114,9 +149,9 @@
|
||||
(scoped_use_list (self) @keyword)
|
||||
(scoped_identifier (self) @keyword)
|
||||
(super) @keyword
|
||||
"as" @keyword
|
||||
|
||||
(self) @variable.builtin
|
||||
(metavariable) @variable
|
||||
|
||||
[
|
||||
(char_literal)
|
||||
@@ -133,7 +168,44 @@
|
||||
(attribute_item) @attribute
|
||||
(inner_attribute_item) @attribute
|
||||
|
||||
"as" @operator
|
||||
"*" @operator
|
||||
"&" @operator
|
||||
"'" @operator
|
||||
[
|
||||
"*"
|
||||
"'"
|
||||
"->"
|
||||
"=>"
|
||||
"<="
|
||||
"="
|
||||
"=="
|
||||
"!"
|
||||
"!="
|
||||
"%"
|
||||
"%="
|
||||
"&"
|
||||
"&="
|
||||
"&&"
|
||||
"|"
|
||||
"|="
|
||||
"||"
|
||||
"^"
|
||||
"^="
|
||||
"*"
|
||||
"*="
|
||||
"-"
|
||||
"-="
|
||||
"+"
|
||||
"+="
|
||||
"/"
|
||||
"/="
|
||||
">"
|
||||
"<"
|
||||
">="
|
||||
">>"
|
||||
"<<"
|
||||
">>="
|
||||
"@"
|
||||
".."
|
||||
"..="
|
||||
"'"
|
||||
] @operator
|
||||
|
||||
"?" @special
|
||||
|
30
runtime/queries/rust/indents.toml
Normal file
30
runtime/queries/rust/indents.toml
Normal file
@@ -0,0 +1,30 @@
|
||||
indent = [
|
||||
"while_expression",
|
||||
"for_expression",
|
||||
"loop_expression",
|
||||
"if_expression",
|
||||
"if_let_expression",
|
||||
"tuple_expression",
|
||||
"array_expression",
|
||||
"use_list",
|
||||
"block",
|
||||
"match_block",
|
||||
"arguments",
|
||||
"parameters",
|
||||
"declaration_list",
|
||||
"field_declaration_list",
|
||||
"field_initializer_list",
|
||||
"struct_pattern",
|
||||
"tuple_pattern",
|
||||
"enum_variant_list",
|
||||
"binary_expression",
|
||||
"field_expression",
|
||||
"where_clause"
|
||||
]
|
||||
|
||||
outdent = [
|
||||
"where",
|
||||
"}",
|
||||
"]",
|
||||
")"
|
||||
]
|
@@ -1,9 +1,11 @@
|
||||
"attribute" = "#dbbfef" # lilac
|
||||
"keyword" = "#eccdba" # almond
|
||||
"keyword.directive" = "#dbbfef" # lilac -- preprocessor comments (#if in C)
|
||||
"namespace" = "#dbbfef" # lilac
|
||||
"punctuation" = "#a4a0e8" # lavender
|
||||
"punctuation.delimiter" = "#a4a0e8" # lavender
|
||||
"operator" = "#dbbfef" # lilac
|
||||
"special" = "#efba5d" # honey
|
||||
# "property" = "#a4a0e8" # lavender
|
||||
"property" = "#ffffff" # white
|
||||
"variable" = "#a4a0e8" # lavender
|
||||
@@ -31,7 +33,6 @@
|
||||
# TODO: variable as lilac
|
||||
# TODO: mod/use statements as white
|
||||
# TODO: mod stuff as chamois
|
||||
# TODO: add "(scoped_identifier) @path" for std::mem::
|
||||
#
|
||||
# concat (ERROR) @syntax-error and "MISSING ;" selectors for errors
|
||||
|
||||
|
Reference in New Issue
Block a user