diff --git a/lapce-data/src/buffer.rs b/lapce-data/src/buffer.rs index 4b5691e4..5ae03b92 100644 --- a/lapce-data/src/buffer.rs +++ b/lapce-data/src/buffer.rs @@ -573,7 +573,7 @@ pub fn retrieve_file_head( LapceUICommand::LoadBufferHead { path, content: Rope::from(resp.content), - id: resp.id, + version: resp.version, }, Target::Widget(tab_id), ); @@ -2213,7 +2213,7 @@ fn buffer_diff( Some(changes) } -fn rope_diff( +pub fn rope_diff( left_rope: Rope, right_rope: Rope, rev: u64, diff --git a/lapce-data/src/command.rs b/lapce-data/src/command.rs index e4a5bb00..764f05ee 100644 --- a/lapce-data/src/command.rs +++ b/lapce-data/src/command.rs @@ -639,7 +639,7 @@ pub enum LapceUICommand { }, LoadBufferHead { path: PathBuf, - id: String, + version: String, content: Rope, }, LoadBufferAndGoToPosition { diff --git a/lapce-data/src/data.rs b/lapce-data/src/data.rs index 3b8af356..d91dadc4 100644 --- a/lapce-data/src/data.rs +++ b/lapce-data/src/data.rs @@ -2431,7 +2431,7 @@ pub fn go_to_location( Vec2::new(info.scroll_offset.0, info.scroll_offset.1); doc.cursor_offset = info.cursor_offset; } - doc.retrieve_file(self.proxy.clone(), vec![(editor_view_id, location)]); + doc.retrieve_file(vec![(editor_view_id, location)]); self.open_docs.insert(path.clone(), Arc::new(doc)); self.open_files.insert( path.clone(), @@ -2459,14 +2459,9 @@ pub fn go_to_location( None => (doc.cursor_offset, Some(&doc.scroll_offset)), }; - if let Some(compare) = location.history.as_ref() { - // if !buffer.histories().contains_key(compare) { - // buffer.retrieve_file_head( - // *self.tab_id, - // self.proxy.clone(), - // ctx.get_external_handle(), - // ); - // } + if let Some(version) = location.history.as_ref() { + let doc = self.open_docs.get_mut(&path).unwrap(); + Arc::make_mut(doc).retrieve_history(version); } let editor = self.get_editor_or_new( @@ -2475,8 +2470,8 @@ pub fn go_to_location( Some(location.path.clone()), config, ); - if let Some(compare) = location.history.as_ref() { - editor.view = EditorView::Diff(compare.to_string()); + if let Some(version) = location.history.as_ref() { + editor.view = EditorView::Diff(version.to_string()); } editor.content = BufferContent::File(path.clone()); editor.compare = location.history.clone(); @@ -2606,7 +2601,7 @@ pub fn new( active: Arc::new(None), active_tab: Arc::new(None), register: Arc::new(Register::default()), - proxy: proxy.clone(), + proxy, palette_preview_editor: Arc::new(palette_preview_editor), show_code_actions: false, current_code_actions: 0, @@ -2629,11 +2624,8 @@ pub fn new( ); main_split_data.split_id = Arc::new(split_data.widget_id); for (path, locations) in positions.into_iter() { - main_split_data - .open_docs - .get(&path) - .unwrap() - .retrieve_file(proxy.clone(), locations.clone()); + Arc::make_mut(main_split_data.open_docs.get_mut(&path).unwrap()) + .retrieve_file(locations.clone()); } } else { main_split_data.splits.insert( diff --git a/lapce-data/src/document.rs b/lapce-data/src/document.rs index 413207f9..95778ee9 100644 --- a/lapce-data/src/document.rs +++ b/lapce-data/src/document.rs @@ -3,7 +3,10 @@ collections::{HashMap, HashSet}, path::PathBuf, rc::Rc, - sync::{atomic, Arc}, + sync::{ + atomic::{self}, + Arc, + }, }; use druid::{ @@ -26,18 +29,19 @@ word::WordCursor, }; use lapce_rpc::{ - buffer::{BufferId, NewBufferResponse}, + buffer::{BufferHeadResponse, BufferId, NewBufferResponse}, style::{LineStyle, LineStyles, Style}, }; use lsp_types::CodeActionResponse; -use xi_rope::{spans::Spans, RopeDelta}; +use xi_rope::{spans::Spans, Rope, RopeDelta}; use crate::{ - buffer::{BufferContent, LocalBufferKind}, + buffer::{rope_diff, BufferContent, DiffLines, DiffResult, LocalBufferKind}, command::{LapceUICommand, LAPCE_UI_COMMAND}, config::{Config, LapceTheme}, editor::EditorLocationNew, find::{Find, FindProgress}, + history::DocumentHisotry, proxy::LapceProxy, }; @@ -53,15 +57,16 @@ fn put_string(&mut self, s: impl AsRef) { } } -struct TextLayoutCache { +#[derive(Clone, Default)] +pub struct TextLayoutCache { font_size: usize, font_family: FontFamily, tab_wdith: usize, - layouts: HashMap>, + pub layouts: HashMap>, } impl TextLayoutCache { - fn new() -> Self { + pub fn new() -> Self { Self { font_size: 0, font_family: FontFamily::SYSTEM_UI, @@ -74,7 +79,7 @@ fn clear(&mut self) { self.layouts.clear(); } - fn check_attributes( + pub fn check_attributes( &mut self, font_size: usize, font_family: FontFamily, @@ -95,7 +100,7 @@ fn check_attributes( #[derive(Clone)] pub struct Document { id: BufferId, - tab_id: WidgetId, + pub tab_id: WidgetId, buffer: Buffer, content: BufferContent, syntax: Option, @@ -104,13 +109,14 @@ pub struct Document { text_layouts: Rc>, load_started: Rc>, loaded: bool, + histories: im::HashMap, pub cursor_offset: usize, pub scroll_offset: Vec2, pub code_actions: im::HashMap, pub find: Rc>, find_progress: Rc>, - event_sink: ExtEventSink, - proxy: Arc, + pub event_sink: ExtEventSink, + pub proxy: Arc, } impl Document { @@ -130,6 +136,7 @@ pub fn new( text_layouts: Rc::new(RefCell::new(TextLayoutCache::new())), semantic_styles: None, load_started: Rc::new(RefCell::new(false)), + histories: im::HashMap::new(), loaded: false, cursor_offset: 0, scroll_offset: Vec2::ZERO, @@ -165,11 +172,7 @@ pub fn load_content(&mut self, content: &str) { self.on_update(None); } - pub fn retrieve_file( - &self, - proxy: Arc, - locations: Vec<(WidgetId, EditorLocationNew)>, - ) { + pub fn retrieve_file(&mut self, locations: Vec<(WidgetId, EditorLocationNew)>) { if self.loaded || *self.load_started.borrow() { return; } @@ -180,6 +183,7 @@ pub fn retrieve_file( let tab_id = self.tab_id; let path = path.clone(); let event_sink = self.event_sink.clone(); + let proxy = self.proxy.clone(); std::thread::spawn(move || { proxy.new_buffer( id, @@ -204,6 +208,58 @@ pub fn retrieve_file( ) }); } + + self.retrieve_history("head"); + } + + pub fn retrieve_history(&mut self, version: &str) { + if self.histories.contains_key(version) { + return; + } + + let history = DocumentHisotry::new(version.to_string()); + history.retrieve(self); + self.histories.insert(version.to_string(), history); + } + + pub fn load_history(&mut self, version: &str, content: Rope) { + let mut history = DocumentHisotry::new(version.to_string()); + history.load_content(content, self); + self.histories.insert(version.to_string(), history); + } + + pub fn get_history(&self, version: &str) -> Option<&DocumentHisotry> { + self.histories.get(version) + } + + fn trigger_head_change(&self) { + if let Some(head) = self.histories.get("head") { + head.trigger_update_change(self); + } + } + + pub fn update_history_changes( + &mut self, + rev: u64, + version: &str, + changes: Arc>, + ) { + if rev != self.rev() { + return; + } + if let Some(history) = self.histories.get_mut(version) { + history.update_changes(changes); + } + } + + pub fn update_history_styles( + &mut self, + version: &str, + styles: Arc>, + ) { + if let Some(history) = self.histories.get_mut(version) { + history.update_styles(styles); + } } fn on_update(&mut self, delta: Option<&RopeDelta>) { @@ -211,6 +267,7 @@ fn on_update(&mut self, delta: Option<&RopeDelta>) { *self.find_progress.borrow_mut() = FindProgress::Started; self.clear_style_cache(); self.trigger_syntax_change(delta); + self.trigger_head_change(); self.notify_special(); } diff --git a/lapce-data/src/history.rs b/lapce-data/src/history.rs index 7fd02160..c94fa6e6 100644 --- a/lapce-data/src/history.rs +++ b/lapce-data/src/history.rs @@ -1 +1,238 @@ -pub struct DocumentHisotry {} +use std::{ + cell::RefCell, + rc::Rc, + sync::{atomic, Arc}, +}; + +use druid::{ + piet::{PietText, PietTextLayout, Text, TextAttribute, TextLayoutBuilder}, + Target, +}; +use lapce_core::{buffer::Buffer, style::line_styles, syntax::Syntax}; +use lapce_rpc::{ + buffer::BufferHeadResponse, + style::{LineStyle, LineStyles, Style}, +}; +use xi_rope::{spans::Spans, Rope}; + +use crate::{ + buffer::{rope_diff, BufferContent, DiffLines}, + command::{LapceUICommand, LAPCE_UI_COMMAND}, + config::{Config, LapceTheme}, + document::{Document, TextLayoutCache}, +}; + +#[derive(Clone)] +pub struct DocumentHisotry { + version: String, + buffer: Buffer, + styles: Arc>, + line_styles: Rc>, + changes: Arc>, + text_layouts: Rc>, +} + +impl DocumentHisotry { + pub fn new(version: String) -> Self { + Self { + version, + buffer: Buffer::new(""), + styles: Arc::new(Spans::default()), + line_styles: Rc::new(RefCell::new(LineStyles::new())), + text_layouts: Rc::new(RefCell::new(TextLayoutCache::new())), + changes: Arc::new(Vec::new()), + } + } + + pub fn load_content(&mut self, content: Rope, doc: &Document) { + self.buffer.load_content(&content.slice_to_cow(..)); + self.trigger_update_change(doc); + self.retrieve_history_styles(doc); + } + + pub fn get_text_layout( + &self, + text: &mut PietText, + line: usize, + config: &Config, + ) -> Arc { + self.text_layouts.borrow_mut().check_attributes( + config.editor.font_size, + config.editor.font_family(), + config.editor.tab_width, + ); + if self.text_layouts.borrow().layouts.get(&line).is_none() { + self.text_layouts + .borrow_mut() + .layouts + .insert(line, Arc::new(self.new_text_layout(text, line, config))); + } + self.text_layouts + .borrow() + .layouts + .get(&line) + .cloned() + .unwrap() + } + + fn new_text_layout( + &self, + text: &mut PietText, + line: usize, + config: &Config, + ) -> PietTextLayout { + let line_content = self.buffer.line_content(line); + let font_family = config.editor.font_family(); + let font_size = config.editor.font_size; + let tab_width = + config.tab_width(text, config.editor.font_family(), font_size); + let mut layout_builder = text + .new_text_layout(line_content.to_string()) + .font(font_family, font_size as f64) + .text_color( + config + .get_color_unchecked(LapceTheme::EDITOR_FOREGROUND) + .clone(), + ) + .set_tab_width(tab_width); + + let styles = self.line_style(line); + for line_style in styles.iter() { + if let Some(fg_color) = line_style.style.fg_color.as_ref() { + if let Some(fg_color) = config.get_style_color(fg_color) { + layout_builder = layout_builder.range_attribute( + line_style.start..line_style.end, + TextAttribute::TextColor(fg_color.clone()), + ); + } + } + } + + layout_builder.build().unwrap() + } + + fn line_style(&self, line: usize) -> Arc> { + if self.line_styles.borrow().get(&line).is_none() { + let line_styles = line_styles(self.buffer.text(), line, &self.styles); + self.line_styles + .borrow_mut() + .insert(line, Arc::new(line_styles)); + } + self.line_styles.borrow().get(&line).cloned().unwrap() + } + + pub fn retrieve(&self, doc: &Document) { + if let BufferContent::File(path) = &doc.content() { + let id = doc.id(); + let tab_id = doc.tab_id; + let path = path.clone(); + let proxy = doc.proxy.clone(); + let event_sink = doc.event_sink.clone(); + std::thread::spawn(move || { + proxy.get_buffer_head( + id, + path.clone(), + Box::new(move |result| { + if let Ok(res) = result { + if let Ok(resp) = + serde_json::from_value::(res) + { + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::LoadBufferHead { + path, + content: Rope::from(resp.content), + version: resp.version, + }, + Target::Widget(tab_id), + ); + } + } + }), + ) + }); + } + } + + pub fn trigger_update_change(&self, doc: &Document) { + if let BufferContent::File(path) = &doc.content() { + let id = doc.id(); + let rev = doc.rev(); + let atomic_rev = doc.buffer().atomic_rev(); + let path = path.clone(); + let left_rope = self.buffer.text().clone(); + let right_rope = doc.buffer().text().clone(); + let event_sink = doc.event_sink.clone(); + let tab_id = doc.tab_id; + rayon::spawn(move || { + if atomic_rev.load(atomic::Ordering::Acquire) != rev { + return; + } + let changes = + rope_diff(left_rope, right_rope, rev, atomic_rev.clone()); + if changes.is_none() { + return; + } + let changes = changes.unwrap(); + if atomic_rev.load(atomic::Ordering::Acquire) != rev { + return; + } + + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::UpdateHistoryChanges { + id, + path, + rev, + history: "head".to_string(), + changes: Arc::new(changes), + }, + Target::Widget(tab_id), + ); + }); + } + } + + pub fn changes(&self) -> &[DiffLines] { + &self.changes + } + + pub fn update_changes(&mut self, changes: Arc>) { + self.changes = changes; + } + + pub fn update_styles(&mut self, styles: Arc>) { + self.styles = styles; + self.line_styles.borrow_mut().clear(); + } + + fn retrieve_history_styles(&self, doc: &Document) { + if let BufferContent::File(path) = &doc.content() { + let id = doc.id(); + let path = path.clone(); + let tab_id = doc.tab_id; + let version = self.version.to_string(); + let event_sink = doc.event_sink.clone(); + + let content = self.buffer.text().clone(); + rayon::spawn(move || { + if let Some(syntax) = + Syntax::init(&path).map(|s| s.parse(0, content, None)) + { + if let Some(styles) = syntax.styles { + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::UpdateHistoryStyle { + id, + path, + history: version, + highlights: styles, + }, + Target::Widget(tab_id), + ); + } + } + }); + } + } +} diff --git a/lapce-proxy/src/dispatch.rs b/lapce-proxy/src/dispatch.rs index d67d60f8..ed4f880a 100644 --- a/lapce-proxy/src/dispatch.rs +++ b/lapce-proxy/src/dispatch.rs @@ -404,7 +404,7 @@ fn handle_request(&self, id: RequestId, rpc: ProxyRequest) { let result = file_get_head(&workspace, &path); if let Ok((_blob_id, content)) = result { let resp = BufferHeadResponse { - id: "head".to_string(), + version: "head".to_string(), content, }; let _ = self.sender.send(json!({ diff --git a/lapce-rpc/src/buffer.rs b/lapce-rpc/src/buffer.rs index b2403b3f..a48436ce 100644 --- a/lapce-rpc/src/buffer.rs +++ b/lapce-rpc/src/buffer.rs @@ -19,6 +19,6 @@ pub struct NewBufferResponse { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BufferHeadResponse { - pub id: String, + pub version: String, pub content: String, } diff --git a/lapce-ui/src/editor.rs b/lapce-ui/src/editor.rs index 207df12a..58059193 100644 --- a/lapce-ui/src/editor.rs +++ b/lapce-ui/src/editor.rs @@ -13,6 +13,7 @@ use lapce_core::command::FocusCommand; use lapce_core::mode::{Mode, VisualMode}; use lapce_data::command::CommandKind; +use lapce_data::data::EditorView; use lapce_data::keypress::KeyPressFocus; use lapce_data::{ buffer::{matching_pair_direction, BufferContent, DiffLines, LocalBufferKind}, @@ -479,12 +480,12 @@ fn paint_content( if !data.editor.content.is_input() && data.editor.code_lens { Self::paint_code_lens_content(data, ctx, is_focused); - } else if let Some(compare) = data.editor.compare.as_ref() { - if let Some(changes) = data.buffer.history_changes.get(compare) { + } else if let EditorView::Diff(version) = &data.editor.view { + if let Some(history) = data.doc.get_history(version) { let cursor_line = data.buffer.line_of_offset(data.editor.cursor.offset()); let mut line = 0; - for change in changes.iter() { + for change in history.changes().iter() { match change { DiffLines::Left(range) => { let len = range.len(); @@ -509,24 +510,19 @@ fn paint_content( continue; } let actual_line = l - (line - len) + range.start; - if let Some(text_layout) = - data.buffer.history_text_layout( - ctx, - compare, - actual_line, - None, - [rect.x0, rect.x1], - &data.config, - ) - { - ctx.draw_text( - &text_layout, - Point::new( - 0.0, - line_height * l as f64 + y_shift, - ), - ); - } + let text_layout = history.get_text_layout( + ctx.text(), + actual_line, + &data.config, + ); + ctx.draw_text( + &text_layout, + Point::new( + 0.0, + line_height * l as f64 + y_shift, + ), + ); + if l > end_line { break; } @@ -600,13 +596,10 @@ fn paint_content( char_width, line_height, ); - let text_layout = data.buffer.new_text_layout( - ctx, + let text_layout = data.doc.get_text_layout( + ctx.text(), rope_line, - &data.buffer.line_content(rope_line), - None, font_size, - [rect.x0, rect.x1], &data.config, ); ctx.draw_text( @@ -662,13 +655,10 @@ fn paint_content( char_width, line_height, ); - let text_layout = data.buffer.new_text_layout( - ctx, + let text_layout = data.doc.get_text_layout( + ctx.text(), rope_line, - &data.buffer.line_content(rope_line), - None, font_size, - [rect.x0, rect.x1], &data.config, ); ctx.draw_text( diff --git a/lapce-ui/src/editor/gutter.rs b/lapce-ui/src/editor/gutter.rs index 30981a3f..4c451ad9 100644 --- a/lapce-ui/src/editor/gutter.rs +++ b/lapce-ui/src/editor/gutter.rs @@ -7,7 +7,7 @@ use lapce_data::{ buffer::DiffLines, config::LapceTheme, - data::LapceTabData, + data::{EditorView, LapceTabData}, editor::{LapceEditorBufferData, Syntax}, }; @@ -108,25 +108,28 @@ fn paint_gutter_inline_diff( &self, data: &LapceEditorBufferData, ctx: &mut PaintCtx, - compare: &str, + version: &str, ) { - if data.buffer.history_changes.get(compare).is_none() { + if data.doc.get_history(version).is_none() { return; } + let history = data.doc.get_history(version).unwrap(); let self_size = ctx.size(); let rect = self_size.to_rect(); - let changes = data.buffer.history_changes.get(compare).unwrap(); let line_height = data.config.editor.line_height as f64; let scroll_offset = data.editor.scroll_offset; let start_line = (scroll_offset.y / line_height).floor() as usize; let end_line = (scroll_offset.y + rect.height() / line_height).ceil() as usize; - let current_line = data.editor.cursor.current_line(data.buffer.data()); - let last_line = data.buffer.last_line(); + let current_line = data + .doc + .buffer() + .line_of_offset(data.editor.new_cursor.offset()); + let last_line = data.doc.buffer().last_line(); let width = data.config.editor_char_width(ctx.text()); let mut line = 0; - for change in changes.iter() { + for change in history.changes().iter() { match change { DiffLines::Left(r) => { let len = r.len(); @@ -467,8 +470,8 @@ fn paint_gutter(&self, data: &LapceEditorBufferData, ctx: &mut PaintCtx) { ctx.with_save(|ctx| { let clip_rect = rect; ctx.clip(clip_rect); - if let Some(compare) = data.editor.compare.as_ref() { - self.paint_gutter_inline_diff(data, ctx, compare); + if let EditorView::Diff(version) = &data.editor.view { + self.paint_gutter_inline_diff(data, ctx, version); return; } if data.editor.code_lens { @@ -479,7 +482,7 @@ fn paint_gutter(&self, data: &LapceEditorBufferData, ctx: &mut PaintCtx) { let scroll_offset = data.editor.scroll_offset; let start_line = (scroll_offset.y / line_height).floor() as usize; let num_lines = (ctx.size().height / line_height).floor() as usize; - let last_line = data.buffer.last_line(); + let last_line = data.doc.buffer().last_line(); let current_line = data .doc .buffer() @@ -536,13 +539,13 @@ fn paint_gutter(&self, data: &LapceEditorBufferData, ctx: &mut PaintCtx) { ctx.draw_text(&text_layout, Point::new(x, y)); } - if let Some(changes) = data.buffer.history_changes.get("head") { + if let Some(history) = data.doc.get_history("head") { let end_line = (scroll_offset.y + rect.height() / line_height).ceil() as usize; let mut line = 0; let mut last_change = None; - for change in changes.iter() { + for change in history.changes().iter() { let len = match change { DiffLines::Left(_range) => 0, DiffLines::Skip(_left, right) => right.len(), diff --git a/lapce-ui/src/tab.rs b/lapce-ui/src/tab.rs index cdaea532..0a136f58 100644 --- a/lapce-ui/src/tab.rs +++ b/lapce-ui/src/tab.rs @@ -433,11 +433,19 @@ fn event( matches.clone(); } } - LapceUICommand::LoadBufferHead { path, id, content } => { + LapceUICommand::LoadBufferHead { + path, + version, + content, + } => { let buffer = data.main_split.open_files.get_mut(path).unwrap(); let buffer = Arc::make_mut(buffer); - buffer.load_history(id, content.clone()); + buffer.load_history(version, content.clone()); + + let doc = data.main_split.open_docs.get_mut(path).unwrap(); + let doc = Arc::make_mut(doc); + doc.load_history(version, content.clone()); ctx.set_handled(); } LapceUICommand::UpdateTerminalTitle(term_id, title) => { @@ -974,6 +982,12 @@ fn event( history, changes.clone(), ); + let doc = data.main_split.open_docs.get_mut(path).unwrap(); + Arc::make_mut(doc).update_history_changes( + *rev, + history, + changes.clone(), + ); } LapceUICommand::UpdateHistoryStyle { path, @@ -991,6 +1005,10 @@ fn event( .history_line_styles .borrow_mut() .insert(history.to_string(), HashMap::new()); + + let doc = data.main_split.open_docs.get_mut(path).unwrap(); + Arc::make_mut(doc) + .update_history_styles(history, highlights.to_owned()); } LapceUICommand::UpdatePickerPwd(path) => { Arc::make_mut(&mut data.picker).pwd = path.clone();