auto reload file if changed on disk

This commit is contained in:
Dongdong Zhou 2021-05-04 16:33:11 +01:00
parent 34d67fadac
commit 9fe76d17d4
10 changed files with 197 additions and 115 deletions

1
Cargo.lock generated
View File

@ -1645,6 +1645,7 @@ dependencies = [
"jsonrpc-lite",
"lapce-rpc",
"lsp-types",
"notify",
"parking_lot 0.11.1",
"serde",
"serde_json",

View File

@ -78,7 +78,6 @@ pub struct Buffer {
tab_id: WidgetId,
pub id: BufferId,
pub rope: Rope,
highlight_config: Arc<HighlightConfiguration>,
highlight_names: Vec<String>,
pub semantic_tokens: Option<Vec<(usize, usize, String)>>,
pub highlights: Vec<(usize, usize, Highlight)>,
@ -104,7 +103,6 @@ pub fn new(
tab_id: WidgetId,
buffer_id: BufferId,
path: &str,
event_sink: ExtEventSink,
sender: Sender<(WindowId, WidgetId, BufferId, u64)>,
) -> Buffer {
let state = LAPCE_APP_STATE.get_tab_state(&window_id, &tab_id);
@ -117,26 +115,14 @@ pub fn new(
.unwrap();
let rope = Rope::from_str(&content).unwrap();
// let rope = if let Ok(rope) = load_file(&window_id, &tab_id, path) {
// rope
// } else {
// Rope::from("")
// };
let mut parser = new_parser(LapceLanguage::Rust);
let tree = parser.parse(&rope.to_string(), None).unwrap();
let (highlight_config, highlight_names) =
new_highlight_config(LapceLanguage::Rust);
let path_buf = PathBuf::from_str(path).unwrap();
path_buf.extension().unwrap().to_str().unwrap().to_string();
let mut buffer = Buffer {
window_id: window_id.clone(),
tab_id: tab_id.clone(),
id: buffer_id.clone(),
rope,
highlight_config: Arc::new(highlight_config),
semantic_tokens: None,
highlight_names,
highlights: Vec::new(),
@ -155,27 +141,29 @@ pub fn new(
tree: None,
sender,
};
let language_id = buffer.language_id.clone();
// state.plugins.lock().new_buffer(&PluginBufferInfo {
// buffer_id: buffer_id.clone(),
// language_id: buffer.language_id.clone(),
// path: path.to_string(),
// nb_lines: buffer.num_lines(),
// buf_size: buffer.len(),
// rev: buffer.rev,
// });
// state.lsp.lock().new_buffer(
// &buffer_id,
// path,
// &buffer.language_id,
// buffer.rope.to_string(),
// );
buffer.update_highlights();
buffer
}
pub fn reload(&mut self, rev: u64, new_content: &str) {
if self.rev + 1 != rev {
return;
}
self.rope = Rope::from_str(new_content).unwrap();
self.semantic_tokens = None;
self.highlights = Vec::new();
self.line_highlights = HashMap::new();
self.undos = Vec::new();
self.current_undo = 0;
self.code_actions = HashMap::new();
self.rev += 1;
self.dirty = false;
self.diff = Vec::new();
self.line_changes = HashMap::new();
self.tree = None;
self.update_highlights();
}
pub fn len(&self) -> usize {
self.rope.len()
}
@ -836,33 +824,33 @@ pub fn update_line_layouts(
false
}
pub fn get_text_layout(
&mut self,
text: &mut PietText,
theme: &HashMap<String, Color>,
line: usize,
line_content: String,
env: &Env,
) -> HighlightTextLayout {
let mut layout_builder = text
.new_text_layout(line_content.clone())
.font(env.get(LapceTheme::EDITOR_FONT).family, 13.0)
.text_color(env.get(LapceTheme::EDITOR_FOREGROUND));
for (start, end, hl) in self.get_line_highligh(line) {
if let Some(color) = theme.get(hl) {
layout_builder = layout_builder.range_attribute(
start..end,
TextAttribute::TextColor(color.clone()),
);
}
}
let layout = layout_builder.build().unwrap();
HighlightTextLayout {
layout,
text: line_content,
highlights: self.get_line_highligh(line).clone(),
}
}
// pub fn get_text_layout(
// &mut self,
// text: &mut PietText,
// theme: &HashMap<String, Color>,
// line: usize,
// line_content: String,
// env: &Env,
// ) -> HighlightTextLayout {
// let mut layout_builder = text
// .new_text_layout(line_content.clone())
// .font(env.get(LapceTheme::EDITOR_FONT).family, 13.0)
// .text_color(env.get(LapceTheme::EDITOR_FOREGROUND));
// for (start, end, hl) in self.get_line_highligh(line) {
// if let Some(color) = theme.get(hl) {
// layout_builder = layout_builder.range_attribute(
// start..end,
// TextAttribute::TextColor(color.clone()),
// );
// }
// }
// let layout = layout_builder.build().unwrap();
// HighlightTextLayout {
// layout,
// text: line_content,
// highlights: self.get_line_highligh(line).clone(),
// }
// }
pub fn get_document(&self) -> String {
self.rope.to_string()

View File

@ -205,6 +205,7 @@ pub enum LapceUICommand {
UpdateHighlights(BufferId, u64, Vec<(usize, usize, Highlight)>),
CenterOfWindow,
UpdateLineChanges(BufferId),
ReloadBuffer(BufferId, u64, String),
EnsureVisible((Rect, (f64, f64), Option<EnsureVisiblePosition>)),
EditorViewSize(Size),
Scroll((f64, f64)),

View File

@ -908,7 +908,6 @@ pub fn get_buffer_from_path(
self.tab_id.clone(),
buffer_id.clone(),
path,
ctx.get_external_handle(),
ui_state.highlight_sender.clone(),
);
let num_lines = buffer.num_lines();

View File

@ -225,6 +225,11 @@ pub enum Notification {
line_changes: HashMap<usize, char>,
rev: u64,
},
ReloadBuffer {
buffer_id: BufferId,
new_content: String,
rev: u64,
},
PublishDiagnostics {
diagnostics: PublishDiagnosticsParams,
},
@ -295,6 +300,23 @@ fn handle_notification(
self.tab_id,
);
}
Notification::ReloadBuffer {
buffer_id,
new_content,
rev,
} => {
let state =
LAPCE_APP_STATE.get_tab_state(&self.window_id, &self.tab_id);
let mut editor_split = state.editor_split.lock();
let buffer = editor_split.buffers.get_mut(&buffer_id).unwrap();
if buffer.rev + 1 != rev {
return;
}
LAPCE_APP_STATE.submit_ui_command(
LapceUICommand::ReloadBuffer(buffer_id, rev, new_content),
self.tab_id,
);
}
Notification::ListDir { mut items } => {
items.sort();
let state =

View File

@ -150,6 +150,19 @@ fn event(
LapceUICommand::RequestPaint => {
ctx.request_paint();
}
LapceUICommand::ReloadBuffer(
buffer_id,
rev,
new_content,
) => {
let state = LAPCE_APP_STATE
.get_tab_state(&self.window_id, &self.tab_id);
let mut editor_split = state.editor_split.lock();
let buffer =
editor_split.buffers.get_mut(buffer_id).unwrap();
buffer.reload(*rev, new_content);
editor_split.notify_fill_text_layouts(ctx, buffer_id);
}
LapceUICommand::UpdateLineChanges(buffer_id) => {
let state = LAPCE_APP_STATE
.get_tab_state(&self.window_id, &self.tab_id);
@ -501,7 +514,8 @@ fn event(
let tab = LapceTab::new(self.window_id.clone(), tab_id);
self.tabs.insert(index + 1, WidgetPod::new(tab));
*window_state.active.lock() = tab_id;
ctx.request_layout();
ctx.children_changed();
return;
}
LapceUICommand::CloseTab => {
if self.tabs.len() <= 1 {
@ -525,7 +539,8 @@ fn event(
self.tabs.remove(index);
//window_state.states.lock().remove(&active);
*active = new_active;
ctx.request_layout();
ctx.children_changed();
return;
}
LapceUICommand::NextTab => {
let window_state =

View File

@ -5,6 +5,7 @@ authors = ["Dongdong Zhou <dzhou121@gmail.com>"]
edition = "2018"
[dependencies]
notify = "4.0.16"
lapce-rpc = { path = "../rpc" }
xi-core-lib = "0.3.0"
xi-rope = "0.3.0"

View File

@ -24,6 +24,7 @@ pub struct Buffer {
pub rope: Rope,
pub path: PathBuf,
pub rev: u64,
pub dirty: bool,
sender: Sender<(BufferId, u64)>,
pub mod_time: Option<SystemTime>,
}
@ -48,14 +49,16 @@ pub fn new(
language_id,
rev: 0,
sender,
dirty: false,
mod_time,
}
}
pub fn save(&self, rev: u64) -> Result<()> {
pub fn save(&mut self, rev: u64) -> Result<()> {
if self.rev != rev {
return Err(anyhow!("not the right rev"));
}
self.dirty = false;
let tmp_extension = self.path.extension().map_or_else(
|| OsString::from("swp"),
|ext| {
@ -71,9 +74,22 @@ pub fn save(&self, rev: u64) -> Result<()> {
f.write_all(chunk.as_bytes())?;
}
fs::rename(tmp_path, &self.path)?;
self.mod_time = get_mod_time(&self.path);
Ok(())
}
pub fn reload(&mut self) {
let rope = if let Ok(rope) = load_file(&self.path) {
rope
} else {
Rope::from("")
};
self.rope = rope;
self.rev += 1;
self.sender.send((self.id, self.rev));
}
pub fn update(
&mut self,
delta: &RopeDelta,
@ -83,6 +99,7 @@ pub fn update(
return None;
}
self.rev += 1;
self.dirty = true;
let content_change = get_document_content_changes(delta, self);
self.rope = delta.apply(&self.rope);
let content_change = match content_change {
@ -190,7 +207,7 @@ fn get_document_content_changes(
/// Returns the modification timestamp for the file at a given path,
/// if present.
fn get_mod_time<P: AsRef<Path>>(path: P) -> Option<SystemTime> {
pub fn get_mod_time<P: AsRef<Path>>(path: P) -> Option<SystemTime> {
File::open(path)
.and_then(|f| f.metadata())
.and_then(|meta| meta.modified())

View File

@ -1,4 +1,4 @@
use crate::buffer::{Buffer, BufferId};
use crate::buffer::{get_mod_time, Buffer, BufferId};
use crate::core_proxy::CoreProxy;
use crate::lsp::LspCatalog;
use crate::plugin::PluginCatalog;
@ -7,7 +7,8 @@
use git2::{DiffOptions, Repository};
use jsonrpc_lite::{self, JsonRpc};
use lapce_rpc::{self, Call, RequestId, RpcObject};
use lsp_types::Position;
use lsp_types::{Position, TextDocumentContentChangeEvent};
use notify::DebouncedEvent;
use parking_lot::Mutex;
use serde::{Deserialize, Deserializer, Serialize};
use serde_json::json;
@ -18,7 +19,7 @@
use std::{cmp, fs};
use std::{collections::HashMap, io};
use std::{collections::HashSet, io::BufRead};
use xi_core_lib::watcher::{FileWatcher, Notify, WatchToken};
use xi_core_lib::watcher::{EventQueue, FileWatcher, Notify, WatchToken};
use xi_rope::{RopeDelta, RopeInfo};
pub const OPEN_FILE_EVENT_TOKEN: WatchToken = WatchToken(2);
@ -29,6 +30,7 @@ pub struct Dispatcher {
pub git_sender: Sender<(BufferId, u64)>,
pub workspace: Arc<Mutex<PathBuf>>,
pub buffers: Arc<Mutex<HashMap<BufferId, Buffer>>>,
open_files: Arc<Mutex<HashMap<String, BufferId>>>,
plugins: Arc<Mutex<PluginCatalog>>,
pub lsp: Arc<Mutex<LspCatalog>>,
pub watcher: Arc<Mutex<Option<FileWatcher>>>,
@ -42,9 +44,42 @@ fn notify(&self) {
{ dispatcher.watcher.lock().as_mut().unwrap().take_events() }
.drain(..)
{
match token {
OPEN_FILE_EVENT_TOKEN => {
eprintln!("file watcher received event {:?}", event);
match event {
DebouncedEvent::Write(path) | DebouncedEvent::Create(path) => {
if let Some(buffer_id) = {
dispatcher
.open_files
.lock()
.get(&path.to_str().unwrap().to_string())
} {
if let Some(buffer) =
dispatcher.buffers.lock().get_mut(buffer_id)
{
if get_mod_time(&buffer.path) == buffer.mod_time {
return;
}
if !buffer.dirty {
buffer.reload();
dispatcher.lsp.lock().update(
buffer,
&TextDocumentContentChangeEvent {
range: None,
range_length: None,
text: buffer.get_document(),
},
buffer.rev,
);
dispatcher.sender.send(json!({
"method": "reload_buffer",
"params": {
"buffer_id": buffer_id,
"rev": buffer.rev,
"new_content": buffer.get_document(),
},
}));
}
}
}
}
_ => (),
}
@ -161,6 +196,7 @@ pub fn new(sender: Sender<Value>) -> Dispatcher {
git_sender,
workspace: Arc::new(Mutex::new(PathBuf::new())),
buffers: Arc::new(Mutex::new(HashMap::new())),
open_files: Arc::new(Mutex::new(HashMap::new())),
plugins: Arc::new(Mutex::new(plugins)),
lsp: Arc::new(Mutex::new(LspCatalog::new())),
watcher: Arc::new(Mutex::new(None)),
@ -330,6 +366,9 @@ fn handle_notification(&self, rpc: Notification) {
fn handle_request(&self, id: RequestId, rpc: Request) {
match rpc {
Request::NewBuffer { buffer_id, path } => {
self.open_files
.lock()
.insert(path.to_str().unwrap().to_string(), buffer_id);
let buffer = Buffer::new(buffer_id, path, self.git_sender.clone());
let content = buffer.rope.to_string();
self.buffers.lock().insert(buffer_id, buffer);
@ -457,8 +496,8 @@ fn handle_request(&self, id: RequestId, rpc: Request) {
}
Request::Save { rev, buffer_id } => {
eprintln!("receive save");
let buffers = self.buffers.lock();
let buffer = buffers.get(&buffer_id).unwrap();
let mut buffers = self.buffers.lock();
let buffer = buffers.get_mut(&buffer_id).unwrap();
let resp = buffer.save(rev).map(|r| json!({}));
self.lsp.lock().save_buffer(buffer);
self.respond(id, resp);

View File

@ -60,52 +60,51 @@ fn window_removed(
fn build_app(window_id: WindowId) -> impl Widget<LapceUIState> {
let window = LapceWindow::new(window_id);
window
.env_scope(|env: &mut druid::Env, data: &LapceUIState| {
let theme = &LAPCE_APP_STATE.theme;
if let Some(line_highlight) = theme.get("line_highlight") {
env.set(
LapceTheme::EDITOR_CURRENT_LINE_BACKGROUND,
line_highlight.clone(),
);
};
if let Some(caret) = theme.get("caret") {
env.set(LapceTheme::EDITOR_CURSOR_COLOR, caret.clone());
};
if let Some(foreground) = theme.get("foreground") {
env.set(LapceTheme::EDITOR_FOREGROUND, foreground.clone());
};
if let Some(background) = theme.get("background") {
env.set(LapceTheme::EDITOR_BACKGROUND, background.clone());
};
if let Some(selection) = theme.get("selection") {
env.set(LapceTheme::EDITOR_SELECTION_COLOR, selection.clone());
};
if let Some(color) = theme.get("comment") {
env.set(LapceTheme::EDITOR_COMMENT, color.clone());
};
if let Some(color) = theme.get("error") {
env.set(LapceTheme::EDITOR_ERROR, color.clone());
};
if let Some(color) = theme.get("warn") {
env.set(LapceTheme::EDITOR_WARN, color.clone());
};
env.set(LapceTheme::EDITOR_LINE_HEIGHT, 25.0);
env.set(LapceTheme::PALETTE_BACKGROUND, Color::rgb8(125, 125, 125));
env.set(LapceTheme::PALETTE_INPUT_FOREROUND, Color::rgb8(0, 0, 0));
window.env_scope(|env: &mut druid::Env, data: &LapceUIState| {
let theme = &LAPCE_APP_STATE.theme;
if let Some(line_highlight) = theme.get("line_highlight") {
env.set(
LapceTheme::PALETTE_INPUT_BACKGROUND,
Color::rgb8(255, 255, 255),
LapceTheme::EDITOR_CURRENT_LINE_BACKGROUND,
line_highlight.clone(),
);
env.set(LapceTheme::PALETTE_INPUT_BORDER, Color::rgb8(0, 0, 0));
env.set(
LapceTheme::EDITOR_FONT,
FontDescriptor::new(FontFamily::new_unchecked("Cascadia Code"))
.with_size(13.0),
);
env.set(theme::SCROLLBAR_COLOR, hex_to_color("#c4c4c4").unwrap());
})
.debug_invalidation()
};
if let Some(caret) = theme.get("caret") {
env.set(LapceTheme::EDITOR_CURSOR_COLOR, caret.clone());
};
if let Some(foreground) = theme.get("foreground") {
env.set(LapceTheme::EDITOR_FOREGROUND, foreground.clone());
};
if let Some(background) = theme.get("background") {
env.set(LapceTheme::EDITOR_BACKGROUND, background.clone());
};
if let Some(selection) = theme.get("selection") {
env.set(LapceTheme::EDITOR_SELECTION_COLOR, selection.clone());
};
if let Some(color) = theme.get("comment") {
env.set(LapceTheme::EDITOR_COMMENT, color.clone());
};
if let Some(color) = theme.get("error") {
env.set(LapceTheme::EDITOR_ERROR, color.clone());
};
if let Some(color) = theme.get("warn") {
env.set(LapceTheme::EDITOR_WARN, color.clone());
};
env.set(LapceTheme::EDITOR_LINE_HEIGHT, 25.0);
env.set(LapceTheme::PALETTE_BACKGROUND, Color::rgb8(125, 125, 125));
env.set(LapceTheme::PALETTE_INPUT_FOREROUND, Color::rgb8(0, 0, 0));
env.set(
LapceTheme::PALETTE_INPUT_BACKGROUND,
Color::rgb8(255, 255, 255),
);
env.set(LapceTheme::PALETTE_INPUT_BORDER, Color::rgb8(0, 0, 0));
env.set(
LapceTheme::EDITOR_FONT,
FontDescriptor::new(FontFamily::new_unchecked("Cascadia Code"))
.with_size(13.0),
);
env.set(theme::SCROLLBAR_COLOR, hex_to_color("#c4c4c4").unwrap());
})
// .debug_invalidation()
}
pub fn main() {