From 3b772bfe2d480ba46d3c48efafd0bbbc46d74742 Mon Sep 17 00:00:00 2001 From: Dongdong Zhou Date: Wed, 11 May 2022 21:12:12 +0100 Subject: [PATCH] add support for scratch buffer --- lapce-core/src/buffer.rs | 10 +- lapce-data/src/command.rs | 7 + lapce-data/src/data.rs | 237 +++++++++++++++------- lapce-data/src/db.rs | 15 ++ lapce-data/src/document.rs | 82 +++++++- lapce-data/src/editor.rs | 31 +++ lapce-data/src/proxy.rs | 18 ++ lapce-proxy/src/dispatch.rs | 21 ++ lapce-rpc/src/lib.rs | 41 ++++ lapce-rpc/src/proxy.rs | 6 + lapce-ui/src/editor.rs | 2 +- lapce-ui/src/editor/header.rs | 15 +- lapce-ui/src/editor/tab_header_content.rs | 2 + lapce-ui/src/editor/view.rs | 2 +- lapce-ui/src/settings.rs | 2 +- lapce-ui/src/tab.rs | 24 ++- 16 files changed, 427 insertions(+), 88 deletions(-) diff --git a/lapce-core/src/buffer.rs b/lapce-core/src/buffer.rs index 093a991b..d7beaf9c 100644 --- a/lapce-core/src/buffer.rs +++ b/lapce-core/src/buffer.rs @@ -234,11 +234,17 @@ pub fn init_content(&mut self, content: Rope) { self.set_pristine(); } - pub fn reload(&mut self, content: Rope) -> (RopeDelta, InvalLines) { + pub fn reload( + &mut self, + content: Rope, + set_prisitine: bool, + ) -> (RopeDelta, InvalLines) { let delta = LineHashDiff::compute_delta(&self.text, &content); self.this_edit_type = EditType::Other; let (delta, inval_lines) = self.add_delta(delta); - self.set_pristine(); + if set_prisitine { + self.set_pristine(); + } (delta, inval_lines) } diff --git a/lapce-data/src/command.rs b/lapce-data/src/command.rs index f97e2966..c65f8ccc 100644 --- a/lapce-data/src/command.rs +++ b/lapce-data/src/command.rs @@ -23,6 +23,7 @@ use crate::alert::AlertContentData; use crate::data::LapceWorkspace; +use crate::document::BufferContent; use crate::rich_text::RichText; use crate::{ data::{EditorTabChild, SplitContent}, @@ -240,6 +241,10 @@ pub enum LapceWorkbenchCommand { #[strum(serialize = "new_window")] NewWindow, + #[strum(message = "New File")] + #[strum(serialize = "new_file")] + NewFile, + #[strum(serialize = "connect_ssh_host")] #[strum(message = "Connect to SSH Host")] ConnectSshHost, @@ -469,6 +474,8 @@ pub enum LapceUICommand { Scroll((f64, f64)), ScrollTo((f64, f64)), ForceScrollTo(f64, f64), + SaveAs(BufferContent, PathBuf, WidgetId, bool), + SaveAsSuccess(BufferContent, u64, PathBuf, WidgetId, bool), HomeDir(PathBuf), FileChange(notify::Event), ProxyUpdateStatus(ProxyStatus), diff --git a/lapce-data/src/data.rs b/lapce-data/src/data.rs index aa60d4e6..263561ee 100644 --- a/lapce-data/src/data.rs +++ b/lapce-data/src/data.rs @@ -10,8 +10,7 @@ use anyhow::Result; use crossbeam_channel::{unbounded, Receiver, Sender}; use druid::{ - piet::{PietText, PietTextLayout, Text, TextLayout, TextLayoutBuilder}, - theme, Command, Data, Env, EventCtx, ExtEventSink, FontFamily, Lens, Point, + piet::PietText, theme, Command, Data, Env, EventCtx, ExtEventSink, Lens, Point, Rect, Size, Target, Vec2, WidgetId, WindowId, }; @@ -25,11 +24,10 @@ selection::Selection, }; use lapce_rpc::{ - plugin::PluginDescription, source_control::FileDiff, terminal::TermId, -}; -use lsp_types::{ - CodeActionOrCommand, Diagnostic, Position, ProgressToken, TextEdit, + buffer::BufferId, plugin::PluginDescription, source_control::FileDiff, + terminal::TermId, }; +use lsp_types::{Diagnostic, Position, ProgressToken, TextEdit}; use notify::Watcher; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -707,6 +705,9 @@ pub fn editor_view_content( BufferContent::File(path) => { self.main_split.open_docs.get(path).unwrap().clone() } + BufferContent::Scratch(id) => { + self.main_split.scratch_docs.get(id).unwrap().clone() + } BufferContent::Local(kind) => { self.main_split.local_docs.get(kind).unwrap().clone() } @@ -742,39 +743,12 @@ pub fn code_action_size(&self, text: &mut PietText, _env: &Env) -> Size { BufferContent::File(path) => { let doc = self.main_split.open_docs.get(path).unwrap(); let offset = editor.new_cursor.offset(); - let prev_offset = doc.buffer().prev_code_boundary(offset); - let empty_vec = Vec::new(); - let code_actions = - doc.code_actions.get(&prev_offset).unwrap_or(&empty_vec); - - let action_text_layouts: Vec = code_actions - .iter() - .map(|code_action| { - let title = match code_action { - CodeActionOrCommand::Command(cmd) => { - cmd.title.to_string() - } - CodeActionOrCommand::CodeAction(action) => { - action.title.to_string() - } - }; - - text.new_text_layout(title) - .font(FontFamily::SYSTEM_UI, 14.0) - .build() - .unwrap() - }) - .collect(); - - let mut width = 0.0; - for text_layout in &action_text_layouts { - let line_width = text_layout.size().width + 10.0; - if line_width > width { - width = line_width; - } - } - let line_height = self.config.editor.line_height as f64; - Size::new(width, code_actions.len() as f64 * line_height) + doc.code_action_size(text, offset, &self.config) + } + BufferContent::Scratch(id) => { + let doc = self.main_split.scratch_docs.get(id).unwrap(); + let offset = editor.new_cursor.offset(); + doc.code_action_size(text, offset, &self.config) } } } @@ -810,6 +784,11 @@ pub fn update_from_editor_buffer_data( .open_docs .insert(path.clone(), editor_buffer_data.doc); } + BufferContent::Scratch(id) => { + self.main_split + .scratch_docs + .insert(*id, editor_buffer_data.doc); + } BufferContent::Local(kind) => { self.main_split .local_docs @@ -846,8 +825,8 @@ pub fn code_action_origin( *editor.window_origin.borrow() - self.window_origin.borrow().to_vec2() } - BufferContent::File(path) => { - let doc = self.main_split.open_docs.get(path).unwrap(); + BufferContent::File(_) | BufferContent::Scratch(_) => { + let doc = self.main_split.editor_doc(editor.view_id); let offset = editor.new_cursor.offset(); let (line, col) = doc.buffer().offset_to_line_col(offset); let width = config.editor_char_width(text); @@ -884,8 +863,8 @@ pub fn completion_origin( *editor.window_origin.borrow() - self.window_origin.borrow().to_vec2() } - BufferContent::File(path) => { - let doc = self.main_split.open_docs.get(path).unwrap(); + BufferContent::File(_) | BufferContent::Scratch(_) => { + let doc = self.main_split.editor_doc(editor.view_id); let offset = self.completion.offset; let (line, col) = doc.buffer().offset_to_line_col(offset); let width = config.editor_char_width(text); @@ -940,8 +919,8 @@ pub fn hover_origin( *editor.window_origin.borrow() - self.window_origin.borrow().to_vec2() } - BufferContent::File(path) => { - let doc = self.main_split.open_docs.get(path).unwrap(); + BufferContent::File(_) | BufferContent::Scratch(_) => { + let doc = self.main_split.editor_doc(editor.view_id); let offset = self.hover.offset; let (line, col) = doc.buffer().offset_to_line_col(offset); let point = doc.point_of_line_col( @@ -1117,6 +1096,9 @@ pub fn run_workbench_command( Target::Widget(self.palette.widget_id), )); } + LapceWorkbenchCommand::NewFile => { + self.main_split.new_file(ctx, &self.config); + } LapceWorkbenchCommand::OpenLogFile => { if let Some(path) = Config::log_file() { let editor_view_id = self.main_split.active.clone(); @@ -1391,7 +1373,7 @@ pub fn run_workbench_command( return; } self.proxy.git_commit(message, diffs); - Arc::make_mut(doc).reload(Rope::from("")); + Arc::make_mut(doc).reload(Rope::from(""), true); let editor = self .main_split .editors @@ -1635,7 +1617,7 @@ pub fn set_picker_pwd(&mut self, pwd: PathBuf) { .get_mut(&LocalBufferKind::FilePicker) .unwrap(); let doc = Arc::make_mut(doc); - doc.reload(Rope::from(s)); + doc.reload(Rope::from(s), true); let editor = self .main_split .editors @@ -1860,6 +1842,7 @@ pub struct LapceMainSplitData { pub open_docs: im::HashMap>, pub local_docs: im::HashMap>, pub value_docs: im::HashMap>, + pub scratch_docs: im::HashMap>, pub register: Arc, pub proxy: Arc, pub palette_preview_editor: Arc, @@ -1884,6 +1867,7 @@ pub fn editor_doc(&self, editor_view_id: WidgetId) -> Arc { BufferContent::File(path) => self.open_docs.get(path).unwrap().clone(), BufferContent::Local(kind) => self.local_docs.get(kind).unwrap().clone(), BufferContent::Value(name) => self.value_docs.get(name).unwrap().clone(), + BufferContent::Scratch(id) => self.scratch_docs.get(id).unwrap().clone(), }; doc } @@ -2082,6 +2066,7 @@ fn get_editor_or_new( ctx: &mut EventCtx, editor_view_id: Option, path: Option, + scratch: bool, config: &Config, ) -> &mut LapceEditorData { match editor_view_id { @@ -2093,7 +2078,7 @@ fn get_editor_or_new( match &editor_tab.children[editor_tab.active] { EditorTabChild::Editor(id, _) => { if config.editor.show_tab { - if let Some(path) = path { + if path.is_some() || scratch { let mut editor_size = Size::ZERO; for (i, child) in editor_tab.children.iter().enumerate() @@ -2107,24 +2092,26 @@ fn get_editor_or_new( if current_size.height > 0.0 { editor_size = current_size; } - if editor.content - == BufferContent::File( - path.clone(), - ) - { - editor_tab.active = i; - ctx.submit_command( + if let Some(path) = path.as_ref() { + if editor.content + == BufferContent::File( + path.clone(), + ) + { + editor_tab.active = i; + ctx.submit_command( Command::new( LAPCE_UI_COMMAND, LapceUICommand::Focus, Target::Widget(*id), ), ); - return Arc::make_mut( - self.editors - .get_mut(id) - .unwrap(), - ); + return Arc::make_mut( + self.editors + .get_mut(id) + .unwrap(), + ); + } } } } @@ -2245,7 +2232,8 @@ pub fn jump_to_position( position: Position, config: &Config, ) { - let editor = self.get_editor_or_new(ctx, editor_view_id, None, config); + let editor = + self.get_editor_or_new(ctx, editor_view_id, None, false, config); if let BufferContent::File(path) = &editor.content { let location = EditorLocationNew { path: path.clone(), @@ -2269,6 +2257,7 @@ pub fn jump_to_location( ctx, editor_view_id, Some(location.path.clone()), + false, config, ) .view_id; @@ -2277,6 +2266,7 @@ pub fn jump_to_location( ctx, Some(editor_view_id), Some(location.path.clone()), + false, config, ); editor.save_jump_location(&doc); @@ -2284,6 +2274,24 @@ pub fn jump_to_location( editor_view_id } + pub fn new_file(&mut self, ctx: &mut EventCtx, config: &Config) { + let tab_id = *self.tab_id; + let proxy = self.proxy.clone(); + let buffer_id = BufferId::next(); + let content = BufferContent::Scratch(buffer_id); + let doc = + Document::new(content.clone(), tab_id, ctx.get_external_handle(), proxy); + self.scratch_docs.insert(buffer_id, Arc::new(doc)); + + let editor = self.get_editor_or_new(ctx, None, None, true, config); + editor.content = content; + editor.new_cursor = if config.lapce.modal { + Cursor::new(CursorMode::Normal(0), None, None) + } else { + Cursor::new(CursorMode::Insert(Selection::caret(0)), None, None) + }; + } + pub fn go_to_location( &mut self, ctx: &mut EventCtx, @@ -2296,6 +2304,7 @@ pub fn go_to_location( ctx, editor_view_id, Some(location.path.clone()), + false, config, ) .view_id; @@ -2304,6 +2313,7 @@ pub fn go_to_location( BufferContent::File(path) => path != &location.path, BufferContent::Local(_) => true, BufferContent::Value(_) => true, + BufferContent::Scratch(_) => true, }; if new_buffer { self.db.save_doc_position(&self.workspace, &doc); @@ -2356,6 +2366,7 @@ pub fn go_to_location( ctx, Some(editor_view_id), Some(location.path.clone()), + false, config, ); if let Some(version) = location.history.as_ref() { @@ -2401,7 +2412,7 @@ pub fn jump_to_line( config: &Config, ) { let editor_view_id = self - .get_editor_or_new(ctx, editor_view_id, None, config) + .get_editor_or_new(ctx, editor_view_id, None, false, config) .view_id; let doc = self.editor_doc(editor_view_id); let offset = doc.buffer().first_non_blank_character_on_line(if line > 0 { @@ -2444,6 +2455,7 @@ pub fn new( )), ); let value_docs = im::HashMap::new(); + let scratch_docs = im::HashMap::new(); let editor = LapceEditorData::new( Some(palette_preview_editor), @@ -2462,6 +2474,7 @@ pub fn new( open_docs, local_docs, value_docs, + scratch_docs, active: Arc::new(None), active_tab: Arc::new(None), register: Arc::new(Register::default()), @@ -2590,6 +2603,86 @@ pub fn update_split_layout_rect(&self, split_id: WidgetId, rect: Rect) { *split.layout_rect.borrow_mut() = rect; } + pub fn save_as_success( + &mut self, + ctx: &mut EventCtx, + content: &BufferContent, + rev: u64, + path: &Path, + view_id: WidgetId, + exit: bool, + ) { + match content { + BufferContent::Scratch(id) => { + let doc = self.scratch_docs.get(id).unwrap(); + if doc.rev() == rev { + let new_content = BufferContent::File(path.to_path_buf()); + for (_, editor) in self.editors.iter_mut() { + if editor.content == BufferContent::Scratch(*id) { + Arc::make_mut(editor).content = new_content.clone(); + } + } + + let mut doc = self.scratch_docs.remove(id).unwrap(); + let mut_doc = Arc::make_mut(&mut doc); + mut_doc.buffer_mut().set_pristine(); + mut_doc.set_content(new_content); + self.open_docs.insert(path.to_path_buf(), doc); + if exit { + ctx.submit_command(Command::new( + LAPCE_COMMAND, + LapceCommand { + kind: CommandKind::Focus(FocusCommand::SplitClose), + data: None, + }, + Target::Widget(view_id), + )); + } + } + } + BufferContent::File(_) => {} + _ => {} + } + } + + pub fn save_as( + &mut self, + ctx: &mut EventCtx, + content: &BufferContent, + path: &Path, + view_id: WidgetId, + exit: bool, + ) { + match content { + BufferContent::Scratch(id) => { + let event_sink = ctx.get_external_handle(); + let doc = self.scratch_docs.get(id).unwrap(); + let rev = doc.rev(); + let path = path.to_path_buf(); + let content = content.clone(); + self.proxy.save_buffer_as( + doc.id(), + path.to_path_buf(), + doc.rev(), + doc.buffer().text().to_string(), + Box::new(move |result| { + if let Ok(_r) = result { + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::SaveAsSuccess( + content, rev, path, view_id, exit, + ), + Target::Auto, + ); + } + }), + ); + } + BufferContent::File(_) => {} + _ => {} + } + } + pub fn editor_close( &mut self, ctx: &mut EventCtx, @@ -2597,12 +2690,11 @@ pub fn editor_close( force: bool, ) { let editor = self.editors.get(&view_id).unwrap(); - if let BufferContent::File(path) = &editor.content { - let doc = self.open_docs.get(path).unwrap(); + if let BufferContent::File(_) | BufferContent::Scratch(_) = &editor.content { + let doc = self.editor_doc(view_id); if !force && !doc.buffer().is_pristine() { let exits = self.editors.iter().any(|(_, e)| { - e.content == BufferContent::File(path.to_path_buf()) - && e.view_id != view_id + &e.content == doc.content() && e.view_id != view_id }); if !exits { ctx.submit_command(Command::new( @@ -2610,9 +2702,7 @@ pub fn editor_close( LapceUICommand::ShowAlert(AlertContentData { title: format!( "Do you want to save the changes you made to {}?", - path.file_name() - .and_then(|f| f.to_str()) - .unwrap_or("") + doc.content().file_name() ), msg: "Your changes will be lost if you don't save them." .to_string(), @@ -2644,7 +2734,7 @@ pub fn editor_close( return; } } - self.db.save_doc_position(&self.workspace, doc); + self.db.save_doc_position(&self.workspace, &doc); } if let Some(tab_id) = editor.tab_id { let editor_tab = self.editor_tabs.get(&tab_id).unwrap(); @@ -3080,8 +3170,15 @@ pub fn save_jump_location(&mut self, doc: &Document) { } pub fn editor_info(&self, data: &LapceTabData) -> EditorInfo { + let unsaved = if let BufferContent::Scratch(id) = &self.content { + let doc = data.main_split.scratch_docs.get(id).unwrap(); + Some(doc.buffer().text().to_string()) + } else { + None + }; let info = EditorInfo { content: self.content.clone(), + unsaved, scroll_offset: (self.scroll_offset.x, self.scroll_offset.y), position: if let BufferContent::File(path) = &self.content { let doc = data.main_split.open_docs.get(path).unwrap().clone(); diff --git a/lapce-data/src/db.rs b/lapce-data/src/db.rs index eeaa9ba8..208bd1a0 100644 --- a/lapce-data/src/db.rs +++ b/lapce-data/src/db.rs @@ -12,6 +12,7 @@ use druid::{ExtEventSink, Point, Rect, Size, Vec2, WidgetId}; use lsp_types::Position; use serde::{Deserialize, Serialize}; +use xi_rope::Rope; use crate::{ config::Config, @@ -233,6 +234,7 @@ pub struct BufferInfo { #[derive(Clone, Serialize, Deserialize)] pub struct EditorInfo { pub content: BufferContent, + pub unsaved: Option, pub scroll_offset: (f64, f64), pub position: Option, } @@ -285,6 +287,19 @@ pub fn to_data( )); data.open_docs.insert(path.clone(), doc); } + } else if let BufferContent::Scratch(id) = &self.content { + if !data.scratch_docs.contains_key(id) { + let mut doc = Document::new( + self.content.clone(), + tab_id, + event_sink, + data.proxy.clone(), + ); + if let Some(text) = &self.unsaved { + doc.reload(Rope::from(text), false); + } + data.scratch_docs.insert(*id, Arc::new(doc)); + } } data.insert_editor(Arc::new(editor_data.clone()), config); editor_data diff --git a/lapce-data/src/document.rs b/lapce-data/src/document.rs index 3cbbd8d9..ec3799a5 100644 --- a/lapce-data/src/document.rs +++ b/lapce-data/src/document.rs @@ -13,7 +13,7 @@ piet::{ PietText, PietTextLayout, Text, TextAttribute, TextLayout, TextLayoutBuilder, }, - ExtEventSink, FontFamily, Point, Target, Vec2, WidgetId, + ExtEventSink, FontFamily, Point, Size, Target, Vec2, WidgetId, }; use lapce_core::{ buffer::{Buffer, DiffLines, InvalLines}, @@ -32,7 +32,7 @@ buffer::{BufferId, NewBufferResponse}, style::{LineStyle, LineStyles, Style}, }; -use lsp_types::CodeActionResponse; +use lsp_types::{CodeActionOrCommand, CodeActionResponse}; use serde::{Deserialize, Serialize}; use xi_rope::{spans::Spans, Rope, RopeDelta}; @@ -99,6 +99,7 @@ pub enum BufferContent { File(PathBuf), Local(LocalBufferKind), Value(String), + Scratch(BufferId), } impl BufferContent { @@ -119,6 +120,7 @@ pub fn is_special(&self) -> bool { LocalBufferKind::Empty => false, }, BufferContent::Value(_) => true, + BufferContent::Scratch(_) => false, } } @@ -134,6 +136,7 @@ pub fn is_input(&self) -> bool { LocalBufferKind::Empty | LocalBufferKind::SourceControl => false, }, BufferContent::Value(_) => true, + BufferContent::Scratch(_) => false, } } @@ -141,6 +144,7 @@ pub fn is_search(&self) -> bool { match &self { BufferContent::File(_) => false, BufferContent::Value(_) => false, + BufferContent::Scratch(_) => false, BufferContent::Local(local) => matches!(local, LocalBufferKind::Search), } } @@ -150,6 +154,17 @@ pub fn is_settings(&self) -> bool { BufferContent::File(_) => false, BufferContent::Value(_) => true, BufferContent::Local(_) => false, + BufferContent::Scratch(_) => false, + } + } + + pub fn file_name(&self) -> &str { + match self { + BufferContent::File(p) => { + p.file_name().and_then(|f| f.to_str()).unwrap_or("") + } + BufferContent::Scratch(_) => "[Untitled]", + _ => "", } } } @@ -187,10 +202,15 @@ pub fn new( BufferContent::File(path) => Syntax::init(path), BufferContent::Local(_) => None, BufferContent::Value(_) => None, + BufferContent::Scratch(_) => None, + }; + let id = match &content { + BufferContent::Scratch(id) => *id, + _ => BufferId::next(), }; Self { - id: BufferId::next(), + id, tab_id, buffer: Buffer::new(""), content, @@ -219,6 +239,17 @@ pub fn loaded(&self) -> bool { self.loaded } + pub fn set_content(&mut self, content: BufferContent) { + self.content = content; + self.syntax = match &self.content { + BufferContent::File(path) => Syntax::init(path), + BufferContent::Local(_) => None, + BufferContent::Value(_) => None, + BufferContent::Scratch(_) => None, + }; + self.on_update(None); + } + pub fn content(&self) -> &BufferContent { &self.content } @@ -234,15 +265,15 @@ pub fn init_content(&mut self, content: Rope) { self.on_update(None); } - pub fn reload(&mut self, content: Rope) { + pub fn reload(&mut self, content: Rope, set_pristine: bool) { self.code_actions.clear(); - let delta = self.buffer.reload(content); + let delta = self.buffer.reload(content, set_pristine); self.apply_deltas(&[delta]); } pub fn handle_file_changed(&mut self, content: Rope) { if self.buffer.is_pristine() { - self.reload(content); + self.reload(content, true); } } @@ -442,6 +473,7 @@ fn on_update(&mut self, delta: Option<&RopeDelta>) { fn notify_special(&self) { match &self.content { BufferContent::File(_) => {} + BufferContent::Scratch(_) => {} BufferContent::Local(local) => { let s = self.buffer.text().to_string(); match local { @@ -1384,6 +1416,44 @@ pub fn move_offset( } } + pub fn code_action_size( + &self, + text: &mut PietText, + offset: usize, + config: &Config, + ) -> Size { + let prev_offset = self.buffer.prev_code_boundary(offset); + let empty_vec = Vec::new(); + let code_actions = self.code_actions.get(&prev_offset).unwrap_or(&empty_vec); + + let action_text_layouts: Vec = code_actions + .iter() + .map(|code_action| { + let title = match code_action { + CodeActionOrCommand::Command(cmd) => cmd.title.to_string(), + CodeActionOrCommand::CodeAction(action) => { + action.title.to_string() + } + }; + + text.new_text_layout(title) + .font(FontFamily::SYSTEM_UI, 14.0) + .build() + .unwrap() + }) + .collect(); + + let mut width = 0.0; + for text_layout in &action_text_layouts { + let line_width = text_layout.size().width + 10.0; + if line_width > width { + width = line_width; + } + } + let line_height = config.editor.line_height as f64; + Size::new(width, code_actions.len() as f64 * line_height) + } + pub fn reset_find(&self, current_find: &Find) { { let find = self.find.borrow(); diff --git a/lapce-data/src/editor.rs b/lapce-data/src/editor.rs index fa650675..248932ef 100644 --- a/lapce-data/src/editor.rs +++ b/lapce-data/src/editor.rs @@ -1111,6 +1111,36 @@ fn save(&mut self, ctx: &mut EventCtx, exit: bool) { Target::Auto, ); }); + } else if let BufferContent::Scratch(_) = self.doc.content() { + let workspace = self.main_split.workspace.clone(); + let event_sink = ctx.get_external_handle(); + let tab_id = *self.main_split.tab_id; + let content = self.doc.content().clone(); + let view_id = self.editor.view_id; + thread::spawn(move || { + let dirs = directories::UserDirs::new(); + + let dir = workspace + .path + .as_deref() + .or_else(|| dirs.as_ref().map(|u| u.home_dir())) + .map(|u| u.join("Untitled")); + let dir = dir + .as_ref() + .and_then(|u| u.to_str()) + .unwrap_or("./Untitled"); + + if let Some(path) = + tinyfiledialogs::save_file_dialog("Save File", dir) + { + let path = PathBuf::from(path); + let _ = event_sink.submit_command( + LAPCE_UI_COMMAND, + LapceUICommand::SaveAs(content, path, view_id, exit), + Target::Widget(tab_id), + ); + } + }); } } @@ -1851,6 +1881,7 @@ fn check_condition(&self, condition: &str) -> bool { "input_focus" => self.editor.content.is_input(), "editor_focus" => match self.editor.content { BufferContent::File(_) => true, + BufferContent::Scratch(_) => true, BufferContent::Local(_) => false, BufferContent::Value(_) => false, }, diff --git a/lapce-data/src/proxy.rs b/lapce-data/src/proxy.rs index f9886570..a2329b21 100644 --- a/lapce-data/src/proxy.rs +++ b/lapce-data/src/proxy.rs @@ -16,6 +16,7 @@ use lapce_rpc::buffer::BufferId; use lapce_rpc::core::{CoreNotification, CoreRequest}; use lapce_rpc::plugin::PluginDescription; +use lapce_rpc::proxy::ProxyRequest; use lapce_rpc::source_control::FileDiff; use lapce_rpc::terminal::TermId; use lapce_rpc::RpcHandler; @@ -434,6 +435,23 @@ pub fn new_buffer( ); } + pub fn save_buffer_as( + &self, + buffer_id: BufferId, + path: PathBuf, + rev: u64, + content: String, + f: Box, + ) { + let request = ProxyRequest::SaveBufferAs { + buffer_id, + path, + rev, + content, + }; + self.rpc.send_rpc_request_value_async(request, f); + } + pub fn update(&self, buffer_id: BufferId, delta: &RopeDelta, rev: u64) { self.rpc.send_rpc_notification( "update", diff --git a/lapce-proxy/src/dispatch.rs b/lapce-proxy/src/dispatch.rs index 8818d1a4..5dec6eed 100644 --- a/lapce-proxy/src/dispatch.rs +++ b/lapce-proxy/src/dispatch.rs @@ -29,6 +29,7 @@ use std::sync::Arc; use std::thread; use std::{collections::HashSet, io::BufRead}; +use xi_rope::Rope; const OPEN_FILE_EVENT_TOKEN: WatchToken = WatchToken(1); const WORKSPACE_EVENT_TOKEN: WatchToken = WatchToken(2); @@ -566,6 +567,26 @@ fn handle_request(&self, id: RequestId, rpc: ProxyRequest) { self.lsp.lock().save_buffer(buffer); self.respond(id, resp); } + SaveBufferAs { + buffer_id, + path, + rev, + content, + } => { + let mut buffer = + Buffer::new(buffer_id, path.clone(), self.git_sender.clone()); + buffer.rope = Rope::from(content); + buffer.rev = rev; + let resp = buffer.save(rev).map(|_r| json!({})); + if resp.is_ok() { + self.buffers.lock().insert(buffer_id, buffer); + self.open_files + .lock() + .insert(path.to_str().unwrap().to_string(), buffer_id); + let _ = self.git_sender.send((buffer_id, 0)); + } + self.respond(id, resp); + } GlobalSearch { pattern } => { if let Some(workspace) = self.workspace.lock().clone() { let local_dispatcher = self.clone(); diff --git a/lapce-rpc/src/lib.rs b/lapce-rpc/src/lib.rs index fecea5a2..846b11f6 100644 --- a/lapce-rpc/src/lib.rs +++ b/lapce-rpc/src/lib.rs @@ -160,6 +160,47 @@ fn send_rpc_request_common( } } + fn send_rpc_request_value_common( + &self, + request: T, + rh: ResponseHandler, + ) { + let id = self.id.fetch_add(1, Ordering::Relaxed); + { + let mut pending = self.pending.lock(); + pending.insert(id, rh); + } + let mut request = serde_json::to_value(request).unwrap(); + request + .as_object_mut() + .unwrap() + .insert("id".to_string(), json!(id)); + + if let Err(_e) = self.sender.send(request) { + let mut pending = self.pending.lock(); + if let Some(rh) = pending.remove(&id) { + rh.invoke(Err(json!("io error"))); + } + } + } + + pub fn send_rpc_request_value( + &self, + request: T, + ) -> Result { + let (tx, rx) = crossbeam_channel::bounded(1); + self.send_rpc_request_value_common(request, ResponseHandler::Chan(tx)); + rx.recv().unwrap_or_else(|_| Err(json!("io error"))) + } + + pub fn send_rpc_request_value_async( + &self, + request: T, + f: Box, + ) { + self.send_rpc_request_value_common(request, ResponseHandler::Callback(f)); + } + pub fn send_rpc_request( &self, method: &str, diff --git a/lapce-rpc/src/proxy.rs b/lapce-rpc/src/proxy.rs index b0c42e53..80e9127d 100644 --- a/lapce-rpc/src/proxy.rs +++ b/lapce-rpc/src/proxy.rs @@ -110,6 +110,12 @@ pub enum ProxyRequest { rev: u64, buffer_id: BufferId, }, + SaveBufferAs { + buffer_id: BufferId, + path: PathBuf, + rev: u64, + content: String, + }, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/lapce-ui/src/editor.rs b/lapce-ui/src/editor.rs index 5227f1ff..ab1a2588 100644 --- a/lapce-ui/src/editor.rs +++ b/lapce-ui/src/editor.rs @@ -285,7 +285,7 @@ pub fn get_size( let line_height = data.config.editor.line_height as f64; let width = data.config.editor_char_width(text); match &data.editor.content { - BufferContent::File(_) => { + BufferContent::File(_) | BufferContent::Scratch(_) => { if data.editor.code_lens { if let Some(syntax) = data.doc.syntax() { let height = diff --git a/lapce-ui/src/editor/header.rs b/lapce-ui/src/editor/header.rs index 09f3d4f3..15600db5 100644 --- a/lapce-ui/src/editor/header.rs +++ b/lapce-ui/src/editor/header.rs @@ -1,4 +1,4 @@ -use std::iter::Iterator; +use std::{iter::Iterator, path::PathBuf}; use druid::{ piet::{Text, TextLayout as TextLayoutTrait, TextLayoutBuilder}, @@ -135,10 +135,19 @@ pub fn paint_buffer( clip_rect.x1 = icon.rect.x0; } } - if let BufferContent::File(path) = data.doc.content() { + if let BufferContent::File(_) | BufferContent::Scratch(_) = + data.doc.content() + { + let mut path = match data.doc.content() { + BufferContent::File(path) => path.to_path_buf(), + BufferContent::Scratch(_) => { + PathBuf::from(data.doc.content().file_name()) + } + _ => PathBuf::from(""), + }; + ctx.with_save(|ctx| { ctx.clip(clip_rect); - let mut path = path.clone(); let svg = file_svg_new(&path); let width = 13.0; diff --git a/lapce-ui/src/editor/tab_header_content.rs b/lapce-ui/src/editor/tab_header_content.rs index db3fa27b..c595dbb2 100644 --- a/lapce-ui/src/editor/tab_header_content.rs +++ b/lapce-ui/src/editor/tab_header_content.rs @@ -359,6 +359,8 @@ fn layout( text = s.to_string(); } } + } else if let BufferContent::Scratch(_) = &editor.content { + text = editor.content.file_name().to_string(); } } } diff --git a/lapce-ui/src/editor/view.rs b/lapce-ui/src/editor/view.rs index 14da0d0a..4d33a6c6 100644 --- a/lapce-ui/src/editor/view.rs +++ b/lapce-ui/src/editor/view.rs @@ -112,7 +112,7 @@ pub fn request_focus( )); } match &editor.content { - BufferContent::File(_) => { + BufferContent::File(_) | BufferContent::Scratch(_) => { data.focus_area = FocusArea::Editor; data.main_split.active = Arc::new(Some(self.view_id)); data.main_split.active_tab = Arc::new(editor.tab_id); diff --git a/lapce-ui/src/settings.rs b/lapce-ui/src/settings.rs index bfdcc56e..b870fe7e 100644 --- a/lapce-ui/src/settings.rs +++ b/lapce-ui/src/settings.rs @@ -668,7 +668,7 @@ pub fn new( event_sink, data.proxy.clone(), ); - doc.reload(Rope::from(&input)); + doc.reload(Rope::from(&input), true); data.main_split.value_docs.insert(name, Arc::new(doc)); let editor = LapceEditorData::new(None, None, content, &data.config); let view_id = editor.view_id; diff --git a/lapce-ui/src/tab.rs b/lapce-ui/src/tab.rs index 1e0ed2bb..f70a631d 100644 --- a/lapce-ui/src/tab.rs +++ b/lapce-ui/src/tab.rs @@ -330,7 +330,7 @@ fn handle_event( .local_docs .get_mut(&LocalBufferKind::Palette) .unwrap(); - Arc::make_mut(doc).reload(Rope::from(pattern)); + Arc::make_mut(doc).reload(Rope::from(pattern), true); let editor = data .main_split .editors @@ -354,7 +354,7 @@ fn handle_event( .get_mut(&LocalBufferKind::Search) .unwrap(); if &doc.buffer().text().to_string() != pattern { - Arc::make_mut(doc).reload(Rope::from(pattern)); + Arc::make_mut(doc).reload(Rope::from(pattern), true); } if pattern.is_empty() { Arc::make_mut(&mut data.find).unset(); @@ -624,7 +624,7 @@ fn handle_event( location, } => { let doc = data.main_split.open_docs.get_mut(path).unwrap(); - Arc::make_mut(doc).reload(Rope::from(content)); + Arc::make_mut(doc).reload(Rope::from(content), true); data.main_split.go_to_location( ctx, Some(*editor_view_id), @@ -819,6 +819,22 @@ fn handle_event( } ctx.set_handled(); } + LapceUICommand::SaveAs(content, path, view_id, exit) => { + data.main_split.save_as(ctx, content, path, *view_id, *exit); + ctx.set_handled(); + } + LapceUICommand::SaveAsSuccess( + content, + rev, + path, + view_id, + exit, + ) => { + data.main_split.save_as_success( + ctx, content, *rev, path, *view_id, *exit, + ); + ctx.set_handled(); + } LapceUICommand::OpenFileChanged { path, content } => { let doc = data.main_split.open_docs.get_mut(path).unwrap(); let doc = Arc::make_mut(doc); @@ -828,7 +844,7 @@ fn handle_event( let doc = data.main_split.open_docs.get_mut(path).unwrap(); if doc.rev() + 1 == *rev { let doc = Arc::make_mut(doc); - doc.reload(content.to_owned()); + doc.reload(content.to_owned(), true); for (_, editor) in data.main_split.editors.iter_mut() { if &editor.content == doc.content()