Weave through view_id references so that views into one file have independent selects.

pull/11/head
Blaž Hrastnik 3 years ago
parent 9eaef6e333
commit 6c4093c946

File diff suppressed because it is too large Load Diff

@ -21,6 +21,7 @@ use lsp::CompletionItem;
pub struct Completion { pub struct Completion {
popup: Popup<Menu<CompletionItem>>, // TODO: Popup<Menu> need to be able to access contents. popup: Popup<Menu<CompletionItem>>, // TODO: Popup<Menu> need to be able to access contents.
trigger_offset: usize, trigger_offset: usize,
// TODO: maintain a completioncontext with trigger kind & trigger char
} }
impl Completion { impl Completion {
@ -43,7 +44,9 @@ impl Completion {
// doc.state = snapshot.clone(); // doc.state = snapshot.clone();
} }
PromptEvent::Validate => { 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]; let doc = &mut editor.documents[id];
// revert state to what it was before the last update // revert state to what it was before the last update
@ -89,18 +92,18 @@ impl Completion {
} }
// if more text was entered, remove it // if more text was entered, remove it
let cursor = doc.selection().cursor(); let cursor = doc.selection(view_id).cursor();
if trigger_offset < cursor { if trigger_offset < cursor {
let remove = Transaction::change( let remove = Transaction::change(
doc.text(), doc.text(),
vec![(trigger_offset, cursor, None)].into_iter(), vec![(trigger_offset, cursor, None)].into_iter(),
); );
doc.apply(&remove); doc.apply(&remove, view_id);
} }
let transaction = let transaction =
util::generate_transaction_from_edits(doc.text(), vec![edit]); 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 // recompute menu based on matches
let menu = self.popup.contents(); 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 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 { if self.trigger_offset <= cursor {
let fragment = doc.text().slice(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); let text = Cow::from(fragment);
// TODO: logic is same as ui/picker // TODO: logic is same as ui/picker
menu.score(&text); menu.score(&text);
// TODO: if after scoring the selection is 0 items, remove popup
} }
} }

