Compare commits

...

19 Commits

Author SHA1 Message Date
Blaž Hrastnik
f1dc25a774 Support count for indent too 2021-05-19 00:37:01 +09:00
Blaž Hrastnik
4f335fabc8 Fix unindent to work with tabs, take a count 2021-05-19 00:35:33 +09:00
Blaž Hrastnik
f366b97bce Update dependencies 2021-05-18 18:30:48 +09:00
Blaž Hrastnik
9c24f1ec0e Drop selection_lines completely, change move_line_start binding 2021-05-18 18:28:32 +09:00
Blaž Hrastnik
f99a683991 Fix crash if appending at end of line on the last line of the file 2021-05-18 18:17:14 +09:00
Blaž Hrastnik
9edae7e1f8 syntax: golang: Indent type declarations 2021-05-18 17:54:18 +09:00
Blaž Hrastnik
51d1d43289 Double the UI picker file limit. 2021-05-18 17:53:58 +09:00
Blaž Hrastnik
5a245b83a0 Append :fmt as a separate history state 2021-05-18 17:53:00 +09:00
Blaž Hrastnik
2100f5a2c0 Address clippy lint. 2021-05-17 23:01:45 +09:00
Blaž Hrastnik
8f6f329057 If switching to a previously open buffer in the same view, keep it's old offset 2021-05-17 16:36:13 +09:00
Blaž Hrastnik
8949347e2c Completion: apply additionalTextEdits.
Used for adding imports to the file when completing.
2021-05-17 16:35:34 +09:00
Blaž Hrastnik
54de768915 Fix crash if typing | (regex or) into the prompt.
Zero-width matches at the start of the file make no sense to us.
2021-05-16 18:58:43 +09:00
Blaž Hrastnik
6e03019a2c Adjust highlighting for rust. 2021-05-16 18:58:27 +09:00
Blaž Hrastnik
31d41080ed Add indentation queries for golang. 2021-05-15 17:17:26 +09:00
Blaž Hrastnik
5e6b46e7c5 Use array::IntoIter. 2021-05-15 10:52:07 +09:00
Blaž Hrastnik
354b822d21 Fix crash on xa<Enter> if we were on the last line. 2021-05-15 10:50:36 +09:00
Blaž Hrastnik
fae2127a11 Drop cx.view_id, it was used before we had cx.current. 2021-05-15 10:50:36 +09:00
Blaž Hrastnik
0e5b421646 When calculating a new selection, we need to take newly inserted text into account. 2021-05-15 10:50:36 +09:00
Blaž Hrastnik
4a9d1163e0 Hacky way to specify indent scopes per language via toml configs.
Can't do it via a scm query nicely because it returns an iterator over
all the matches, whereas we want to traverse the tree ourselves.

Can't extract the pattern data from a parsed query either.

Oh well, toml files for now.
2021-05-14 19:21:46 +09:00
19 changed files with 336 additions and 153 deletions

21
Cargo.lock generated
View File

@@ -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",

View File

@@ -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.

View File

@@ -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

View File

@@ -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 |

View File

@@ -22,5 +22,6 @@ once_cell = "1.4"
regex = "1"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"
etcetera = "0.3"

View File

@@ -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)))
}

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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);
}
}
}
_ => (),
};

View File

@@ -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,

View File

@@ -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(),

View File

@@ -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 {

View File

@@ -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;
}

View 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",
"}",
"]",
")"
]

View File

@@ -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

View 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",
"}",
"]",
")"
]

View File

@@ -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