aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-03-31 08:17:01 +0000
committerBlaž Hrastnik2021-03-31 08:17:01 +0000
commit6c4093c94634a94adbccb4783cddb90076881c02 (patch)
tree34d4691fe6daca0d14ce77b434830cdef72deb47
parent9eaef6e33376407931162780cd402297c44f26c4 (diff)
Weave through view_id references so that views into one file have independent selects.
-rw-r--r--helix-term/src/commands.rs412
-rw-r--r--helix-term/src/ui/completion.rs32
-rw-r--r--helix-term/src/ui/editor.rs12
-rw-r--r--helix-term/src/ui/mod.rs17
-rw-r--r--helix-view/src/document.rs51
-rw-r--r--helix-view/src/editor.rs27
-rw-r--r--helix-view/src/view.rs2
7 files changed, 345 insertions, 208 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 1b46aa62..d0795d74 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -19,7 +19,7 @@ use std::path::{Path, PathBuf};
use helix_view::{
document::Mode,
view::{View, PADDING},
- Document, DocumentId, Editor,
+ Document, DocumentId, Editor, ViewId,
};
use crossterm::event::{KeyCode, KeyEvent};
@@ -31,6 +31,7 @@ use crate::application::{LspCallbackWrapper, LspCallbacks};
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)>>,
@@ -101,9 +102,10 @@ pub type Command = fn(cx: &mut Context);
pub fn move_char_left(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_horizontally(
text,
range,
@@ -112,14 +114,15 @@ pub fn move_char_left(cx: &mut Context) {
false, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_char_right(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_horizontally(
text,
range,
@@ -128,14 +131,15 @@ pub fn move_char_right(cx: &mut Context) {
false, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_line_up(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_vertically(
text,
range,
@@ -144,14 +148,15 @@ pub fn move_line_up(cx: &mut Context) {
false, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_line_down(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_vertically(
text,
range,
@@ -160,12 +165,13 @@ pub fn move_line_down(cx: &mut Context) {
false, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_line_end(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- let lines = selection_lines(doc.text(), doc.selection());
+ let lines = selection_lines(doc.text(), doc.selection(view_id));
let positions = lines
.into_iter()
@@ -180,12 +186,13 @@ pub fn move_line_end(cx: &mut Context) {
let selection = Selection::new(positions.collect(), 0);
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_line_start(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- let lines = selection_lines(doc.text(), doc.selection());
+ let lines = selection_lines(doc.text(), doc.selection(view_id));
let positions = lines
.into_iter()
@@ -197,7 +204,7 @@ pub fn move_line_start(cx: &mut Context) {
let selection = Selection::new(positions.collect(), 0);
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
// TODO: move vs extend could take an extra type Extend/Move that would
@@ -206,93 +213,101 @@ pub fn move_line_start(cx: &mut Context) {
pub fn move_next_word_start(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
let pos = movement::move_next_word_start(text, range.head, count);
Range::new(pos, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_prev_word_start(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
let pos = movement::move_prev_word_start(text, range.head, count);
Range::new(pos, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_next_word_end(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
let pos = movement::move_next_word_end(text, range.head, count);
Range::new(pos, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn move_file_start(cx: &mut Context) {
push_jump(cx);
+ let view_id = cx.view_id;
let doc = cx.doc();
- doc.set_selection(Selection::point(0));
+ doc.set_selection(view_id, Selection::point(0));
}
pub fn move_file_end(cx: &mut Context) {
push_jump(cx);
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text();
let last_line = text.line_to_char(text.len_lines().saturating_sub(2));
- doc.set_selection(Selection::point(last_line));
+ doc.set_selection(view_id, Selection::point(last_line));
}
pub fn extend_next_word_start(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|mut range| {
+ let selection = doc.selection(view_id).transform(|mut range| {
let pos = movement::move_next_word_start(text, range.head, count);
Range::new(range.anchor, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn extend_prev_word_start(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|mut range| {
+ let selection = doc.selection(view_id).transform(|mut range| {
let pos = movement::move_prev_word_start(text, range.head, count);
Range::new(range.anchor, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn extend_next_word_end(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|mut range| {
+ let selection = doc.selection(view_id).transform(|mut range| {
let pos = movement::move_next_word_end(text, range.head, count);
Range::new(range.anchor, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
#[inline]
@@ -313,10 +328,11 @@ where
..
} = event
{
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|mut range| {
+ let selection = doc.selection(view_id).transform(|mut range| {
if let Some(pos) = search::find_nth_next(text, ch, range.head, count, inclusive) {
if extend {
Range::new(range.anchor, pos)
@@ -330,7 +346,7 @@ where
}
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
})
}
@@ -417,15 +433,16 @@ pub fn replace(cx: &mut Context) {
{
let text = Tendril::from_char(ch);
+ let view_id = cx.view_id;
let doc = cx.doc();
let transaction =
- Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
(range.from(), range.to() + 1, Some(text.clone()))
});
- doc.apply(&transaction);
- doc.append_changes_to_history();
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
}
})
}
@@ -434,7 +451,7 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
use Direction::*;
let view = cx.editor.view();
let doc = cx.editor.document(view.doc).unwrap();
- let cursor = coords_at_pos(doc.text().slice(..), doc.selection().cursor());
+ let cursor = coords_at_pos(doc.text().slice(..), doc.selection(view.id).cursor());
let doc_last_line = doc.text().len_lines() - 1;
let last_line = view.last_line(doc);
@@ -465,6 +482,8 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
last_line.saturating_sub(scrolloff),
);
+ let view_id = view.id;
+
// view drops here
// upgrade to mut reference
@@ -473,7 +492,7 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
let text = doc.text().slice(..);
let pos = pos_at_coords(text, Position::new(line, cursor.col)); // this func will properly truncate to line end
- doc.set_selection(Selection::point(pos));
+ doc.set_selection(view_id, Selection::point(pos));
}
pub fn page_up(cx: &mut Context) {
@@ -502,9 +521,10 @@ pub fn half_page_down(cx: &mut Context) {
pub fn extend_char_left(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_horizontally(
text,
range,
@@ -513,14 +533,15 @@ pub fn extend_char_left(cx: &mut Context) {
true, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn extend_char_right(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_horizontally(
text,
range,
@@ -529,14 +550,15 @@ pub fn extend_char_right(cx: &mut Context) {
true, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn extend_line_up(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_vertically(
text,
range,
@@ -545,14 +567,15 @@ pub fn extend_line_up(cx: &mut Context) {
true, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn extend_line_down(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
movement::move_vertically(
text,
range,
@@ -561,21 +584,24 @@ pub fn extend_line_down(cx: &mut Context) {
true, /* extend */
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn select_all(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let end = doc.text().len_chars().saturating_sub(1);
- doc.set_selection(Selection::single(0, end))
+ doc.set_selection(view_id, Selection::single(0, end))
}
pub fn select_regex(cx: &mut Context) {
- let prompt = ui::regex_prompt(cx, "select:".to_string(), |doc, regex| {
+ let view_id = cx.view_id;
+ let prompt = ui::regex_prompt(cx, "select:".to_string(), move |doc, regex| {
let text = doc.text().slice(..);
- if let Some(selection) = selection::select_on_matches(text, doc.selection(), &regex) {
- doc.set_selection(selection);
+ if let Some(selection) = selection::select_on_matches(text, doc.selection(view_id), &regex)
+ {
+ doc.set_selection(view_id, selection);
}
});
@@ -583,23 +609,25 @@ pub fn select_regex(cx: &mut Context) {
}
pub fn split_selection(cx: &mut Context) {
- let prompt = ui::regex_prompt(cx, "split:".to_string(), |doc, regex| {
+ let view_id = cx.view_id;
+ let prompt = ui::regex_prompt(cx, "split:".to_string(), move |doc, regex| {
let text = doc.text().slice(..);
- let selection = selection::split_on_matches(text, doc.selection(), &regex);
- doc.set_selection(selection);
+ let selection = selection::split_on_matches(text, doc.selection(view_id), &regex);
+ doc.set_selection(view_id, selection);
});
cx.push_layer(Box::new(prompt));
}
pub fn split_selection_on_newline(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
// only compile the regex once
#[allow(clippy::trivial_regex)]
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\n").unwrap());
- let selection = selection::split_on_matches(text, doc.selection(), &REGEX);
- doc.set_selection(selection);
+ let selection = selection::split_on_matches(text, doc.selection(view_id), &REGEX);
+ doc.set_selection(view_id, selection);
}
// search: searches for the first occurence in file, provides a prompt
@@ -609,9 +637,9 @@ pub fn split_selection_on_newline(cx: &mut Context) {
// I'd probably collect all the matches right now and store the current index. The cache needs
// wiping if input happens.
-fn _search(doc: &mut Document, contents: &str, regex: &Regex) {
+fn _search(doc: &mut Document, view_id: ViewId, contents: &str, regex: &Regex) {
let text = doc.text();
- let start = doc.selection().cursor();
+ let start = doc.selection(view_id).cursor();
// TODO: use find_at to find the next match after the cursor, loop around the end
if let Some(mat) = regex.find_at(&contents, start) {
@@ -619,7 +647,7 @@ fn _search(doc: &mut Document, contents: &str, regex: &Regex) {
let end = text.byte_to_char(mat.end());
let selection = Selection::single(start, end - 1);
// TODO: (first_match, regex) stuff in register?
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
};
}
@@ -633,10 +661,11 @@ 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 prompt = ui::regex_prompt(cx, "search:".to_string(), move |doc, regex| {
let text = doc.text();
- let start = doc.selection().cursor();
- _search(doc, &contents, &regex);
+ let start = doc.selection(view_id).cursor();
+ _search(doc, view_id, &contents, &regex);
// TODO: only store on enter (accept), not update
register::set('\\', vec![regex.as_str().to_string()]);
@@ -648,17 +677,19 @@ pub fn search(cx: &mut Context) {
pub fn search_next(cx: &mut Context) {
if let Some(query) = register::get('\\') {
let query = query.first().unwrap();
+ let view_id = cx.view_id;
let doc = cx.doc();
let contents = doc.text().slice(..).to_string();
let regex = Regex::new(&query).unwrap();
- _search(doc, &contents, &regex);
+ _search(doc, view_id, &contents, &regex);
}
}
pub fn search_selection(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let contents = doc.text().slice(..);
- let query = doc.selection().primary().fragment(contents);
+ let query = doc.selection(view_id).primary().fragment(contents);
let regex = regex::escape(&query);
register::set('\\', vec![regex]);
search_next(cx);
@@ -672,22 +703,24 @@ pub fn search_selection(cx: &mut Context) {
pub fn select_line(cx: &mut Context) {
let count = cx.count;
+ let view_id = cx.view_id;
let doc = cx.doc();
- let pos = doc.selection().primary();
+ let pos = doc.selection(view_id).primary();
let text = doc.text();
let line = text.char_to_line(pos.head);
let start = text.line_to_char(line);
let end = text.line_to_char(line + count).saturating_sub(1);
- doc.set_selection(Selection::single(start, end));
+ doc.set_selection(view_id, Selection::single(start, end));
}
pub fn extend_line(cx: &mut Context) {
+ let view_id = cx.view_id;
let count = cx.count;
let doc = cx.doc();
- let pos = doc.selection().primary();
+ let pos = doc.selection(view_id).primary();
let text = doc.text();
let line_start = text.char_to_line(pos.anchor);
@@ -700,47 +733,52 @@ pub fn extend_line(cx: &mut Context) {
let start = text.line_to_char(line_start);
let end = text.line_to_char(line + 1).saturating_sub(1);
- doc.set_selection(Selection::single(start, end));
+ doc.set_selection(view_id, Selection::single(start, end));
}
// heuristic: append changes to history after each command, unless we're in insert mode
-fn _delete_selection(doc: &mut Document) {
- let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
- (range.from(), range.to() + 1, None)
- });
- doc.apply(&transaction);
+fn _delete_selection(doc: &mut Document, view_id: ViewId) {
+ let transaction =
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+ (range.from(), range.to() + 1, None)
+ });
+ doc.apply(&transaction, view_id);
}
pub fn delete_selection(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- _delete_selection(doc);
+ _delete_selection(doc, view_id);
- doc.append_changes_to_history();
+ doc.append_changes_to_history(view_id);
}
pub fn change_selection(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- _delete_selection(doc);
+ _delete_selection(doc, view_id);
enter_insert_mode(doc);
}
pub fn collapse_selection(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let selection = doc
- .selection()
+ .selection(view_id)
.transform(|range| Range::new(range.head, range.head));
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
pub fn flip_selections(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let selection = doc
- .selection()
+ .selection(view_id)
.transform(|range| Range::new(range.head, range.anchor));
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
fn enter_insert_mode(doc: &mut Document) {
@@ -749,29 +787,31 @@ fn enter_insert_mode(doc: &mut Document) {
// inserts at the start of each selection
pub fn insert_mode(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
enter_insert_mode(doc);
let selection = doc
- .selection()
+ .selection(view_id)
.transform(|range| Range::new(range.to(), range.from()));
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
// inserts at the end of each selection
pub fn append_mode(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
enter_insert_mode(doc);
doc.restore_cursor = true;
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
Range::new(
range.from(),
graphemes::next_grapheme_boundary(text, range.to()), // to() + next char
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
const COMMAND_LIST: &[&str] = &["write", "open", "quit"];
@@ -931,25 +971,27 @@ pub fn prepend_to_line(cx: &mut Context) {
pub fn append_to_line(cx: &mut Context) {
move_line_end(cx);
+ let view_id = cx.view_id;
let doc = cx.doc();
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().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
let pos = range.head + 1;
Range::new(pos, pos)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
// o inserts a new line after each line with a selection
pub fn open_below(cx: &mut Context) {
+ let view_id = cx.view_id;
let count = cx.count;
let doc = cx.doc();
enter_insert_mode(doc);
- let lines = selection_lines(doc.text(), doc.selection());
+ 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 (next line minus one)
@@ -991,16 +1033,17 @@ pub fn open_below(cx: &mut Context) {
let transaction =
Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
- doc.apply(&transaction);
+ doc.apply(&transaction, view_id);
}
// O inserts a new line before each line with a selection
pub fn open_above(cx: &mut Context) {
+ let view_id = cx.view_id;
let count = cx.count;
let doc = cx.doc();
enter_insert_mode(doc);
- let lines = selection_lines(doc.text(), doc.selection());
+ 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 previous line
@@ -1043,26 +1086,27 @@ pub fn open_above(cx: &mut Context) {
let transaction =
Transaction::change(doc.text(), changes.into_iter()).with_selection(selection);
- doc.apply(&transaction);
+ doc.apply(&transaction, view_id);
}
pub fn normal_mode(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
doc.mode = Mode::Normal;
- doc.append_changes_to_history();
+ doc.append_changes_to_history(view_id);
// if leaving append mode, move cursor back by 1
if doc.restore_cursor {
let text = doc.text().slice(..);
- let selection = doc.selection().transform(|range| {
+ let selection = doc.selection(view_id).transform(|range| {
Range::new(
range.from(),
graphemes::prev_grapheme_boundary(text, range.to()),
)
});
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
doc.restore_cursor = false;
}
@@ -1071,13 +1115,15 @@ pub fn normal_mode(cx: &mut Context) {
// Store a jump on the jumplist.
fn push_jump(cx: &mut Context) {
let jump = {
+ let view_id = cx.view_id;
let doc = cx.doc();
- (doc.id(), doc.selection().clone())
+ (doc.id(), doc.selection(view_id).clone())
};
cx.view().jumps.push(jump);
}
pub fn goto_mode(cx: &mut Context) {
+ let view_id = cx.view_id;
let count = cx.count;
if count > 1 {
@@ -1087,7 +1133,7 @@ pub fn goto_mode(cx: &mut Context) {
// to 1g
let doc = cx.doc();
let pos = doc.text().line_to_char(count - 1);
- doc.set_selection(Selection::point(pos));
+ doc.set_selection(view_id, Selection::point(pos));
return;
}
@@ -1128,10 +1174,11 @@ fn _goto(cx: &mut Context, locations: Vec<lsp::Location>) {
let id = editor
.open(PathBuf::from(location.uri.path()), action)
.expect("editor.open failed");
+ let view_id = editor.view().id;
let doc = &mut editor.documents[id];
let definition_pos = location.range.start;
let new_pos = helix_lsp::util::lsp_pos_to_pos(doc.text(), definition_pos);
- doc.set_selection(Selection::point(new_pos));
+ doc.set_selection(view_id, Selection::point(new_pos));
}
match locations.as_slice() {
@@ -1155,6 +1202,7 @@ fn _goto(cx: &mut Context, locations: Vec<lsp::Location>) {
}
pub fn goto_definition(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
Some(language_server) => language_server,
@@ -1162,7 +1210,7 @@ pub fn goto_definition(cx: &mut Context) {
};
// TODO: blocking here is not ideal
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
let res =
@@ -1171,6 +1219,7 @@ pub fn goto_definition(cx: &mut Context) {
}
pub fn goto_type_definition(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
Some(language_server) => language_server,
@@ -1178,7 +1227,7 @@ pub fn goto_type_definition(cx: &mut Context) {
};
// TODO: blocking here is not ideal
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
let res = smol::block_on(language_server.goto_type_definition(doc.identifier(), pos))
@@ -1187,6 +1236,7 @@ pub fn goto_type_definition(cx: &mut Context) {
}
pub fn goto_implementation(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
Some(language_server) => language_server,
@@ -1194,7 +1244,7 @@ pub fn goto_implementation(cx: &mut Context) {
};
// TODO: blocking here is not ideal
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
let res = smol::block_on(language_server.goto_implementation(doc.identifier(), pos))
@@ -1203,6 +1253,7 @@ pub fn goto_implementation(cx: &mut Context) {
}
pub fn goto_reference(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
Some(language_server) => language_server,
@@ -1210,7 +1261,7 @@ pub fn goto_reference(cx: &mut Context) {
};
// TODO: blocking here is not ideal
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
let res =
@@ -1219,6 +1270,7 @@ pub fn goto_reference(cx: &mut Context) {
}
pub fn signature_help(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
@@ -1227,7 +1279,7 @@ pub fn signature_help(cx: &mut Context) {
};
// TODO: blocking here is not ideal
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
@@ -1320,20 +1372,21 @@ pub mod insert {
// TODO: insert means add text just before cursor, on exit we should be on the last letter.
pub fn insert_char(cx: &mut Context, c: char) {
+ let view_id = cx.view_id;
let doc = cx.doc();
// run through insert hooks, stopping on the first one that returns Some(t)
for hook in HOOKS {
- if let Some(transaction) = hook(doc.text(), doc.selection(), c) {
- doc.apply(&transaction);
+ if let Some(transaction) = hook(doc.text(), doc.selection(view_id), c) {
+ doc.apply(&transaction, view_id);
return;
}
}
let t = Tendril::from_char(c);
- let transaction = Transaction::insert(doc.text(), doc.selection(), t);
+ let transaction = Transaction::insert(doc.text(), doc.selection(view_id), t);
- doc.apply(&transaction);
+ doc.apply(&transaction, view_id);
// TODO: need a post insert hook too for certain triggers (autocomplete, signature help, etc)
// this could also generically look at Transaction, but it's a bit annoying to look at
@@ -1344,58 +1397,69 @@ pub mod insert {
}
pub fn insert_tab(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
// TODO: round out to nearest indentation level (for example a line with 3 spaces should
// indent by one to reach 4 spaces).
let indent = Tendril::from(doc.indent_unit());
- let transaction = Transaction::insert(doc.text(), doc.selection(), indent);
- doc.apply(&transaction);
+ let transaction = Transaction::insert(doc.text(), doc.selection(view_id), indent);
+ doc.apply(&transaction, view_id);
}
pub fn insert_newline(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text().slice(..);
- let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
- // TODO: offset range.head by 1? when calculating?
- let indent_level =
- helix_core::indent::suggested_indent_for_pos(doc.syntax(), text, range.head, 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);
- (range.head, range.head, Some(text.into()))
- });
- doc.apply(&transaction);
+ let transaction =
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+ // TODO: offset range.head by 1? when calculating?
+ let indent_level = helix_core::indent::suggested_indent_for_pos(
+ doc.syntax(),
+ text,
+ range.head,
+ 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);
+ (range.head, range.head, Some(text.into()))
+ });
+ doc.apply(&transaction, view_id);
}
// TODO: handle indent-aware delete
pub fn delete_char_backward(cx: &mut Context) {
+ let view_id = cx.view_id;
let count = cx.count;
let doc = cx.doc();
let text = doc.text().slice(..);
- let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
- (
- graphemes::nth_prev_grapheme_boundary(text, range.head, count),
- range.head,
- None,
- )
- });
- doc.apply(&transaction);
+ let transaction =
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+ (
+ graphemes::nth_prev_grapheme_boundary(text, range.head, count),
+ range.head,
+ None,
+ )
+ });
+ doc.apply(&transaction, view_id);
}
pub fn delete_char_forward(cx: &mut Context) {
+ let view_id = cx.view_id;
let count = cx.count;
let doc = cx.doc();
let text = doc.text().slice(..);
- let transaction = Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
- (
- range.head,
- graphemes::nth_next_grapheme_boundary(text, range.head, count),
- None,
- )
- });
- doc.apply(&transaction);
+ let transaction =
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
+ (
+ range.head,
+ graphemes::nth_next_grapheme_boundary(text, range.head, count),
+ None,
+ )
+ });
+ doc.apply(&transaction, view_id);
}
}
@@ -1405,20 +1469,23 @@ pub mod insert {
// storing it?
pub fn undo(cx: &mut Context) {
- cx.doc().undo();
+ let view_id = cx.view_id;
+ cx.doc().undo(view_id);
}
pub fn redo(cx: &mut Context) {
- cx.doc().redo();
+ let view_id = cx.view_id;
+ cx.doc().redo(view_id);
}
// Yank / Paste
pub fn yank(cx: &mut Context) {
// TODO: should selections be made end inclusive?
+ let view_id = cx.view_id;
let doc = cx.doc();
let values: Vec<String> = doc
- .selection()
+ .selection(view_id)
.fragments(doc.text().slice(..))
.map(|cow| cow.into_owned())
.collect();
@@ -1461,32 +1528,33 @@ pub fn paste(cx: &mut Context) {
let mut values = values.into_iter().map(Tendril::from).chain(repeat);
+ let view_id = cx.view_id;
let doc = cx.doc();
let transaction = if linewise {
// paste on the next line
// TODO: can simply take a range + modifier and compute the right pos without ifs
let text = doc.text();
- Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
let line_end = text.line_to_char(text.char_to_line(range.head) + 1);
(line_end, line_end, Some(values.next().unwrap()))
})
} else {
- Transaction::change_by_selection(doc.text(), doc.selection(), |range| {
+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
(range.head + 1, range.head + 1, Some(values.next().unwrap()))
})
};
- doc.apply(&transaction);
- doc.append_changes_to_history();
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
}
}
-fn get_lines(doc: &Document) -> Vec<usize> {
+fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
let mut lines = Vec::new();
// Get all line numbers
- for range in doc.selection() {
+ for range in doc.selection(view_id) {
let start = doc.text().char_to_line(range.from());
let end = doc.text().char_to_line(range.to());
@@ -1500,8 +1568,9 @@ fn get_lines(doc: &Document) -> Vec<usize> {
}
pub fn indent(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- let lines = get_lines(doc);
+ let lines = get_lines(doc, view_id);
// Indent by one level
let indent = Tendril::from(doc.indent_unit());
@@ -1513,13 +1582,14 @@ pub fn indent(cx: &mut Context) {
(pos, pos, Some(indent.clone()))
}),
);
- doc.apply(&transaction);
- doc.append_changes_to_history();
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
}
pub fn unindent(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- let lines = get_lines(doc);
+ let lines = get_lines(doc, view_id);
let mut changes = Vec::with_capacity(lines.len());
let tab_width = doc.tab_width();
@@ -1547,12 +1617,13 @@ pub fn unindent(cx: &mut Context) {
let transaction = Transaction::change(doc.text(), changes.into_iter());
- doc.apply(&transaction);
- doc.append_changes_to_history();
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
}
pub fn format_selections(cx: &mut Context) {
use helix_lsp::lsp;
+ let view_id = cx.view_id;
let doc = cx.doc();
// via lsp if available
@@ -1561,7 +1632,7 @@ pub fn format_selections(cx: &mut Context) {
// TODO: blocking here is not ideal
let ranges: Vec<lsp::Range> = doc
- .selection()
+ .selection(view_id)
.iter()
.map(|range| helix_lsp::util::range_to_lsp_range(doc.text(), *range))
.collect();
@@ -1583,14 +1654,15 @@ pub fn format_selections(cx: &mut Context) {
let transaction = helix_lsp::util::generate_transaction_from_edits(doc.text(), edits);
- doc.apply(&transaction);
+ doc.apply(&transaction, view_id);
}
- doc.append_changes_to_history();
+ doc.append_changes_to_history(view_id);
}
pub fn join_selections(cx: &mut Context) {
use movement::skip_over_next;
+ let view_id = cx.view_id;
let doc = cx.doc();
let text = doc.text();
let slice = doc.text().slice(..);
@@ -1598,7 +1670,7 @@ pub fn join_selections(cx: &mut Context) {
let mut changes = Vec::new();
let fragment = Tendril::from(" ");
- for selection in doc.selection() {
+ for selection in doc.selection(view_id) {
let start = text.char_to_line(selection.from());
let mut end = text.char_to_line(selection.to());
if start == end {
@@ -1629,17 +1701,18 @@ pub fn join_selections(cx: &mut Context) {
// TODO: select inserted spaces
// .with_selection(selection);
- doc.apply(&transaction);
- doc.append_changes_to_history();
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
}
pub fn keep_selections(cx: &mut Context) {
// keep selections matching regex
- let prompt = ui::regex_prompt(cx, "keep:".to_string(), |doc, regex| {
+ let view_id = cx.view_id;
+ let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |doc, regex| {
let text = doc.text().slice(..);
- if let Some(selection) = selection::keep_matches(text, doc.selection(), &regex) {
- doc.set_selection(selection);
+ if let Some(selection) = selection::keep_matches(text, doc.selection(view_id), &regex) {
+ doc.set_selection(view_id, selection);
}
});
@@ -1647,11 +1720,12 @@ pub fn keep_selections(cx: &mut Context) {
}
pub fn keep_primary_selection(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- let range = doc.selection().primary();
+ let range = doc.selection(view_id).primary();
let selection = Selection::single(range.anchor, range.head);
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
}
//
@@ -1700,6 +1774,7 @@ pub fn completion(cx: &mut Context) {
// The prefix still has to satisfy `company-minimum-prefix-length' before that
// happens. The value of nil means no idle completion."
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
@@ -1708,13 +1783,13 @@ pub fn completion(cx: &mut Context) {
};
// TODO: blocking here is not ideal
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
let res = smol::block_on(language_server.completion(doc.identifier(), pos)).unwrap();
- let trigger_offset = doc.selection().cursor();
+ let trigger_offset = doc.selection(view_id).cursor();
cx.callback(
res,
@@ -1751,6 +1826,7 @@ pub fn completion(cx: &mut Context) {
pub fn hover(cx: &mut Context) {
use helix_lsp::lsp;
+ let view_id = cx.view_id;
let doc = cx.doc();
let language_server = match doc.language_server() {
@@ -1762,7 +1838,7 @@ pub fn hover(cx: &mut Context) {
// TODO: blocking here is not ideal, make commands async fn?
// not like we can process additional input meanwhile though
- let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection().cursor());
+ let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), doc.selection(view_id).cursor());
// TODO: handle fails
let res = smol::block_on(language_server.text_document_hover(doc.identifier(), pos))
@@ -1798,33 +1874,36 @@ pub fn next_view(cx: &mut Context) {
// comments
pub fn toggle_comments(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
- let transaction = comment::toggle_line_comments(doc.text(), doc.selection());
+ let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view_id));
- doc.apply(&transaction);
- doc.append_changes_to_history();
+ doc.apply(&transaction, view_id);
+ doc.append_changes_to_history(view_id);
}
// tree sitter node selection
pub fn expand_selection(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
if let Some(syntax) = doc.syntax() {
let text = doc.text().slice(..);
- let selection = object::expand_selection(syntax, text, doc.selection());
- doc.set_selection(selection);
+ let selection = object::expand_selection(syntax, text, doc.selection(view_id));
+ doc.set_selection(view_id, selection);
}
}
pub fn match_brackets(cx: &mut Context) {
+ let view_id = cx.view_id;
let doc = cx.doc();
if let Some(syntax) = doc.syntax() {
- let pos = doc.selection().cursor();
+ let pos = doc.selection(view_id).cursor();
if let Some(pos) = match_brackets::find(syntax, doc.text(), pos) {
let selection = Selection::point(pos);
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
};
}
}
@@ -1836,6 +1915,7 @@ pub fn jump_forward(cx: &mut Context) {
let view = cx.view();
if let Some((id, selection)) = view.jumps.forward(count) {
+ // TODO: position first_line so that main cursor is centered
view.first_line = 0;
view.doc = *id;
};
@@ -1846,11 +1926,13 @@ pub fn jump_backward(cx: &mut Context) {
let view = cx.view();
if let Some((id, selection)) = view.jumps.backward(count) {
+ // TODO: position first_line so that main cursor is centered
view.first_line = 0;
view.doc = *id;
let selection = selection.clone();
+ let view_id = view.id;
let doc = cx.doc();
- doc.set_selection(selection);
+ doc.set_selection(view_id, selection);
};
}
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 322e5b7b..9c753007 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -21,6 +21,7 @@ use lsp::CompletionItem;
pub struct Completion {
popup: Popup<Menu<CompletionItem>>, // TODO: Popup<Menu> need to be able to access contents.
trigger_offset: usize,
+ // TODO: maintain a completioncontext with trigger kind & trigger char
}
impl Completion {
@@ -43,7 +44,9 @@ impl Completion {
// doc.state = snapshot.clone();
}
PromptEvent::Validate => {
- let id = editor.view().doc;
+ let view = editor.view();
+ let view_id = view.id;
+ let id = view.doc;
let doc = &mut editor.documents[id];
// revert state to what it was before the last update
@@ -89,18 +92,18 @@ impl Completion {
}
// if more text was entered, remove it
- let cursor = doc.selection().cursor();
+ let cursor = doc.selection(view_id).cursor();
if trigger_offset < cursor {
let remove = Transaction::change(
doc.text(),
vec![(trigger_offset, cursor, None)].into_iter(),
);
- doc.apply(&remove);
+ doc.apply(&remove, view_id);
}
let transaction =
util::generate_transaction_from_edits(doc.text(), vec![edit]);
- doc.apply(&transaction);
+ doc.apply(&transaction, view_id);
}
_ => (),
};
@@ -124,15 +127,32 @@ impl Component for Completion {
{
// recompute menu based on matches
let menu = self.popup.contents();
- let id = cx.editor.view().doc;
+ let view = cx.editor.view();
+ let view_id = view.id;
+ let id = view.doc;
let doc = cx.editor.document(id).unwrap();
- let cursor = doc.selection().cursor();
+ // cx.hooks()
+ // cx.add_hook(enum type, ||)
+ // cx.trigger_hook(enum type, &str, ...) <-- there has to be enough to identify doc/view
+ // callback with editor & compositor
+ //
+ // trigger_hook sends event into channel, that's consumed in the global loop and
+ // triggers all registered callbacks
+ // TODO: hooks should get processed immediately so maybe do it after select!(), before
+ // looping?
+
+ let cursor = doc.selection(view_id).cursor();
if self.trigger_offset <= cursor {
let fragment = doc.text().slice(self.trigger_offset..cursor);
+ // ^ problem seems to be that we handle events here before the editor layer, so the
+ // keypress isn't included in the editor layer yet...
+ // so we can't use ..= for now.
let text = Cow::from(fragment);
// TODO: logic is same as ui/picker
menu.score(&text);
+
+ // TODO: if after scoring the selection is 0 items, remove popup
}
}
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 1f4bf6bd..7ceeb6ca 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -64,7 +64,7 @@ impl EditorView {
// TODO: this seems to prevent setting style later
// surface.set_style(viewport, theme.get("ui.background"));
- self.render_diagnostics(&doc, area, surface, theme, is_focused);
+ self.render_diagnostics(&doc, view, area, surface, theme, is_focused);
let area = Rect::new(
viewport.x,
@@ -224,7 +224,7 @@ impl EditorView {
let selection_style = Style::default().bg(Color::Rgb(84, 0, 153));
for selection in doc
- .selection()
+ .selection(view.id)
.iter()
.filter(|range| range.overlaps(&screen))
{
@@ -332,6 +332,7 @@ impl EditorView {
pub fn render_diagnostics(
&self,
doc: &Document,
+ view: &View,
viewport: Rect,
surface: &mut Surface,
theme: &Theme,
@@ -344,7 +345,7 @@ impl EditorView {
widgets::{Paragraph, Widget},
};
- let cursor = doc.selection().cursor();
+ let cursor = doc.selection(view.id).cursor();
let line = doc.text().char_to_line(cursor);
let diagnostics = doc.diagnostics.iter().filter(|diagnostic| {
@@ -486,11 +487,14 @@ impl Component for EditorView {
EventResult::Consumed(None)
}
Event::Key(event) => {
- let id = cx.editor.view().doc;
+ let view = cx.editor.view();
+ let view_id = view.id;
+ let id = view.doc;
let mode = cx.editor.document(id).unwrap().mode();
let mut cxt = commands::Context {
editor: &mut cx.editor,
+ view_id,
count: 1,
callback: None,
callbacks: cx.callbacks,
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 47c75d2f..341a30e0 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -35,7 +35,8 @@ pub fn regex_prompt(
prompt: String,
fun: impl Fn(&mut Document, Regex) + 'static,
) -> Prompt {
- let snapshot = cx.doc().selection().clone();
+ let view_id = cx.view().id;
+ let snapshot = cx.doc().selection(view_id).clone();
Prompt::new(
prompt,
@@ -44,9 +45,11 @@ pub fn regex_prompt(
match event {
PromptEvent::Abort => {
// TODO: also revert text
- let id = editor.view().doc;
+ let view = editor.view();
+ let view_id = view.id;
+ let id = view.doc;
let doc = &mut editor.documents[id];
- doc.set_selection(snapshot.clone());
+ doc.set_selection(view_id, snapshot.clone());
}
PromptEvent::Validate => {
// TODO: push_jump to store selection just before jump
@@ -60,16 +63,18 @@ pub fn regex_prompt(
match Regex::new(input) {
Ok(regex) => {
// let view = &mut editor.view_mut();
- let id = editor.view().doc;
+ let view = editor.view();
+ let view_id = view.id;
+ let id = view.doc;
let doc = &mut editor.documents[id];
// revert state to what it was before the last update
// TODO: also revert text
- doc.set_selection(snapshot.clone());
+ doc.set_selection(view_id, snapshot.clone());
fun(doc, regex);
- editor.ensure_cursor_in_view(editor.view().id);
+ editor.ensure_cursor_in_view(view_id);
}
Err(_err) => (), // TODO: mark command line as error
}
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index cf160cca..d79cddde 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -8,7 +8,9 @@ use helix_core::{
ChangeSet, Diagnostic, History, Rope, Selection, State, Syntax, Transaction,
};
-use crate::DocumentId;
+use crate::{DocumentId, ViewId};
+
+use std::collections::HashMap;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mode {
@@ -21,6 +23,7 @@ pub struct Document {
// rope + selection
pub(crate) id: DocumentId,
state: State,
+ pub(crate) selections: HashMap<ViewId, Selection>,
path: Option<PathBuf>,
@@ -73,6 +76,7 @@ impl Document {
id: DocumentId::default(),
path: None,
state: State::new(text),
+ selections: HashMap::default(),
mode: Mode::Normal,
restore_cursor: false,
syntax: None,
@@ -178,12 +182,12 @@ impl Document {
self.language_server = language_server;
}
- pub fn set_selection(&mut self, selection: Selection) {
+ pub fn set_selection(&mut self, view_id: ViewId, selection: Selection) {
// TODO: use a transaction?
- self.state.selection = selection;
+ self.selections.insert(view_id, selection);
}
- fn _apply(&mut self, transaction: &Transaction) -> bool {
+ fn _apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
let old_doc = self.text().clone();
let success = transaction.changes().apply(&mut self.state.doc);
@@ -191,10 +195,11 @@ impl Document {
if !transaction.changes().is_empty() {
// update the selection: either take the selection specified in the transaction, or map the
// current selection through changes.
- self.state.selection = transaction
+ let selection = transaction
.selection()
.cloned()
- .unwrap_or_else(|| self.selection().clone().map(transaction.changes()));
+ .unwrap_or_else(|| self.selection(view_id).clone().map(transaction.changes()));
+ self.set_selection(view_id, selection);
self.version += 1;
@@ -227,14 +232,14 @@ impl Document {
success
}
- pub fn apply(&mut self, transaction: &Transaction) -> bool {
+ pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
// store the state just before any changes are made. This allows us to undo to the
// state just before a transaction was applied.
if self.changes.is_empty() && !transaction.changes().is_empty() {
self.old_state = Some(self.state.clone());
}
- let success = self._apply(&transaction);
+ let success = self._apply(&transaction, view_id);
self.modified = true;
// TODO: be smarter about modified by keeping track of saved version instead. That way if
@@ -249,9 +254,9 @@ impl Document {
success
}
- pub fn undo(&mut self) -> bool {
+ pub fn undo(&mut self, view_id: ViewId) -> bool {
if let Some(transaction) = self.history.undo() {
- let success = self._apply(&transaction);
+ let success = self._apply(&transaction, view_id);
// reset changeset to fix len
self.changes = ChangeSet::new(self.text());
@@ -261,9 +266,9 @@ impl Document {
false
}
- pub fn redo(&mut self) -> bool {
+ pub fn redo(&mut self, view_id: ViewId) -> bool {
if let Some(transaction) = self.history.redo() {
- let success = self._apply(&transaction);
+ let success = self._apply(&transaction, view_id);
// reset changeset to fix len
self.changes = ChangeSet::new(self.text());
@@ -273,7 +278,7 @@ impl Document {
false
}
- pub fn append_changes_to_history(&mut self) {
+ pub fn append_changes_to_history(&mut self, view_id: ViewId) {
if self.changes.is_empty() {
return;
}
@@ -282,7 +287,8 @@ impl Document {
let changes = std::mem::replace(&mut self.changes, new_changeset);
// Instead of doing this messy merge we could always commit, and based on transaction
// annotations either add a new layer or compose into the previous one.
- let transaction = Transaction::from(changes).with_selection(self.selection().clone());
+ let transaction =
+ Transaction::from(changes).with_selection(self.selection(view_id).clone());
// HAXX: we need to reconstruct the state as it was before the changes..
let old_state = self.old_state.take().expect("no old_state available");
@@ -362,8 +368,8 @@ impl Document {
&self.state.doc
}
- pub fn selection(&self) -> &Selection {
- &self.state.selection
+ pub fn selection(&self, view_id: ViewId) -> &Selection {
+ &self.selections[&view_id]
}
pub fn relative_path(&self) -> Option<&Path> {
@@ -400,13 +406,14 @@ mod test {
use helix_lsp::{lsp, Client};
let text = Rope::from("hello");
let mut doc = Document::new(text);
- doc.set_selection(Selection::single(5, 5));
+ let view = ViewId::default();
+ doc.set_selection(view, Selection::single(5, 5));
// insert
- let transaction = Transaction::insert(doc.text(), doc.selection(), " world".into());
+ let transaction = Transaction::insert(doc.text(), doc.selection(view), " world".into());
let old_doc = doc.state.clone();
- doc.apply(&transaction);
+ doc.apply(&transaction, view);
let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
assert_eq!(
@@ -425,7 +432,7 @@ mod test {
let transaction = transaction.invert(&old_doc.doc);
let old_doc = doc.state.clone();
- doc.apply(&transaction);
+ doc.apply(&transaction, view);
let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
// line: 0-based.
@@ -450,13 +457,13 @@ mod test {
// also tests that changes are layered, positions depend on previous changes.
- doc.state.selection = Selection::single(0, 5);
+ doc.set_selection(view, Selection::single(0, 5));
let transaction = Transaction::change(
&doc.state.doc,
vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
);
// aeilou
- doc.apply(&transaction);
+ doc.apply(&transaction, view);
let changes =
Client::changeset_to_changes(&doc.state.doc, doc.text(), transaction.changes());
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index f062b55d..f74bbb13 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -91,24 +91,40 @@ impl Editor {
};
use crate::tree::Layout;
+ use helix_core::Selection;
match action {
Action::Replace => {
let view = self.view();
- let jump = (view.doc, self.documents[view.doc].selection().clone());
+ let jump = (
+ view.doc,
+ self.documents[view.doc].selection(view.id).clone(),
+ );
let view = self.view_mut();
view.jumps.push(jump);
view.doc = id;
view.first_line = 0;
+ let view_id = view.id;
+
+ // initialize selection for view
+ let doc = &mut self.documents[id];
+ doc.selections.insert(view_id, Selection::point(0));
+
return Ok(id);
}
Action::HorizontalSplit => {
let view = View::new(id)?;
- self.tree.split(view, Layout::Horizontal);
+ let view_id = self.tree.split(view, Layout::Horizontal);
+ // initialize selection for view
+ let doc = &mut self.documents[id];
+ doc.selections.insert(view_id, Selection::point(0));
}
Action::VerticalSplit => {
let view = View::new(id)?;
- self.tree.split(view, Layout::Vertical);
+ let view_id = self.tree.split(view, Layout::Vertical);
+ // initialize selection for view
+ let doc = &mut self.documents[id];
+ doc.selections.insert(view_id, Selection::point(0));
}
}
@@ -134,6 +150,9 @@ impl Editor {
smol::block_on(language_server.text_document_did_close(doc.identifier())).unwrap();
}
+ // remove selection
+ self.documents[view.doc].selections.remove(&id);
+
// self.documents.remove(view.doc);
self.tree.remove(id);
self._refresh();
@@ -183,7 +202,7 @@ impl Editor {
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
let view = self.view();
let doc = &self.documents[view.doc];
- let cursor = doc.selection().cursor();
+ let cursor = doc.selection(view.id).cursor();
if let Some(mut pos) = view.screen_coords_at_pos(doc, doc.text().slice(..), cursor) {
pos.col += view.area.x as usize + OFFSET as usize;
pos.row += view.area.y as usize;
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs
index 9e4c1373..89caaf3e 100644
--- a/helix-view/src/view.rs
+++ b/helix-view/src/view.rs
@@ -74,7 +74,7 @@ impl View {
}
pub fn ensure_cursor_in_view(&mut self, doc: &Document) {
- let cursor = doc.selection().cursor();
+ let cursor = doc.selection(self.id).cursor();
let line = doc.text().char_to_line(cursor);
let document_end = self.first_line + (self.area.height as usize).saturating_sub(2);