@ -64,7 +64,7 @@ impl EditorView {
// TODO: this seems to prevent setting style later // TODO: this seems to prevent setting style later
// surface.set_style(viewport, theme.get("ui.background")); // 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( let area = Rect::new(
viewport.x, viewport.x,
@ -224,7 +224,7 @@ impl EditorView {
let selection_style = Style::default().bg(Color::Rgb(84, 0, 153)); let selection_style = Style::default().bg(Color::Rgb(84, 0, 153));
for selection in doc for selection in doc
.selection() .selection(view.id)
.iter() .iter()
.filter(|range| range.overlaps(&screen)) .filter(|range| range.overlaps(&screen))
{ {
@ -332,6 +332,7 @@ impl EditorView {
pub fn render_diagnostics( pub fn render_diagnostics(
&self, &self,
doc: &Document, doc: &Document,
view: &View,
viewport: Rect, viewport: Rect,
surface: &mut Surface, surface: &mut Surface,
theme: &Theme, theme: &Theme,
@ -344,7 +345,7 @@ impl EditorView {
widgets::{Paragraph, Widget}, widgets::{Paragraph, Widget},
}; };
let cursor = doc.selection().cursor(); let cursor = doc.selection(view.id).cursor();
let line = doc.text().char_to_line(cursor); let line = doc.text().char_to_line(cursor);
let diagnostics = doc.diagnostics.iter().filter(|diagnostic| { let diagnostics = doc.diagnostics.iter().filter(|diagnostic| {
@ -486,11 +487,14 @@ impl Component for EditorView {
EventResult::Consumed(None) EventResult::Consumed(None)
} }
Event::Key(event) => { 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 mode = cx.editor.document(id).unwrap().mode();
let mut cxt = commands::Context { let mut cxt = commands::Context {
editor: &mut cx.editor, editor: &mut cx.editor,
view_id,
count: 1, count: 1,
callback: None, callback: None,
callbacks: cx.callbacks, callbacks: cx.callbacks,

@ -35,7 +35,8 @@ pub fn regex_prompt(
prompt: String, prompt: String,
fun: impl Fn(&mut Document, Regex) + 'static, fun: impl Fn(&mut Document, Regex) + 'static,
) -> Prompt { ) -> Prompt {
let snapshot = cx.doc().selection().clone(); let view_id = cx.view().id;
let snapshot = cx.doc().selection(view_id).clone();
Prompt::new( Prompt::new(
prompt, prompt,
@ -44,9 +45,11 @@ pub fn regex_prompt(
match event { match event {
PromptEvent::Abort => { PromptEvent::Abort => {
// TODO: also revert text // 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]; let doc = &mut editor.documents[id];
doc.set_selection(snapshot.clone()); doc.set_selection(view_id, snapshot.clone());
} }
PromptEvent::Validate => { PromptEvent::Validate => {
// TODO: push_jump to store selection just before jump // TODO: push_jump to store selection just before jump
@ -60,16 +63,18 @@ pub fn regex_prompt(
match Regex::new(input) { match Regex::new(input) {
Ok(regex) => { Ok(regex) => {
// let view = &mut editor.view_mut(); // 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]; let doc = &mut editor.documents[id];
// revert state to what it was before the last update // revert state to what it was before the last update
// TODO: also revert text // TODO: also revert text
doc.set_selection(snapshot.clone()); doc.set_selection(view_id, snapshot.clone());
fun(doc, regex); 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 Err(_err) => (), // TODO: mark command line as error
} }

@ -8,7 +8,9 @@ use helix_core::{
ChangeSet, Diagnostic, History, Rope, Selection, State, Syntax, Transaction, 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)] #[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum Mode { pub enum Mode {
@ -21,6 +23,7 @@ pub struct Document {
// rope + selection // rope + selection
pub(crate) id: DocumentId, pub(crate) id: DocumentId,
state: State, state: State,
pub(crate) selections: HashMap<ViewId, Selection>,
path: Option<PathBuf>, path: Option<PathBuf>,
@ -73,6 +76,7 @@ impl Document {
id: DocumentId::default(), id: DocumentId::default(),
path: None, path: None,
state: State::new(text), state: State::new(text),
selections: HashMap::default(),
mode: Mode::Normal, mode: Mode::Normal,
restore_cursor: false, restore_cursor: false,
syntax: None, syntax: None,
@ -178,12 +182,12 @@ impl Document {
self.language_server = language_server; 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? // 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 old_doc = self.text().clone();
let success = transaction.changes().apply(&mut self.state.doc); let success = transaction.changes().apply(&mut self.state.doc);
@ -191,10 +195,11 @@ impl Document {
if !transaction.changes().is_empty() { if !transaction.changes().is_empty() {
// update the selection: either take the selection specified in the transaction, or map the // update the selection: either take the selection specified in the transaction, or map the
// current selection through changes. // current selection through changes.
self.state.selection = transaction let selection = transaction
.selection() .selection()
.cloned() .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; self.version += 1;
@ -227,14 +232,14 @@ impl Document {
success 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 // store the state just before any changes are made. This allows us to undo to the
// state just before a transaction was applied. // state just before a transaction was applied.
if self.changes.is_empty() && !transaction.changes().is_empty() { if self.changes.is_empty() && !transaction.changes().is_empty() {
self.old_state = Some(self.state.clone()); self.old_state = Some(self.state.clone());
} }
let success = self._apply(&transaction); let success = self._apply(&transaction, view_id);
self.modified = true; self.modified = true;
// TODO: be smarter about modified by keeping track of saved version instead. That way if // TODO: be smarter about modified by keeping track of saved version instead. That way if
@ -249,9 +254,9 @@ impl Document {
success success
} }
pub fn undo(&mut self) -> bool { pub fn undo(&mut self, view_id: ViewId) -> bool {
if let Some(transaction) = self.history.undo() { if let Some(transaction) = self.history.undo() {
let success = self._apply(&transaction); let success = self._apply(&transaction, view_id);
// reset changeset to fix len // reset changeset to fix len
self.changes = ChangeSet::new(self.text()); self.changes = ChangeSet::new(self.text());
@ -261,9 +266,9 @@ impl Document {
false false
} }
pub fn redo(&mut self) -> bool { pub fn redo(&mut self, view_id: ViewId) -> bool {
if let Some(transaction) = self.history.redo() { if let Some(transaction) = self.history.redo() {
let success = self._apply(&transaction); let success = self._apply(&transaction, view_id);
// reset changeset to fix len // reset changeset to fix len
self.changes = ChangeSet::new(self.text()); self.changes = ChangeSet::new(self.text());
@ -273,7 +278,7 @@ impl Document {
false 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() { if self.changes.is_empty() {
return; return;
} }
@ -282,7 +287,8 @@ impl Document {
let changes = std::mem::replace(&mut self.changes, new_changeset); let changes = std::mem::replace(&mut self.changes, new_changeset);
// Instead of doing this messy merge we could always commit, and based on transaction // 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. // 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.. // 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"); let old_state = self.old_state.take().expect("no old_state available");
@ -362,8 +368,8 @@ impl Document {
&self.state.doc &self.state.doc
} }
pub fn selection(&self) -> &Selection { pub fn selection(&self, view_id: ViewId) -> &Selection {
&self.state.selection &self.selections[&view_id]
} }
pub fn relative_path(&self) -> Option<&Path> { pub fn relative_path(&self) -> Option<&Path> {
@ -400,13 +406,14 @@ mod test {
use helix_lsp::{lsp, Client}; use helix_lsp::{lsp, Client};
let text = Rope::from("hello"); let text = Rope::from("hello");
let mut doc = Document::new(text); 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 // 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(); 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()); let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
assert_eq!( assert_eq!(
@ -425,7 +432,7 @@ mod test {
let transaction = transaction.invert(&old_doc.doc); let transaction = transaction.invert(&old_doc.doc);
let old_doc = doc.state.clone(); 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()); let changes = Client::changeset_to_changes(&old_doc.doc, doc.text(), transaction.changes());
// line: 0-based. // line: 0-based.
@ -450,13 +457,13 @@ mod test {
// also tests that changes are layered, positions depend on previous changes. // 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( let transaction = Transaction::change(
&doc.state.doc, &doc.state.doc,
vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(), vec![(0, 2, Some("aei".into())), (3, 5, Some("ou".into()))].into_iter(),
); );
// aeilou // aeilou
doc.apply(&transaction); doc.apply(&transaction, view);
let changes = let changes =
Client::changeset_to_changes(&doc.state.doc, doc.text(), transaction.changes()); Client::changeset_to_changes(&doc.state.doc, doc.text(), transaction.changes());

@ -91,24 +91,40 @@ impl Editor {
}; };
use crate::tree::Layout; use crate::tree::Layout;
use helix_core::Selection;
match action { match action {
Action::Replace => { Action::Replace => {
let view = self.view(); 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(); let view = self.view_mut();
view.jumps.push(jump); view.jumps.push(jump);
view.doc = id; view.doc = id;
view.first_line = 0; 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); return Ok(id);
} }
Action::HorizontalSplit => { Action::HorizontalSplit => {
let view = View::new(id)?; 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 => { Action::VerticalSplit => {
let view = View::new(id)?; 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(); 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.documents.remove(view.doc);
self.tree.remove(id); self.tree.remove(id);
self._refresh(); self._refresh();
@ -183,7 +202,7 @@ impl Editor {
const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
let view = self.view(); let view = self.view();
let doc = &self.documents[view.doc]; 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) { 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.col += view.area.x as usize + OFFSET as usize;
pos.row += view.area.y as usize; pos.row += view.area.y as usize;

@ -74,7 +74,7 @@ impl View {
} }
pub fn ensure_cursor_in_view(&mut self, doc: &Document) { 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 line = doc.text().char_to_line(cursor);
let document_end = self.first_line + (self.area.height as usize).saturating_sub(2); let document_end = self.first_line + (self.area.height as usize).saturating_sub(2);

Loading…
Cancel
Save