diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eef6b9e393..39a7cc45b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -166,8 +166,8 @@ jobs: os-version: lunar - os-name: ubuntu os-version: mantic - # - os-name: ubuntu - # os-version: noble + - os-name: ubuntu + os-version: noble steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index e31fa27cad..2d770c0584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ ## Unreleased +### Features/Changes + +- Add Ubuntu 24.04 LTS (noble) build + +### Bug Fixes + +## 0.4.2 + +### Features/Changes +- Implement "Run in terminal" +- Implement document symbols in a panel +- Implement "Go To Location" functionality in the Diff editor. +- Implement on screen find which is similar to `f` in vim but for the whole screen. +- Make file explorer horizontal scrollable +- Implement "Reveal in system file explorer" + +### Bug Fixes +- Fix markdown syntax highlighting +- Fix click issue on window error message + +## 0.4.1 + ### Features/Changes - Add fedora builds - Finish tree sitter dynamic libary support by downloading from https://github.com/lapce/tree-sitter-grammars diff --git a/Cargo.lock b/Cargo.lock index f08869645e..512e479805 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1684,7 +1684,7 @@ checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] name = "floem" version = "0.1.1" -source = "git+https://github.com/lapce/floem?rev=54f0d1bcf0e1a91d82492ee7300a526adb60eb5c#54f0d1bcf0e1a91d82492ee7300a526adb60eb5c" +source = "git+https://github.com/lapce/floem?rev=157631a49d6ba13a3467dcb994eb46a98c52eb76#157631a49d6ba13a3467dcb994eb46a98c52eb76" dependencies = [ "bitflags 2.6.0", "copypasta", @@ -1702,7 +1702,6 @@ dependencies = [ "image", "indexmap", "lapce-xi-rope", - "once_cell", "parking_lot", "peniko", "raw-window-handle 0.6.0", @@ -1722,13 +1721,12 @@ dependencies = [ [[package]] name = "floem-editor-core" version = "0.1.1" -source = "git+https://github.com/lapce/floem?rev=54f0d1bcf0e1a91d82492ee7300a526adb60eb5c#54f0d1bcf0e1a91d82492ee7300a526adb60eb5c" +source = "git+https://github.com/lapce/floem?rev=157631a49d6ba13a3467dcb994eb46a98c52eb76#157631a49d6ba13a3467dcb994eb46a98c52eb76" dependencies = [ "bitflags 2.6.0", "itertools 0.12.1", "lapce-xi-rope", "memchr", - "once_cell", "serde", "strum", "strum_macros", @@ -1800,7 +1798,7 @@ dependencies = [ [[package]] name = "floem_reactive" version = "0.1.1" -source = "git+https://github.com/lapce/floem?rev=54f0d1bcf0e1a91d82492ee7300a526adb60eb5c#54f0d1bcf0e1a91d82492ee7300a526adb60eb5c" +source = "git+https://github.com/lapce/floem?rev=157631a49d6ba13a3467dcb994eb46a98c52eb76#157631a49d6ba13a3467dcb994eb46a98c52eb76" dependencies = [ "smallvec", ] @@ -1808,7 +1806,7 @@ dependencies = [ [[package]] name = "floem_renderer" version = "0.1.1" -source = "git+https://github.com/lapce/floem?rev=54f0d1bcf0e1a91d82492ee7300a526adb60eb5c#54f0d1bcf0e1a91d82492ee7300a526adb60eb5c" +source = "git+https://github.com/lapce/floem?rev=157631a49d6ba13a3467dcb994eb46a98c52eb76#157631a49d6ba13a3467dcb994eb46a98c52eb76" dependencies = [ "cosmic-text", "image", @@ -1821,7 +1819,7 @@ dependencies = [ [[package]] name = "floem_tiny_skia_renderer" version = "0.1.1" -source = "git+https://github.com/lapce/floem?rev=54f0d1bcf0e1a91d82492ee7300a526adb60eb5c#54f0d1bcf0e1a91d82492ee7300a526adb60eb5c" +source = "git+https://github.com/lapce/floem?rev=157631a49d6ba13a3467dcb994eb46a98c52eb76#157631a49d6ba13a3467dcb994eb46a98c52eb76" dependencies = [ "anyhow", "bytemuck", @@ -1838,7 +1836,7 @@ dependencies = [ [[package]] name = "floem_vger_renderer" version = "0.1.1" -source = "git+https://github.com/lapce/floem?rev=54f0d1bcf0e1a91d82492ee7300a526adb60eb5c#54f0d1bcf0e1a91d82492ee7300a526adb60eb5c" +source = "git+https://github.com/lapce/floem?rev=157631a49d6ba13a3467dcb994eb46a98c52eb76#157631a49d6ba13a3467dcb994eb46a98c52eb76" dependencies = [ "anyhow", "floem-vger", @@ -2906,7 +2904,7 @@ dependencies = [ [[package]] name = "lapce" -version = "0.4.0" +version = "0.4.2" dependencies = [ "lapce-app", "lapce-proxy", @@ -2914,7 +2912,7 @@ dependencies = [ [[package]] name = "lapce-app" -version = "0.4.0" +version = "0.4.2" dependencies = [ "Inflector", "alacritty_terminal", @@ -2971,14 +2969,14 @@ dependencies = [ "tracing-subscriber", "unicode-width", "url", - "windows-sys 0.52.0", + "windows-sys 0.36.1", "zip", "zstd", ] [[package]] name = "lapce-core" -version = "0.4.0" +version = "0.4.2" dependencies = [ "ahash", "anyhow", @@ -3006,7 +3004,7 @@ dependencies = [ [[package]] name = "lapce-proxy" -version = "0.4.0" +version = "0.4.2" dependencies = [ "alacritty_terminal", "anyhow", @@ -3055,7 +3053,7 @@ dependencies = [ [[package]] name = "lapce-rpc" -version = "0.4.0" +version = "0.4.2" dependencies = [ "anyhow", "crossbeam-channel", diff --git a/Cargo.toml b/Cargo.toml index 658c97f93d..b142f39dc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ path = "lapce-proxy/src/bin/lapce-proxy.rs" members = ["lapce-app", "lapce-proxy", "lapce-rpc", "lapce-core"] [workspace.package] -version = "0.4.0" +version = "0.4.2" edition = "2021" rust-version = "1.77.0" license = "Apache-2.0" @@ -76,9 +76,9 @@ lapce-core = { path = "./lapce-core" } lapce-rpc = { path = "./lapce-rpc" } lapce-proxy = { path = "./lapce-proxy" } -floem = { git = "https://github.com/lapce/floem", rev = "54f0d1bcf0e1a91d82492ee7300a526adb60eb5c", features = ["editor", "serde", "default-image-formats", "rfd-async-std"] } +floem = { git = "https://github.com/lapce/floem", rev = "157631a49d6ba13a3467dcb994eb46a98c52eb76", features = ["editor", "serde", "default-image-formats", "rfd-async-std"] } # floem = { path = "../floem", features = ["editor", "serde", "default-image-formats", "rfd-async-std"] } -floem-editor-core = { git = "https://github.com/lapce/floem", rev = "54f0d1bcf0e1a91d82492ee7300a526adb60eb5c", features = ["serde"] } +floem-editor-core = { git = "https://github.com/lapce/floem", rev = "157631a49d6ba13a3467dcb994eb46a98c52eb76", features = ["serde"] } # floem-editor-core = { path = "../floem/editor-core/", features = ["serde"] } [patch.crates-io] diff --git a/defaults/dark-theme.toml b/defaults/dark-theme.toml index 478d501a64..8d3c6ce0c4 100644 --- a/defaults/dark-theme.toml +++ b/defaults/dark-theme.toml @@ -68,6 +68,14 @@ dim-text = "#5C6370" "variable.other.member" = "$red" "tag" = "$blue" +"markup.heading" = "$red" +"markup.bold" = "$orange" +"markup.italic" = "$orange" +"markup.list" = "$orange" +"markup.link.url" = "$blue" +"markup.link.label" = "$purple" +"markup.link.text" = "$purple" + "bracket.color.1" = "$blue" "bracket.color.2" = "$yellow" "bracket.color.3" = "$purple" diff --git a/defaults/icon-theme.toml b/defaults/icon-theme.toml index 8bda5b1de8..4a818c9bee 100644 --- a/defaults/icon-theme.toml +++ b/defaults/icon-theme.toml @@ -36,6 +36,7 @@ name = "Lapce Codicons" "keyboard" = "keyboard.svg" "breadcrumb_separator" = "chevron-right.svg" "symbol_color" = "symbol-color.svg" +"type_hierarchy" = "type-hierarchy.svg" "window.close" = "chrome-close.svg" "window.restore" = "chrome-restore.svg" @@ -103,6 +104,7 @@ name = "Lapce Codicons" "search.replace" = "replace.svg" "search.replace_all" = "replace-all.svg" +"document_symbol" = "symbol-class.svg" "symbol_kind.array" = "symbol-array.svg" "symbol_kind.boolean" = "symbol-boolean.svg" "symbol_kind.class" = "symbol-class.svg" diff --git a/defaults/light-theme.toml b/defaults/light-theme.toml index 5cef4861b2..570e6f75b2 100644 --- a/defaults/light-theme.toml +++ b/defaults/light-theme.toml @@ -74,6 +74,14 @@ dim-text = "#A0A1A7" "variable.other.member" = "$red" "tag" = "$blue" +"markup.heading" = "$red" +"markup.bold" = "$orange" +"markup.italic" = "$orange" +"markup.list" = "$orange" +"markup.link.url" = "$blue" +"markup.link.label" = "$purple" +"markup.link.text" = "$purple" + "bracket.color.1" = "$blue" "bracket.color.2" = "$yellow" "bracket.color.3" = "$purple" diff --git a/defaults/settings.toml b/defaults/settings.toml index 99da8916f3..845b6da860 100644 --- a/defaults/settings.toml +++ b/defaults/settings.toml @@ -5,6 +5,7 @@ modal = false color-theme = "Lapce Dark" icon-theme = "Lapce Codicons" custom-titlebar = true +file-exploerer-double-click = false [editor] font-family = "monospace" @@ -21,7 +22,9 @@ wrap-style = "editor-width" wrap-column = 80 wrap-width = 600 # px sticky-header = true +completion-width = 600 completion-show-documentation = true +completion-item-show-detail = false show-signature = true signature-label-code-block = true auto-closing-matching-pairs = true diff --git a/extra/linux/dev.lapce.lapce.metainfo.xml b/extra/linux/dev.lapce.lapce.metainfo.xml index 99d322aff3..2156280bdf 100644 --- a/extra/linux/dev.lapce.lapce.metainfo.xml +++ b/extra/linux/dev.lapce.lapce.metainfo.xml @@ -30,6 +30,6 @@ - + diff --git a/extra/macos/Lapce.app/Contents/Info.plist b/extra/macos/Lapce.app/Contents/Info.plist index 2f6f78e208..45b6321f10 100644 --- a/extra/macos/Lapce.app/Contents/Info.plist +++ b/extra/macos/Lapce.app/Contents/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.0 + 0.4.2 CFBundleSupportedPlatforms MacOSX diff --git a/extra/windows/wix/lapce.wxs b/extra/windows/wix/lapce.wxs index b2d0e4c600..dbc0890515 100644 --- a/extra/windows/wix/lapce.wxs +++ b/extra/windows/wix/lapce.wxs @@ -1,6 +1,6 @@ - + diff --git a/icons/codicons/type-hierarchy.svg b/icons/codicons/type-hierarchy.svg new file mode 100644 index 0000000000..bcbc902e40 --- /dev/null +++ b/icons/codicons/type-hierarchy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lapce-app/src/about.rs b/lapce-app/src/about.rs index 7a3c47b913..a800a99ad3 100644 --- a/lapce-app/src/about.rs +++ b/lapce-app/src/about.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use floem::{ event::EventListener, keyboard::Modifiers, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate}, style::{CursorStyle, Display, Position}, views::{container, label, stack, svg, Decorators}, View, diff --git a/lapce-app/src/alert.rs b/lapce-app/src/alert.rs index c56f32eb8b..8e2b7ac853 100644 --- a/lapce-app/src/alert.rs +++ b/lapce-app/src/alert.rs @@ -6,7 +6,7 @@ use std::{ use floem::{ event::EventListener, - reactive::{ReadSignal, RwSignal, Scope}, + reactive::{ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate}, style::CursorStyle, views::{container, dyn_stack, label, stack, svg, Decorators}, View, diff --git a/lapce-app/src/app.rs b/lapce-app/src/app.rs index 36a04eb11d..22037ddcd9 100644 --- a/lapce-app/src/app.rs +++ b/lapce-app/src/app.rs @@ -23,7 +23,7 @@ use floem::{ }, reactive::{ create_effect, create_memo, create_rw_signal, provide_context, use_context, - ReadSignal, RwSignal, Scope, + ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith, }, style::{ AlignItems, CursorStyle, Display, FlexDirection, JustifyContent, Position, @@ -38,9 +38,7 @@ use floem::{ views::{ clip, container, drag_resize_window_area, drag_window_area, dyn_stack, empty, label, rich_text, - scroll::{ - scroll, HideBar, PropagatePointerWheel, VerticalScrollAsHorizontal, - }, + scroll::{scroll, PropagatePointerWheel, VerticalScrollAsHorizontal}, stack, svg, tab, text, tooltip, virtual_stack, Decorators, VirtualDirection, VirtualItemSize, VirtualVector, }, @@ -256,7 +254,9 @@ impl AppData { match cmd { AppCommand::SaveApp => { let db: Arc = use_context().unwrap(); - let _ = db.save_app(self); + if let Err(err) = db.save_app(self) { + tracing::error!("{:?}", err); + } } AppCommand::WindowClosed(window_id) => { if self.app_terminated.get_untracked() { @@ -264,7 +264,9 @@ impl AppData { } let db: Arc = use_context().unwrap(); if self.windows.with_untracked(|w| w.len()) == 1 { - let _ = db.insert_app(self.clone()); + if let Err(err) = db.insert_app(self.clone()) { + tracing::error!("{:?}", err); + } } let window_data = self .windows @@ -273,7 +275,9 @@ impl AppData { if let Some(window_data) = window_data { window_data.scope.dispose(); } - let _ = db.save_app(self); + if let Err(err) = db.save_app(self) { + tracing::error!("{:?}", err); + } } AppCommand::CloseWindow(window_id) => { floem::close_window(window_id); @@ -359,24 +363,29 @@ impl AppData { } } else if files.is_empty() { // There were no dirs and no files specified, so we'll load the last windows - if let Ok(app_info) = db.get_app() { - for info in app_info.windows { - let config = self - .default_window_config() - .size(info.size) - .position(info.pos); - let config = if cfg!(target_os = "macos") - || self.config.get_untracked().core.custom_titlebar - { - config.show_titlebar(false) - } else { - config - }; - let app_data = self.clone(); - app = app.window( - move |window_id| app_data.app_view(window_id, info), - Some(config), - ); + match db.get_app() { + Ok(app_info) => { + for info in app_info.windows { + let config = self + .default_window_config() + .size(info.size) + .position(info.pos); + let config = if cfg!(target_os = "macos") + || self.config.get_untracked().core.custom_titlebar + { + config.show_titlebar(false) + } else { + config + }; + let app_data = self.clone(); + app = app.window( + move |window_id| app_data.app_view(window_id, info), + Some(config), + ); + } + } + Err(err) => { + tracing::error!("{:?}", err); } } } @@ -1055,9 +1064,9 @@ fn editor_tab_header( .with_untracked(|editor_tab| editor_tab.children[active].1) .get_untracked() }) + .scroll_style(|s| s.hide_bars(true)) .style(|s| { - s.set(HideBar, true) - .set(VerticalScrollAsHorizontal, true) + s.set(VerticalScrollAsHorizontal, true) .absolute() .size_full() }), @@ -2261,10 +2270,11 @@ fn palette_item( .style(move |s| { let config = config.get(); let size = config.ui.icon_size() as f32; - s.min_width(size) - .size(size, size) - .margin_right(5.0) - .color(config.color(LapceColor::LAPCE_ICON_ACTIVE)) + s.min_width(size).size(size, size).margin_right(5.0).color( + config.symbol_color(&kind).unwrap_or_else(|| { + config.color(LapceColor::LAPCE_ICON_ACTIVE) + }), + ) }), focus_text( move || text.clone(), @@ -2795,10 +2805,10 @@ fn window_message_view( }), stack(( text(title.clone()).style(|s| { - s.min_width(0.0).line_height(1.6).font_weight(Weight::BOLD) + s.min_width(0.0).line_height(1.8).font_weight(Weight::BOLD) }), text(message.message.clone()).style(|s| { - s.min_width(0.0).line_height(1.6).margin_top(5.0) + s.min_width(0.0).line_height(1.8).margin_top(5.0) }), )) .style(move |s| { @@ -2818,6 +2828,7 @@ fn window_message_view( ) .style(|s| s.margin_left(6.0)), )) + .on_event_stop(EventListener::PointerDown, |_| {}) .style(move |s| { let config = config.get(); s.width_full() @@ -2846,7 +2857,11 @@ fn window_message_view( .style(|s| s.flex_col().width_full()), ) .style(|s| { - s.absolute().width_full().min_height(0.0).max_height_full() + s.absolute() + .width_full() + .min_height(0.0) + .max_height_full() + .set(PropagatePointerWheel, false) }), ) .style(|s| s.size_full()), @@ -2930,6 +2945,7 @@ fn hover(window_tab_data: Rc) -> impl View { layout_rect.set(rect); }) .on_event_stop(EventListener::PointerMove, |_| {}) + .on_event_stop(EventListener::PointerDown, |_| {}) .style(move |s| { let active = window_tab_data.common.hover.active.get(); if !active { @@ -2996,7 +3012,16 @@ fn completion(window_tab_data: Rc) -> impl View { ) }), focus_text( - move || item.item.label.clone(), + move || { + if config.get().editor.completion_item_show_detail { + item.item + .detail + .clone() + .unwrap_or(item.item.label.clone()) + } else { + item.item.label.clone() + } + }, move || item.indices.clone(), move || config.get().color(LapceColor::EDITOR_FOCUS), ) @@ -3060,7 +3085,7 @@ fn completion(window_tab_data: Rc) -> impl View { let config = config.get(); let origin = window_tab_data.completion_origin(); s.position(Position::Absolute) - .width(400.0) + .width(config.editor.completion_width as i32) .max_height(400.0) .margin_left(origin.x as f32) .margin_top(origin.y as f32) @@ -3617,7 +3642,11 @@ fn window(window_data: WindowData) -> impl View { } pub fn launch() { - logging::panic_hook(); + let cli = Cli::parse(); + + if !cli.wait { + logging::panic_hook(); + } let (reload_handle, _guard) = logging::logging(); trace!(TraceLevel::INFO, "Starting up Lapce.."); @@ -3653,8 +3682,6 @@ pub fn launch() { load_shell_env(); } - let cli = Cli::parse(); - // small hack to unblock terminal if launched from it // launch it as a separate process that waits if !cli.wait { @@ -3702,18 +3729,25 @@ pub fn launch() { // If the cli is not requesting a new window, and we're not developing a plugin, we try to open // in the existing Lapce process if !cli.new { - if let Ok(socket) = get_socket() { - if let Err(e) = try_open_in_existing_process(socket, &cli.paths) { - trace!(TraceLevel::ERROR, "failed to open path(s): {e}"); - }; - return; + match get_socket() { + Ok(socket) => { + if let Err(e) = try_open_in_existing_process(socket, &cli.paths) { + trace!(TraceLevel::ERROR, "failed to open path(s): {e}"); + }; + return; + } + Err(err) => { + tracing::error!("{:?}", err); + } } } #[cfg(feature = "updater")] crate::update::cleanup(); - let _ = lapce_proxy::register_lapce_path(); + if let Err(err) = lapce_proxy::register_lapce_path() { + tracing::error!("{:?}", err); + } let db = match LapceDb::new() { Ok(db) => Arc::new(db), Err(e) => { @@ -3736,16 +3770,24 @@ pub fn launch() { let (tx, rx) = crossbeam_channel::bounded(1); let mut watcher = notify::recommended_watcher(ConfigWatcher::new(tx)).unwrap(); if let Some(path) = LapceConfig::settings_file() { - let _ = watcher.watch(&path, notify::RecursiveMode::Recursive); + if let Err(err) = watcher.watch(&path, notify::RecursiveMode::Recursive) { + tracing::error!("{:?}", err); + } } if let Some(path) = Directory::themes_directory() { - let _ = watcher.watch(&path, notify::RecursiveMode::Recursive); + if let Err(err) = watcher.watch(&path, notify::RecursiveMode::Recursive) { + tracing::error!("{:?}", err); + } } if let Some(path) = LapceConfig::keymaps_file() { - let _ = watcher.watch(&path, notify::RecursiveMode::Recursive); + if let Err(err) = watcher.watch(&path, notify::RecursiveMode::Recursive) { + tracing::error!("{:?}", err); + } } if let Some(path) = Directory::plugins_directory() { - let _ = watcher.watch(&path, notify::RecursiveMode::Recursive); + if let Err(err) = watcher.watch(&path, notify::RecursiveMode::Recursive) { + tracing::error!("{:?}", err); + } } let windows = scope.create_rw_signal(im::HashMap::new()); @@ -3848,7 +3890,9 @@ pub fn launch() { }); std::thread::spawn(move || loop { if let Ok(release) = crate::update::get_latest_release() { - let _ = tx.send(release); + if let Err(err) = tx.send(release) { + tracing::error!("{:?}", err); + } } std::thread::sleep(std::time::Duration::from_secs(60 * 60)); }); @@ -3866,7 +3910,9 @@ pub fn launch() { } }); std::thread::spawn(move || { - let _ = listen_local_socket(tx); + if let Err(err) = listen_local_socket(tx) { + tracing::error!("{:?}", err); + } }); } @@ -3880,7 +3926,9 @@ pub fn launch() { app.on_event(move |event| match event { floem::AppEvent::WillTerminate => { app_data.app_terminated.set(true); - let _ = db.insert_app(app_data.clone()); + if let Err(err) = db.insert_app(app_data.clone()) { + tracing::error!("{:?}", err); + } } floem::AppEvent::Reopen { has_visible_windows, @@ -3894,7 +3942,7 @@ pub fn launch() { } /// Uses a login shell to load the correct shell environment for the current user. -fn load_shell_env() { +pub fn load_shell_env() { use std::process::Command; use tracing::warn; @@ -3921,7 +3969,10 @@ fn load_shell_env() { command.args(["--login", "-c", "printenv"]); #[cfg(windows)] - command.args(["{ ls env: | foreach { '{0}={1}' -f $_.Name, $_.Value } }"]); + command.args(&[ + "-Command", + "Get-ChildItem env: | ForEach-Object { \"{0}={1}\" -f $_.Name, $_.Value }", + ]); let env = match command.output() { Ok(output) => String::from_utf8(output.stdout).unwrap_or_default(), @@ -3937,7 +3988,8 @@ fn load_shell_env() { env.split('\n') .filter_map(|line| line.split_once('=')) - .for_each(|(key, value)| { + .for_each(|(key, value)| unsafe { + let value = value.trim_matches('\r'); if let Ok(v) = std::env::var(key) { if v != value { warn!("Overwriting '{key}', previous value: '{v}', new value '{value}'"); @@ -3986,7 +4038,11 @@ pub fn try_open_in_existing_process( fn listen_local_socket(tx: Sender) -> Result<()> { let local_socket = Directory::local_socket() .ok_or_else(|| anyhow!("can't get local socket folder"))?; - let _ = std::fs::remove_file(&local_socket); + if local_socket.exists() { + if let Err(err) = std::fs::remove_file(&local_socket) { + tracing::error!("{:?}", err); + } + } let socket = interprocess::local_socket::LocalSocketListener::bind(local_socket)?; @@ -4005,8 +4061,12 @@ fn listen_local_socket(tx: Sender) -> Result<()> { } let stream_ref = reader.get_mut(); - let _ = stream_ref.write_all(b"received"); - let _ = stream_ref.flush(); + if let Err(err) = stream_ref.write_all(b"received") { + tracing::error!("{:?}", err); + } + if let Err(err) = stream_ref.flush() { + tracing::error!("{:?}", err); + } } }); } diff --git a/lapce-app/src/app/logging.rs b/lapce-app/src/app/logging.rs index 4a033ac218..1c083c1373 100644 --- a/lapce-app/src/app/logging.rs +++ b/lapce-app/src/app/logging.rs @@ -50,6 +50,7 @@ pub(super) fn logging() -> (Handle, Option) { fmt::Subscriber::default() .with_line_number(true) .with_target(true) + .with_thread_names(true) .with_filter(console_filter_targets), ) .init(); diff --git a/lapce-app/src/code_action.rs b/lapce-app/src/code_action.rs index 0db20e3d17..f3b93903ff 100644 --- a/lapce-app/src/code_action.rs +++ b/lapce-app/src/code_action.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use floem::{ keyboard::Modifiers, peniko::kurbo::Rect, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate}, }; use lapce_core::{command::FocusCommand, mode::Mode, movement::Movement}; use lapce_rpc::plugin::PluginId; diff --git a/lapce-app/src/code_lens.rs b/lapce-app/src/code_lens.rs index 3dddfd164f..94d4d2b483 100644 --- a/lapce-app/src/code_lens.rs +++ b/lapce-app/src/code_lens.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use lapce_rpc::dap_types::RunDebugConfig; +use lapce_rpc::dap_types::{ConfigSource, RunDebugConfig, RunDebugProgram}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -39,12 +39,12 @@ impl CodeLensData { pub fn run(&self, command: &str, args: Vec) { match command { "rust-analyzer.runSingle" | "rust-analyzer.debugSingle" => { - if let Some(config) = self.get_rust_command_config(&args) { - let mode = if command == "rust-analyzer.runSingle" { - RunDebugMode::Run - } else { - RunDebugMode::Debug - }; + let mode = if command == "rust-analyzer.runSingle" { + RunDebugMode::Run + } else { + RunDebugMode::Debug + }; + if let Some(config) = self.get_rust_command_config(&args, mode) { self.common .internal_command .send(InternalCommand::RunAndDebug { mode, config }); @@ -56,7 +56,11 @@ impl CodeLensData { } } - fn get_rust_command_config(&self, args: &[Value]) -> Option { + fn get_rust_command_config( + &self, + args: &[Value], + mode: RunDebugMode, + ) -> Option { if let Some(args) = args.first() { let Ok(mut cargo_args) = serde_json::from_value::(args.clone()) @@ -68,7 +72,36 @@ impl CodeLensData { .args .cargo_args .extend(cargo_args.args.cargo_extra_args); - if !cargo_args.args.executable_args.is_empty() { + + let mut prelaunch = None; + let mut program = cargo_args.kind; + let mut tracing_output = false; + let mut ty = None; + if mode == RunDebugMode::Debug + && cargo_args + .args + .cargo_args + .first() + .map(|x| x == "run") + .unwrap_or_default() + && &program == "cargo" + { + ty = Some("lldb".to_owned()); + cargo_args.args.cargo_args[0] = "build".to_owned(); + let mut args = Vec::with_capacity(cargo_args.args.cargo_args.len()); + std::mem::swap(&mut args, &mut cargo_args.args.cargo_args); + args.push("--message-format=json".to_owned()); + prelaunch = Some(RunDebugProgram { + program: "cargo".to_string(), + args: Some(args), + }); + cargo_args + .args + .cargo_args + .extend(cargo_args.args.executable_args); + program = "____".to_owned(); + tracing_output = true; + } else if !cargo_args.args.executable_args.is_empty() { cargo_args.args.cargo_args.push("--".to_string()); cargo_args .args @@ -76,15 +109,17 @@ impl CodeLensData { .extend(cargo_args.args.executable_args); } Some(RunDebugConfig { - ty: None, + ty, name: cargo_args.label, - program: cargo_args.kind, + program, args: Some(cargo_args.args.cargo_args), cwd: None, env: None, - prelaunch: None, + prelaunch, debug_command: None, dap_id: Default::default(), + tracing_output, + config_source: ConfigSource::RustCodeLens, }) } else { tracing::error!("no args"); diff --git a/lapce-app/src/command.rs b/lapce-app/src/command.rs index 1ea715861c..20352612e9 100644 --- a/lapce-app/src/command.rs +++ b/lapce-app/src/command.rs @@ -187,9 +187,35 @@ pub enum LapceWorkbenchCommand { #[strum(message = "Show Call Hierarchy")] ShowCallHierarchy, - #[strum(serialize = "reveal_in_file_tree")] - #[strum(message = "Reveal in File Tree")] - RevealInFileTree, + #[strum(serialize = "find_references")] + #[strum(message = "Find References")] + FindReferences, + + #[strum(serialize = "go_to_implementation")] + #[strum(message = "Go to Implementation")] + GoToImplementation, + + #[strum(serialize = "reveal_in_panel")] + #[strum(message = "Reveal in Panel")] + RevealInPanel, + + #[strum(serialize = "source_control_open_active_file_remote_url")] + #[strum(message = "Source Control: Open Remote File Url")] + SourceControlOpenActiveFileRemoteUrl, + + #[cfg(not(target_os = "macos"))] + #[strum(serialize = "reveal_in_file_explorer")] + #[strum(message = "Reveal in System File Explorer")] + RevealInFileExplorer, + + #[cfg(target_os = "macos")] + #[strum(serialize = "reveal_in_file_explorer")] + #[strum(message = "Reveal in Finder")] + RevealInFileExplorer, + + #[strum(serialize = "run_in_terminal")] + #[strum(message = "Run in Terminal")] + RunInTerminal, #[strum(serialize = "reveal_active_file_in_file_explorer")] #[strum(message = "Reveal Active File in File Explorer")] @@ -372,6 +398,10 @@ pub enum LapceWorkbenchCommand { #[strum(serialize = "palette.palette_help")] PaletteHelp, + #[strum(message = "List Palette Types and Files")] + #[strum(serialize = "palette.palette_help_and_file")] + PaletteHelpAndFile, + #[strum(message = "Run and Debug Restart Current Running")] #[strum(serialize = "palette.run_and_debug_restart")] RunAndDebugRestart, @@ -562,6 +592,14 @@ pub enum LapceWorkbenchCommand { #[strum(serialize = "quit")] #[strum(message = "Quit Editor")] Quit, + + #[strum(serialize = "go_to_location")] + #[strum(message = "Go to Location")] + GoToLocation, + + #[strum(serialize = "add_run_debug_config")] + #[strum(message = "Add Run Debug Config")] + AddRunDebugConfig, } #[derive(Clone, Debug)] @@ -570,6 +608,9 @@ pub enum InternalCommand { OpenFile { path: PathBuf, }, + OpenAndConfirmedFile { + path: PathBuf, + }, OpenFileInNewTab { path: PathBuf, }, diff --git a/lapce-app/src/completion.rs b/lapce-app/src/completion.rs index b71cfe1dae..201612651e 100644 --- a/lapce-app/src/completion.rs +++ b/lapce-app/src/completion.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, path::PathBuf, str::FromStr, sync::Arc}; use floem::{ peniko::kurbo::Rect, - reactive::{ReadSignal, RwSignal, Scope}, + reactive::{ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::editor::{id::EditorId, text::Document}, }; use lapce_core::{ diff --git a/lapce-app/src/config.rs b/lapce-app/src/config.rs index 4258f636a0..1611db2b78 100644 --- a/lapce-app/src/config.rs +++ b/lapce-app/src/config.rs @@ -566,10 +566,13 @@ impl LapceConfig { let path = Directory::config_directory()?.join("settings.toml"); if !path.exists() { - let _ = std::fs::OpenOptions::new() + if let Err(err) = std::fs::OpenOptions::new() .create_new(true) .write(true) - .open(&path); + .open(&path) + { + tracing::error!("{:?}", err); + } } Some(path) @@ -579,10 +582,13 @@ impl LapceConfig { let path = Directory::config_directory()?.join("keymaps.toml"); if !path.exists() { - let _ = std::fs::OpenOptions::new() + if let Err(err) = std::fs::OpenOptions::new() .create_new(true) .write(true) - .open(&path); + .open(&path) + { + tracing::error!("{:?}", err); + } } Some(path) @@ -655,6 +661,36 @@ impl LapceConfig { Some(self.ui_svg(kind_str)) } + pub fn symbol_color(&self, kind: &SymbolKind) -> Option { + let theme_str = match *kind { + SymbolKind::METHOD => "method", + SymbolKind::FUNCTION => "method", + SymbolKind::ENUM => "enum", + SymbolKind::ENUM_MEMBER => "enum-member", + SymbolKind::CLASS => "class", + SymbolKind::VARIABLE => "field", + SymbolKind::STRUCT => "structure", + SymbolKind::CONSTANT => "constant", + SymbolKind::PROPERTY => "property", + SymbolKind::FIELD => "field", + SymbolKind::INTERFACE => "interface", + SymbolKind::ARRAY => "", + SymbolKind::BOOLEAN => "", + SymbolKind::EVENT => "", + SymbolKind::FILE => "", + SymbolKind::KEY => "", + SymbolKind::OBJECT => "", + SymbolKind::NAMESPACE => "", + SymbolKind::NUMBER => "number", + SymbolKind::OPERATOR => "", + SymbolKind::TYPE_PARAMETER => "", + SymbolKind::STRING => "string", + _ => return None, + }; + + self.style_color(theme_str) + } + pub fn logo_svg(&self) -> String { self.svg_store.read().logo_svg() } diff --git a/lapce-app/src/config/core.rs b/lapce-app/src/config/core.rs index fce5ddfb8f..27d128d226 100644 --- a/lapce-app/src/config/core.rs +++ b/lapce-app/src/config/core.rs @@ -14,4 +14,8 @@ pub struct CoreConfig { desc = "Enable customised titlebar and disable OS native one (Linux, BSD, Windows)" )] pub custom_titlebar: bool, + #[field_names( + desc = "Only allow double-click to open files in the file explorer" + )] + pub file_exploerer_double_click: bool, } diff --git a/lapce-app/src/config/editor.rs b/lapce-app/src/config/editor.rs index 68ac74438b..506edac4f4 100644 --- a/lapce-app/src/config/editor.rs +++ b/lapce-app/src/config/editor.rs @@ -96,10 +96,16 @@ pub struct EditorConfig { desc = "Show code context like functions and classes at the top of editor when scroll" )] pub sticky_header: bool, + #[field_names(desc = "The number of pixels to show completion")] + pub completion_width: usize, #[field_names( desc = "If the editor should show the documentation of the current completion item" )] pub completion_show_documentation: bool, + #[field_names( + desc = "Should the completion item use the `detail` field to replace the label `field`?" + )] + pub completion_item_show_detail: bool, #[field_names( desc = "If the editor should show the signature of the function as the parameters are being typed" )] diff --git a/lapce-app/src/config/icon.rs b/lapce-app/src/config/icon.rs index a53812e88b..bce4e47bdf 100644 --- a/lapce-app/src/config/icon.rs +++ b/lapce-app/src/config/icon.rs @@ -38,6 +38,7 @@ impl LapceIcons { pub const KEYBOARD: &'static str = "keyboard"; pub const BREADCRUMB_SEPARATOR: &'static str = "breadcrumb_separator"; pub const SYMBOL_COLOR: &'static str = "symbol_color"; + pub const TYPE_HIERARCHY: &'static str = "type_hierarchy"; pub const FILE: &'static str = "file"; pub const FILE_EXPLORER: &'static str = "file_explorer"; @@ -108,6 +109,12 @@ impl LapceIcons { pub const FILE_TYPE_SYMLINK_FILE: &'static str = "file-symlink-file"; pub const FILE_TYPE_SYMLINK_DIRECTORY: &'static str = "file-symlink-directory"; + pub const DOCUMENT_SYMBOL: &'static str = "document_symbol"; + + pub const REFERENCES: &'static str = "document_symbol"; + + pub const IMPLEMENTATION: &'static str = "document_symbol"; + pub const SYMBOL_KIND_ARRAY: &'static str = "symbol_kind.array"; pub const SYMBOL_KIND_BOOLEAN: &'static str = "symbol_kind.boolean"; pub const SYMBOL_KIND_CLASS: &'static str = "symbol_kind.class"; diff --git a/lapce-app/src/config/watcher.rs b/lapce-app/src/config/watcher.rs index c5ea690322..7f65fd65f6 100644 --- a/lapce-app/src/config/watcher.rs +++ b/lapce-app/src/config/watcher.rs @@ -9,8 +9,8 @@ pub struct ConfigWatcher { impl notify::EventHandler for ConfigWatcher { fn handle_event(&mut self, event: notify::Result) { - if let Ok(event) = event { - match event.kind { + match event { + Ok(event) => match event.kind { notify::EventKind::Create(_) | notify::EventKind::Modify(_) | notify::EventKind::Remove(_) => { @@ -30,13 +30,18 @@ impl notify::EventHandler for ConfigWatcher { std::thread::sleep(std::time::Duration::from_millis( 500, )); - let _ = tx.send(()); + if let Err(err) = tx.send(()) { + tracing::error!("{:?}", err); + } config_mutex .store(false, std::sync::atomic::Ordering::Relaxed); }); } } _ => {} + }, + Err(err) => { + tracing::error!("{:?}", err); } } } diff --git a/lapce-app/src/db.rs b/lapce-app/src/db.rs index 1ed885931f..74531187f5 100644 --- a/lapce-app/src/db.rs +++ b/lapce-app/src/db.rs @@ -6,7 +6,7 @@ use std::{ use anyhow::{anyhow, Result}; use crossbeam_channel::{unbounded, Sender}; -use floem::peniko::kurbo::Vec2; +use floem::{peniko::kurbo::Vec2, reactive::SignalGet}; use lapce_core::directory::Directory; use lapce_rpc::plugin::VoltID; use sha2::{Digest, Sha256}; @@ -14,7 +14,7 @@ use sha2::{Digest, Sha256}; use crate::{ app::{AppData, AppInfo}, doc::DocInfo, - panel::{data::PanelOrder, kind::PanelKind, position::PanelPosition}, + panel::{data::PanelOrder, kind::PanelKind}, window::{WindowData, WindowInfo}, window_tab::WindowTabData, workspace::{LapceWorkspace, WorkspaceInfo}, @@ -51,7 +51,9 @@ impl LapceDb { .ok_or_else(|| anyhow!("can't get config directory"))? .join("db"); let workspace_folder = folder.join("workspaces"); - let _ = std::fs::create_dir_all(&workspace_folder); + if let Err(err) = std::fs::create_dir_all(&workspace_folder) { + tracing::error!("{:?}", err); + } let (save_tx, save_rx) = unbounded(); @@ -66,26 +68,44 @@ impl LapceDb { let event = save_rx.recv()?; match event { SaveEvent::App(info) => { - let _ = local_db.insert_app_info(info); + if let Err(err) = local_db.insert_app_info(info) { + tracing::error!("{:?}", err); + } } SaveEvent::Workspace(workspace, info) => { - let _ = local_db.insert_workspace(&workspace, &info); + if let Err(err) = + local_db.insert_workspace(&workspace, &info) + { + tracing::error!("{:?}", err); + } } SaveEvent::RecentWorkspace(workspace) => { - let _ = local_db.insert_recent_workspace(workspace); + if let Err(err) = local_db.insert_recent_workspace(workspace) + { + tracing::error!("{:?}", err); + } } SaveEvent::Doc(info) => { - let _ = local_db.insert_doc(&info); + if let Err(err) = local_db.insert_doc(&info) { + tracing::error!("{:?}", err); + } } SaveEvent::DisabledVolts(volts) => { - let _ = local_db.insert_disabled_volts(volts); + if let Err(err) = local_db.insert_disabled_volts(volts) { + tracing::error!("{:?}", err); + } } SaveEvent::WorkspaceDisabledVolts(workspace, volts) => { - let _ = local_db - .insert_workspace_disabled_volts(workspace, volts); + if let Err(err) = local_db + .insert_workspace_disabled_volts(workspace, volts) + { + tracing::error!("{:?}", err); + } } SaveEvent::PanelOrder(order) => { - let _ = local_db.insert_panel_orders(&order); + if let Err(err) = local_db.insert_panel_orders(&order) { + tracing::error!("{:?}", err); + } } } } @@ -100,7 +120,9 @@ impl LapceDb { } pub fn save_disabled_volts(&self, volts: Vec) { - let _ = self.save_tx.send(SaveEvent::DisabledVolts(volts)); + if let Err(err) = self.save_tx.send(SaveEvent::DisabledVolts(volts)) { + tracing::error!("{:?}", err); + } } pub fn save_workspace_disabled_volts( @@ -108,9 +130,12 @@ impl LapceDb { workspace: Arc, volts: Vec, ) { - let _ = self + if let Err(err) = self .save_tx - .send(SaveEvent::WorkspaceDisabledVolts(workspace, volts)); + .send(SaveEvent::WorkspaceDisabledVolts(workspace, volts)) + { + tracing::error!("{:?}", err); + } } pub fn insert_disabled_volts(&self, volts: Vec) -> Result<()> { @@ -127,7 +152,9 @@ impl LapceDb { let folder = self .workspace_folder .join(workspace_folder_name(&workspace)); - let _ = std::fs::create_dir_all(&folder); + if let Err(err) = std::fs::create_dir_all(&folder) { + tracing::error!("{:?}", err); + } let volts = serde_json::to_string_pretty(&volts)?; std::fs::write(folder.join(DISABLED_VOLTS), volts)?; @@ -219,7 +246,9 @@ impl LapceDb { info: &WorkspaceInfo, ) -> Result<()> { let folder = self.workspace_folder.join(workspace_folder_name(workspace)); - let _ = std::fs::create_dir_all(&folder); + if let Err(err) = std::fs::create_dir_all(&folder) { + tracing::error!("{:?}", err); + } let workspace_info = serde_json::to_string_pretty(info)?; std::fs::write(folder.join(WORKSPACE_INFO), workspace_info)?; Ok(()) @@ -228,7 +257,9 @@ impl LapceDb { pub fn save_app(&self, data: &AppData) -> Result<()> { let windows = data.windows.get_untracked(); for (_, window) in &windows { - let _ = self.save_window(window.clone()); + if let Err(err) = self.save_window(window.clone()) { + tracing::error!("{:?}", err); + } } let info = AppInfo { @@ -259,7 +290,9 @@ impl LapceDb { return Ok(()); } for (_, window) in &windows { - let _ = self.insert_window(window.clone()); + if let Err(err) = self.insert_window(window.clone()) { + tracing::error!("{:?}", err); + } } let info = AppInfo { windows: windows @@ -299,14 +332,18 @@ impl LapceDb { pub fn save_window(&self, data: WindowData) -> Result<()> { for (_, window_tab) in data.window_tabs.get_untracked().into_iter() { - let _ = self.save_window_tab(window_tab); + if let Err(err) = self.save_window_tab(window_tab) { + tracing::error!("{:?}", err); + } } Ok(()) } pub fn insert_window(&self, data: WindowData) -> Result<()> { for (_, window_tab) in data.window_tabs.get_untracked().into_iter() { - let _ = self.insert_window_tab(window_tab); + if let Err(err) = self.insert_window_tab(window_tab) { + tracing::error!("{:?}", err); + } } let info = data.info(); let info = serde_json::to_string_pretty(&info)?; @@ -331,7 +368,8 @@ impl LapceDb { use strum::IntoEnumIterator; for kind in PanelKind::iter() { if kind.position(&panel_orders).is_none() { - let panels = panel_orders.entry(PanelPosition::LeftTop).or_default(); + let panels = + panel_orders.entry(kind.default_position()).or_default(); panels.push_back(kind); } } @@ -340,7 +378,9 @@ impl LapceDb { } pub fn save_panel_orders(&self, order: PanelOrder) { - let _ = self.save_tx.send(SaveEvent::PanelOrder(order)); + if let Err(err) = self.save_tx.send(SaveEvent::PanelOrder(order)) { + tracing::error!("{:?}", err); + } } fn insert_panel_orders(&self, order: &PanelOrder) -> Result<()> { @@ -362,7 +402,9 @@ impl LapceDb { scroll_offset: (scroll_offset.x, scroll_offset.y), cursor_offset, }; - let _ = self.save_tx.send(SaveEvent::Doc(info)); + if let Err(err) = self.save_tx.send(SaveEvent::Doc(info)) { + tracing::error!("{:?}", err); + } } fn insert_doc(&self, info: &DocInfo) -> Result<()> { @@ -370,7 +412,9 @@ impl LapceDb { .workspace_folder .join(workspace_folder_name(&info.workspace)) .join(WORKSPACE_FILES); - let _ = std::fs::create_dir_all(&folder); + if let Err(err) = std::fs::create_dir_all(&folder) { + tracing::error!("{:?}", err); + } let contents = serde_json::to_string_pretty(info)?; std::fs::write(folder.join(doc_path_name(&info.path)), contents)?; Ok(()) diff --git a/lapce-app/src/debug.rs b/lapce-app/src/debug.rs index 71aebe827b..a213af5b79 100644 --- a/lapce-app/src/debug.rs +++ b/lapce-app/src/debug.rs @@ -8,7 +8,7 @@ use std::{ use floem::{ ext_event::create_ext_action, - reactive::{Memo, RwSignal, Scope}, + reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::VirtualVector, }; use lapce_rpc::{ diff --git a/lapce-app/src/doc.rs b/lapce-app/src/doc.rs index cd64958b1c..3369db3d3a 100644 --- a/lapce-app/src/doc.rs +++ b/lapce-app/src/doc.rs @@ -17,7 +17,9 @@ use floem::{ ext_event::create_ext_action, keyboard::Modifiers, peniko::Color, - reactive::{batch, ReadSignal, RwSignal, Scope}, + reactive::{ + batch, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith, + }, text::{Attrs, AttrsList, FamilyOwned, TextLayout}, views::editor::{ actions::CommonAction, @@ -64,8 +66,8 @@ use lapce_xi_rope::{ Interval, Rope, RopeDelta, Transformer, }; use lsp_types::{ - CodeActionOrCommand, CodeLens, Diagnostic, DiagnosticSeverity, InlayHint, - InlayHintLabel, + CodeActionOrCommand, CodeLens, Diagnostic, DiagnosticSeverity, + DocumentSymbolResponse, InlayHint, InlayHintLabel, }; use serde::{Deserialize, Serialize}; use smallvec::SmallVec; @@ -78,7 +80,10 @@ use crate::{ history::DocumentHistory, keypress::KeyPressFocus, main_split::Editors, - panel::kind::PanelKind, + panel::{ + document_symbol::{SymbolData, SymbolInformationItemData}, + kind::PanelKind, + }, window_tab::{CommonData, Focus}, workspace::LapceWorkspace, }; @@ -205,6 +210,8 @@ pub struct Doc { editors: Editors, pub common: Rc, + + pub document_symbol_data: RwSignal>, } impl Doc { pub fn new( @@ -249,6 +256,7 @@ impl Doc { editors, common, code_lens: cx.create_rw_signal(im::HashMap::new()), + document_symbol_data: cx.create_rw_signal(None), } } @@ -298,6 +306,7 @@ impl Doc { editors, common, code_lens: cx.create_rw_signal(im::HashMap::new()), + document_symbol_data: cx.create_rw_signal(None), } } @@ -347,6 +356,7 @@ impl Doc { editors, common, code_lens: cx.create_rw_signal(im::HashMap::new()), + document_symbol_data: cx.create_rw_signal(None), } } @@ -640,6 +650,7 @@ impl Doc { self.clear_code_actions(); self.clear_style_cache(); self.get_code_lens(); + self.get_document_symbol(); }); } @@ -880,6 +891,9 @@ impl Doc { }; doc.code_lens.update(|code_lens| { for codelens in codelens { + if codelens.command.is_none() { + continue; + } let entry = code_lens .entry(codelens.range.start.line as usize) .or_insert_with(|| { @@ -904,6 +918,46 @@ impl Doc { } } + pub fn get_document_symbol(&self) { + let cx = self.scope; + let doc = self.clone(); + let rev = self.rev(); + if let DocContent::File { path, .. } = doc.content.get_untracked() { + let send = create_ext_action(cx, { + let path = path.clone(); + move |result| { + if rev != doc.rev() { + return; + } + if let Ok(ProxyResponse::GetDocumentSymbols { resp }) = result { + let items: Vec> = + match resp { + DocumentSymbolResponse::Flat(_symbols) => { + Vec::with_capacity(0) + } + DocumentSymbolResponse::Nested(symbols) => symbols + .into_iter() + .map(|x| { + cx.create_rw_signal( + SymbolInformationItemData::from((x, cx)), + ) + }) + .collect(), + }; + let symbol_new = Some(SymbolData { items, path }); + doc.document_symbol_data.update(|symbol| { + *symbol = symbol_new; + }); + } + } + }); + + self.common.proxy.get_document_symbols(path, move |result| { + send(result); + }); + } + } + /// Request inlay hints for the buffer from the LSP through the proxy. fn get_inlay_hints(&self) { if !self.loaded() { diff --git a/lapce-app/src/editor.rs b/lapce-app/src/editor.rs index bf7655523e..a9307900ca 100644 --- a/lapce-app/src/editor.rs +++ b/lapce-app/src/editor.rs @@ -13,7 +13,10 @@ use floem::{ kurbo::{Point, Rect, Vec2}, menu::{Menu, MenuItem}, pointer::{PointerButton, PointerInputEvent, PointerMoveEvent}, - reactive::{batch, use_context, ReadSignal, RwSignal, Scope}, + reactive::{ + batch, use_context, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, + SignalWith, + }, views::editor::{ command::CommandExecuted, id::EditorId, @@ -51,6 +54,7 @@ use lsp_types::{ HoverContents, InlayHint, InlayHintLabel, InlineCompletionTriggerKind, Location, MarkedString, MarkupKind, Range, TextEdit, }; +use nucleo::Utf32Str; use serde::{Deserialize, Serialize}; use self::{ @@ -72,7 +76,11 @@ use crate::{ markdown::{ from_marked_string, from_plaintext, parse_markdown, MarkdownContent, }, - panel::{call_hierarchy_view::CallHierarchyItemData, kind::PanelKind}, + panel::{ + call_hierarchy_view::CallHierarchyItemData, + implementation_view::init_implementation_root, kind::PanelKind, + references_view::init_references_root, + }, snippet::Snippet, tracing::*, window_tab::{CommonData, Focus, WindowTabData}, @@ -187,6 +195,13 @@ impl EditorViewKind { } } +#[derive(Clone)] +pub struct OnScreenFind { + pub active: bool, + pub pattern: String, + pub regions: Vec, +} + pub type SnippetIndex = Vec<(usize, (usize, usize))>; /// Shares data between cloned instances as long as the signals aren't swapped out. @@ -198,6 +213,7 @@ pub struct EditorData { pub confirmed: RwSignal, pub snippet: RwSignal>, pub inline_find: RwSignal>, + pub on_screen_find: RwSignal, pub last_inline_find: RwSignal>, pub find_focus: RwSignal, pub editor: Rc, @@ -231,6 +247,11 @@ impl EditorData { confirmed, snippet: cx.create_rw_signal(None), inline_find: cx.create_rw_signal(None), + on_screen_find: cx.create_rw_signal(OnScreenFind { + active: false, + pattern: "".to_string(), + regions: Vec::new(), + }), last_inline_find: cx.create_rw_signal(None), find_focus: cx.create_rw_signal(false), editor: Rc::new(editor), @@ -435,6 +456,7 @@ impl EditorData { // Cancel so that there's no flickering self.cancel_inline_completion(); self.update_inline_completion(InlineCompletionTriggerKind::Automatic); + self.quit_on_screen_find(); } else if show_inline_completion(cmd) { self.update_inline_completion(InlineCompletionTriggerKind::Automatic); } else { @@ -444,6 +466,7 @@ impl EditorData { self.apply_deltas(&deltas); if let EditCommand::NormalMode = cmd { self.snippet.set(None); + self.quit_on_screen_find(); } CommandExecuted::Yes @@ -750,7 +773,9 @@ impl EditorData { self.cancel_completion(); } FocusCommand::SplitVertical => { - if let Some(editor_tab_id) = self.editor_tab_id.get_untracked() { + if let Some(editor_tab_id) = + self.editor_tab_id.read_only().get_untracked() + { self.common.internal_command.send(InternalCommand::Split { direction: SplitDirection::Vertical, editor_tab_id, @@ -1028,6 +1053,13 @@ impl EditorData { FocusCommand::InlineFindRight => { self.inline_find.set(Some(InlineFindDirection::Right)); } + FocusCommand::OnScreenFind => { + self.on_screen_find.update(|find| { + find.active = true; + find.pattern.clear(); + find.regions.clear(); + }); + } FocusCommand::RepeatLastInlineFind => { if let Some((direction, c)) = self.last_inline_find.get_untracked() { self.inline_find(direction, &c); @@ -1130,6 +1162,72 @@ impl EditorData { } } + fn quit_on_screen_find(&self) { + if self.on_screen_find.with_untracked(|s| s.active) { + self.on_screen_find.update(|f| { + f.active = false; + f.pattern.clear(); + f.regions.clear(); + }) + } + } + + fn on_screen_find(&self, pattern: &str) -> Vec { + let screen_lines = self.screen_lines().get_untracked(); + let lines: HashSet = + screen_lines.lines.iter().map(|l| l.line).collect(); + + let mut matcher = nucleo::Matcher::new(nucleo::Config::DEFAULT); + let pattern = nucleo::pattern::Pattern::parse( + pattern, + nucleo::pattern::CaseMatching::Ignore, + nucleo::pattern::Normalization::Smart, + ); + let mut indices = Vec::new(); + let mut filter_text_buf = Vec::new(); + let mut items = Vec::new(); + + let buffer = self.doc().buffer; + + for line in lines { + filter_text_buf.clear(); + indices.clear(); + + buffer.with_untracked(|buffer| { + let start = buffer.offset_of_line(line); + let end = buffer.offset_of_line(line + 1); + let text = buffer.text().slice_to_cow(start..end); + let filter_text = Utf32Str::new(&text, &mut filter_text_buf); + + if let Some(score) = + pattern.indices(filter_text, &mut matcher, &mut indices) + { + indices.sort(); + let left = + start + indices.first().copied().unwrap_or(0) as usize; + let right = + start + indices.last().copied().unwrap_or(0) as usize + 1; + let right = if right == left { left + 1 } else { right }; + items.push((score, left, right)); + } + }); + } + + items.sort_by_key(|(score, _, _)| -(*score as i64)); + if let Some((_, offset, _)) = items.first().copied() { + self.run_move_command( + &lapce_core::movement::Movement::Offset(offset), + None, + Modifiers::empty(), + ); + } + + items + .into_iter() + .map(|(_, start, end)| SelRegion::new(start, end, None)) + .collect() + } + fn go_to_definition(&self) { let doc = self.doc(); let path = match if doc.loaded() { @@ -1325,6 +1423,80 @@ impl EditorData { ); } + pub fn find_refenrence(&self, window_tab_data: WindowTabData) { + let doc = self.doc(); + let path = match if doc.loaded() { + doc.content.with_untracked(|c| c.path().cloned()) + } else { + None + } { + Some(path) => path, + None => return, + }; + + let offset = self.cursor().with_untracked(|c| c.offset()); + let (_start_position, position) = doc.buffer.with_untracked(|buffer| { + let start_offset = buffer.prev_code_boundary(offset); + let start_position = buffer.offset_to_position(start_offset); + let position = buffer.offset_to_position(offset); + (start_position, position) + }); + let scope = window_tab_data.scope; + self.common.proxy.get_references( + path, + position, + create_ext_action(self.scope, move |result| { + if let Ok(ProxyResponse::GetReferencesResponse { references }) = + result + { + window_tab_data + .main_split + .references + .update(|x| *x = init_references_root(references, scope)); + window_tab_data.show_panel(PanelKind::References); + } + }), + ); + } + + pub fn go_to_implementation(&self, window_tab_data: WindowTabData) { + let doc = self.doc(); + let path = match if doc.loaded() { + doc.content.with_untracked(|c| c.path().cloned()) + } else { + None + } { + Some(path) => path, + None => return, + }; + + let offset = self.cursor().with_untracked(|c| c.offset()); + let (_start_position, position) = doc.buffer.with_untracked(|buffer| { + let start_offset = buffer.prev_code_boundary(offset); + let start_position = buffer.offset_to_position(start_offset); + let position = buffer.offset_to_position(offset); + (start_position, position) + }); + let scope = window_tab_data.scope; + self.common.proxy.go_to_implementation( + path, + position, + create_ext_action(self.scope, move |result| { + if let Ok(ProxyResponse::GotoImplementationResponse { + resp, .. + }) = result + { + tracing::info!("{:?}", resp); + window_tab_data + .main_split + .implementations + .update(|x| *x = init_implementation_root(resp, scope)); + window_tab_data.show_panel(PanelKind::Implementation); + } + }), + ); + } + fn scroll(&self, down: bool, count: usize, mods: Modifiers) { self.editor.scroll( self.sticky_header_height.get_untracked(), @@ -1353,7 +1525,9 @@ impl EditorData { return; }; - let _ = item.apply(self, start_offset); + if let Err(err) = item.apply(self, start_offset) { + tracing::error!("{:?}", err); + } } fn next_inline_completion(&self) { @@ -1519,7 +1693,9 @@ impl EditorData { { return; } - let _ = editor.apply_completion_item(&item); + if let Err(err) = editor.apply_completion_item(&item) { + tracing::error!("{:?}", err); + } }); self.common.proxy.completion_resolve( item.plugin_id, @@ -1537,8 +1713,8 @@ impl EditorData { send(item); }, ); - } else { - let _ = self.apply_completion_item(&item.item); + } else if let Err(err) = self.apply_completion_item(&item.item) { + tracing::error!("{:?}", err); } } } @@ -2144,7 +2320,9 @@ impl EditorData { let proxy = self.common.proxy.clone(); std::thread::spawn(move || { proxy.get_document_formatting(path, move |result| { - let _ = tx.send(result); + if let Err(err) = tx.send(result) { + tracing::error!("{:?}", err); + } }); let result = rx.recv_timeout(std::time::Duration::from_secs(1)); send(result); @@ -2176,7 +2354,9 @@ impl EditorData { let proxy = self.common.proxy.clone(); std::thread::spawn(move || { proxy.get_document_formatting(path, move |result| { - let _ = tx.send(result); + if let Err(err) = tx.send(result) { + tracing::error!("{:?}", err); + } }); let result = rx.recv_timeout(std::time::Duration::from_secs(1)); send(result); @@ -2638,29 +2818,81 @@ impl EditorData { self.single_click(pointer_event); } - let is_file = doc.content.with_untracked(|content| content.is_file()); + let (path, is_file) = doc.content.with_untracked(|content| match content { + DocContent::File { path, .. } => { + (Some(path.to_path_buf()), path.is_file()) + } + DocContent::Local + | DocContent::History(_) + | DocContent::Scratch { .. } => (None, false), + }); let mut menu = Menu::new(""); - let cmds = if is_file { - vec![ - Some(CommandKind::Focus(FocusCommand::GotoDefinition)), - Some(CommandKind::Focus(FocusCommand::GotoTypeDefinition)), - Some(CommandKind::Workbench( - LapceWorkbenchCommand::ShowCallHierarchy, - )), - None, - Some(CommandKind::Focus(FocusCommand::Rename)), - None, - Some(CommandKind::Edit(EditCommand::ClipboardCut)), - Some(CommandKind::Edit(EditCommand::ClipboardCopy)), - Some(CommandKind::Edit(EditCommand::ClipboardPaste)), - None, - Some(CommandKind::Workbench( - LapceWorkbenchCommand::RevealInFileTree, - )), - Some(CommandKind::Workbench( - LapceWorkbenchCommand::PaletteCommand, - )), - ] + let mut cmds = if is_file { + if path + .as_ref() + .and_then(|x| x.file_name().and_then(|x| x.to_str())) + .map(|x| x == "run.toml") + .unwrap_or_default() + { + vec![ + Some(CommandKind::Workbench( + LapceWorkbenchCommand::RevealInPanel, + )), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::RevealInFileExplorer, + )), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::SourceControlOpenActiveFileRemoteUrl, + )), + None, + Some(CommandKind::Edit(EditCommand::ClipboardCut)), + Some(CommandKind::Edit(EditCommand::ClipboardCopy)), + Some(CommandKind::Edit(EditCommand::ClipboardPaste)), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::AddRunDebugConfig, + )), + None, + Some(CommandKind::Workbench( + LapceWorkbenchCommand::PaletteCommand, + )), + ] + } else { + vec![ + Some(CommandKind::Focus(FocusCommand::GotoDefinition)), + Some(CommandKind::Focus(FocusCommand::GotoTypeDefinition)), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::ShowCallHierarchy, + )), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::FindReferences, + )), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::GoToImplementation, + )), + Some(CommandKind::Focus(FocusCommand::Rename)), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::RunInTerminal, + )), + None, + Some(CommandKind::Workbench( + LapceWorkbenchCommand::RevealInPanel, + )), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::RevealInFileExplorer, + )), + Some(CommandKind::Workbench( + LapceWorkbenchCommand::SourceControlOpenActiveFileRemoteUrl, + )), + None, + Some(CommandKind::Edit(EditCommand::ClipboardCut)), + Some(CommandKind::Edit(EditCommand::ClipboardCopy)), + Some(CommandKind::Edit(EditCommand::ClipboardPaste)), + None, + Some(CommandKind::Workbench( + LapceWorkbenchCommand::PaletteCommand, + )), + ] + } } else { vec![ Some(CommandKind::Edit(EditCommand::ClipboardCut)), @@ -2672,6 +2904,11 @@ impl EditorData { )), ] }; + if self.diff_editor_id.get_untracked().is_some() && is_file { + cmds.push(Some(CommandKind::Workbench( + LapceWorkbenchCommand::GoToLocation, + ))); + } let lapce_command = self.common.lapce_command; for cmd in cmds { if let Some(cmd) = cmd { @@ -2946,6 +3183,9 @@ impl KeyPressFocus for EditorData { Condition::ListFocus => self.has_completions(), Condition::CompletionFocus => self.has_completions(), Condition::InlineCompletionVisible => self.has_inline_completions(), + Condition::OnScreenFindActive => { + self.on_screen_find.with_untracked(|f| f.active) + } Condition::InSnippet => self.snippet.with_untracked(|s| s.is_some()), Condition::EditorFocus => self .doc() @@ -3052,6 +3292,7 @@ impl KeyPressFocus for EditorData { false } else { self.inline_find.with_untracked(|f| f.is_some()) + || self.on_screen_find.with_untracked(|f| f.active) } } @@ -3097,6 +3338,12 @@ impl KeyPressFocus for EditorData { self.inline_find(direction.clone(), c); self.last_inline_find.set(Some((direction, c.to_string()))); self.inline_find.set(None); + } else if self.on_screen_find.with_untracked(|f| f.active) { + self.on_screen_find.update(|find| { + let pattern = format!("{}{c}", find.pattern); + find.regions = self.on_screen_find(&pattern); + find.pattern = pattern; + }); } } } @@ -3292,7 +3539,7 @@ pub(crate) fn compute_screen_lines( let is_right = diff_info.is_right; let line_y = |info: VLineInfo<()>, vline_y: usize| -> usize { - vline_y - info.rvline.line_index * line_height + vline_y.saturating_sub(info.rvline.line_index * line_height) }; while let Some(change) = changes.next() { @@ -3501,10 +3748,10 @@ fn parse_hover_resp( ) -> Vec { match hover.contents { HoverContents::Scalar(text) => match text { - MarkedString::String(text) => parse_markdown(&text, 1.5, config), + MarkedString::String(text) => parse_markdown(&text, 1.8, config), MarkedString::LanguageString(code) => parse_markdown( &format!("```{}\n{}\n```", code.language, code.value), - 1.5, + 1.8, config, ), }, @@ -3519,8 +3766,8 @@ fn parse_hover_resp( }) .unwrap_or_default(), HoverContents::Markup(content) => match content.kind { - MarkupKind::PlainText => from_plaintext(&content.value, 1.5, config), - MarkupKind::Markdown => parse_markdown(&content.value, 1.5, config), + MarkupKind::PlainText => from_plaintext(&content.value, 1.8, config), + MarkupKind::Markdown => parse_markdown(&content.value, 1.8, config), }, } } diff --git a/lapce-app/src/editor/diff.rs b/lapce-app/src/editor/diff.rs index 5af8d9a919..e4f8152b0a 100644 --- a/lapce-app/src/editor/diff.rs +++ b/lapce-app/src/editor/diff.rs @@ -3,7 +3,7 @@ use std::{rc::Rc, sync::atomic}; use floem::{ event::EventListener, ext_event::create_ext_action, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, style::CursorStyle, views::{ clip, dyn_stack, editor::id::EditorId, empty, label, stack, svg, Decorators, diff --git a/lapce-app/src/editor/gutter.rs b/lapce-app/src/editor/gutter.rs index 373ab81de1..d75183aa68 100644 --- a/lapce-app/src/editor/gutter.rs +++ b/lapce-app/src/editor/gutter.rs @@ -1,7 +1,7 @@ use floem::{ context::PaintCx, peniko::kurbo::{Point, Rect, Size}, - reactive::Memo, + reactive::{Memo, SignalGet, SignalWith}, text::{Attrs, AttrsList, FamilyOwned, TextLayout}, Renderer, View, ViewId, }; diff --git a/lapce-app/src/editor/view.rs b/lapce-app/src/editor/view.rs index df80fc1478..5cab4b7dae 100644 --- a/lapce-app/src/editor/view.rs +++ b/lapce-app/src/editor/view.rs @@ -11,6 +11,7 @@ use floem::{ }, reactive::{ create_effect, create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, + SignalGet, SignalUpdate, SignalWith, }, style::{CursorColor, CursorStyle, Style, TextColor}, taffy::prelude::NodeId, @@ -30,7 +31,7 @@ use floem::{ ShowIndentGuide, SmartTab, VisibleWhitespaceColor, WrapProp, }, empty, label, - scroll::{scroll, HideBar, PropagatePointerWheel}, + scroll::{scroll, PropagatePointerWheel}, stack, svg, Decorators, }, Renderer, View, ViewId, @@ -39,6 +40,7 @@ use itertools::Itertools; use lapce_core::{ buffer::{diff::DiffLines, rope_text::RopeText, Buffer}, cursor::{CursorAffinity, CursorMode}, + selection::SelRegion, }; use lapce_rpc::{ dap_types::{DapId, SourceBreakpoint}, @@ -477,8 +479,8 @@ impl EditorView { } fn paint_find(&self, cx: &mut PaintCx, screen_lines: &ScreenLines) { - let visual = self.editor.common.find.visual; - if !visual.get_untracked() { + let find_visual = self.editor.common.find.visual.get_untracked(); + if !find_visual && self.editor.on_screen_find.with_untracked(|f| !f.active) { return; } if screen_lines.lines.is_empty() { @@ -499,78 +501,108 @@ impl EditorView { let config = config.get_untracked(); let line_height = config.editor.line_height() as f64; + let color = config.color(LapceColor::EDITOR_FOREGROUND); - doc.update_find(); let start = ed.offset_of_line(min_line); let end = ed.offset_of_line(max_line + 1); // TODO: The selection rect creation logic for find is quite similar to the version // within insert cursor. It would be good to deduplicate it. - let mut rects = Vec::new(); - for region in occurrences.with_untracked(|selection| { - selection.regions_in_range(start, end).to_vec() - }) { - let start = region.min(); - let end = region.max(); - - // TODO(minor): the proper affinity here should probably be tracked by selregion - let (start_rvline, start_col) = - ed.rvline_col_of_offset(start, CursorAffinity::Forward); - let (end_rvline, end_col) = - ed.rvline_col_of_offset(end, CursorAffinity::Backward); - - for line_info in screen_lines.iter_line_info() { - let rvline_info = line_info.vline_info; - let rvline = rvline_info.rvline; - let line = rvline.line; - - if rvline < start_rvline { - continue; - } + if find_visual { + doc.update_find(); + for region in occurrences.with_untracked(|selection| { + selection.regions_in_range(start, end).to_vec() + }) { + self.paint_find_region( + cx, + ed, + ®ion, + color, + screen_lines, + line_height, + ); + } + } - if rvline > end_rvline { - break; + self.editor.on_screen_find.with_untracked(|find| { + if find.active { + for region in &find.regions { + self.paint_find_region( + cx, + ed, + region, + color, + screen_lines, + line_height, + ); } + } + }); + } - let left_col = if rvline == start_rvline { start_col } else { 0 }; - let (right_col, _vline_end) = if rvline == end_rvline { - let max_col = ed.last_col(rvline_info, true); - (end_col.min(max_col), false) - } else { - (ed.last_col(rvline_info, true), true) - }; + fn paint_find_region( + &self, + cx: &mut PaintCx, + ed: &Editor, + region: &SelRegion, + color: Color, + screen_lines: &ScreenLines, + line_height: f64, + ) { + let start = region.min(); + let end = region.max(); - // TODO(minor): sel region should have the affinity of the start/end - let x0 = ed - .line_point_of_line_col( - line, - left_col, - CursorAffinity::Forward, - true, - ) - .x; - let x1 = ed - .line_point_of_line_col( - line, - right_col, - CursorAffinity::Backward, - true, - ) - .x; + // TODO(minor): the proper affinity here should probably be tracked by selregion + let (start_rvline, start_col) = + ed.rvline_col_of_offset(start, CursorAffinity::Forward); + let (end_rvline, end_col) = + ed.rvline_col_of_offset(end, CursorAffinity::Backward); - if !rvline_info.is_empty() && start != end && left_col != right_col { - rects.push( - Size::new(x1 - x0, line_height) - .to_rect() - .with_origin(Point::new(x0, line_info.vline_y)), - ); - } + for line_info in screen_lines.iter_line_info() { + let rvline_info = line_info.vline_info; + let rvline = rvline_info.rvline; + let line = rvline.line; + + if rvline < start_rvline { + continue; } - } - let color = config.color(LapceColor::EDITOR_FOREGROUND); - for rect in rects { - cx.stroke(&rect, color, 1.0); + if rvline > end_rvline { + break; + } + + let left_col = if rvline == start_rvline { start_col } else { 0 }; + let (right_col, _vline_end) = if rvline == end_rvline { + let max_col = ed.last_col(rvline_info, true); + (end_col.min(max_col), false) + } else { + (ed.last_col(rvline_info, true), true) + }; + + // TODO(minor): sel region should have the affinity of the start/end + let x0 = ed + .line_point_of_line_col( + line, + left_col, + CursorAffinity::Forward, + true, + ) + .x; + let x1 = ed + .line_point_of_line_col( + line, + right_col, + CursorAffinity::Backward, + true, + ) + .x; + + if !rvline_info.is_empty() && start != end && left_col != right_col { + let rect = Size::new(x1 - x0, line_height) + .to_rect() + .with_origin(Point::new(x0, line_info.vline_y)); + cx.stroke(&rect, color, 1.0); + } } } @@ -1866,9 +1898,9 @@ fn editor_breadcrumbs( doc.track(); Some(Point::new(3000.0, 0.0)) }) + .scroll_style(|s| s.hide_bars(true)) .style(move |s| { - s.set(HideBar, true) - .absolute() + s.absolute() .size_pct(100.0, 100.0) .border_bottom(1.0) .border_color(config.get().color(LapceColor::LAPCE_BORDER)) @@ -1923,6 +1955,8 @@ fn editor_content( }); } + let current_scroll = create_rw_signal(Rect::ZERO); + scroll({ let editor_content_view = editor_view(e_data.get_untracked(), debug_breakline, is_active).style( @@ -1970,10 +2004,14 @@ fn editor_content( .on_move(move |point| { window_origin.set(point); }) - .on_scroll(move |_| { - let e_data = e_data.get_untracked(); - e_data.cancel_completion(); - e_data.cancel_inline_completion(); + .on_scroll(move |rect| { + if rect.y0 != current_scroll.get_untracked().y0 { + // only cancel completion if scrolled vertically + let e_data = e_data.get_untracked(); + e_data.cancel_completion(); + e_data.cancel_inline_completion(); + } + current_scroll.set(rect); }) .scroll_to(move || scroll_to.get().map(|s| s.to_point())) .scroll_delta(move || scroll_delta.get()) diff --git a/lapce-app/src/editor_tab.rs b/lapce-app/src/editor_tab.rs index 3ba7a96b09..0bfe7c0aea 100644 --- a/lapce-app/src/editor_tab.rs +++ b/lapce-app/src/editor_tab.rs @@ -9,7 +9,10 @@ use floem::{ kurbo::{Point, Rect}, Color, }, - reactive::{create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, Scope}, + reactive::{ + create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, Scope, SignalGet, + SignalUpdate, SignalWith, + }, views::editor::id::EditorId, }; use lapce_rpc::plugin::VoltID; diff --git a/lapce-app/src/file_explorer/data.rs b/lapce-app/src/file_explorer/data.rs index 0ea95b8930..a931c09bf8 100644 --- a/lapce-app/src/file_explorer/data.rs +++ b/lapce-app/src/file_explorer/data.rs @@ -4,6 +4,7 @@ use std::{ ffi::OsStr, path::{Path, PathBuf}, rc::Rc, + sync::Arc, }; use floem::{ @@ -12,7 +13,7 @@ use floem::{ ext_event::create_ext_action, keyboard::Modifiers, menu::{Menu, MenuItem}, - reactive::{RwSignal, Scope}, + reactive::{ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::editor::text::SystemClipboard, }; use globset::Glob; @@ -22,12 +23,16 @@ use lapce_core::{ register::Clipboard, }; use lapce_rpc::{ - file::{Duplicating, FileNodeItem, Naming, NamingState, NewNode, Renaming}, + file::{ + Duplicating, FileNodeItem, FileNodeViewKind, Naming, NamingState, NewNode, + Renaming, + }, proxy::ProxyResponse, }; use crate::{ command::{CommandExecuted, CommandKind, InternalCommand, LapceCommand}, + config::LapceConfig, editor::EditorData, keypress::{condition::Condition, KeyPressFocus}, main_split::Editors, @@ -51,6 +56,7 @@ pub struct FileExplorerData { pub common: Rc, pub scroll_to_line: RwSignal>, left_diff_path: RwSignal>, + pub select: RwSignal>, } impl KeyPressFocus for FileExplorerData { @@ -131,6 +137,7 @@ impl FileExplorerData { common, scroll_to_line: cx.create_rw_signal(None), left_diff_path: cx.create_rw_signal(None), + select: cx.create_rw_signal(None), }; if data.common.workspace.path.is_some() { // only fill in the child files if there is open folder @@ -389,10 +396,10 @@ impl FileExplorerData { self.naming.set(Naming::None); } - pub fn click(&self, path: &Path) { + pub fn click(&self, path: &Path, config: ReadSignal>) { if self.is_dir(path) { self.toggle_expand(path); - } else { + } else if !config.get_untracked().core.file_exploerer_double_click { self.common .internal_command .send(InternalCommand::OpenFile { @@ -448,14 +455,26 @@ impl FileExplorerData { let (found, line) = self.root.with_untracked(|x| x.find_file_at_line(&path)); if found { - self.scroll_to_line.set(Some(line)) + self.scroll_to_line.set(Some(line)); + self.select.set(Some(FileNodeViewKind::Path(path))); } } } - pub fn double_click(&self, path: &Path) -> EventPropagation { + pub fn double_click( + &self, + path: &Path, + config: ReadSignal>, + ) -> EventPropagation { if self.is_dir(path) { EventPropagation::Continue + } else if config.get_untracked().core.file_exploerer_double_click { + self.common.internal_command.send( + InternalCommand::OpenAndConfirmedFile { + path: path.to_path_buf(), + }, + ); + EventPropagation::Stop } else { self.common .internal_command @@ -540,21 +559,23 @@ impl FileExplorerData { // TODO: there are situations where we can open the file explorer to remote files if !common.workspace.kind.is_remote() { let path = path_a.clone(); - menu = menu.entry(MenuItem::new("Reveal in file explorer").action( - move || { - let path = path.parent().unwrap_or(&path); - if !path.exists() { - return; - } + #[cfg(not(target_os = "macos"))] + let title = "Reveal in system file explorer"; + #[cfg(target_os = "macos")] + let title = "Reveal in Finder"; + menu = menu.entry(MenuItem::new(title).action(move || { + let path = path.parent().unwrap_or(&path); + if !path.exists() { + return; + } - if let Err(err) = open::that(path) { - tracing::error!( - "Failed to reveal file in system file explorer: {}", - err - ); - } - }, - )); + if let Err(err) = open::that(path) { + tracing::error!( + "Failed to reveal file in system file explorer: {}", + err + ); + } + })); } if !is_workspace { diff --git a/lapce-app/src/file_explorer/view.rs b/lapce-app/src/file_explorer/view.rs index 752497699d..d3ab9868db 100644 --- a/lapce-app/src/file_explorer/view.rs +++ b/lapce-app/src/file_explorer/view.rs @@ -2,8 +2,11 @@ use std::{path::Path, rc::Rc, sync::Arc}; use floem::{ event::{Event, EventListener}, + kurbo::Rect, peniko::Color, - reactive::{create_rw_signal, ReadSignal, RwSignal}, + reactive::{ + create_rw_signal, ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, + }, style::{AlignItems, CursorStyle, Position, Style}, text::Style as FontStyle, views::{ @@ -86,10 +89,8 @@ pub fn file_explorer_panel( ) .add( "File Explorer", - container( - new_file_node_view(data, source_control).style(|s| s.absolute()), - ) - .style(|s| s.size_full().line_height(1.8)), + container(file_explorer_view(data, source_control)) + .style(|s| s.size_full()), window_tab_data .panel .section_open(PanelSection::FileExplorer), @@ -173,7 +174,7 @@ fn file_node_text_view( let config = data.common.config; let ui_line_height = data.common.ui_line_height; - let view = match node.kind.clone() { + match node.kind.clone() { FileNodeViewKind::Path(path) => container( label(move || { path.file_name() @@ -181,8 +182,7 @@ fn file_node_text_view( .unwrap_or_default() }) .style(move |s| { - s.flex_grow(1.0) - .height(ui_line_height.get()) + s.height(ui_line_height.get()) .color(file_node_text_color( config, node.clone(), @@ -212,9 +212,7 @@ fn file_node_text_view( file_node_input_view(data, err.clone()) } - }; - - view.style(|s| s.flex_grow(1.0).padding(0.0).margin(0.0)) + } } /// Input used for naming a file/directory @@ -282,7 +280,7 @@ fn file_node_input_view(data: FileExplorerData, err: Option) -> Containe } } -fn new_file_node_view( +fn file_explorer_view( data: FileExplorerData, source_control: SourceControlData, ) -> impl View { @@ -291,7 +289,9 @@ fn new_file_node_view( let config = data.common.config; let naming = data.naming; let scroll_to_line = data.scroll_to_line; + let select = data.select; let secondary_click_data = data.clone(); + let scroll_rect = create_rw_signal(Rect::ZERO); scroll( virtual_stack( @@ -375,31 +375,55 @@ fn new_file_node_view( }, file_node_text_view(data, node, source_control.clone()), )) - .style(move |s| { - s.padding_right(5.0) - .padding_left((level * 10) as f32) - .align_items(AlignItems::Center) - .hover(|s| { - s.background( - config - .get() - .color(LapceColor::PANEL_HOVERED_BACKGROUND), + .style({ + let kind = kind.clone(); + move |s| { + s.padding_right(15.0) + .min_width_full() + .padding_left((level * 10) as f32) + .align_items(AlignItems::Center) + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + .apply_if( + select.get().map(|x| x == kind).unwrap_or_default(), + |x| { + x.background( + config.get().color( + LapceColor::PANEL_CURRENT_BACKGROUND, + ), + ) + }, ) - .cursor(CursorStyle::Pointer) - }) - }); + } + }) + .debug_name("file item"); // Only handle click events if we are not naming the file node - if let FileNodeViewKind::Path(path) = kind { + if let FileNodeViewKind::Path(path) = &kind { let click_path = path.clone(); let double_click_path = path.clone(); let secondary_click_path = path.clone(); - let aux_click_path = path; - view.on_click_stop(move |_| { - click_data.click(&click_path); + let aux_click_path = path.clone(); + view.on_click_stop({ + let kind = kind.clone(); + let config = config.clone(); + move |_| { + click_data.click(&click_path, config); + select.update(|x| *x = Some(kind.clone())); + } }) - .on_double_click(move |_| { - double_click_data.double_click(&double_click_path) + .on_double_click({ + let config = config.clone(); + move |_| { + double_click_data + .double_click(&double_click_path, config) + } }) .on_secondary_click_stop(move |_| { secondary_click_data.secondary_click(&secondary_click_path); @@ -419,9 +443,9 @@ fn new_file_node_view( } }, ) - .style(|s| s.flex_col().align_items(AlignItems::Stretch).width_full()), + .style(|s| s.absolute().flex_col().min_width_full()), ) - .style(|s| s.size_full()) + .style(|s| s.absolute().size_full().line_height(1.8)) .on_secondary_click_stop(move |_| { if let Naming::None = naming.get_untracked() { if let Some(path) = &secondary_click_data.common.workspace.path { @@ -429,10 +453,19 @@ fn new_file_node_view( } } }) + .on_resize(move |rect| { + scroll_rect.set(rect); + }) .scroll_to(move || { if let Some(line) = scroll_to_line.get() { - let line_height = ui_line_height.get(); - Some((0.0, line * line_height).into()) + let line_height = ui_line_height.get_untracked(); + Some( + ( + 0.0, + line * line_height - scroll_rect.get_untracked().height() / 2.0, + ) + .into(), + ) } else { None } diff --git a/lapce-app/src/find.rs b/lapce-app/src/find.rs index a723f501a8..111478996e 100644 --- a/lapce-app/src/find.rs +++ b/lapce-app/src/find.rs @@ -1,6 +1,6 @@ use std::cmp::{max, min}; -use floem::reactive::{RwSignal, Scope}; +use floem::reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}; use lapce_core::{ selection::{SelRegion, Selection}, word::WordCursor, diff --git a/lapce-app/src/global_search.rs b/lapce-app/src/global_search.rs index 41f0b0bf09..b94132972e 100644 --- a/lapce-app/src/global_search.rs +++ b/lapce-app/src/global_search.rs @@ -3,7 +3,7 @@ use std::{ops::Range, path::PathBuf, rc::Rc}; use floem::{ ext_event::create_ext_action, keyboard::Modifiers, - reactive::{Memo, RwSignal, Scope}, + reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::VirtualVector, }; use indexmap::IndexMap; diff --git a/lapce-app/src/inline_completion.rs b/lapce-app/src/inline_completion.rs index fc4da549fa..da19a5d173 100644 --- a/lapce-app/src/inline_completion.rs +++ b/lapce-app/src/inline_completion.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ops::Range, path::PathBuf, str::FromStr}; -use floem::reactive::{batch, RwSignal, Scope}; +use floem::reactive::{batch, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}; use lapce_core::{ buffer::{ rope_text::{RopeText, RopeTextRef}, diff --git a/lapce-app/src/keymap.rs b/lapce-app/src/keymap.rs index 2cc6b2c6bc..8ae2c52323 100644 --- a/lapce-app/src/keymap.rs +++ b/lapce-app/src/keymap.rs @@ -4,7 +4,7 @@ use floem::{ event::{Event, EventListener}, reactive::{ create_effect, create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, - Scope, + Scope, SignalGet, SignalUpdate, SignalWith, }, style::CursorStyle, views::{ @@ -410,6 +410,7 @@ fn keyboard_picker_view( s.padding_horiz(5.0) .padding_vert(1.0) .margin_right(5.0) + .height(ui_line_height.get() as f32) .border(1.0) .border_radius(6.0) .border_color( @@ -424,7 +425,7 @@ fn keyboard_picker_view( .justify_center() .width_pct(100.0) .margin_top(20.0) - .height(ui_line_height.get() as f32 + 16.0) + .height((ui_line_height.get() as f32) * 1.2) .border(1.0) .border_radius(6.0) .border_color(config.color(LapceColor::LAPCE_BORDER)) @@ -518,18 +519,19 @@ fn keyboard_picker_view( .on_event_stop(EventListener::KeyDown, move |event| { if let Event::KeyDown(key_event) = event { if let Some(keypress) = KeyPressData::keypress(key_event) { - let keypress = keypress.keymap_press(); - picker.keys.update(|keys| { - if let Some((last_key, last_key_confirmed)) = keys.last() { - if !*last_key_confirmed && last_key.is_modifiers() { - keys.pop(); + if let Some(keypress) = keypress.keymap_press() { + picker.keys.update(|keys| { + if let Some((last_key, last_key_confirmed)) = keys.last() { + if !*last_key_confirmed && last_key.is_modifiers() { + keys.pop(); + } } - } - if keys.len() == 2 { - keys.clear(); - } - keys.push((keypress, false)); - }) + if keys.len() == 2 { + keys.clear(); + } + keys.push((keypress, false)); + }) + } } } }) @@ -548,7 +550,8 @@ fn keyboard_picker_view( .items_center() .justify_center() .apply_if(picker.keymap.with(|keymap| keymap.is_none()), |s| s.hide()) - }); + }) + .debug_name("keyboard picker"); let id = view.id(); create_effect(move |_| { diff --git a/lapce-app/src/keypress.rs b/lapce-app/src/keypress.rs index 3a14175073..a22b46042d 100644 --- a/lapce-app/src/keypress.rs +++ b/lapce-app/src/keypress.rs @@ -4,13 +4,13 @@ pub mod keymap; mod loader; mod press; -use std::{path::PathBuf, rc::Rc, str::FromStr}; +use std::{path::PathBuf, rc::Rc, str::FromStr, time::SystemTime}; use anyhow::Result; use floem::{ keyboard::{Key, KeyEvent, KeyEventExtModifierSupplement, Modifiers, NamedKey}, pointer::{PointerButton, PointerInputEvent}, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalUpdate, SignalWith}, }; use indexmap::IndexMap; use itertools::Itertools; @@ -147,7 +147,7 @@ pub struct KeyPressHandle { #[derive(Clone, Debug)] pub struct KeyPressData { count: RwSignal>, - pending_keypress: RwSignal>, + pending_keypress: RwSignal<(Vec, Option)>, pub commands: Rc>, pub keymaps: Rc, Vec>>, pub command_keymaps: Rc>>, @@ -161,7 +161,7 @@ impl KeyPressData { Self::get_keymaps(config).unwrap_or((IndexMap::new(), IndexMap::new())); let mut keypress = Self { count: cx.create_rw_signal(None), - pending_keypress: cx.create_rw_signal(Vec::new()), + pending_keypress: cx.create_rw_signal((Vec::new(), None)), keymaps: Rc::new(keymaps), command_keymaps: Rc::new(command_keymaps), commands: Rc::new(lapce_internal_commands()), @@ -192,7 +192,12 @@ impl KeyPressData { } for (_, cmd) in self.commands.iter() { - if !self.command_keymaps.contains_key(cmd.kind.str()) { + if self + .command_keymaps + .get(cmd.kind.str()) + .map(|x| x.is_empty()) + .unwrap_or(true) + { commands_without_keymap.push(cmd.clone()); } } @@ -259,6 +264,7 @@ impl KeyPressData { physical: ev.key.physical_key, key_without_modifiers: ev.key.key_without_modifiers(), location: ev.key.location, + repeat: ev.key.repeat, }, mods: Self::get_key_modifiers(ev), }, @@ -297,13 +303,26 @@ impl KeyPressData { }; } - self.pending_keypress.update(|pending_keypress| { - pending_keypress.push(keypress.clone()); - }); + self.pending_keypress + .update(|(pending_keypress, last_time)| { + let last_time = last_time.replace(SystemTime::now()); + if let Some(last_time_val) = last_time { + if last_time_val + .elapsed() + .map(|x| x.as_millis() > 1000) + .unwrap_or_default() + { + pending_keypress.clear(); + } + } + pending_keypress.push(keypress.clone()); + }); - let keymatch = self.pending_keypress.with_untracked(|pending_keypress| { - self.match_keymap(pending_keypress, focus) - }); + let keymatch = + self.pending_keypress + .with_untracked(|(pending_keypress, _)| { + self.match_keymap(pending_keypress, focus) + }); self.handle_keymatch(focus, keymatch, keypress) } @@ -316,9 +335,11 @@ impl KeyPressData { let mods = keypress.mods; match &keymatch { KeymapMatch::Full(command) => { - self.pending_keypress.update(|pending_keypress| { - pending_keypress.clear(); - }); + self.pending_keypress + .update(|(pending_keypress, last_time)| { + last_time.take(); + pending_keypress.clear(); + }); let count = self.count.try_update(|count| count.take()).unwrap(); let handled = self.run_command(command, count, mods, focus) == CommandExecuted::Yes; @@ -329,9 +350,11 @@ impl KeyPressData { }; } KeymapMatch::Multiple(commands) => { - self.pending_keypress.update(|pending_keypress| { - pending_keypress.clear(); - }); + self.pending_keypress + .update(|(pending_keypress, last_time)| { + last_time.take(); + pending_keypress.clear(); + }); let count = self.count.try_update(|count| count.take()).unwrap(); for command in commands { let handled = self.run_command(command, count, mods, focus) @@ -361,9 +384,11 @@ impl KeyPressData { }; } KeymapMatch::None => { - self.pending_keypress.update(|pending_keypress| { - pending_keypress.clear(); - }); + self.pending_keypress + .update(|(pending_keypress, last_time)| { + pending_keypress.clear(); + last_time.take(); + }); if focus.get_mode() == Mode::Insert { let old_keypress = keypress.clone(); let mut keypress = keypress.clone(); @@ -449,7 +474,7 @@ impl KeyPressData { check: &T, ) -> KeymapMatch { let keypresses: Vec = - keypresses.iter().map(|k| k.keymap_press()).collect(); + keypresses.iter().filter_map(|k| k.keymap_press()).collect(); let matches: Vec<_> = self .keymaps .get(&keypresses) diff --git a/lapce-app/src/keypress/condition.rs b/lapce-app/src/keypress/condition.rs index 3d6e61c56c..cb2d23cf74 100644 --- a/lapce-app/src/keypress/condition.rs +++ b/lapce-app/src/keypress/condition.rs @@ -65,6 +65,8 @@ pub enum Condition { RenameFocus, #[strum(serialize = "search_active")] SearchActive, + #[strum(serialize = "on_screen_find_active")] + OnScreenFindActive, #[strum(serialize = "search_focus")] SearchFocus, #[strum(serialize = "replace_focus")] diff --git a/lapce-app/src/keypress/key.rs b/lapce-app/src/keypress/key.rs index 2b40a3a3fc..905c47b4cc 100644 --- a/lapce-app/src/keypress/key.rs +++ b/lapce-app/src/keypress/key.rs @@ -1,7 +1,6 @@ use floem::keyboard::{Key, KeyLocation, NamedKey, PhysicalKey}; use super::keymap::KeyMapKey; -use crate::tracing::*; #[derive(Clone, Debug)] pub(crate) enum KeyInput { @@ -10,25 +9,43 @@ pub(crate) enum KeyInput { logical: Key, location: KeyLocation, key_without_modifiers: Key, + repeat: bool, }, Pointer(floem::pointer::PointerButton), } impl KeyInput { - #[instrument] - pub fn keymap_key(&self) -> KeyMapKey { - match self { + pub fn keymap_key(&self) -> Option { + if let KeyInput::Keyboard { + repeat, logical, .. + } = self + { + if *repeat + && (matches!( + logical, + Key::Named(NamedKey::Meta) + | Key::Named(NamedKey::Shift) + | Key::Named(NamedKey::Alt) + | Key::Named(NamedKey::Control), + )) + { + return None; + } + } + + Some(match self { KeyInput::Pointer(b) => KeyMapKey::Pointer(*b), KeyInput::Keyboard { physical, key_without_modifiers, logical, location, + .. } => { #[allow(clippy::single_match)] match location { KeyLocation::Numpad => { - return KeyMapKey::Logical(logical.to_owned()) + return Some(KeyMapKey::Logical(logical.to_owned())) } _ => {} } @@ -52,6 +69,6 @@ impl KeyInput { Key::Dead(_) => KeyMapKey::Physical(*physical), } } - } + }) } } diff --git a/lapce-app/src/keypress/keymap.rs b/lapce-app/src/keypress/keymap.rs index 53c92a055c..ac0d2aacbe 100644 --- a/lapce-app/src/keypress/keymap.rs +++ b/lapce-app/src/keypress/keymap.rs @@ -317,19 +317,29 @@ impl FromStr for KeyMapKey { impl Display for KeyMapPress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.mods.contains(Modifiers::CONTROL) { - let _ = f.write_str("Ctrl+"); + if let Err(err) = f.write_str("Ctrl+") { + tracing::error!("{:?}", err); + } } if self.mods.contains(Modifiers::ALT) { - let _ = f.write_str("Alt+"); + if let Err(err) = f.write_str("Alt+") { + tracing::error!("{:?}", err); + } } if self.mods.contains(Modifiers::ALTGR) { - let _ = f.write_str("AltGr+"); + if let Err(err) = f.write_str("AltGr+") { + tracing::error!("{:?}", err); + } } if self.mods.contains(Modifiers::META) { - let _ = f.write_str("Meta+"); + if let Err(err) = f.write_str("Meta+") { + tracing::error!("{:?}", err); + } } if self.mods.contains(Modifiers::SHIFT) { - let _ = f.write_str("Shift+"); + if let Err(err) = f.write_str("Shift+") { + tracing::error!("{:?}", err); + } } f.write_str(&self.key.to_string()) } diff --git a/lapce-app/src/keypress/press.rs b/lapce-app/src/keypress/press.rs index a598d274dd..af262acfd4 100644 --- a/lapce-app/src/keypress/press.rs +++ b/lapce-app/src/keypress/press.rs @@ -9,11 +9,10 @@ pub struct KeyPress { } impl KeyPress { - #[tracing::instrument] - pub fn keymap_press(&self) -> KeyMapPress { - KeyMapPress { - key: self.key.keymap_key(), + pub fn keymap_press(&self) -> Option { + self.key.keymap_key().map(|key| KeyMapPress { + key, mods: self.mods, - } + }) } } diff --git a/lapce-app/src/listener.rs b/lapce-app/src/listener.rs index 813da30799..55e4fa74b8 100644 --- a/lapce-app/src/listener.rs +++ b/lapce-app/src/listener.rs @@ -1,4 +1,4 @@ -use floem::reactive::{RwSignal, Scope}; +use floem::reactive::{RwSignal, Scope, SignalGet, SignalUpdate}; /// A signal listener that receives 'events' from the outside and runs the callback. /// This is implemented using effects and normal rw signals. This should be used when it doesn't diff --git a/lapce-app/src/main_split.rs b/lapce-app/src/main_split.rs index 01f696bc11..e1481326af 100644 --- a/lapce-app/src/main_split.rs +++ b/lapce-app/src/main_split.rs @@ -10,7 +10,7 @@ use floem::{ file::{FileDialogOptions, FileInfo}, keyboard::Modifiers, peniko::kurbo::{Point, Rect, Vec2}, - reactive::{Memo, RwSignal, Scope}, + reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::editor::id::EditorId, }; use itertools::Itertools; @@ -50,6 +50,7 @@ use crate::{ ThemeColorSettingsId, VoltViewId, }, keypress::{EventRef, KeyPressData, KeyPressHandle}, + panel::references_view::ReferencesRoot, window_tab::{CommonData, Focus, WindowTabData}, }; @@ -381,6 +382,8 @@ pub struct MainSplitData { pub docs: RwSignal>>, pub scratch_docs: RwSignal>>, pub diagnostics: RwSignal>, + pub references: RwSignal, + pub implementations: RwSignal, pub active_editor: Memo>, pub find_editor: EditorData, pub replace_editor: EditorData, @@ -413,6 +416,8 @@ impl MainSplitData { cx.create_rw_signal(im::HashMap::new()); let scratch_docs = cx.create_rw_signal(im::HashMap::new()); let locations = cx.create_rw_signal(im::Vector::new()); + let references = cx.create_rw_signal(ReferencesRoot::default()); + let implementations = cx.create_rw_signal(ReferencesRoot::default()); let current_location = cx.create_rw_signal(0); let diagnostics = cx.create_rw_signal(im::HashMap::new()); let find_editor = editors.make_local(cx, common.clone()); @@ -472,6 +477,8 @@ impl MainSplitData { width: cx.create_rw_signal(0.0), code_lens: cx.create_rw_signal(CodeLensData::new(common.clone())), common, + references, + implementations, } } @@ -640,6 +647,7 @@ impl MainSplitData { }); } doc.get_code_lens(); + doc.get_document_symbol(); (doc, true) } } @@ -2914,6 +2922,20 @@ impl MainSplitData { } } } + + pub fn get_active_editor(&self) -> Option { + let active_editor_tab = self.active_editor_tab.get()?; + let editor_tabs = self.editor_tabs; + let editor_tab = editor_tabs + .with(|editor_tabs| editor_tabs.get(&active_editor_tab).copied())?; + let (_, _, child) = editor_tab.with(|editor_tab| { + editor_tab.children.get(editor_tab.active).cloned() + })?; + match child { + EditorTabChild::Editor(editor_id) => self.editors.editor(editor_id), + _ => None, + } + } } fn workspace_edits(edit: &WorkspaceEdit) -> Option>> { diff --git a/lapce-app/src/markdown.rs b/lapce-app/src/markdown.rs index f9e384365a..623c310464 100644 --- a/lapce-app/src/markdown.rs +++ b/lapce-app/src/markdown.rs @@ -310,14 +310,14 @@ pub fn from_marked_string( config: &LapceConfig, ) -> Vec { match text { - MarkedString::String(text) => parse_markdown(&text, 1.5, config), + MarkedString::String(text) => parse_markdown(&text, 1.8, config), // This is a short version of a code block MarkedString::LanguageString(code) => { // TODO: We could simply construct the MarkdownText directly // Simply construct the string as if it was written directly parse_markdown( &format!("```{}\n{}\n```", code.language, code.value), - 1.5, + 1.8, config, ) } diff --git a/lapce-app/src/palette.rs b/lapce-app/src/palette.rs index 8c97c677be..2fdc31fc4c 100644 --- a/lapce-app/src/palette.rs +++ b/lapce-app/src/palette.rs @@ -15,8 +15,12 @@ use crossbeam_channel::{Receiver, Sender, TryRecvError}; use floem::{ ext_event::{create_ext_action, create_signal_from_channel}, keyboard::Modifiers, - reactive::{use_context, ReadSignal, RwSignal, Scope}, + reactive::{ + use_context, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, + SignalWith, + }, }; +use im::Vector; use itertools::Itertools; use lapce_core::{ buffer::rope_text::RopeText, command::FocusCommand, language::LapceLanguage, @@ -25,7 +29,7 @@ use lapce_core::{ }; use lapce_rpc::proxy::ProxyResponse; use lapce_xi_rope::Rope; -use lsp_types::DocumentSymbolResponse; +use lsp_types::{DocumentSymbol, DocumentSymbolResponse}; use nucleo::Utf32Str; use strum::{EnumMessage, IntoEnumIterator}; use tracing::error; @@ -55,7 +59,7 @@ use crate::{ pub mod item; pub mod kind; -const DEFAULT_RUN_TOML: &str = include_str!("../../defaults/run.toml"); +pub const DEFAULT_RUN_TOML: &str = include_str!("../../defaults/run.toml"); #[derive(Clone, PartialEq, Eq)] pub enum PaletteStatus { @@ -151,7 +155,11 @@ impl PaletteData { let run_id = run_id.get_untracked(); let preselect_index = preselect_index.try_update(|i| i.take()).unwrap(); - let _ = tx.send((run_id, input.input, items, preselect_index)); + if let Err(err) = + tx.send((run_id, input.input, items, preselect_index)) + { + tracing::error!("{:?}", err); + } }); } // this effect only monitors input change @@ -163,7 +171,9 @@ impl PaletteData { } let items = items.get_untracked(); let run_id = run_id.get_untracked(); - let _ = tx.send((run_id, input.input, items, None)); + if let Err(err) = tx.send((run_id, input.input, items, None)) { + tracing::error!("{:?}", err); + } kind }); } @@ -358,6 +368,7 @@ impl PaletteData { PaletteKind::File | PaletteKind::DiffFiles => { self.get_files(); } + PaletteKind::HelpAndFile => self.get_palette_help_and_file(), PaletteKind::Line => { self.get_lines(); } @@ -407,7 +418,12 @@ impl PaletteData { /// Initialize the palette with a list of the available palette kinds. fn get_palette_help(&self) { - let items = PaletteKind::iter() + let items = self.get_palette_help_items(); + self.items.set(items); + } + + fn get_palette_help_items(&self) -> Vector { + PaletteKind::iter() .filter_map(|kind| { // Don't include PaletteHelp as the user is already here. (kind != PaletteKind::PaletteHelp) @@ -434,13 +450,18 @@ impl PaletteData { indices: vec![], } }) - .collect(); + .collect() + } - self.items.set(items); + fn get_palette_help_and_file(&self) { + let help_items: Vector = self.get_palette_help_items(); + self.get_files_and_prepend(Some(help_items)); } - /// Initialize the palette with the files in the current workspace. - fn get_files(&self) { + // get the files in the current workspace + // and prepend items if prepend is some + // e.g. help_and_file + fn get_files_and_prepend(&self, prepend: Option>) { let workspace = self.workspace.clone(); let set_items = self.items.write_only(); let send = @@ -467,7 +488,12 @@ impl PaletteData { } }) .collect::>(); - set_items.set(items); + let mut new_items = im::Vector::new(); + if let Some(prepend) = prepend { + new_items.append(prepend); + } + new_items.append(items); + set_items.set(new_items); }); self.common.proxy.get_files(move |result| { if let Ok(ProxyResponse::GetFilesResponse { items }) = result { @@ -476,6 +502,11 @@ impl PaletteData { }); } + /// Initialize the palette with the files in the current workspace. + fn get_files(&self) { + self.get_files_and_prepend(None); + } + /// Initialize the palette with the lines in the current document. fn get_lines(&self) { let editor = self.main_split.active_editor.get_untracked(); @@ -646,42 +677,7 @@ impl PaletteData { let set_items = self.items.write_only(); let send = create_ext_action(self.common.scope, move |result| { if let Ok(ProxyResponse::GetDocumentSymbols { resp }) = result { - let items: im::Vector = match resp { - DocumentSymbolResponse::Flat(symbols) => symbols - .iter() - .map(|s| { - let mut filter_text = s.name.clone(); - if let Some(container_name) = s.container_name.as_ref() { - filter_text += container_name; - } - PaletteItem { - content: PaletteItemContent::DocumentSymbol { - kind: s.kind, - name: s.name.clone(), - range: s.location.range, - container_name: s.container_name.clone(), - }, - filter_text, - score: 0, - indices: Vec::new(), - } - }) - .collect(), - DocumentSymbolResponse::Nested(symbols) => symbols - .iter() - .map(|s| PaletteItem { - content: PaletteItemContent::DocumentSymbol { - kind: s.kind, - name: s.name.clone(), - range: s.range, - container_name: None, - }, - filter_text: s.name.clone(), - score: 0, - indices: Vec::new(), - }) - .collect(), - }; + let items = Self::format_document_symbol_resp(resp); set_items.set(items); } else { set_items.update(|items| items.clear()); @@ -693,6 +689,64 @@ impl PaletteData { }); } + fn format_document_symbol_resp( + resp: DocumentSymbolResponse, + ) -> im::Vector { + match resp { + DocumentSymbolResponse::Flat(symbols) => symbols + .iter() + .map(|s| { + let mut filter_text = s.name.clone(); + if let Some(container_name) = s.container_name.as_ref() { + filter_text += container_name; + } + PaletteItem { + content: PaletteItemContent::DocumentSymbol { + kind: s.kind, + name: s.name.replace('\n', "↵"), + range: s.location.range, + container_name: s.container_name.clone(), + }, + filter_text, + score: 0, + indices: Vec::new(), + } + }) + .collect(), + DocumentSymbolResponse::Nested(symbols) => { + let mut items = im::Vector::new(); + for s in symbols { + Self::format_document_symbol(&mut items, None, s) + } + items + } + } + } + + fn format_document_symbol( + items: &mut im::Vector, + parent: Option, + s: DocumentSymbol, + ) { + items.push_back(PaletteItem { + content: PaletteItemContent::DocumentSymbol { + kind: s.kind, + name: s.name.replace('\n', "↵"), + range: s.range, + container_name: parent, + }, + filter_text: s.name.clone(), + score: 0, + indices: Vec::new(), + }); + if let Some(children) = s.children { + let parent = Some(s.name.replace('\n', "↵")); + for child in children { + Self::format_document_symbol(items, parent.clone(), child); + } + } + } + fn get_workspace_symbols(&self) { let input = self.input.get_untracked().input; @@ -1506,6 +1560,8 @@ impl PaletteData { // NOTE: We collect into a Vec to sort as we are hitting a worst-case behavior in // `im::Vector` that can lead to a stack overflow! let mut filtered_items = Vec::new(); + let mut indices = Vec::new(); + let mut filter_text_buf = Vec::new(); for i in &items { // If the run id has ever changed, then we'll just bail out of this filtering to avoid // wasting effort. This would happen, for example, on the user continuing to type. @@ -1513,14 +1569,14 @@ impl PaletteData { return None; } - let mut indices = Vec::new(); - let mut filter_text_buf = Vec::new(); + indices.clear(); + filter_text_buf.clear(); let filter_text = Utf32Str::new(&i.filter_text, &mut filter_text_buf); if let Some(score) = pattern.indices(filter_text, matcher, &mut indices) { let mut item = i.clone(); item.score = score; - item.indices = indices.into_iter().map(|i| i as usize).collect(); + item.indices = indices.iter().map(|i| *i as usize).collect(); filtered_items.push(item); } } @@ -1582,12 +1638,14 @@ impl PaletteData { items, &mut matcher, ) { - let _ = resp_tx.send(( + if let Err(err) = resp_tx.send(( current_run_id, input, filtered_items, preselect_index, - )); + )) { + tracing::error!("{:?}", err); + } } } else { return; diff --git a/lapce-app/src/palette/kind.rs b/lapce-app/src/palette/kind.rs index 5cd123073d..1a1b894134 100644 --- a/lapce-app/src/palette/kind.rs +++ b/lapce-app/src/palette/kind.rs @@ -23,6 +23,7 @@ pub enum PaletteKind { SCMReferences, TerminalProfile, DiffFiles, + HelpAndFile, } impl PaletteKind { @@ -46,6 +47,7 @@ impl PaletteKind { | PaletteKind::Language | PaletteKind::LineEnding | PaletteKind::SCMReferences + | PaletteKind::HelpAndFile | PaletteKind::DiffFiles => "", #[cfg(windows)] PaletteKind::WslHost => "", @@ -80,6 +82,9 @@ impl PaletteKind { PaletteKind::Workspace => Some(LapceWorkbenchCommand::PaletteWorkspace), PaletteKind::Command => Some(LapceWorkbenchCommand::PaletteCommand), PaletteKind::File => Some(LapceWorkbenchCommand::Palette), + PaletteKind::HelpAndFile => { + Some(LapceWorkbenchCommand::PaletteHelpAndFile) + } PaletteKind::Reference => None, // InternalCommand::PaletteReferences PaletteKind::SshHost => Some(LapceWorkbenchCommand::ConnectSshHost), #[cfg(windows)] @@ -124,7 +129,7 @@ impl PaletteKind { | PaletteKind::IconTheme | PaletteKind::Language | PaletteKind::LineEnding - | PaletteKind::SCMReferences + | PaletteKind::SCMReferences | PaletteKind::HelpAndFile | PaletteKind::DiffFiles => input, PaletteKind::PaletteHelp | PaletteKind::Command @@ -141,9 +146,17 @@ impl PaletteKind { /// Get the palette kind that it should be considered as based on the current /// [`PaletteKind`] and the current input. pub fn get_palette_kind(&self, input: &str) -> PaletteKind { - if self != &PaletteKind::File && self.symbol() == "" { + if self == &PaletteKind::HelpAndFile && input.is_empty() { + return *self; + } + + if self != &PaletteKind::File + && self != &PaletteKind::HelpAndFile + && self.symbol() == "" + { return *self; } + PaletteKind::from_input(input) } } diff --git a/lapce-app/src/panel/call_hierarchy_view.rs b/lapce-app/src/panel/call_hierarchy_view.rs index 6e76bbf4c4..8d90c6347b 100644 --- a/lapce-app/src/panel/call_hierarchy_view.rs +++ b/lapce-app/src/panel/call_hierarchy_view.rs @@ -1,14 +1,13 @@ use std::{ops::AddAssign, rc::Rc}; use floem::{ - event::EventPropagation, - reactive::RwSignal, - style::{AlignItems, CursorStyle}, + reactive::{RwSignal, SignalGet, SignalUpdate, SignalWith}, + style::CursorStyle, views::{ - container, label, scroll, stack, svg, virtual_stack, Decorators, + container, empty, label, scroll, stack, svg, virtual_stack, Decorators, VirtualDirection, VirtualItemSize, VirtualVector, }, - View, ViewId, + IntoView, View, ViewId, }; use lsp_types::{CallHierarchyItem, Range}; @@ -17,8 +16,7 @@ use crate::{ command::InternalCommand, config::{color::LapceColor, icon::LapceIcons}, editor::location::EditorLocation, - window_tab::CommonData, - window_tab::WindowTabData, + window_tab::{CommonData, WindowTabData}, }; #[derive(Clone, Debug)] @@ -141,24 +139,26 @@ pub fn show_hierarchy_panel( move |(_, level, rw_data)| { let data = rw_data.get_untracked(); let open = data.open; + let kind = data.item.kind; stack(( - svg(move || { - let config = config.get(); - let svg_str = match open.get() { - true => LapceIcons::ITEM_OPENED, - false => LapceIcons::ITEM_CLOSED, - }; - config.ui_svg(svg_str) - }) - .style(move |s| { - let config = config.get(); - let size = config.ui.icon_size() as f32; - s.size(size, size) - .flex_shrink(0.0) - .margin_left(10.0) - .margin_right(6.0) - .color(config.color(LapceColor::LAPCE_ICON_ACTIVE)) - }).on_click_stop({ + container( + svg(move || { + let config = config.get(); + let svg_str = match open.get() { + true => LapceIcons::ITEM_OPENED, + false => LapceIcons::ITEM_CLOSED, + }; + config.ui_svg(svg_str) + }) + .style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.size(size, size) + .color(config.color(LapceColor::LAPCE_ICON_ACTIVE)) + }) + ) + .style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0)) + .on_click_stop({ let window_tab_data = window_tab_data.clone(); move |_x| { open.update(|x| { @@ -173,27 +173,37 @@ pub fn show_hierarchy_panel( } } }), - container( - label(move || { - format!( - "{} {} {}", - data.item.name, - data.item.detail.as_deref().unwrap_or(""), data.from_range.start.line - ) - }) - .style(move |s| { - s.flex_grow(1.0) - .height(ui_line_height.get()) - .selectable(false) - .align_items(AlignItems::Center) + svg(move || { + let config = config.get(); + config + .symbol_svg(&kind) + .unwrap_or_else(|| config.ui_svg(LapceIcons::FILE)) + }).style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.min_width(size) + .size(size, size) + .margin_right(5.0) + .color(config.symbol_color(&kind).unwrap_or_else(|| { + config.color(LapceColor::LAPCE_ICON_ACTIVE) + })) }), - ) - .style(|s| s.flex_grow(1.0).padding(0.0).margin(0.0)), + data.item.name.clone().into_view(), + if data.item.detail.is_some() { + label(move || { + data.item.detail.clone().unwrap_or_default().replace('\n', "↵") + }).style(move |s| s.margin_left(6.0) + .color(config.get().color(LapceColor::EDITOR_DIM)) + ).into_any() + } else { + empty().into_any() + }, )) .style(move |s| { s.padding_right(5.0) + .height(ui_line_height.get()) .padding_left((level * 10) as f32) - .align_items(AlignItems::Center) + .items_center() .hover(|s| { s.background( config @@ -203,13 +213,15 @@ pub fn show_hierarchy_panel( .cursor(CursorStyle::Pointer) }) }) - .on_double_click({ + .on_click_stop({ let window_tab_data = window_tab_data.clone(); let data = rw_data; move |_| { - window_tab_data.common.internal_command.send( - InternalCommand::CallHierarchyIncoming { item_id: rw_data.get_untracked().view_id }, - ); + if !rw_data.get_untracked().init { + window_tab_data.common.internal_command.send( + InternalCommand::CallHierarchyIncoming { item_id: rw_data.get_untracked().view_id }, + ); + } let data = data.get_untracked(); if let Ok(path) = data.item.uri.to_file_path() { window_tab_data @@ -223,14 +235,13 @@ pub fn show_hierarchy_panel( same_editor_tab: false, } }); } - EventPropagation::Stop } }) }, ) - .style(|s| s.flex_col().align_items(AlignItems::Stretch).width_full()), + .style(|s| s.flex_col().absolute().min_width_full()), ) - .style(|s| s.size_full()) + .style(|s| s.absolute().size_full()) .scroll_to(move || { if let Some(line) = scroll_to_line.get() { let line_height = ui_line_height.get(); diff --git a/lapce-app/src/panel/data.rs b/lapce-app/src/panel/data.rs index edc270678e..4957510de0 100644 --- a/lapce-app/src/panel/data.rs +++ b/lapce-app/src/panel/data.rs @@ -2,7 +2,9 @@ use std::{rc::Rc, sync::Arc}; use floem::{ kurbo::Size, - reactive::{use_context, Memo, RwSignal, Scope}, + reactive::{ + use_context, Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith, + }, }; use serde::{Deserialize, Serialize}; @@ -35,9 +37,15 @@ pub fn default_panel_order() -> PanelOrder { PanelKind::Terminal, PanelKind::Search, PanelKind::Problem, - PanelKind::CallHierarchy + PanelKind::CallHierarchy, + PanelKind::References, + PanelKind::Implementation ], ); + order.insert( + PanelPosition::RightTop, + im::vector![PanelKind::DocumentSymbol,], + ); order } diff --git a/lapce-app/src/panel/debug_view.rs b/lapce-app/src/panel/debug_view.rs index a88bc29088..4c796de4ef 100644 --- a/lapce-app/src/panel/debug_view.rs +++ b/lapce-app/src/panel/debug_view.rs @@ -3,7 +3,9 @@ use std::{rc::Rc, sync::Arc}; use floem::{ event::EventListener, peniko::Color, - reactive::{create_rw_signal, ReadSignal, RwSignal}, + reactive::{ + create_rw_signal, ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, + }, style::CursorStyle, text::Style as FontStyle, views::{ diff --git a/lapce-app/src/panel/document_symbol.rs b/lapce-app/src/panel/document_symbol.rs new file mode 100644 index 0000000000..6d887a64e4 --- /dev/null +++ b/lapce-app/src/panel/document_symbol.rs @@ -0,0 +1,305 @@ +use std::{ops::AddAssign, path::PathBuf, rc::Rc}; + +use floem::{ + peniko::Color, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, + style::CursorStyle, + views::{ + container, editor::id::Id, label, scroll, stack, svg, virtual_stack, + Decorators, VirtualDirection, VirtualItemSize, VirtualVector, + }, + View, +}; +use lsp_types::DocumentSymbol; + +use super::position::PanelPosition; +use crate::{ + command::InternalCommand, + config::{color::LapceColor, icon::LapceIcons}, + editor::location::EditorLocation, + window_tab::WindowTabData, +}; + +#[derive(Clone, Debug)] +pub struct SymbolData { + pub items: Vec>, + pub path: PathBuf, +} + +impl SymbolData { + fn get_children( + &self, + min: usize, + max: usize, + ) -> Vec<( + usize, + usize, + Rc, + RwSignal, + )> { + let mut children = Vec::new(); + let path = Rc::new(self.path.clone()); + let level: usize = 0; + let mut next = 0; + for item in &self.items { + if next >= max { + return children; + } + let child_children = + get_children(*item, &mut next, min, max, level, path.clone()); + children.extend(child_children); + } + children + } +} + +#[derive(Debug, Clone)] +pub struct SymbolInformationItemData { + pub id: Id, + pub name: String, + pub detail: Option, + pub item: DocumentSymbol, + pub open: RwSignal, + pub children: Vec>, +} + +impl From<(DocumentSymbol, Scope)> for SymbolInformationItemData { + fn from((mut item, cx): (DocumentSymbol, Scope)) -> Self { + let children = if let Some(children) = item.children.take() { + children + .into_iter() + .map(|x| cx.create_rw_signal(Self::from((x, cx)))) + .collect() + } else { + Vec::with_capacity(0) + }; + Self { + id: Id::next(), + name: item.name.clone(), + detail: item.detail.clone(), + item, + open: cx.create_rw_signal(true), + children, + } + } +} + +impl SymbolInformationItemData { + pub fn child_count(&self) -> usize { + let mut count = 1; + if self.open.get() { + for child in &self.children { + count += child.with(|x| x.child_count()) + } + } + count + } +} + +fn get_children( + data: RwSignal, + next: &mut usize, + min: usize, + max: usize, + level: usize, + path: Rc, +) -> Vec<( + usize, + usize, + Rc, + RwSignal, +)> { + let mut children = Vec::new(); + if *next >= min && *next < max { + children.push((*next, level, path.clone(), data)); + } else if *next >= max { + return children; + } + next.add_assign(1); + if data.get_untracked().open.get() { + for child in data.get().children { + let child_children = + get_children(child, next, min, max, level + 1, path.clone()); + children.extend(child_children); + if *next > max { + break; + } + } + } + children +} + +pub struct VirtualList { + root: Option>>, +} + +impl VirtualList { + pub fn new(root: Option>>) -> Self { + Self { root } + } +} + +impl + VirtualVector<( + usize, + usize, + Rc, + RwSignal, + )> for VirtualList +{ + fn total_len(&self) -> usize { + if let Some(root) = self.root.as_ref().and_then(|x| x.get()) { + let len = root.items.iter().fold(0, |mut x, item| { + x += item.get_untracked().child_count(); + x + }); + len + } else { + 0 + } + } + + fn slice( + &mut self, + range: std::ops::Range, + ) -> impl Iterator< + Item = ( + usize, + usize, + Rc, + RwSignal, + ), + > { + if let Some(root) = self.root.as_ref().and_then(|x| x.get()) { + let min = range.start; + let max = range.end; + let children = root.get_children(min, max); + children.into_iter() + } else { + Vec::new().into_iter() + } + } +} + +pub fn symbol_panel( + window_tab_data: Rc, + _position: PanelPosition, +) -> impl View { + let config = window_tab_data.common.config; + let ui_line_height = window_tab_data.common.ui_line_height; + scroll( + virtual_stack( + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(move || ui_line_height.get())), + { + let window_tab_data = window_tab_data.clone(); + move || { + let editor = window_tab_data.main_split.get_active_editor(); + VirtualList::new(editor.map(|x| x.doc().document_symbol_data)) + } + }, + move |(_, _, _, item)| item.get_untracked().id, + move |(_, level, path, rw_data)| { + let data = rw_data.get_untracked(); + let open = data.open; + let has_child = !data.children.is_empty(); + let kind = data.item.kind; + stack(( + container( + svg(move || { + let config = config.get(); + let svg_str = match open.get() { + true => LapceIcons::ITEM_OPENED, + false => LapceIcons::ITEM_CLOSED, + }; + config.ui_svg(svg_str) + }) + .style(move |s| { + let config = config.get(); + let color = if has_child { + config.color(LapceColor::LAPCE_ICON_ACTIVE) + } else { + Color::TRANSPARENT + }; + let size = config.ui.icon_size() as f32; + s.size(size, size) + .color(color) + }) + ).style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0)) + .on_click_stop({ + move |_x| { + if has_child { + open.update(|x| { + *x = !*x; + }); + } + } + }), + svg(move || { + let config = config.get(); + config + .symbol_svg(&kind) + .unwrap_or_else(|| config.ui_svg(LapceIcons::FILE)) + }).style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.min_width(size) + .size(size, size) + .margin_right(5.0) + .color(config.symbol_color(&kind).unwrap_or_else(|| { + config.color(LapceColor::LAPCE_ICON_ACTIVE) + })) + }), + label(move || { + data.name.replace('\n', "↵") + }) + .style(move |s| { + s.selectable(false) + }), + label(move || { + data.detail.clone().unwrap_or_default() + }).style(move |s| s.margin_left(6.0) + .color(config.get().color(LapceColor::EDITOR_DIM)) + .selectable(false) + .apply_if( + data.item.detail.clone().is_none(), + |s| s.hide()) + ), + )) + .style(move |s| { + s.padding_right(5.0) + .padding_left((level * 10) as f32) + .items_center() + .height(ui_line_height.get()) + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }) + .on_click_stop({ + let window_tab_data = window_tab_data.clone(); + let data = rw_data; + move |_| { + let data = data.get_untracked(); + window_tab_data + .common + .internal_command + .send(InternalCommand::GoToLocation { location: EditorLocation { + path: path.to_path_buf(), + position: Some(crate::editor::location::EditorPosition::Position(data.item.selection_range.start)), + scroll_offset: None, + ignore_unconfirmed: false, + same_editor_tab: false, + } }); + } + }) + }, + ) + .style(|s| s.flex_col().absolute().min_width_full()), + ) + .style(|s| s.absolute().size_full()) +} diff --git a/lapce-app/src/panel/global_search_view.rs b/lapce-app/src/panel/global_search_view.rs index ccbbf0eb68..23636b011e 100644 --- a/lapce-app/src/panel/global_search_view.rs +++ b/lapce-app/src/panel/global_search_view.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, rc::Rc, sync::Arc}; use floem::{ event::EventListener, - reactive::ReadSignal, + reactive::{ReadSignal, SignalGet, SignalUpdate}, style::{CursorStyle, Style}, views::{ container, label, scroll, stack, svg, virtual_stack, Decorators, diff --git a/lapce-app/src/panel/implementation_view.rs b/lapce-app/src/panel/implementation_view.rs new file mode 100644 index 0000000000..b843e25401 --- /dev/null +++ b/lapce-app/src/panel/implementation_view.rs @@ -0,0 +1,236 @@ +use std::rc::Rc; + +use floem::{ + reactive::{Scope, SignalGet, SignalUpdate}, + style::CursorStyle, + views::{ + container, label, scroll, stack, svg, virtual_stack, Decorators, + VirtualDirection, VirtualItemSize, + }, + IntoView, View, ViewId, +}; +use im::HashMap; +use lsp_types::{request::GotoImplementationResponse, SymbolKind}; + +use super::position::PanelPosition; +use crate::{ + command::InternalCommand, + config::{color::LapceColor, icon::LapceIcons}, + editor::location::EditorLocation, + panel::references_view::{Reference, ReferenceLocation, ReferencesRoot}, + window_tab::WindowTabData, +}; + +pub fn implementation_panel( + window_tab_data: Rc, + _position: PanelPosition, +) -> impl View { + let main_split = window_tab_data.main_split.clone(); + let config = window_tab_data.common.config; + let ui_line_height = window_tab_data.common.ui_line_height; + scroll( + virtual_stack( + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(move || ui_line_height.get())), + move || main_split.implementations.get(), + move |(_, _, data)| data.view_id(), + move |(_, level, rw_data)| { + match rw_data { + ReferenceLocation::File { path, open, .. } => { + stack(( + container(svg(move || { + let config = config.get(); + let svg_str = match open.get() { + true => LapceIcons::ITEM_OPENED, + false => LapceIcons::ITEM_CLOSED, + }; + config.ui_svg(svg_str) + }) + .style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.size(size, size) + .color(config.color(LapceColor::LAPCE_ICON_ACTIVE)) + }) + ) + .style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0)) + .on_click_stop({ + move |_x| { + open.update(|x| { + *x = !*x; + }); + } + }), + svg(move || { + let config = config.get(); + config + .symbol_svg(&SymbolKind::FILE) + .unwrap_or_else(|| config.ui_svg(LapceIcons::FILE)) + }).style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.min_width(size) + .size(size, size) + .margin_right(5.0) + .color(config.symbol_color(&SymbolKind::FILE).unwrap_or_else(|| { + config.color(LapceColor::LAPCE_ICON_ACTIVE) + })) + }), + label(move || { + format!("{:?}", path) + }).style(move |s| s.margin_left(6.0) + .color(config.get().color(LapceColor::EDITOR_DIM)) + ).into_any() + )) + .style(move |s| { + s.padding_right(5.0) + .height(ui_line_height.get()) + .padding_left((level * 10) as f32) + .items_center() + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }) + } + ReferenceLocation::Line { path, range, .. } => { + stack( + ( + container( + label( + move || { + format!("{}", range.line + 1) + }) + .style(move |s| s.margin_left(6.0) + .color(config.get().color(LapceColor::EDITOR_DIM)) + ).into_any() + ) + .style(move |s| { + s.padding_right(5.0) + .height(ui_line_height.get()) + .padding_left((level * 20) as f32) + .items_center() + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }), + ) + ).on_click_stop({ + let window_tab_data = window_tab_data.clone(); + move |_| + { + let range = range; + window_tab_data + .common + .internal_command + .send(InternalCommand::GoToLocation { + location: EditorLocation { + path: path.clone(), + position: Some(crate::editor::location::EditorPosition::Position(range)), + scroll_offset: None, + ignore_unconfirmed: false, + same_editor_tab: false, + } + }); + } + }) + } + }.style(move |s| { + s.padding_right(5.0) + .height(ui_line_height.get()) + .padding_left((level * 10) as f32) + .items_center() + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }) + }, + ) + .style(|s| s.flex_col().absolute().min_width_full()), + ) + .style(|s| s.absolute().size_full()) + .debug_name("references panel") +} + +pub fn init_implementation_root( + resp: Option, + scope: Scope, +) -> ReferencesRoot { + let Some(resp) = resp else { + return ReferencesRoot::default(); + }; + let mut refs_map = HashMap::new(); + match resp { + GotoImplementationResponse::Scalar(local) => { + if let Ok(path) = local.uri.to_file_path() { + let entry = refs_map.entry(path.clone()).or_insert(Vec::new()); + (*entry).push(Reference::Line { + location: ReferenceLocation::Line { + view_id: ViewId::new(), + path, + range: local.range.start, + }, + }) + } + } + GotoImplementationResponse::Array(items) => { + for item in items { + if let Ok(path) = item.uri.to_file_path() { + let entry = refs_map.entry(path.clone()).or_insert(Vec::new()); + (*entry).push(Reference::Line { + location: ReferenceLocation::Line { + view_id: ViewId::new(), + path, + range: item.range.start, + }, + }) + } + } + } + GotoImplementationResponse::Link(items) => { + for item in items { + if let Ok(path) = item.target_uri.to_file_path() { + let entry = refs_map.entry(path.clone()).or_insert(Vec::new()); + (*entry).push(Reference::Line { + location: ReferenceLocation::Line { + view_id: ViewId::new(), + path, + range: item.target_range.start, + }, + }) + } + } + } + } + + let mut refs = Vec::new(); + for (path, items) in refs_map { + let open = scope.create_rw_signal(true); + let ref_item = Reference::File { + location: ReferenceLocation::File { + open, + path, + view_id: ViewId::new(), + }, + children: items, + open, + }; + refs.push(ref_item); + } + tracing::debug!("children {}", refs.len()); + ReferencesRoot { children: refs } +} diff --git a/lapce-app/src/panel/kind.rs b/lapce-app/src/panel/kind.rs index 671571ebbf..695990e07c 100644 --- a/lapce-app/src/panel/kind.rs +++ b/lapce-app/src/panel/kind.rs @@ -16,6 +16,9 @@ pub enum PanelKind { Problem, Debug, CallHierarchy, + DocumentSymbol, + References, + Implementation, } impl PanelKind { @@ -28,7 +31,10 @@ impl PanelKind { PanelKind::Search => LapceIcons::SEARCH, PanelKind::Problem => LapceIcons::PROBLEM, PanelKind::Debug => LapceIcons::DEBUG, - PanelKind::CallHierarchy => LapceIcons::LINK, + PanelKind::CallHierarchy => LapceIcons::TYPE_HIERARCHY, + PanelKind::DocumentSymbol => LapceIcons::DOCUMENT_SYMBOL, + PanelKind::References => LapceIcons::REFERENCES, + PanelKind::Implementation => LapceIcons::IMPLEMENTATION, } } @@ -41,4 +47,20 @@ impl PanelKind { } None } + + pub fn default_position(&self) -> PanelPosition { + match self { + PanelKind::Terminal => PanelPosition::BottomLeft, + PanelKind::FileExplorer => PanelPosition::LeftTop, + PanelKind::SourceControl => PanelPosition::LeftTop, + PanelKind::Plugin => PanelPosition::LeftTop, + PanelKind::Search => PanelPosition::BottomLeft, + PanelKind::Problem => PanelPosition::BottomLeft, + PanelKind::Debug => PanelPosition::LeftTop, + PanelKind::CallHierarchy => PanelPosition::BottomLeft, + PanelKind::DocumentSymbol => PanelPosition::RightTop, + PanelKind::References => PanelPosition::BottomLeft, + PanelKind::Implementation => PanelPosition::BottomLeft, + } + } } diff --git a/lapce-app/src/panel/mod.rs b/lapce-app/src/panel/mod.rs index a539e9f9f3..016192ef88 100644 --- a/lapce-app/src/panel/mod.rs +++ b/lapce-app/src/panel/mod.rs @@ -1,11 +1,14 @@ pub mod call_hierarchy_view; pub mod data; pub mod debug_view; +pub mod document_symbol; pub mod global_search_view; +pub mod implementation_view; pub mod kind; pub mod plugin_view; pub mod position; pub mod problem_view; +pub mod references_view; pub mod source_control_view; pub mod style; pub mod terminal_view; diff --git a/lapce-app/src/panel/plugin_view.rs b/lapce-app/src/panel/plugin_view.rs index 3315f3ddba..29ac62767d 100644 --- a/lapce-app/src/panel/plugin_view.rs +++ b/lapce-app/src/panel/plugin_view.rs @@ -3,13 +3,13 @@ use std::{ops::Range, rc::Rc}; use floem::{ event::EventListener, peniko::kurbo::{Point, Rect, Size}, - reactive::{create_memo, create_rw_signal, RwSignal}, + reactive::{ + create_memo, create_rw_signal, RwSignal, SignalGet, SignalUpdate, SignalWith, + }, style::CursorStyle, views::{ - container, dyn_container, img, label, - scroll::{scroll, HideBar}, - stack, svg, virtual_stack, Decorators, VirtualDirection, VirtualItemSize, - VirtualVector, + container, dyn_container, img, label, scroll::scroll, stack, svg, + virtual_stack, Decorators, VirtualDirection, VirtualItemSize, VirtualVector, }, IntoView, View, }; @@ -363,10 +363,10 @@ fn available_view(plugin: PluginData, core_rpc: CoreRpcHandler) -> impl View { .on_event_cont(EventListener::PointerDown, move |_| { focus.set(Focus::Panel(PanelKind::Plugin)); }) + .scroll_style(|s| s.hide_bars(true)) .style(move |s| { let config = config.get(); - s.set(HideBar, true) - .width_pct(100.0) + s.width_pct(100.0) .cursor(CursorStyle::Text) .items_center() .background(config.color(LapceColor::EDITOR_BACKGROUND)) diff --git a/lapce-app/src/panel/problem_view.rs b/lapce-app/src/panel/problem_view.rs index 1e413b7683..0bf51e8b65 100644 --- a/lapce-app/src/panel/problem_view.rs +++ b/lapce-app/src/panel/problem_view.rs @@ -2,7 +2,10 @@ use std::{path::PathBuf, rc::Rc, sync::Arc}; use floem::{ peniko::Color, - reactive::{create_effect, create_rw_signal, ReadSignal}, + reactive::{ + create_effect, create_rw_signal, ReadSignal, SignalGet, SignalUpdate, + SignalWith, + }, style::{CursorStyle, Style}, views::{container, dyn_stack, label, scroll, stack, svg, Decorators}, View, diff --git a/lapce-app/src/panel/references_view.rs b/lapce-app/src/panel/references_view.rs new file mode 100644 index 0000000000..c8604daca4 --- /dev/null +++ b/lapce-app/src/panel/references_view.rs @@ -0,0 +1,660 @@ +use std::{ops::AddAssign, path::PathBuf, rc::Rc}; + +use floem::{ + reactive::{RwSignal, Scope, SignalGet, SignalUpdate}, + style::CursorStyle, + views::{ + container, label, scroll, stack, svg, virtual_stack, Decorators, + VirtualDirection, VirtualItemSize, VirtualVector, + }, + IntoView, View, ViewId, +}; +use im::HashMap; +use lsp_types::{Location, Position, SymbolKind}; + +use super::position::PanelPosition; +use crate::{ + command::InternalCommand, + config::{color::LapceColor, icon::LapceIcons}, + editor::location::EditorLocation, + window_tab::WindowTabData, +}; + +pub fn references_panel( + window_tab_data: Rc, + _position: PanelPosition, +) -> impl View { + let main_split = window_tab_data.main_split.clone(); + let config = window_tab_data.common.config; + let ui_line_height = window_tab_data.common.ui_line_height; + scroll( + virtual_stack( + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(move || ui_line_height.get())), + move || main_split.references.get(), + move |(_, _, data)| data.view_id(), + move |(_, level, rw_data)| { + match rw_data { + ReferenceLocation::File { path, open, .. } => { + stack(( + container(svg(move || { + let config = config.get(); + let svg_str = match open.get() { + true => LapceIcons::ITEM_OPENED, + false => LapceIcons::ITEM_CLOSED, + }; + config.ui_svg(svg_str) + }) + .style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.size(size, size) + .color(config.color(LapceColor::LAPCE_ICON_ACTIVE)) + }) + ) + .style(|s| s.padding(4.0).margin_left(6.0).margin_right(2.0)) + .on_click_stop({ + move |_x| { + open.update(|x| { + *x = !*x; + }); + } + }), + svg(move || { + let config = config.get(); + config + .symbol_svg(&SymbolKind::FILE) + .unwrap_or_else(|| config.ui_svg(LapceIcons::FILE)) + }).style(move |s| { + let config = config.get(); + let size = config.ui.icon_size() as f32; + s.min_width(size) + .size(size, size) + .margin_right(5.0) + .color(config.symbol_color(&SymbolKind::FILE).unwrap_or_else(|| { + config.color(LapceColor::LAPCE_ICON_ACTIVE) + })) + }), + label(move || { + format!("{:?}", path) + }).style(move |s| s.margin_left(6.0) + .color(config.get().color(LapceColor::EDITOR_DIM)) + ).into_any() + )) + .style(move |s| { + s.padding_right(5.0) + .height(ui_line_height.get()) + .padding_left((level * 10) as f32) + .items_center() + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }) + } + ReferenceLocation::Line { path, range, .. } => { + stack( + ( + container( + label( + move || { + format!("{}", range.line + 1) + }) + .style(move |s| s.margin_left(6.0) + .color(config.get().color(LapceColor::EDITOR_DIM)) + ).into_any() + ) + .style(move |s| { + s.padding_right(5.0) + .height(ui_line_height.get()) + .padding_left((level * 20) as f32) + .items_center() + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }), + ) + ).on_click_stop({ + let window_tab_data = window_tab_data.clone(); + move |_| + { + let range = range; + window_tab_data + .common + .internal_command + .send(InternalCommand::GoToLocation { + location: EditorLocation { + path: path.clone(), + position: Some(crate::editor::location::EditorPosition::Position(range)), + scroll_offset: None, + ignore_unconfirmed: false, + same_editor_tab: false, + } + }); + } + }) + } + }.style(move |s| { + s.padding_right(5.0) + .height(ui_line_height.get()) + .padding_left((level * 10) as f32) + .items_center() + .hover(|s| { + s.background( + config + .get() + .color(LapceColor::PANEL_HOVERED_BACKGROUND), + ) + .cursor(CursorStyle::Pointer) + }) + }) + }, + ) + .style(|s| s.flex_col().absolute().min_width_full()), + ) + .style(|s| s.absolute().size_full()) + .debug_name("references panel") +} + +pub fn init_references_root(items: Vec, scope: Scope) -> ReferencesRoot { + let mut refs_map = HashMap::new(); + for item in items { + if let Ok(path) = item.uri.to_file_path() { + let entry = refs_map.entry(path.clone()).or_insert(Vec::new()); + (*entry).push(Reference::Line { + location: ReferenceLocation::Line { + view_id: ViewId::new(), + path, + range: item.range.start, + }, + }) + } + } + let mut refs = Vec::new(); + for (path, items) in refs_map { + let open = scope.create_rw_signal(true); + let ref_item = Reference::File { + location: crate::panel::references_view::ReferenceLocation::File { + open, + path, + view_id: ViewId::new(), + }, + children: items, + open, + }; + refs.push(ref_item); + } + ReferencesRoot { children: refs } +} + +#[derive(Clone, Default)] +pub struct ReferencesRoot { + pub(crate) children: Vec, +} +impl VirtualVector<(usize, usize, ReferenceLocation)> for ReferencesRoot { + fn total_len(&self) -> usize { + self.total() + } + + fn slice( + &mut self, + range: std::ops::Range, + ) -> impl Iterator { + let min = range.start; + let max = range.end; + let children = self.get_children(&mut 0, min, max, 0); + children.into_iter() + } +} + +impl ReferencesRoot { + pub fn total(&self) -> usize { + let mut total = 0; + for child in &self.children { + total += child.total_len() + } + total + } + + fn get_children( + &self, + next: &mut usize, + min: usize, + max: usize, + level: usize, + ) -> Vec<(usize, usize, ReferenceLocation)> { + let mut children = Vec::new(); + for child in &self.children { + let child_children = child.get_children(next, min, max, level + 1); + if !child_children.is_empty() { + children.extend(child_children); + } + if *next > max { + break; + } + } + children + } +} + +#[derive(Clone)] +pub enum Reference { + File { + location: ReferenceLocation, + open: RwSignal, + children: Vec, + }, + Line { + location: ReferenceLocation, + }, +} + +#[derive(Clone)] +pub enum ReferenceLocation { + File { + path: PathBuf, + open: RwSignal, + view_id: ViewId, + }, + Line { + path: PathBuf, + range: Position, + view_id: ViewId, + }, +} + +impl ReferenceLocation { + pub fn view_id(&self) -> ViewId { + match self { + ReferenceLocation::File { view_id, .. } => *view_id, + ReferenceLocation::Line { view_id, .. } => *view_id, + } + } +} + +impl Reference { + pub fn location(&self) -> ReferenceLocation { + match self { + Reference::File { location, .. } => location.clone(), + Reference::Line { location } => location.clone(), + } + } + pub fn total_len(&self) -> usize { + match self { + Reference::File { children, .. } => { + let mut total = 1; + for child in children { + total += child.total_len() + } + total + } + Reference::Line { .. } => 1, + } + } + pub fn children(&self) -> Option<&Vec> { + match self { + Reference::File { children, open, .. } => { + if open.get() { + return Some(children); + } + None + } + Reference::Line { .. } => None, + } + } + + fn get_children( + &self, + next: &mut usize, + min: usize, + max: usize, + level: usize, + ) -> Vec<(usize, usize, ReferenceLocation)> { + let mut children = Vec::new(); + if *next >= min && *next < max { + children.push((*next, level, self.location())); + } else if *next >= max { + return children; + } + next.add_assign(1); + if let Some(children_tmp) = self.children() { + for child in children_tmp { + let child_children = child.get_children(next, min, max, level + 1); + if !child_children.is_empty() { + children.extend(child_children); + } + if *next > max { + break; + } + } + } + children + } +} +// +// fn file_view( +// workspace: Arc, +// path: PathBuf, +// diagnostic_data: DiagnosticData, +// severity: DiagnosticSeverity, +// internal_command: Listener, +// config: ReadSignal>, +// ) -> impl View { +// let collpased = create_rw_signal(false); +// +// let diagnostics = create_rw_signal(im::Vector::new()); +// create_effect(move |_| { +// let span = diagnostic_data.diagnostics_span.get(); +// let d = if !span.is_empty() { +// span.iter() +// .filter_map(|(iv, diag)| { +// if diag.severity == Some(severity) { +// Some(EditorDiagnostic { +// range: Some((iv.start, iv.end)), +// diagnostic: diag.to_owned(), +// }) +// } else { +// None +// } +// }) +// .collect::>() +// } else { +// let diagnostics = diagnostic_data.diagnostics.get(); +// let diagnostics: im::Vector = diagnostics +// .into_iter() +// .filter_map(|d| { +// if d.severity == Some(severity) { +// Some(EditorDiagnostic { +// range: None, +// diagnostic: d, +// }) +// } else { +// None +// } +// }) +// .collect(); +// diagnostics +// }; +// diagnostics.set(d); +// }); +// +// let full_path = path.clone(); +// let path = if let Some(workspace_path) = workspace.path.as_ref() { +// path.strip_prefix(workspace_path) +// .unwrap_or(&full_path) +// .to_path_buf() +// } else { +// path +// }; +// let style_path = path.clone(); +// +// let icon = match severity { +// DiagnosticSeverity::ERROR => LapceIcons::ERROR, +// _ => LapceIcons::WARNING, +// }; +// let icon_color = move || { +// let config = config.get(); +// match severity { +// DiagnosticSeverity::ERROR => config.color(LapceColor::LAPCE_ERROR), +// _ => config.color(LapceColor::LAPCE_WARN), +// } +// }; +// +// let file_name = path +// .file_name() +// .and_then(|s| s.to_str()) +// .unwrap_or("") +// .to_string(); +// +// let folder = path +// .parent() +// .and_then(|s| s.to_str()) +// .unwrap_or("") +// .to_string(); +// +// stack(( +// stack(( +// container( +// stack(( +// label(move || file_name.clone()).style(|s| { +// s.margin_right(6.0) +// .max_width_pct(100.0) +// .text_ellipsis() +// .selectable(false) +// }), +// label(move || folder.clone()).style(move |s| { +// s.color(config.get().color(LapceColor::EDITOR_DIM)) +// .min_width(0.0) +// .text_ellipsis() +// .selectable(false) +// }), +// )) +// .style(move |s| s.width_pct(100.0).min_width(0.0)), +// ) +// .on_click_stop(move |_| { +// collpased.update(|collpased| *collpased = !*collpased); +// }) +// .style(move |s| { +// let config = config.get(); +// s.width_pct(100.0) +// .min_width(0.0) +// .padding_left(10.0 + (config.ui.icon_size() as f32 + 6.0) * 2.0) +// .padding_right(10.0) +// .hover(|s| { +// s.cursor(CursorStyle::Pointer).background( +// config.color(LapceColor::PANEL_HOVERED_BACKGROUND), +// ) +// }) +// }), +// stack(( +// svg(move || { +// config.get().ui_svg(if collpased.get() { +// LapceIcons::ITEM_CLOSED +// } else { +// LapceIcons::ITEM_OPENED +// }) +// }) +// .style(move |s| { +// let config = config.get(); +// let size = config.ui.icon_size() as f32; +// s.margin_right(6.0) +// .size(size, size) +// .color(config.color(LapceColor::LAPCE_ICON_ACTIVE)) +// }), +// svg(move || config.get().file_svg(&path).0).style(move |s| { +// let config = config.get(); +// let size = config.ui.icon_size() as f32; +// let color = config.file_svg(&style_path).1; +// s.min_width(size) +// .size(size, size) +// .apply_opt(color, Style::color) +// }), +// label(|| " ".to_string()).style(move |s| s.selectable(false)), +// )) +// .style(|s| s.absolute().items_center().margin_left(10.0)), +// )) +// .style(move |s| s.width_pct(100.0).min_width(0.0)), +// dyn_stack( +// move || { +// if collpased.get() { +// im::Vector::new() +// } else { +// diagnostics.get() +// } +// }, +// |d| (d.range, d.diagnostic.range), +// move |d| { +// item_view( +// full_path.clone(), +// d, +// icon, +// icon_color, +// internal_command, +// config, +// ) +// }, +// ) +// .style(|s| s.flex_col().width_pct(100.0).min_width_pct(0.0)), +// )) +// .style(move |s| { +// s.width_pct(100.0) +// .items_start() +// .flex_col() +// .apply_if(diagnostics.with(|d| d.is_empty()), |s| s.hide()) +// }) +// } +// +// fn item_view( +// path: PathBuf, +// d: EditorDiagnostic, +// icon: &'static str, +// icon_color: impl Fn() -> Color + 'static, +// internal_command: Listener, +// config: ReadSignal>, +// ) -> impl View { +// let related = d.diagnostic.related_information.unwrap_or_default(); +// let position = if let Some((start, _)) = d.range { +// EditorPosition::Offset(start) +// } else { +// EditorPosition::Position(d.diagnostic.range.start) +// }; +// let location = EditorLocation { +// path, +// position: Some(position), +// scroll_offset: None, +// ignore_unconfirmed: false, +// same_editor_tab: false, +// }; +// stack(( +// container({ +// stack(( +// label(move || d.diagnostic.message.clone()).style(move |s| { +// s.width_pct(100.0) +// .min_width(0.0) +// .padding_left( +// 10.0 + (config.get().ui.icon_size() as f32 + 6.0) * 3.0, +// ) +// .padding_right(10.0) +// }), +// stack(( +// svg(move || config.get().ui_svg(icon)).style(move |s| { +// let config = config.get(); +// let size = config.ui.icon_size() as f32; +// s.size(size, size).color(icon_color()) +// }), +// label(|| " ".to_string()).style(move |s| s.selectable(false)), +// )) +// .style(move |s| { +// s.absolute().items_center().margin_left( +// 10.0 + (config.get().ui.icon_size() as f32 + 6.0) * 2.0, +// ) +// }), +// )) +// .style(move |s| { +// s.width_pct(100.0).min_width(0.0).hover(|s| { +// s.cursor(CursorStyle::Pointer).background( +// config.get().color(LapceColor::PANEL_HOVERED_BACKGROUND), +// ) +// }) +// }) +// }) +// .on_click_stop(move |_| { +// internal_command.send(InternalCommand::JumpToLocation { +// location: location.clone(), +// }); +// }) +// .style(|s| s.width_pct(100.0).min_width_pct(0.0)), +// related_view(related, internal_command, config), +// )) +// .style(|s| s.width_pct(100.0).min_width_pct(0.0).flex_col()) +// } +// +// fn related_view( +// related: Vec, +// internal_command: Listener, +// config: ReadSignal>, +// ) -> impl View { +// let is_empty = related.is_empty(); +// stack(( +// dyn_stack( +// move || related.clone(), +// |_| 0, +// move |related| { +// let full_path = path_from_url(&related.location.uri); +// let path = full_path +// .file_name() +// .and_then(|f| f.to_str()) +// .map(|f| { +// format!( +// "{f} [{}, {}]: ", +// related.location.range.start.line, +// related.location.range.start.character +// ) +// }) +// .unwrap_or_default(); +// let location = EditorLocation { +// path: full_path, +// position: Some(EditorPosition::Position( +// related.location.range.start, +// )), +// scroll_offset: None, +// ignore_unconfirmed: false, +// same_editor_tab: false, +// }; +// let message = format!("{path}{}", related.message); +// container( +// label(move || message.clone()) +// .style(move |s| s.width_pct(100.0).min_width(0.0)), +// ) +// .on_click_stop(move |_| { +// internal_command.send(InternalCommand::JumpToLocation { +// location: location.clone(), +// }); +// }) +// .style(move |s| { +// let config = config.get(); +// s.padding_left(10.0 + (config.ui.icon_size() as f32 + 6.0) * 4.0) +// .padding_right(10.0) +// .width_pct(100.0) +// .min_width(0.0) +// .hover(|s| { +// s.cursor(CursorStyle::Pointer).background( +// config.color(LapceColor::PANEL_HOVERED_BACKGROUND), +// ) +// }) +// }) +// }, +// ) +// .style(|s| s.width_pct(100.0).min_width(0.0).flex_col()), +// stack(( +// svg(move || config.get().ui_svg(LapceIcons::LINK)).style(move |s| { +// let config = config.get(); +// let size = config.ui.icon_size() as f32; +// s.size(size, size) +// .color(config.color(LapceColor::EDITOR_DIM)) +// }), +// label(|| " ".to_string()).style(move |s| s.selectable(false)), +// )) +// .style(move |s| { +// s.absolute() +// .items_center() +// .margin_left(10.0 + (config.get().ui.icon_size() as f32 + 6.0) * 3.0) +// }), +// )) +// .style(move |s| { +// s.width_pct(100.0) +// .min_width(0.0) +// .items_start() +// .color(config.get().color(LapceColor::EDITOR_DIM)) +// .apply_if(is_empty, |s| s.hide()) +// }) +// } diff --git a/lapce-app/src/panel/source_control_view.rs b/lapce-app/src/panel/source_control_view.rs index e5b1d01a8b..7bb69e986c 100644 --- a/lapce-app/src/panel/source_control_view.rs +++ b/lapce-app/src/panel/source_control_view.rs @@ -5,7 +5,7 @@ use floem::{ event::{Event, EventListener}, menu::{Menu, MenuItem}, peniko::kurbo::Rect, - reactive::{create_memo, create_rw_signal}, + reactive::{create_memo, create_rw_signal, SignalGet, SignalUpdate, SignalWith}, style::{CursorStyle, Style}, views::{ container, dyn_stack, diff --git a/lapce-app/src/panel/terminal_view.rs b/lapce-app/src/panel/terminal_view.rs index 1be37f0ea2..c35cd30b6b 100644 --- a/lapce-app/src/panel/terminal_view.rs +++ b/lapce-app/src/panel/terminal_view.rs @@ -5,7 +5,7 @@ use floem::{ event::{Event, EventListener, EventPropagation}, kurbo::Size, menu::{Menu, MenuItem}, - reactive::create_rw_signal, + reactive::{create_rw_signal, SignalGet, SignalUpdate, SignalWith}, views::{ container, dyn_stack, empty, label, scroll::{scroll, Thickness, VerticalScrollAsHorizontal}, @@ -38,12 +38,6 @@ pub fn terminal_panel(window_tab_data: Rc) -> impl View { focus.set(Focus::Panel(PanelKind::Terminal)); } }) - .on_double_click(move |_| { - window_tab_data - .panel - .toggle_maximize(&crate::panel::kind::PanelKind::Terminal); - EventPropagation::Stop - }) .style(|s| s.absolute().size_pct(100.0, 100.0).flex_col()) .debug_name("Terminal Panel") } @@ -249,6 +243,12 @@ fn terminal_tab_header(window_tab_data: Rc) -> impl View { header_height.set(size.height); } }) + .on_double_click(move |_| { + window_tab_data + .panel + .toggle_maximize(&crate::panel::kind::PanelKind::Terminal); + EventPropagation::Stop + }) .style(move |s| { let config = config.get(); s.width_pct(100.0) diff --git a/lapce-app/src/panel/view.rs b/lapce-app/src/panel/view.rs index 1fdc4c40b5..54544fae57 100644 --- a/lapce-app/src/panel/view.rs +++ b/lapce-app/src/panel/view.rs @@ -3,7 +3,9 @@ use std::{rc::Rc, sync::Arc}; use floem::{ event::{Event, EventListener, EventPropagation}, kurbo::{Point, Size}, - reactive::{create_rw_signal, ReadSignal, RwSignal}, + reactive::{ + create_rw_signal, ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, + }, style::{CursorStyle, Style}, taffy::AlignItems, unit::PxPctAuto, @@ -28,7 +30,11 @@ use crate::{ app::{clickable_icon, clickable_icon_base}, config::{color::LapceColor, icon::LapceIcons, LapceConfig}, file_explorer::view::file_explorer_panel, - panel::call_hierarchy_view::show_hierarchy_panel, + panel::{ + call_hierarchy_view::show_hierarchy_panel, document_symbol::symbol_panel, + implementation_view::implementation_panel, + references_view::references_panel, + }, window_tab::{DragContent, WindowTabData}, }; @@ -489,6 +495,16 @@ fn panel_view( show_hierarchy_panel(window_tab_data.clone(), position) .into_any() } + PanelKind::DocumentSymbol => { + symbol_panel(window_tab_data.clone(), position).into_any() + } + PanelKind::References => { + references_panel(window_tab_data.clone(), position).into_any() + } + PanelKind::Implementation => { + implementation_panel(window_tab_data.clone(), position) + .into_any() + } }; view.style(|s| s.size_pct(100.0, 100.0)) }, @@ -533,18 +549,20 @@ fn panel_picker( |p| *p, move |p| { let window_tab_data = window_tab_data.clone(); - let (icon, tooltip) = match p { - PanelKind::Terminal => (LapceIcons::TERMINAL, "Terminal"), - PanelKind::FileExplorer => { - (LapceIcons::FILE_EXPLORER, "File Explorer") - } - PanelKind::SourceControl => (LapceIcons::SCM, "Source Control"), - PanelKind::Plugin => (LapceIcons::EXTENSIONS, "Plugins"), - PanelKind::Search => (LapceIcons::SEARCH, "Search"), - PanelKind::Problem => (LapceIcons::PROBLEM, "Problems"), - PanelKind::Debug => (LapceIcons::DEBUG_ALT, "Debug"), - PanelKind::CallHierarchy => (LapceIcons::LINK, "Call Hierarchy"), + let tooltip = match p { + PanelKind::Terminal => "Terminal", + PanelKind::FileExplorer => "File Explorer", + PanelKind::SourceControl => "Source Control", + PanelKind::Plugin => "Plugins", + PanelKind::Search => "Search", + PanelKind::Problem => "Problems", + PanelKind::Debug => "Debug", + PanelKind::CallHierarchy => "Call Hierarchy", + PanelKind::DocumentSymbol => "Document Symbol", + PanelKind::References => "References", + PanelKind::Implementation => "Implementation", }; + let icon = p.svg_name(); let is_active = { let window_tab_data = window_tab_data.clone(); move || { diff --git a/lapce-app/src/plugin.rs b/lapce-app/src/plugin.rs index 7f3da3e0de..d659cef297 100644 --- a/lapce-app/src/plugin.rs +++ b/lapce-app/src/plugin.rs @@ -13,6 +13,7 @@ use floem::{ menu::{Menu, MenuItem}, reactive::{ create_effect, create_memo, create_rw_signal, use_context, RwSignal, Scope, + SignalGet, SignalUpdate, SignalWith, }, style::CursorStyle, views::{ @@ -433,7 +434,9 @@ impl PluginData { let buf = resp.bytes()?.to_vec(); if let Some(path) = cache_file_path.as_ref() { - let _ = std::fs::write(path, &buf); + if let Err(err) = std::fs::write(path, &buf) { + tracing::error!("{:?}", err); + } } buf diff --git a/lapce-app/src/proxy.rs b/lapce-app/src/proxy.rs index 6bacb93561..28503a0c67 100644 --- a/lapce-app/src/proxy.rs +++ b/lapce-app/src/proxy.rs @@ -128,12 +128,17 @@ pub fn new_proxy( impl CoreHandler for Proxy { fn handle_notification(&mut self, rpc: lapce_rpc::core::CoreNotification) { if let CoreNotification::UpdateTerminal { term_id, content } = &rpc { - let _ = self + if let Err(err) = self .term_tx - .send((*term_id, TermEvent::UpdateContent(content.to_vec()))); + .send((*term_id, TermEvent::UpdateContent(content.to_vec()))) + { + tracing::error!("{:?}", err); + } return; } - let _ = self.tx.send(rpc); + if let Err(err) = self.tx.send(rpc) { + tracing::error!("{:?}", err); + } } fn handle_request( diff --git a/lapce-app/src/proxy/remote.rs b/lapce-app/src/proxy/remote.rs index 73a487451a..e4aa7e066e 100644 --- a/lapce-app/src/proxy/remote.rs +++ b/lapce-app/src/proxy/remote.rs @@ -6,7 +6,10 @@ use std::{ use anyhow::{anyhow, Result}; use flate2::read::GzDecoder; -use lapce_core::{directory::Directory, meta}; +use lapce_core::{ + directory::Directory, + meta::{self, ReleaseType}, +}; use lapce_rpc::{ core::CoreRpcHandler, proxy::{ProxyRpc, ProxyRpcHandler}, @@ -118,7 +121,7 @@ pub fn start_remote( .args([&remote_proxy_file, "--version"]) .output() .map(|output| { - if meta::VERSION == "debug" { + if meta::RELEASE == ReleaseType::Debug { String::from_utf8_lossy(&output.stdout).starts_with("Lapce-proxy") } else { String::from_utf8_lossy(&output.stdout).trim() @@ -178,14 +181,26 @@ pub fn start_remote( for msg in local_proxy_rpc.rx() { match msg { ProxyRpc::Request(id, rpc) => { - let _ = local_writer_tx.send(RpcMessage::Request(id, rpc)); + if let Err(err) = + local_writer_tx.send(RpcMessage::Request(id, rpc)) + { + tracing::error!("{:?}", err); + } } ProxyRpc::Notification(rpc) => { - let _ = local_writer_tx.send(RpcMessage::Notification(rpc)); + if let Err(err) = + local_writer_tx.send(RpcMessage::Notification(rpc)) + { + tracing::error!("{:?}", err); + } } ProxyRpc::Shutdown => { - let _ = child.kill(); - let _ = child.wait(); + if let Err(err) = child.kill() { + tracing::error!("{:?}", err); + } + if let Err(err) = child.wait() { + tracing::error!("{:?}", err); + } return; } } @@ -200,10 +215,18 @@ pub fn start_remote( let core_rpc = core_rpc.clone(); std::thread::spawn(move || match core_rpc.request(req) { Ok(resp) => { - let _ = writer_tx.send(RpcMessage::Response(id, resp)); + if let Err(err) = + writer_tx.send(RpcMessage::Response(id, resp)) + { + tracing::error!("{:?}", err); + } } Err(e) => { - let _ = writer_tx.send(RpcMessage::Error(id, e)); + if let Err(err) = + writer_tx.send(RpcMessage::Error(id, e)) + { + tracing::error!("{:?}", err); + } } }); } @@ -267,10 +290,10 @@ fn download_remote( _ => { let proxy_script = general_purpose::STANDARD.encode(UNIX_PROXY_SCRIPT); - let version = if meta::VERSION == "debug" { - "nightly" - } else { - meta::VERSION + let version = match meta::RELEASE { + ReleaseType::Debug => "nightly".to_string(), + ReleaseType::Nightly => "nightly".to_string(), + ReleaseType::Stable => format!("v{}", meta::VERSION), }; let cmd = remote .command_builder() @@ -283,7 +306,7 @@ fn download_remote( "|", "sh", "/dev/stdin", - version, + &version, remote_proxy_path, ]) .output()?; diff --git a/lapce-app/src/rename.rs b/lapce-app/src/rename.rs index 102c65802f..74943b43db 100644 --- a/lapce-app/src/rename.rs +++ b/lapce-app/src/rename.rs @@ -4,7 +4,7 @@ use floem::{ ext_event::create_ext_action, keyboard::Modifiers, peniko::kurbo::Rect, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, }; use lapce_core::{command::FocusCommand, mode::Mode, selection::Selection}; use lapce_rpc::proxy::ProxyResponse; diff --git a/lapce-app/src/settings.rs b/lapce-app/src/settings.rs index fe845827d5..a48f080be7 100644 --- a/lapce-app/src/settings.rs +++ b/lapce-app/src/settings.rs @@ -7,7 +7,7 @@ use floem::{ peniko::kurbo::{Point, Rect, Size}, reactive::{ create_effect, create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, - Scope, + Scope, SignalGet, SignalUpdate, SignalWith, }, style::CursorStyle, text::{Attrs, AttrsList, FamilyOwned, TextLayout}, diff --git a/lapce-app/src/source_control.rs b/lapce-app/src/source_control.rs index 0da881b6dc..70d9e852ef 100644 --- a/lapce-app/src/source_control.rs +++ b/lapce-app/src/source_control.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, rc::Rc}; use floem::{ keyboard::Modifiers, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalWith}, }; use indexmap::IndexMap; use lapce_core::mode::Mode; diff --git a/lapce-app/src/status.rs b/lapce-app/src/status.rs index 31a6c31b49..7bc0376493 100644 --- a/lapce-app/src/status.rs +++ b/lapce-app/src/status.rs @@ -5,7 +5,9 @@ use std::{ use floem::{ event::EventPropagation, - reactive::{create_memo, Memo, ReadSignal, RwSignal}, + reactive::{ + create_memo, Memo, ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, + }, style::{AlignItems, CursorStyle, Display, FlexWrap}, views::{dyn_stack, label, stack, svg, Decorators}, View, diff --git a/lapce-app/src/terminal/data.rs b/lapce-app/src/terminal/data.rs index b7488cb3ee..2471ef8c9b 100644 --- a/lapce-app/src/terminal/data.rs +++ b/lapce-app/src/terminal/data.rs @@ -10,7 +10,7 @@ use alacritty_terminal::{ use anyhow::anyhow; use floem::{ keyboard::{Key, KeyEvent, Modifiers, NamedKey}, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::editor::text::SystemClipboard, }; use lapce_core::{ @@ -32,7 +32,7 @@ use super::{ }; use crate::{ command::{CommandExecuted, CommandKind, InternalCommand}, - debug::RunDebugProcess, + debug::{RunDebugMode, RunDebugProcess}, keypress::{condition::Condition, KeyPressFocus}, window_tab::CommonData, workspace::LapceWorkspace, @@ -420,7 +420,11 @@ impl TerminalData { { let raw = raw.clone(); - let _ = common.term_tx.send((term_id, TermEvent::NewTerminal(raw))); + if let Err(err) = + common.term_tx.send((term_id, TermEvent::NewTerminal(raw))) + { + tracing::error!("{:?}", err); + } common.proxy.new_terminal(term_id, profile); } @@ -742,6 +746,20 @@ impl TerminalData { .proxy .terminal_resize(self.term_id, width, height); } + + pub fn stop(&self) { + if let Some(dap_id) = self.run_debug.with_untracked(|x| { + if let Some(process) = x { + if !process.is_prelaunch && process.mode == RunDebugMode::Debug { + return Some(process.config.dap_id); + } + } + None + }) { + self.common.proxy.dap_stop(dap_id); + } + self.common.proxy.terminal_close(self.term_id); + } } /// [`RunDebugConfig`] with expanded out program/arguments/etc. Used for creating the terminal. diff --git a/lapce-app/src/terminal/event.rs b/lapce-app/src/terminal/event.rs index 03359b22a9..19455954df 100644 --- a/lapce-app/src/terminal/event.rs +++ b/lapce-app/src/terminal/event.rs @@ -50,13 +50,19 @@ pub fn terminal_update_process( if last_event.is_some() { if last_redraw.elapsed().as_millis() > 10 { last_redraw = Instant::now(); - let _ = term_notification_tx - .send(TermNotification::RequestPaint); + if let Err(err) = term_notification_tx + .send(TermNotification::RequestPaint) + { + tracing::error!("{:?}", err); + } } } else { last_redraw = Instant::now(); - let _ = term_notification_tx - .send(TermNotification::RequestPaint); + if let Err(err) = + term_notification_tx.send(TermNotification::RequestPaint) + { + tracing::error!("{:?}", err); + } } } } diff --git a/lapce-app/src/terminal/panel.rs b/lapce-app/src/terminal/panel.rs index 817a1ca356..15f5240756 100644 --- a/lapce-app/src/terminal/panel.rs +++ b/lapce-app/src/terminal/panel.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, path::PathBuf, rc::Rc, sync::Arc}; use floem::{ ext_event::create_ext_action, - reactive::{Memo, RwSignal, Scope}, + reactive::{Memo, RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, }; use lapce_core::mode::Mode; use lapce_rpc::{ @@ -12,16 +12,20 @@ use lapce_rpc::{ proxy::ProxyResponse, terminal::{TermId, TerminalProfile}, }; +use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; use super::{data::TerminalData, tab::TerminalTabData}; use crate::{ debug::{ - DapData, DapVariable, RunDebugData, RunDebugMode, RunDebugProcess, - ScopeOrVar, + DapData, DapVariable, RunDebugConfigs, RunDebugData, RunDebugMode, + RunDebugProcess, ScopeOrVar, }, id::TerminalTabId, keypress::{EventRef, KeyPressData, KeyPressFocus, KeyPressHandle}, + main_split::MainSplitData, panel::kind::PanelKind, + terminal::raw::RawTerminal, window_tab::{CommonData, Focus}, workspace::LapceWorkspace, }; @@ -39,6 +43,7 @@ pub struct TerminalPanelData { pub debug: RunDebugData, pub breakline: Memo>, pub common: Rc, + pub main_split: MainSplitData, } impl TerminalPanelData { @@ -46,6 +51,7 @@ impl TerminalPanelData { workspace: Arc, profile: Option, common: Rc, + main_split: MainSplitData, ) -> Self { let terminal_tab = TerminalTabData::new(workspace.clone(), profile, common.clone()); @@ -108,6 +114,7 @@ impl TerminalPanelData { debug, breakline, common, + main_split, } } @@ -220,19 +227,40 @@ impl TerminalPanelData { } pub fn close_tab(&self, terminal_tab_id: Option) { - self.tab_info.update(|info| { - if let Some(terminal_tab_id) = terminal_tab_id { - info.tabs - .retain(|(_, t)| t.terminal_tab_id != terminal_tab_id); - } else { - let active = info.active.min(info.tabs.len().saturating_sub(1)); - if !info.tabs.is_empty() { - info.tabs.remove(active); + if let Some(close_tab) = self + .tab_info + .try_update(|info| { + let mut close_tab = None; + if let Some(terminal_tab_id) = terminal_tab_id { + if let Some(index) = + info.tabs.iter().enumerate().find_map(|(index, (_, t))| { + if t.terminal_tab_id == terminal_tab_id { + Some(index) + } else { + None + } + }) + { + close_tab = Some( + info.tabs.remove(index).1.terminals.get_untracked(), + ); + } + } else { + let active = info.active.min(info.tabs.len().saturating_sub(1)); + if !info.tabs.is_empty() { + info.tabs.remove(active); + } } + let new_active = info.active.min(info.tabs.len().saturating_sub(1)); + info.active = new_active; + close_tab + }) + .flatten() + { + for (_, data) in close_tab { + data.stop(); } - let new_active = info.active.min(info.tabs.len().saturating_sub(1)); - info.active = new_active; - }); + } self.update_debug_active_term(); } @@ -356,7 +384,7 @@ impl TerminalPanelData { } } - pub fn terminal_stopped(&self, term_id: &TermId) { + pub fn terminal_stopped(&self, term_id: &TermId, exit_code: Option) { if let Some(terminal) = self.get_terminal(term_id) { if terminal.run_debug.with_untracked(|r| r.is_some()) { let was_prelaunch = terminal @@ -381,10 +409,15 @@ impl TerminalPanelData { } }) .unwrap(); - if was_prelaunch == Some(true) { + let exit_code = exit_code.unwrap_or(0); + if was_prelaunch == Some(true) && exit_code == 0 { let run_debug = terminal.run_debug.get_untracked(); - if let Some(run_debug) = run_debug { + if let Some(mut run_debug) = run_debug { if run_debug.mode == RunDebugMode::Debug { + update_executable( + &mut run_debug, + terminal.raw.get_untracked().clone(), + ); self.common.proxy.dap_start( run_debug.config, self.debug.source_breakpoints(), @@ -438,14 +471,22 @@ impl TerminalPanelData { }) } - pub fn restart_run_debug(&self, term_id: TermId) -> Option<()> { + /// Return whether it is in debug mode. + pub fn restart_run_debug(&self, term_id: TermId) -> Option { let (_, terminal_tab, index, terminal) = self.get_terminal_in_tab(&term_id)?; - let run_debug = terminal.run_debug.get_untracked()?; + let mut run_debug = terminal.run_debug.get_untracked()?; + if run_debug.config.config_source.from_palette() { + if let Some(new_config) = + self.get_run_config_by_name(&run_debug.config.name) + { + run_debug.config = new_config; + } + } + let mut is_debug = false; let new_term_id = match run_debug.mode { RunDebugMode::Run => { self.common.proxy.terminal_close(term_id); - let mut run_debug = run_debug; run_debug.stopped = false; run_debug.is_prelaunch = true; @@ -465,6 +506,7 @@ impl TerminalPanelData { new_term_id } RunDebugMode::Debug => { + is_debug = true; let dap_id = terminal.run_debug.get_untracked().as_ref()?.config.dap_id; let daps = self.debug.daps.get_untracked(); @@ -478,7 +520,27 @@ impl TerminalPanelData { self.focus_terminal(new_term_id); - Some(()) + Some(is_debug) + } + + fn get_run_config_by_name(&self, name: &str) -> Option { + if let Some(workspace) = self.common.workspace.path.as_deref() { + let run_toml = workspace.join(".lapce").join("run.toml"); + let (doc, new_doc) = self.main_split.get_doc(run_toml.clone(), None); + if !new_doc { + let content = doc.buffer.with_untracked(|b| b.to_string()); + match toml::from_str::(&content) { + Ok(configs) => { + return configs.configs.into_iter().find(|x| x.name == name); + } + Err(err) => { + // todo show message window + tracing::error!("deser fail {:?}", err); + } + } + } + } + None } pub fn focus_terminal(&self, term_id: TermId) { @@ -763,3 +825,55 @@ impl TerminalPanelData { } } } + +fn update_executable( + run_debug: &mut RunDebugProcess, + raw: Arc>, +) { + if run_debug.config.config_source.from_rust_code_lens() { + let lines = raw.write_arc().output(5); + if let Some(executable) = lines.into_iter().rev().find_map(|x| { + if let Ok(map) = serde_json::from_str::(&x) { + return map.artifact(); + } else { + tracing::debug!("{}", x); + } + None + }) { + run_debug.config.program = executable; + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct Profile { + pub test: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Target { + pub kind: Vec, + pub crate_types: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +struct RustArtifact { + pub reason: String, + pub target: Target, + pub profile: Profile, + pub executable: String, +} + +impl RustArtifact { + pub fn artifact(self) -> Option { + if &self.reason == "compiler-artifact" && !self.executable.is_empty() { + let is_binary = self.target.kind.contains(&"bin".to_owned()); + let is_build_script = + self.target.crate_types.contains(&"custom-build".to_owned()); + if (is_binary && !is_build_script) || self.profile.test { + return Some(self.executable); + } + } + None + } +} diff --git a/lapce-app/src/terminal/raw.rs b/lapce-app/src/terminal/raw.rs index ba88bbae74..e1ee3cbffc 100644 --- a/lapce-app/src/terminal/raw.rs +++ b/lapce-app/src/terminal/raw.rs @@ -3,6 +3,7 @@ use alacritty_terminal::{ grid::Dimensions, index::{Column, Direction, Line, Point}, term::{ + cell::{Flags, LineLength}, search::{Match, RegexIter, RegexSearch}, test::TermSize, }, @@ -27,15 +28,22 @@ impl EventListener for EventProxy { self.proxy.terminal_write(self.term_id, s); } alacritty_terminal::event::Event::MouseCursorDirty => { - let _ = self + if let Err(err) = self .term_notification_tx - .send(TermNotification::RequestPaint); + .send(TermNotification::RequestPaint) + { + tracing::error!("{:?}", err); + } } alacritty_terminal::event::Event::Title(s) => { - let _ = self.term_notification_tx.send(TermNotification::SetTitle { - term_id: self.term_id, - title: s, - }); + if let Err(err) = + self.term_notification_tx.send(TermNotification::SetTitle { + term_id: self.term_id, + title: s, + }) + { + tracing::error!("{:?}", err); + } } _ => (), } @@ -80,6 +88,42 @@ impl RawTerminal { self.parser.advance(&mut self.term, byte); } } + + pub fn output(&self, line_num: usize) -> Vec { + let grid = self.term.grid(); + let mut lines = Vec::with_capacity(5); + let mut rows = Vec::new(); + for line in (grid.topmost_line().0..=grid.bottommost_line().0) + .map(Line) + .rev() + { + let row_cell = &grid[line]; + if row_cell[Column(row_cell.len() - 1)] + .flags + .contains(Flags::WRAPLINE) + { + rows.push(row_cell); + } else { + if !rows.is_empty() { + let mut new_line = Vec::new(); + std::mem::swap(&mut rows, &mut new_line); + let line_str: String = new_line + .into_iter() + .rev() + .flat_map(|x| { + x.into_iter().take(x.line_length().0).map(|x| x.c) + }) + .collect(); + lines.push(line_str); + if lines.len() >= line_num { + break; + } + } + rows.push(row_cell); + } + } + lines + } } pub fn visible_regex_match_iter<'a, EventProxy>( diff --git a/lapce-app/src/terminal/tab.rs b/lapce-app/src/terminal/tab.rs index 5e66eb7d08..32214b77a3 100644 --- a/lapce-app/src/terminal/tab.rs +++ b/lapce-app/src/terminal/tab.rs @@ -1,6 +1,6 @@ use std::{rc::Rc, sync::Arc}; -use floem::reactive::{RwSignal, Scope}; +use floem::reactive::{RwSignal, Scope, SignalGet, SignalWith}; use lapce_rpc::terminal::TerminalProfile; use super::data::TerminalData; diff --git a/lapce-app/src/terminal/view.rs b/lapce-app/src/terminal/view.rs index f0f69c618a..3bee251484 100644 --- a/lapce-app/src/terminal/view.rs +++ b/lapce-app/src/terminal/view.rs @@ -14,7 +14,7 @@ use floem::{ Color, }, pointer::PointerInputEvent, - reactive::{create_effect, ReadSignal, RwSignal}, + reactive::{create_effect, ReadSignal, RwSignal, SignalGet, SignalWith}, text::{Attrs, AttrsList, FamilyOwned, TextLayout, Weight}, views::editor::{core::register::Clipboard, text::SystemClipboard}, Renderer, View, ViewId, diff --git a/lapce-app/src/text_area.rs b/lapce-app/src/text_area.rs index cb54e45a40..c9f89a3bfc 100644 --- a/lapce-app/src/text_area.rs +++ b/lapce-app/src/text_area.rs @@ -1,6 +1,8 @@ use floem::{ peniko::kurbo::Rect, - reactive::{create_effect, create_rw_signal}, + reactive::{ + create_effect, create_rw_signal, SignalGet, SignalUpdate, SignalWith, + }, text::{Attrs, AttrsList, LineHeightValue, TextLayout}, views::{container, label, rich_text, scroll, stack, Decorators}, View, diff --git a/lapce-app/src/text_input.rs b/lapce-app/src/text_input.rs index 05f9f85b9e..d49165cd29 100644 --- a/lapce-app/src/text_input.rs +++ b/lapce-app/src/text_input.rs @@ -11,7 +11,7 @@ use floem::{ prop_extractor, reactive::{ create_effect, create_memo, create_rw_signal, Memo, ReadSignal, RwSignal, - Scope, + Scope, SignalGet, SignalUpdate, SignalWith, }, style::{ CursorStyle, FontFamily, FontSize, FontStyle, FontWeight, LineHeight, diff --git a/lapce-app/src/title.rs b/lapce-app/src/title.rs index 60e3cdfdb7..4acff239d0 100644 --- a/lapce-app/src/title.rs +++ b/lapce-app/src/title.rs @@ -4,7 +4,9 @@ use floem::{ event::EventListener, menu::{Menu, MenuItem}, peniko::Color, - reactive::{create_memo, Memo, ReadSignal, RwSignal}, + reactive::{ + create_memo, Memo, ReadSignal, RwSignal, SignalGet, SignalUpdate, SignalWith, + }, style::{AlignItems, CursorStyle, JustifyContent}, views::{container, drag_window_area, empty, label, stack, svg, Decorators}, View, @@ -253,7 +255,7 @@ fn middle( .on_event_stop(EventListener::PointerDown, |_| {}) .on_click_stop(move |_| { if workspace.clone().path.is_some() { - workbench_command.send(LapceWorkbenchCommand::Palette); + workbench_command.send(LapceWorkbenchCommand::PaletteHelpAndFile); } else { workbench_command.send(LapceWorkbenchCommand::PaletteWorkspace); } diff --git a/lapce-app/src/update.rs b/lapce-app/src/update.rs index 535a62a168..e3ed5e8a4f 100644 --- a/lapce-app/src/update.rs +++ b/lapce-app/src/update.rs @@ -217,7 +217,10 @@ pub fn cleanup() { // Clean up backup exe after an update if let Ok(process_path) = std::env::current_exe() { if let Some(dst_parent) = process_path.parent() { - let _ = std::fs::remove_file(dst_parent.join("lapce.exe.bak")); + if let Err(err) = std::fs::remove_file(dst_parent.join("lapce.exe.bak")) + { + tracing::error!("{:?}", err); + } } } } diff --git a/lapce-app/src/window.rs b/lapce-app/src/window.rs index 9750ceb718..c377093ac1 100644 --- a/lapce-app/src/window.rs +++ b/lapce-app/src/window.rs @@ -3,7 +3,10 @@ use std::{path::PathBuf, rc::Rc, sync::Arc}; use floem::{ action::TimerToken, peniko::kurbo::{Point, Size}, - reactive::{use_context, Memo, ReadSignal, RwSignal, Scope}, + reactive::{ + use_context, Memo, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, + SignalWith, + }, window::WindowId, ViewId, }; @@ -200,13 +203,19 @@ impl WindowData { match cmd { WindowCommand::SetWorkspace { workspace } => { let db: Arc = use_context().unwrap(); - let _ = db.update_recent_workspace(&workspace); + if let Err(err) = db.update_recent_workspace(&workspace) { + tracing::error!("{:?}", err); + } let active = self.active.get_untracked(); self.window_tabs.with_untracked(|window_tabs| { if !window_tabs.is_empty() { let active = window_tabs.len().saturating_sub(1).min(active); - let _ = db.insert_window_tab(window_tabs[active].1.clone()); + if let Err(err) = + db.insert_window_tab(window_tabs[active].1.clone()) + { + tracing::error!("{:?}", err); + } } }); @@ -231,7 +240,9 @@ impl WindowData { } WindowCommand::NewWorkspaceTab { workspace, end } => { let db: Arc = use_context().unwrap(); - let _ = db.update_recent_workspace(&workspace); + if let Err(err) = db.update_recent_workspace(&workspace) { + tracing::error!("{:?}", err); + } let window_tab = Rc::new(WindowTabData::new( self.scope, @@ -272,7 +283,9 @@ impl WindowData { let (_, old_window_tab) = window_tabs.remove(index); old_window_tab.proxy.shutdown(); let db: Arc = use_context().unwrap(); - let _ = db.save_window_tab(old_window_tab); + if let Err(err) = db.save_window_tab(old_window_tab) { + tracing::error!("{:?}", err); + } } }); diff --git a/lapce-app/src/window_tab.rs b/lapce-app/src/window_tab.rs index ed487a9542..486484db3d 100644 --- a/lapce-app/src/window_tab.rs +++ b/lapce-app/src/window_tab.rs @@ -1,4 +1,3 @@ -use alacritty_terminal::vte::ansi::Handler; use std::{ collections::{BTreeMap, HashSet}, env, @@ -8,6 +7,7 @@ use std::{ time::Instant, }; +use alacritty_terminal::vte::ansi::Handler; use crossbeam_channel::Sender; use floem::{ action::{open_file, remove_overlay, TimerToken}, @@ -16,8 +16,12 @@ use floem::{ keyboard::Modifiers, kurbo::Size, peniko::kurbo::{Point, Rect, Vec2}, - reactive::{use_context, Memo, ReadSignal, RwSignal, Scope, WriteSignal}, + reactive::{ + use_context, Memo, ReadSignal, RwSignal, Scope, SignalGet, SignalUpdate, + SignalWith, WriteSignal, + }, text::{Attrs, AttrsList, FamilyOwned, LineHeightValue, TextLayout}, + views::editor::core::buffer::rope_text::RopeText, ViewId, }; use indexmap::IndexMap; @@ -28,7 +32,7 @@ use lapce_core::{ }; use lapce_rpc::{ core::CoreNotification, - dap_types::RunDebugConfig, + dap_types::{ConfigSource, RunDebugConfig}, file::{Naming, PathObject}, plugin::PluginId, proxy::{ProxyResponse, ProxyRpcHandler, ProxyStatus}, @@ -68,7 +72,7 @@ use crate::{ listener::Listener, lsp::path_from_url, main_split::{MainSplitData, SplitData, SplitDirection, SplitMoveDirection}, - palette::{kind::PaletteKind, PaletteData, PaletteStatus}, + palette::{kind::PaletteKind, PaletteData, PaletteStatus, DEFAULT_RUN_TOML}, panel::{ call_hierarchy_view::{CallHierarchyData, CallHierarchyItemData}, data::{default_panel_order, PanelData, PanelSection}, @@ -478,6 +482,7 @@ impl WindowTabData { workspace.clone(), common.config.get_untracked().terminal.get_default_profile(), common.clone(), + main_split.clone(), ); if let Some(workspace_info) = workspace_info.as_ref() { terminal.debug.breakpoints.set( @@ -684,7 +689,12 @@ impl WindowTabData { OpenFolder => { if !self.workspace.kind.is_remote() { let window_command = self.common.window_common.window_command; - let options = FileDialogOptions::new().select_directories(); + let mut options = FileDialogOptions::new().select_directories(); + options = if let Some(parent) = self.workspace.path.as_ref().and_then(|x| x.parent()) { + options.force_starting_directory(parent) + } else { + options + }; open_file(options, move |file| { if let Some(mut file) = file { let workspace = LapceWorkspace { @@ -1049,6 +1059,7 @@ impl WindowTabData { // ==== Palette Commands ==== PaletteHelp => self.palette.run(PaletteKind::PaletteHelp), + PaletteHelpAndFile => self.palette.run(PaletteKind::HelpAndFile), PaletteLine => { self.palette.run(PaletteKind::Line); } @@ -1088,10 +1099,14 @@ impl WindowTabData { // ==== Running / Debugging ==== RunAndDebugRestart => { let active_term = self.terminal.debug.active_term.get_untracked(); - if active_term + if let Some(is_debug) = active_term .and_then(|term_id| self.terminal.restart_run_debug(term_id)) - .is_none() { + self.panel.show_panel(&PanelKind::Terminal); + if is_debug { + self.panel.show_panel(&PanelKind::Debug); + } + } else { self.palette.run(PaletteKind::RunAndDebug); } } @@ -1373,7 +1388,7 @@ impl WindowTabData { Quit => { floem::quit_app(); } - RevealInFileTree => { + RevealInPanel => { if let Some(editor_data) = self.main_split.active_editor.get_untracked() { @@ -1387,6 +1402,50 @@ impl WindowTabData { } } } + SourceControlOpenActiveFileRemoteUrl => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + if let DocContent::File {path, ..} = editor_data.doc().content.get_untracked() { + let offset = editor_data.cursor().with_untracked(|c| c.offset()); + let line = editor_data.doc() + .buffer + .with_untracked(|buffer| buffer.line_of_offset(offset)); + self.common.proxy.git_get_remote_file_url( + path, + create_ext_action(self.scope, move |result| { + if let Ok(ProxyResponse::GitGetRemoteFileUrl { + file_url + }) = result + { + if let Err(err) = open::that(format!("{}#L{}", file_url, line)) { + error!("Failed to open file in github: {}", err); + } + } + }), + ); + + } + } + } + RevealInFileExplorer => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + if let DocContent::File {path, ..} = editor_data.doc().content.get_untracked() { + let path = path.parent().unwrap_or(&path); + if !path.exists() { + return; + } + if let Err(err) = open::that(path) { + error!( + "Failed to reveal file in system file explorer: {}", + err + ); + } + } + } + } ShowCallHierarchy => { if let Some(editor_data) = self.main_split.active_editor.get_untracked() @@ -1394,6 +1453,88 @@ impl WindowTabData { editor_data.call_hierarchy(self.clone()); } } + FindReferences => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + editor_data.find_refenrence(self.clone()); + } + } + GoToImplementation => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + editor_data.go_to_implementation(self.clone()); + } + } + RunInTerminal => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + let name = editor_data.word_at_cursor(); + if !name.is_empty() { + let mut args_str = name.split(" "); + let program = args_str.next().map(|x| x.to_string()).unwrap(); + let args: Vec = args_str.map(|x| x.to_string()).collect(); + let args = if args.is_empty() { + None + } else { + Some(args) + }; + + let config = RunDebugConfig { + ty: None, + name, + program, + args, + cwd: None, + env: None, + prelaunch: None, + debug_command: None, + dap_id: Default::default(), + tracing_output: false, + config_source: ConfigSource::RunInTerminal, + }; + self.common + .internal_command + .send(InternalCommand::RunAndDebug { mode: RunDebugMode::Run, config }); + } + } + } + GoToLocation => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + let doc = editor_data.doc(); + let path = match if doc.loaded() { + doc.content.with_untracked(|c| c.path().cloned()) + } else { + None + } { + Some(path) => path, + None => return, + }; + let offset = editor_data.cursor().with_untracked(|c| c.offset()); + let internal_command = self.common.internal_command; + + internal_command.send(InternalCommand::MakeConfirmed); + internal_command.send(InternalCommand::GoToLocation { location: EditorLocation { + path, + position: Some(EditorPosition::Offset(offset)), + scroll_offset: None, + ignore_unconfirmed: false, + same_editor_tab: false, + } }); + } + } + AddRunDebugConfig => { + if let Some(editor_data) = + self.main_split.active_editor.get_untracked() + { + editor_data.receive_char(DEFAULT_RUN_TOML); + } + } + } } @@ -1424,6 +1565,21 @@ impl WindowTabData { None, ); } + InternalCommand::OpenAndConfirmedFile { path } => { + self.main_split.jump_to_location( + EditorLocation { + path, + position: None, + scroll_offset: None, + ignore_unconfirmed: false, + same_editor_tab: false, + }, + None, + ); + if let Some(editor) = self.main_split.active_editor.get_untracked() { + editor.confirmed.set(true); + } + } InternalCommand::OpenFileInNewTab { path } => { self.main_split.jump_to_location( EditorLocation { @@ -1948,17 +2104,22 @@ impl WindowTabData { self.main_split.docs.with_untracked(|x| { for doc in x.values() { doc.get_code_lens(); + doc.get_document_symbol(); doc.get_semantic_styles(); } }); } } - CoreNotification::TerminalProcessStopped { term_id } => { - let _ = self + CoreNotification::TerminalProcessStopped { term_id, exit_code } => { + debug!("TerminalProcessStopped {:?}, {:?}", term_id, exit_code); + if let Err(err) = self .common .term_tx - .send((*term_id, TermEvent::CloseTerminal)); - self.terminal.terminal_stopped(term_id); + .send((*term_id, TermEvent::CloseTerminal)) + { + tracing::error!("{:?}", err); + } + self.terminal.terminal_stopped(term_id, *exit_code); if self .terminal .tab_info @@ -1988,6 +2149,7 @@ impl WindowTabData { stack_frames, variables, } => { + self.show_panel(PanelKind::Debug); self.terminal .dap_stopped(dap_id, stopped, stack_frames, variables); } @@ -2412,7 +2574,10 @@ impl WindowTabData { | PanelKind::Plugin | PanelKind::Problem | PanelKind::Debug - | PanelKind::CallHierarchy => { + | PanelKind::CallHierarchy + | PanelKind::DocumentSymbol + | PanelKind::References + | PanelKind::Implementation => { // Some panels don't accept focus (yet). Fall back to visibility check // in those cases. self.panel.is_panel_visible(&kind) @@ -2527,6 +2692,9 @@ impl WindowTabData { self.terminal.debug.source_breakpoints(), ) }; + if !self.panel.is_panel_visible(&PanelKind::Debug) { + self.panel.show_panel(&PanelKind::Debug); + } } } } diff --git a/lapce-core/src/directory.rs b/lapce-core/src/directory.rs index 6541b3352e..42849773fd 100644 --- a/lapce-core/src/directory.rs +++ b/lapce-core/src/directory.rs @@ -37,7 +37,9 @@ impl Directory { Some(dir) => { let dir = dir.data_local_dir(); if !dir.exists() { - let _ = std::fs::create_dir_all(dir); + if let Err(err) = std::fs::create_dir_all(dir) { + tracing::error!("{:?}", err); + } } Some(dir.to_path_buf()) } @@ -51,7 +53,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("logs"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) } else { @@ -64,7 +68,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("cache"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) } else { @@ -79,7 +85,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("proxy"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) } else { @@ -92,7 +100,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("themes"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) } else { @@ -106,7 +116,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("plugins"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) } else { @@ -120,7 +132,9 @@ impl Directory { Some(dir) => { let dir = dir.config_dir(); if !dir.exists() { - let _ = std::fs::create_dir_all(dir); + if let Err(err) = std::fs::create_dir_all(dir) { + tracing::error!("{:?}", err); + } } Some(dir.to_path_buf()) } @@ -136,7 +150,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("updates"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) } else { @@ -148,7 +164,9 @@ impl Directory { if let Some(dir) = Self::config_directory() { let dir = dir.join("queries"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) @@ -161,7 +179,9 @@ impl Directory { if let Some(dir) = Self::data_local_directory() { let dir = dir.join("grammars"); if !dir.exists() { - let _ = std::fs::create_dir(&dir); + if let Err(err) = std::fs::create_dir(&dir) { + tracing::error!("{:?}", err); + } } Some(dir) diff --git a/lapce-core/src/language.rs b/lapce-core/src/language.rs index 53c9ae4991..9d0c95d2b4 100644 --- a/lapce-core/src/language.rs +++ b/lapce-core/src/language.rs @@ -713,7 +713,7 @@ const LANGUAGES: &[SyntaxProperties] = &[ SyntaxProperties { id: LapceLanguage::Dockerfile, indent: Indent::space(2), - files: &["dockerfile", "containerfile"], + files: &["Dockerfile", "Containerfile"], extensions: &["containerfile", "dockerfile"], comment: comment_properties!("#"), tree_sitter: TreeSitterProperties::DEFAULT, @@ -1069,8 +1069,8 @@ const LANGUAGES: &[SyntaxProperties] = &[ SyntaxProperties { id: LapceLanguage::Just, indent: Indent::tab(), - files: &[], - extensions: &[], + files: &["justfile", "Justfile", ".justfile", ".Justfile"], + extensions: &["just"], comment: comment_properties!(), tree_sitter: TreeSitterProperties::DEFAULT, }, @@ -1169,8 +1169,8 @@ const LANGUAGES: &[SyntaxProperties] = &[ extensions: &[], comment: comment_properties!(), tree_sitter: TreeSitterProperties { - grammar: Some("markdown"), - grammar_fn: None, + grammar: Some("markdown_inline"), + grammar_fn: Some("markdown_inline"), query: Some("markdown.inline"), code_glance: (DEFAULT_CODE_GLANCE_LIST, DEFAULT_CODE_GLANCE_IGNORE_LIST), sticky_headers: &[], @@ -1567,7 +1567,7 @@ const LANGUAGES: &[SyntaxProperties] = &[ SyntaxProperties { id: LapceLanguage::Toml, indent: Indent::space(2), - files: &[], + files: &["Cargo.lock"], extensions: &["toml"], comment: comment_properties!("#"), tree_sitter: TreeSitterProperties::DEFAULT, @@ -1683,20 +1683,14 @@ impl LapceLanguage { } pub fn from_path_raw(path: &Path) -> Option { - let filename = path - .file_stem() - .and_then(|s| s.to_str().map(|s| s.to_lowercase())); + let filename = path.file_name().and_then(|s| s.to_str()); let extension = path .extension() .and_then(|s| s.to_str().map(|s| s.to_lowercase())); // NOTE: This is a linear search. It is assumed that this function // isn't called in any tight loop. for properties in LANGUAGES { - if properties - .files - .iter() - .any(|f| Some(*f) == filename.as_deref()) - { + if properties.files.iter().any(|f| Some(*f) == filename) { return Some(properties.id); } if properties @@ -1771,10 +1765,16 @@ impl LapceLanguage { let grammar_fn_name = self.grammar_fn_name(); if let Some(grammars_dir) = Directory::grammars_directory() { - if let Ok(grammar) = - self::load_grammar(&grammar_name, &grammar_fn_name, &grammars_dir) + match self::load_grammar(&grammar_name, &grammar_fn_name, &grammars_dir) { - return Some(grammar); + Ok(grammar) => { + return Some(grammar); + } + Err(err) => { + if self != &LapceLanguage::PlainText { + tracing::error!("{:?} {:?}", self, err); + } + } } }; diff --git a/lapce-core/src/meta.rs b/lapce-core/src/meta.rs index d79ab2f8d2..1add227339 100644 --- a/lapce-core/src/meta.rs +++ b/lapce-core/src/meta.rs @@ -1,4 +1,4 @@ -#[derive(strum_macros::AsRefStr)] +#[derive(strum_macros::AsRefStr, PartialEq, Eq)] pub enum ReleaseType { Debug, Stable, diff --git a/lapce-core/src/style.rs b/lapce-core/src/style.rs index fed6642423..9f6a5cd211 100644 --- a/lapce-core/src/style.rs +++ b/lapce-core/src/style.rs @@ -33,6 +33,14 @@ pub const SCOPES: &[&str] = &[ "conceal", "none", "tag", + "markup.bold", + "markup.italic", + "markup.list", + "markup.quote", + "markup.heading", + "markup.link.url", + "markup.link.label", + "markup.link.text", ]; pub fn line_styles( diff --git a/lapce-core/src/syntax/mod.rs b/lapce-core/src/syntax/mod.rs index 0a7c764bcf..8295432d03 100644 --- a/lapce-core/src/syntax/mod.rs +++ b/lapce-core/src/syntax/mod.rs @@ -491,7 +491,9 @@ impl SyntaxLayers { let cancel_flag = AtomicUsize::new(0); if let Some(source) = source { - let _ = syntax.update(0, 0, source, None, &cancel_flag); + if let Err(err) = syntax.update(0, 0, source, None, &cancel_flag) { + tracing::error!("{:?}", err); + } } syntax @@ -723,22 +725,26 @@ impl SyntaxLayers { if let (Some(injection_capture), Some(content_node)) = (injection_capture, content_node) { - if let Ok(config) = - (injection_callback)(&injection_capture) - { - let ranges = intersect_ranges( - &layer.ranges, - &[content_node], - included_children, - ); - - if !ranges.is_empty() { - if content_node.start_byte() < last_injection_end - { - continue; + match (injection_callback)(&injection_capture) { + Ok(config) => { + let ranges = intersect_ranges( + &layer.ranges, + &[content_node], + included_children, + ); + + if !ranges.is_empty() { + if content_node.start_byte() + < last_injection_end + { + continue; + } + last_injection_end = content_node.end_byte(); + injections.push((config, ranges)); } - last_injection_end = content_node.end_byte(); - injections.push((config, ranges)); + } + Err(err) => { + tracing::error!("{:?}", err); } } } @@ -750,14 +756,19 @@ impl SyntaxLayers { if let (Some(lang_name), false) = (lang_name, content_nodes.is_empty()) { - if let Ok(config) = (injection_callback)(&lang_name) { - let ranges = intersect_ranges( - &layer.ranges, - &content_nodes, - included_children, - ); - if !ranges.is_empty() { - injections.push((config, ranges)); + match (injection_callback)(&lang_name) { + Ok(config) => { + let ranges = intersect_ranges( + &layer.ranges, + &content_nodes, + included_children, + ); + if !ranges.is_empty() { + injections.push((config, ranges)); + } + } + Err(err) => { + tracing::error!("{:?}", err); } } } @@ -949,8 +960,11 @@ impl Syntax { None => return, }; let edits = edits.filter(|edits| new_rev == self.rev + edits.len() as u64); - let _ = - layers.update(self.rev, new_rev, &new_text, edits, &self.cancel_flag); + if let Err(err) = + layers.update(self.rev, new_rev, &new_text, edits, &self.cancel_flag) + { + tracing::error!("{:?}", err); + } let tree = layers.try_tree(); let styles = if tree.is_some() { diff --git a/lapce-proxy/src/buffer.rs b/lapce-proxy/src/buffer.rs index 9c2ca90708..1e95fae8e4 100644 --- a/lapce-proxy/src/buffer.rs +++ b/lapce-proxy/src/buffer.rs @@ -131,8 +131,8 @@ impl Buffer { self.rope.to_string() } - pub fn offset_of_line(&self, offset: usize) -> usize { - self.rope.offset_of_line(offset) + pub fn offset_of_line(&self, line: usize) -> usize { + self.rope.offset_of_line(line) } pub fn line_of_offset(&self, offset: usize) -> usize { @@ -163,6 +163,11 @@ impl Buffer { self.rope.slice_to_cow(range) } + pub fn line_to_cow(&self, line: usize) -> Cow { + self.rope + .slice_to_cow(self.offset_of_line(line)..self.offset_of_line(line + 1)) + } + /// Iterate over (utf8_offset, char) values in the given range /// This uses `iter_chunks` and so does not allocate, compared to `slice_to_cow` which can pub fn char_indices_iter( diff --git a/lapce-proxy/src/dispatch.rs b/lapce-proxy/src/dispatch.rs index d27e397afe..4fe0fe90e5 100644 --- a/lapce-proxy/src/dispatch.rs +++ b/lapce-proxy/src/dispatch.rs @@ -21,8 +21,10 @@ use grep_regex::RegexMatcherBuilder; use grep_searcher::{sinks::UTF8, SearcherBuilder}; use indexmap::IndexMap; use lapce_rpc::{ + buffer::BufferId, core::{CoreNotification, CoreRpcHandler}, file::FileNodeItem, + file_line::FileLine, proxy::{ ProxyHandler, ProxyNotification, ProxyRequest, ProxyResponse, ProxyRpcHandler, SearchMatch, @@ -163,7 +165,9 @@ impl ProxyHandler for Dispatcher { ); } UpdatePluginConfigs { configs } => { - let _ = self.catalog_rpc.update_plugin_configs(configs); + if let Err(err) = self.catalog_rpc.update_plugin_configs(configs) { + tracing::error!("{:?}", err); + } } NewTerminal { term_id, profile } => { let mut terminal = match Terminal::new(term_id, profile, 50, 10) { @@ -177,12 +181,12 @@ impl ProxyHandler for Dispatcher { #[allow(unused)] let mut child_id = None; + #[cfg(target_os = "windows")] + { + child_id = terminal.pty.child_watcher().pid().map(|x| x.get()); + } #[cfg(not(target_os = "windows"))] { - // Alacritty currently doesn't expose the child process ID on windows, so this won't compile - // Alacritty does acquire this information, but it is discarded - // This is currently only used for debug adapter protocol's RunInTerminal request, which we - // specify isn't supported on Windows at the moment child_id = Some(terminal.pty.child().id()); } @@ -226,57 +230,86 @@ impl ProxyHandler for Dispatcher { config, breakpoints, } => { - let _ = self.catalog_rpc.dap_start(config, breakpoints); + if let Err(err) = self.catalog_rpc.dap_start(config, breakpoints) { + tracing::error!("{:?}", err); + } } DapProcessId { dap_id, process_id, term_id, } => { - let _ = self.catalog_rpc.dap_process_id(dap_id, process_id, term_id); + if let Err(err) = + self.catalog_rpc.dap_process_id(dap_id, process_id, term_id) + { + tracing::error!("{:?}", err); + } } DapContinue { dap_id, thread_id } => { - let _ = self.catalog_rpc.dap_continue(dap_id, thread_id); + if let Err(err) = self.catalog_rpc.dap_continue(dap_id, thread_id) { + tracing::error!("{:?}", err); + } } DapPause { dap_id, thread_id } => { - let _ = self.catalog_rpc.dap_pause(dap_id, thread_id); + if let Err(err) = self.catalog_rpc.dap_pause(dap_id, thread_id) { + tracing::error!("{:?}", err); + } } DapStepOver { dap_id, thread_id } => { - let _ = self.catalog_rpc.dap_step_over(dap_id, thread_id); + if let Err(err) = self.catalog_rpc.dap_step_over(dap_id, thread_id) { + tracing::error!("{:?}", err); + } } DapStepInto { dap_id, thread_id } => { - let _ = self.catalog_rpc.dap_step_into(dap_id, thread_id); + if let Err(err) = self.catalog_rpc.dap_step_into(dap_id, thread_id) { + tracing::error!("{:?}", err); + } } DapStepOut { dap_id, thread_id } => { - let _ = self.catalog_rpc.dap_step_out(dap_id, thread_id); + if let Err(err) = self.catalog_rpc.dap_step_out(dap_id, thread_id) { + tracing::error!("{:?}", err); + } } DapStop { dap_id } => { - let _ = self.catalog_rpc.dap_stop(dap_id); + if let Err(err) = self.catalog_rpc.dap_stop(dap_id) { + tracing::error!("{:?}", err); + } } DapDisconnect { dap_id } => { - let _ = self.catalog_rpc.dap_disconnect(dap_id); + if let Err(err) = self.catalog_rpc.dap_disconnect(dap_id) { + tracing::error!("{:?}", err); + } } DapRestart { dap_id, breakpoints, } => { - let _ = self.catalog_rpc.dap_restart(dap_id, breakpoints); + if let Err(err) = self.catalog_rpc.dap_restart(dap_id, breakpoints) { + tracing::error!("{:?}", err); + } } DapSetBreakpoints { dap_id, path, breakpoints, } => { - let _ = + if let Err(err) = self.catalog_rpc - .dap_set_breakpoints(dap_id, path, breakpoints); + .dap_set_breakpoints(dap_id, path, breakpoints) + { + tracing::error!("{:?}", err); + } } InstallVolt { volt } => { let catalog_rpc = self.catalog_rpc.clone(); - let _ = catalog_rpc.install_volt(volt); + if let Err(err) = catalog_rpc.install_volt(volt) { + tracing::error!("{:?}", err); + } } ReloadVolt { volt } => { - let _ = self.catalog_rpc.reload_volt(volt); + if let Err(err) = self.catalog_rpc.reload_volt(volt) { + tracing::error!("{:?}", err); + } } RemoveVolt { volt } => { self.catalog_rpc.remove_volt(volt); @@ -285,7 +318,9 @@ impl ProxyHandler for Dispatcher { self.catalog_rpc.stop_volt(volt); } EnableVolt { volt } => { - let _ = self.catalog_rpc.enable_volt(volt); + if let Err(err) = self.catalog_rpc.enable_volt(volt) { + tracing::error!("{:?}", err); + } } GitCommit { message, diffs } => { if let Some(workspace) = self.workspace.as_ref() { @@ -1073,6 +1108,62 @@ impl ProxyHandler for Dispatcher { proxy_rpc.handle_response(id, result); }); } + GetCodeLensResolve { code_lens, path } => { + let proxy_rpc = self.proxy_rpc.clone(); + self.catalog_rpc.get_code_lens_resolve( + &path, + &code_lens, + move |plugin_id, result| { + let result = result.map(|resp| { + ProxyResponse::GetCodeLensResolveResponse { + plugin_id, + resp, + } + }); + proxy_rpc.handle_response(id, result); + }, + ); + } + GotoImplementation { path, position } => { + let proxy_rpc = self.proxy_rpc.clone(); + self.catalog_rpc.go_to_implementation( + &path, + position, + move |plugin_id, result| { + let result = result.map(|resp| { + ProxyResponse::GotoImplementationResponse { + plugin_id, + resp, + } + }); + proxy_rpc.handle_response(id, result); + }, + ); + } + ReferencesResolve { items } => { + let items: Vec = items + .into_iter() + .filter_map(|location| { + let Ok(path) = location.uri.to_file_path() else { + tracing::error!( + "get file path fail: {:?}", + location.uri + ); + return None; + }; + let buffer = self.get_buffer_or_insert(path.clone()); + let line_num = location.range.start.line as usize; + let content = buffer.line_to_cow(line_num).to_string(); + Some(FileLine { + path, + position: location.range.start, + content, + }) + }) + .collect(); + let resp = ProxyResponse::ReferencesResolveResponse { items }; + self.proxy_rpc.handle_response(id, Ok(resp)); + } } } } @@ -1100,6 +1191,12 @@ impl Dispatcher { fn respond_rpc(&self, id: RequestId, result: Result) { self.proxy_rpc.handle_response(id, result); } + + fn get_buffer_or_insert(&mut self, path: PathBuf) -> &mut Buffer { + self.buffers + .entry(path.clone()) + .or_insert(Buffer::new(BufferId::next(), path)) + } } struct FileWatchNotifier { @@ -1176,14 +1273,18 @@ impl FileWatchNotifier { if let Some(sender) = handler.as_mut() { if explorer_change { // only send the value if we need to update file explorer as well - let _ = sender.send(explorer_change); + if let Err(err) = sender.send(explorer_change) { + tracing::error!("{:?}", err); + } } return; } let (sender, receiver) = crossbeam_channel::unbounded(); if explorer_change { // only send the value if we need to update file explorer as well - let _ = sender.send(explorer_change); + if let Err(err) = sender.send(explorer_change) { + tracing::error!("{:?}", err); + } } let local_handler = self.workspace_fs_change_handler.clone(); @@ -1555,7 +1656,7 @@ fn search_in_path( if path.is_file() { let mut line_matches = Vec::new(); - let _ = searcher.search_path( + if let Err(err) = searcher.search_path( &matcher, path.clone(), UTF8(|lnum, line| { @@ -1594,7 +1695,11 @@ fn search_in_path( }); Ok(true) }), - ); + ) { + { + tracing::error!("{:?}", err); + } + } if !line_matches.is_empty() { matches.insert(path.clone(), line_matches); } diff --git a/lapce-proxy/src/lib.rs b/lapce-proxy/src/lib.rs index b694870216..7130434142 100644 --- a/lapce-proxy/src/lib.rs +++ b/lapce-proxy/src/lib.rs @@ -66,10 +66,18 @@ pub fn mainloop() { for msg in local_core_rpc.rx() { match msg { CoreRpc::Request(id, rpc) => { - let _ = local_writer_tx.send(RpcMessage::Request(id, rpc)); + if let Err(err) = + local_writer_tx.send(RpcMessage::Request(id, rpc)) + { + tracing::error!("{:?}", err); + } } CoreRpc::Notification(rpc) => { - let _ = local_writer_tx.send(RpcMessage::Notification(rpc)); + if let Err(err) = + local_writer_tx.send(RpcMessage::Notification(rpc)) + { + tracing::error!("{:?}", err); + } } CoreRpc::Shutdown => { return; @@ -87,10 +95,18 @@ pub fn mainloop() { let writer_tx = writer_tx.clone(); local_proxy_rpc.request_async(req, move |result| match result { Ok(resp) => { - let _ = writer_tx.send(RpcMessage::Response(id, resp)); + if let Err(err) = + writer_tx.send(RpcMessage::Response(id, resp)) + { + tracing::error!("{:?}", err); + } } Err(e) => { - let _ = writer_tx.send(RpcMessage::Error(id, e)); + if let Err(err) = + writer_tx.send(RpcMessage::Error(id, e)) + { + tracing::error!("{:?}", err); + } } }); } @@ -110,9 +126,13 @@ pub fn mainloop() { let local_proxy_rpc = proxy_rpc.clone(); std::thread::spawn(move || { - let _ = listen_local_socket(local_proxy_rpc); + if let Err(err) = listen_local_socket(local_proxy_rpc) { + tracing::error!("{:?}", err); + } }); - let _ = register_lapce_path(); + if let Err(err) = register_lapce_path() { + tracing::error!("{:?}", err); + } proxy_rpc.mainloop(&mut dispatcher); } @@ -138,7 +158,9 @@ pub fn register_lapce_path() -> Result<()> { fn listen_local_socket(proxy_rpc: ProxyRpcHandler) -> Result<()> { let local_socket = Directory::local_socket() .ok_or_else(|| anyhow!("can't get local socket folder"))?; - let _ = std::fs::remove_file(&local_socket); + if let Err(err) = std::fs::remove_file(&local_socket) { + tracing::error!("{:?}", err); + } let socket = interprocess::local_socket::LocalSocketListener::bind(local_socket)?; for stream in socket.incoming().flatten() { diff --git a/lapce-proxy/src/plugin/catalog.rs b/lapce-proxy/src/plugin/catalog.rs index be97ef1bb0..b17913e973 100644 --- a/lapce-proxy/src/plugin/catalog.rs +++ b/lapce-proxy/src/plugin/catalog.rs @@ -19,8 +19,8 @@ use lapce_rpc::{ use lapce_xi_rope::{Rope, RopeDelta}; use lsp_types::{ notification::DidOpenTextDocument, request::Request, DidOpenTextDocumentParams, - SemanticTokens, TextDocumentIdentifier, TextDocumentItem, - VersionedTextDocumentIdentifier, + MessageType, SemanticTokens, ShowMessageParams, TextDocumentIdentifier, + TextDocumentItem, VersionedTextDocumentIdentifier, }; use parking_lot::Mutex; use psp_types::Notification; @@ -209,7 +209,11 @@ impl PluginCatalog { self.plugin_configurations.get(&meta.name).cloned(); let plugin_rpc = self.plugin_rpc.clone(); thread::spawn(move || { - let _ = start_volt(workspace, configurations, plugin_rpc, meta); + if let Err(err) = + start_volt(workspace, configurations, plugin_rpc, meta) + { + tracing::error!("{:?}", err); + } }); } } @@ -242,21 +246,31 @@ impl PluginCatalog { { let mut builder = globset::GlobSetBuilder::new(); for glob in globs { - if let Ok(glob) = globset::Glob::new(glob) { - builder.add(glob); + match globset::Glob::new(glob) { + Ok(glob) => { + builder.add(glob); + } + Err(err) => { + tracing::error!("{:?}", err); + } } } - if let Ok(matcher) = builder.build() { - if !matcher.is_empty() { - for entry in walkdir::WalkDir::new(workspace) - .into_iter() - .flatten() - { - if matcher.is_match(entry.path()) { - return Some(id.clone()); + match builder.build() { + Ok(matcher) => { + if !matcher.is_empty() { + for entry in walkdir::WalkDir::new(workspace) + .into_iter() + .flatten() + { + if matcher.is_match(entry.path()) { + return Some(id.clone()); + } } } } + Err(err) => { + tracing::error!("{:?}", err); + } } } } @@ -268,8 +282,13 @@ impl PluginCatalog { } pub fn handle_did_open_text_document(&mut self, document: TextDocumentItem) { - if let Ok(path) = document.uri.to_file_path() { - self.open_files.insert(path, document.language_id.clone()); + match document.uri.to_file_path() { + Ok(path) => { + self.open_files.insert(path, document.language_id.clone()); + } + Err(err) => { + tracing::error!("{:?}", err); + } } let to_be_activated: Vec = self @@ -472,21 +491,25 @@ impl PluginCatalog { } PluginServerLoaded(plugin) => { // TODO: check if the server has did open registered - if let Ok(ProxyResponse::GetOpenFilesContentResponse { items }) = - self.plugin_rpc.proxy_rpc.get_open_files_content() - { - for item in items { - let language_id = Some(item.language_id.clone()); - let path = item.uri.to_file_path().ok(); - plugin.server_notification( - DidOpenTextDocument::METHOD, - DidOpenTextDocumentParams { - text_document: item, - }, - language_id, - path, - true, - ); + match self.plugin_rpc.proxy_rpc.get_open_files_content() { + Ok(ProxyResponse::GetOpenFilesContentResponse { items }) => { + for item in items { + let language_id = Some(item.language_id.clone()); + let path = item.uri.to_file_path().ok(); + plugin.server_notification( + DidOpenTextDocument::METHOD, + DidOpenTextDocumentParams { + text_document: item, + }, + language_id, + path, + true, + ); + } + } + Ok(_) => {} + Err(err) => { + tracing::error!("{:?}", err); } } @@ -512,8 +535,11 @@ impl PluginCatalog { let catalog_rpc = self.plugin_rpc.clone(); catalog_rpc.stop_volt(volt.clone()); thread::spawn(move || { - let _ = - install_volt(catalog_rpc, workspace, configurations, volt); + if let Err(err) = + install_volt(catalog_rpc, workspace, configurations, volt) + { + tracing::error!("{:?}", err); + } }); } ReloadVolt(volt) => { @@ -525,7 +551,9 @@ impl PluginCatalog { plugin.shutdown(); } } - let _ = self.plugin_rpc.unactivated_volts(vec![volt]); + if let Err(err) = self.plugin_rpc.unactivated_volts(vec![volt]) { + tracing::error!("{:?}", err); + } } StopVolt(volt) => { let volt_id = volt.id(); @@ -546,7 +574,9 @@ impl PluginCatalog { } let plugin_rpc = self.plugin_rpc.clone(); thread::spawn(move || { - let _ = enable_volt(plugin_rpc, volt); + if let Err(err) = enable_volt(plugin_rpc, volt) { + tracing::error!("{:?}", err); + } }); } DapLoaded(dap_rpc) => { @@ -567,7 +597,7 @@ impl PluginCatalog { .and_then(|ty| self.debuggers.get(ty).cloned()) { thread::spawn(move || { - if let Ok(dap_rpc) = DapClient::start( + match DapClient::start( DapServer { program: debugger.program, args: debugger.args.unwrap_or_default(), @@ -577,11 +607,30 @@ impl PluginCatalog { breakpoints, plugin_rpc.clone(), ) { - let _ = plugin_rpc.dap_loaded(dap_rpc.clone()); + Ok(dap_rpc) => { + if let Err(err) = + plugin_rpc.dap_loaded(dap_rpc.clone()) + { + tracing::error!("{:?}", err); + } - let _ = dap_rpc.launch(&config); + if let Err(err) = dap_rpc.launch(&config) { + tracing::error!("{:?}", err); + } + } + Err(err) => { + tracing::error!("{:?}", err); + } } }); + } else { + self.plugin_rpc.core_rpc.show_message( + "debug fail".to_owned(), + ShowMessageParams { + typ: MessageType::ERROR, + message: "Debugger not found. Please install the appropriate plugin.".to_owned(), + }, + ) } } DapProcessId { @@ -590,7 +639,11 @@ impl PluginCatalog { term_id, } => { if let Some(dap) = self.daps.get(&dap_id) { - let _ = dap.termain_process_tx.send((term_id, process_id)); + if let Err(err) = + dap.termain_process_tx.send((term_id, process_id)) + { + tracing::error!("{:?}", err); + } } } DapContinue { dap_id, thread_id } => { @@ -606,7 +659,9 @@ impl PluginCatalog { DapPause { dap_id, thread_id } => { if let Some(dap) = self.daps.get(&dap_id).cloned() { thread::spawn(move || { - let _ = dap.pause_thread(thread_id); + if let Err(err) = dap.pause_thread(thread_id) { + tracing::error!("{:?}", err); + } }); } } @@ -633,7 +688,9 @@ impl PluginCatalog { DapDisconnect { dap_id } => { if let Some(dap) = self.daps.get(&dap_id).cloned() { thread::spawn(move || { - let _ = dap.disconnect(); + if let Err(err) = dap.disconnect() { + tracing::error!("{:?}", err); + } }); } } @@ -656,12 +713,17 @@ impl PluginCatalog { path.clone(), breakpoints, move |result: Result| { - if let Ok(resp) = result { - core_rpc.dap_breakpoints_resp( - dap_id, - path, - resp.breakpoints.unwrap_or_default(), - ); + match result { + Ok(resp) => { + core_rpc.dap_breakpoints_resp( + dap_id, + path, + resp.breakpoints.unwrap_or_default(), + ); + } + Err(err) => { + tracing::error!("{:?}", err); + } } }, ); diff --git a/lapce-proxy/src/plugin/dap.rs b/lapce-proxy/src/plugin/dap.rs index ba22379048..d7fbb11a44 100644 --- a/lapce-proxy/src/plugin/dap.rs +++ b/lapce-proxy/src/plugin/dap.rs @@ -112,12 +112,14 @@ impl DapClient { thread::spawn(move || -> Result<()> { for msg in io_rx { if let Ok(msg) = serde_json::to_string(&msg) { + tracing::debug!("write to dap server: {}", msg); let msg = format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg); writer.write_all(msg.as_bytes())?; writer.flush()?; } } + tracing::debug!("thread(write to dap) exited"); Ok(()) }); @@ -128,12 +130,15 @@ impl DapClient { loop { match crate::plugin::lsp::read_message(&mut reader) { Ok(message_str) => { + tracing::debug!("read from dap server: {}", message_str); dap_rpc.handle_server_message(&message_str); } Err(_err) => { - let _ = io_tx.send(DapPayload::Event( - DapEvent::Initialized(None), - )); + if let Err(err) = io_tx + .send(DapPayload::Event(DapEvent::Initialized(None))) + { + tracing::error!("{:?}", err); + } plugin_rpc.core_rpc.log( lapce_rpc::core::LogLevel::Error, format!("dap server {program} stopped!"), @@ -141,6 +146,7 @@ impl DapClient { ); dap_rpc.disconnected(); + tracing::debug!("thread(read from dap) exited"); return; } }; @@ -209,18 +215,25 @@ impl DapClient { match event { DapEvent::Initialized(_) => { for (path, breakpoints) in self.breakpoints.clone().into_iter() { - if let Ok(breakpoints) = - self.dap_rpc.set_breakpoints(path.clone(), breakpoints) - { - self.plugin_rpc.core_rpc.dap_breakpoints_resp( - self.config.dap_id, - path, - breakpoints.breakpoints.unwrap_or_default(), - ); + match self.dap_rpc.set_breakpoints(path.clone(), breakpoints) { + Ok(breakpoints) => { + self.plugin_rpc.core_rpc.dap_breakpoints_resp( + self.config.dap_id, + path, + breakpoints.breakpoints.unwrap_or_default(), + ); + } + Err(err) => { + tracing::error!("{:?}", err); + } } } // send dap configurations here - let _ = self.dap_rpc.request::(()); + self.dap_rpc.request_async::((), |rs| { + if let Err(e) = rs { + tracing::error!("request ConfigurationDone: {:?}", e) + } + }); } DapEvent::Stopped(stopped) => { let all_threads_stopped = @@ -288,7 +301,9 @@ impl DapClient { if let Some(term_id) = self.term_id { self.plugin_rpc.proxy_rpc.terminal_close(term_id); } - let _ = self.check_restart(); + if let Err(err) = self.check_restart() { + tracing::error!("{:?}", err); + } } DapEvent::Thread { .. } => {} DapEvent::Output(_) => {} @@ -313,10 +328,6 @@ impl DapClient { path_format: Some("path".to_owned()), supports_variable_type: Some(true), supports_variable_paging: Some(false), - // See comment on dispatch of `NewTerminal` - #[cfg(target_os = "windows")] - supports_run_in_terminal_request: Some(false), - #[cfg(not(target_os = "windows"))] supports_run_in_terminal_request: Some(true), supports_memory_references: Some(false), supports_progress_reporting: Some(false), @@ -341,11 +352,15 @@ impl DapClient { .unwrap_or(false) { thread::spawn(move || { - let _ = dap_rpc.terminate(); + if let Err(err) = dap_rpc.terminate() { + tracing::error!("{:?}", err); + } }); } else { thread::spawn(move || { - let _ = dap_rpc.disconnect(); + if let Err(err) = dap_rpc.disconnect() { + tracing::error!("{:?}", err); + } }); } } @@ -379,7 +394,9 @@ impl DapClient { let dap_rpc = self.dap_rpc.clone(); let config = self.config.clone(); thread::spawn(move || { - let _ = dap_rpc.launch(&config); + if let Err(err) = dap_rpc.launch(&config) { + tracing::error!("{:?}", err); + } }); Ok(()) @@ -390,8 +407,8 @@ impl DapClient { self.breakpoints = breakpoints; if !self.terminated { self.stop(); - } else { - let _ = self.check_restart(); + } else if let Err(err) = self.check_restart() { + tracing::error!("{:?}", err); } } } @@ -449,17 +466,23 @@ impl DapRpcHandler { match msg { DapRpc::HostRequest(req) => { let result = dap_client.handle_host_request(&req); + let seq = self.seq_counter.fetch_add(1, Ordering::Relaxed); let resp = DapResponse { + seq, request_seq: req.seq, success: result.is_ok(), command: req.command.clone(), message: result.as_ref().err().map(|e| e.to_string()), body: result.ok(), }; - let _ = self.io_tx.send(DapPayload::Response(resp)); + if let Err(err) = self.io_tx.send(DapPayload::Response(resp)) { + tracing::error!("{:?}", err); + } } DapRpc::HostEvent(event) => { - let _ = dap_client.handle_host_event(&event); + if let Err(err) = dap_client.handle_host_event(&event) { + tracing::error!("{:?}", err); + } } DapRpc::Stop => { dap_client.stop(); @@ -478,7 +501,9 @@ impl DapRpcHandler { if let Some(term_id) = dap_client.term_id { dap_client.plugin_rpc.proxy_rpc.terminal_close(term_id); } - let _ = dap_client.check_restart(); + if let Err(err) = dap_client.check_restart() { + tracing::error!("{:?}", err); + } } } } @@ -558,11 +583,13 @@ impl DapRpcHandler { let mut pending = self.server_pending.lock(); pending.insert(seq, rh); } - let _ = self.io_tx.send(DapPayload::Request(DapRequest { + if let Err(err) = self.io_tx.send(DapPayload::Request(DapRequest { seq, command: command.to_string(), arguments: Some(arguments), - })); + })) { + tracing::error!("{:?}", err); + } } fn handle_server_response(&self, resp: DapResponse) { @@ -575,10 +602,14 @@ impl DapRpcHandler { if let Ok(payload) = serde_json::from_str::(message_str) { match payload { DapPayload::Request(req) => { - let _ = self.rpc_tx.send(DapRpc::HostRequest(req)); + if let Err(err) = self.rpc_tx.send(DapRpc::HostRequest(req)) { + tracing::error!("{:?}", err); + } } DapPayload::Event(event) => { - let _ = self.rpc_tx.send(DapRpc::HostEvent(event)); + if let Err(err) = self.rpc_tx.send(DapRpc::HostEvent(event)) { + tracing::error!("{:?}", err); + } } DapPayload::Response(resp) => { self.handle_server_response(resp); @@ -601,15 +632,21 @@ impl DapRpcHandler { } pub fn stop(&self) { - let _ = self.rpc_tx.send(DapRpc::Stop); + if let Err(err) = self.rpc_tx.send(DapRpc::Stop) { + tracing::error!("{:?}", err); + } } pub fn restart(&self, breakpoints: HashMap>) { - let _ = self.rpc_tx.send(DapRpc::Restart(breakpoints)); + if let Err(err) = self.rpc_tx.send(DapRpc::Restart(breakpoints)) { + tracing::error!("{:?}", err); + } } fn disconnected(&self) { - let _ = self.rpc_tx.send(DapRpc::Disconnected); + if let Err(err) = self.rpc_tx.send(DapRpc::Disconnected) { + tracing::error!("{:?}", err); + } } pub fn disconnect(&self) -> Result<()> { diff --git a/lapce-proxy/src/plugin/lsp.rs b/lapce-proxy/src/plugin/lsp.rs index ca8473e0ef..fd62aa23b6 100644 --- a/lapce-proxy/src/plugin/lsp.rs +++ b/lapce-proxy/src/plugin/lsp.rs @@ -114,7 +114,9 @@ impl PluginServerHandler for LspClient { params: Params, from: String, ) { - let _ = self.host.handle_notification(method, params, from); + if let Err(err) = self.host.handle_notification(method, params, from) { + tracing::error!("{:?}", err); + } } fn handle_did_save_text_document( @@ -185,10 +187,13 @@ impl LspClient { "file" => { let path = server_uri.to_file_path().map_err(|_| anyhow!(""))?; #[cfg(unix)] - let _ = std::process::Command::new("chmod") + if let Err(err) = std::process::Command::new("chmod") .arg("+x") .arg(&path) - .output(); + .output() + { + tracing::error!("{:?}", err); + } path.to_str().ok_or_else(|| anyhow!(""))?.to_string() } "urn" => server_uri.path().to_string(), @@ -218,10 +223,15 @@ impl LspClient { break; } if let Ok(msg) = serde_json::to_string(&msg) { + tracing::debug!("write to lsp: {}", msg); let msg = format!("Content-Length: {}\r\n\r\n{}", msg.len(), msg); - let _ = writer.write(msg.as_bytes()); - let _ = writer.flush(); + if let Err(err) = writer.write(msg.as_bytes()) { + tracing::error!("{:?}", err); + } + if let Err(err) = writer.flush() { + tracing::error!("{:?}", err); + } } } }); @@ -235,12 +245,17 @@ impl LspClient { loop { match read_message(&mut reader) { Ok(message_str) => { + if !message_str.contains("$/progress") { + tracing::debug!("read from lsp: {}", message_str); + } if let Some(resp) = handle_plugin_server_message( &local_server_rpc, &message_str, &name, ) { - let _ = io_tx.send(resp); + if let Err(err) = io_tx.send(resp) { + tracing::error!("{:?}", err); + } } } Err(_err) => { @@ -347,6 +362,7 @@ impl LspClient { .workspace .clone() .map(|p| Url::from_directory_path(p).unwrap()); + tracing::debug!("initialization_options {:?}", self.options); #[allow(deprecated)] let params = InitializeParams { process_id: Some(process::id()), @@ -368,29 +384,35 @@ impl LspClient { root_path: None, work_done_progress_params: WorkDoneProgressParams::default(), }; - if let Ok(value) = self.server_rpc.server_request( + match self.server_rpc.server_request( Initialize::METHOD, params, None, None, false, ) { - let result: InitializeResult = serde_json::from_value(value).unwrap(); - self.host.server_capabilities = result.capabilities; - self.server_rpc.server_notification( - Initialized::METHOD, - InitializedParams {}, - None, - None, - false, - ); - if self - .plugin_rpc - .plugin_server_loaded(self.server_rpc.clone()) - .is_err() - { - self.server_rpc.shutdown(); - self.shutdown(); + Ok(value) => { + let result: InitializeResult = + serde_json::from_value(value).unwrap(); + self.host.server_capabilities = result.capabilities; + self.server_rpc.server_notification( + Initialized::METHOD, + InitializedParams {}, + None, + None, + false, + ); + if self + .plugin_rpc + .plugin_server_loaded(self.server_rpc.clone()) + .is_err() + { + self.server_rpc.shutdown(); + self.shutdown(); + } + } + Err(err) => { + tracing::error!("{:?}", err); } } // move |result| { @@ -406,8 +428,12 @@ impl LspClient { } fn shutdown(&mut self) { - let _ = self.process.kill(); - let _ = self.process.wait(); + if let Err(err) = self.process.kill() { + tracing::error!("{:?}", err); + } + if let Err(err) = self.process.wait() { + tracing::error!("{:?}", err); + } } fn process( diff --git a/lapce-proxy/src/plugin/mod.rs b/lapce-proxy/src/plugin/mod.rs index 009a899047..677c3c4926 100644 --- a/lapce-proxy/src/plugin/mod.rs +++ b/lapce-proxy/src/plugin/mod.rs @@ -34,12 +34,13 @@ use lapce_xi_rope::{Rope, RopeDelta}; use lsp_types::{ request::{ CallHierarchyIncomingCalls, CallHierarchyPrepare, CodeActionRequest, - CodeActionResolveRequest, CodeLensRequest, Completion, - DocumentSymbolRequest, Formatting, GotoDefinition, GotoTypeDefinition, - GotoTypeDefinitionParams, GotoTypeDefinitionResponse, HoverRequest, - InlayHintRequest, InlineCompletionRequest, PrepareRenameRequest, References, - Rename, Request, ResolveCompletionItem, SelectionRangeRequest, - SemanticTokensFullRequest, SignatureHelpRequest, WorkspaceSymbolRequest, + CodeActionResolveRequest, CodeLensRequest, CodeLensResolve, Completion, + DocumentSymbolRequest, Formatting, GotoDefinition, GotoImplementation, + GotoImplementationResponse, GotoTypeDefinition, GotoTypeDefinitionParams, + GotoTypeDefinitionResponse, HoverRequest, InlayHintRequest, + InlineCompletionRequest, PrepareRenameRequest, References, Rename, Request, + ResolveCompletionItem, SelectionRangeRequest, SemanticTokensFullRequest, + SignatureHelpRequest, WorkspaceSymbolRequest, }, CallHierarchyClientCapabilities, CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, CallHierarchyPrepareParams, @@ -49,10 +50,10 @@ use lsp_types::{ CodeActionResponse, CodeLens, CodeLensParams, CompletionClientCapabilities, CompletionItem, CompletionItemCapability, CompletionItemCapabilityResolveSupport, CompletionParams, CompletionResponse, - Diagnostic, DocumentFormattingParams, DocumentSymbolParams, - DocumentSymbolResponse, FormattingOptions, GotoCapability, GotoDefinitionParams, - GotoDefinitionResponse, Hover, HoverClientCapabilities, HoverParams, InlayHint, - InlayHintClientCapabilities, InlayHintParams, + Diagnostic, DocumentFormattingParams, DocumentSymbolClientCapabilities, + DocumentSymbolParams, DocumentSymbolResponse, FormattingOptions, GotoCapability, + GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverClientCapabilities, + HoverParams, InlayHint, InlayHintClientCapabilities, InlayHintParams, InlineCompletionClientCapabilities, InlineCompletionParams, InlineCompletionResponse, InlineCompletionTriggerKind, Location, MarkupKind, MessageActionItemCapabilities, ParameterInformationSettings, @@ -241,7 +242,9 @@ impl PluginCatalogRpcHandler { #[allow(dead_code)] fn handle_response(&self, id: RequestId, result: Result) { if let Some(chan) = { self.pending.lock().remove(&id) } { - let _ = chan.send(result); + if let Err(err) = chan.send(result) { + tracing::error!("{:?}", err); + } } } @@ -354,8 +357,14 @@ impl PluginCatalogRpcHandler { } pub fn shutdown(&self) { - let _ = self.catalog_notification(PluginCatalogNotification::Shutdown); - let _ = self.plugin_tx.send(PluginCatalogRpc::Shutdown); + if let Err(err) = + self.catalog_notification(PluginCatalogNotification::Shutdown) + { + tracing::error!("{:?}", err); + } + if let Err(err) = self.plugin_tx.send(PluginCatalogRpc::Shutdown) { + tracing::error!("{:?}", err); + } } fn catalog_notification( @@ -443,7 +452,9 @@ impl PluginCatalogRpcHandler { check, f: Box::new(f), }; - let _ = self.plugin_tx.send(rpc); + if let Err(err) = self.plugin_tx.send(rpc) { + tracing::error!("{:?}", err); + } } #[allow(clippy::too_many_arguments)] @@ -465,7 +476,9 @@ impl PluginCatalogRpcHandler { path, check, }; - let _ = self.plugin_tx.send(rpc); + if let Err(err) = self.plugin_tx.send(rpc) { + tracing::error!("{:?}", err); + } } pub fn format_semantic_tokens( @@ -475,24 +488,32 @@ impl PluginCatalogRpcHandler { text: Rope, f: Box, RpcError>>, ) { - let _ = self.plugin_tx.send(PluginCatalogRpc::FormatSemanticTokens { - plugin_id, - tokens, - text, - f, - }); + if let Err(err) = + self.plugin_tx.send(PluginCatalogRpc::FormatSemanticTokens { + plugin_id, + tokens, + text, + f, + }) + { + tracing::error!("{:?}", err); + } } pub fn did_save_text_document(&self, path: &Path, text: Rope) { let text_document = TextDocumentIdentifier::new(Url::from_file_path(path).unwrap()); let language_id = language_id_from_path(path).unwrap_or("").to_string(); - let _ = self.plugin_tx.send(PluginCatalogRpc::DidSaveTextDocument { - language_id, - text_document, - path: path.into(), - text, - }); + if let Err(err) = + self.plugin_tx.send(PluginCatalogRpc::DidSaveTextDocument { + language_id, + text_document, + path: path.into(), + text, + }) + { + tracing::error!("{:?}", err); + } } pub fn did_change_text_document( @@ -508,15 +529,18 @@ impl PluginCatalogRpcHandler { rev as i32, ); let language_id = language_id_from_path(path).unwrap_or("").to_string(); - let _ = self - .plugin_tx - .send(PluginCatalogRpc::DidChangeTextDocument { - language_id, - document, - delta, - text, - new_text, - }); + if let Err(err) = + self.plugin_tx + .send(PluginCatalogRpc::DidChangeTextDocument { + language_id, + document, + delta, + text, + new_text, + }) + { + tracing::error!("{:?}", err); + } } pub fn get_definition( @@ -674,6 +698,37 @@ impl PluginCatalogRpcHandler { ); } + pub fn go_to_implementation( + &self, + path: &Path, + position: Position, + cb: impl FnOnce(PluginId, Result, RpcError>) + + Clone + + Send + + 'static, + ) { + let uri = Url::from_file_path(path).unwrap(); + let method = GotoImplementation::METHOD; + let params = GotoTypeDefinitionParams { + text_document_position_params: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri }, + position, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + + let language_id = + Some(language_id_from_path(path).unwrap_or("").to_string()); + self.send_request_to_all_plugins( + method, + params, + language_id, + Some(path.to_path_buf()), + cb, + ); + } + pub fn get_code_actions( &self, path: &Path, @@ -739,6 +794,25 @@ impl PluginCatalogRpcHandler { ); } + pub fn get_code_lens_resolve( + &self, + path: &Path, + code_lens: &CodeLens, + cb: impl FnOnce(PluginId, Result) + Clone + Send + 'static, + ) { + let method = CodeLensResolve::METHOD; + let language_id = + Some(language_id_from_path(path).unwrap_or("").to_string()); + + self.send_request_to_all_plugins( + method, + code_lens, + language_id, + Some(path.to_path_buf()), + cb, + ); + } + pub fn get_inlay_hints( &self, path: &Path, @@ -1039,8 +1113,8 @@ impl PluginCatalogRpcHandler { params, language_id, Some(path.to_path_buf()), - move |plugin_id, result| { - if let Ok(value) = result { + move |plugin_id, result| match result { + Ok(value) => { if let Ok(resp) = serde_json::from_value::(value) { @@ -1048,6 +1122,9 @@ impl PluginCatalogRpcHandler { .completion_response(request_id, input, resp, plugin_id); } } + Err(err) => { + tracing::error!("{:?}", err); + } }, ); } @@ -1118,14 +1195,17 @@ impl PluginCatalogRpcHandler { language_id, Some(path.to_path_buf()), true, - move |plugin_id, result| { - if let Ok(value) = result { + move |plugin_id, result| match result { + Ok(value) => { if let Ok(resp) = serde_json::from_value::(value) { core_rpc .signature_help_response(request_id, resp, plugin_id); } } + Err(err) => { + tracing::error!("{:?}", err); + } }, ); } @@ -1175,14 +1255,18 @@ impl PluginCatalogRpcHandler { ) { match Url::from_file_path(path) { Ok(path) => { - let _ = self.plugin_tx.send(PluginCatalogRpc::DidOpenTextDocument { - document: TextDocumentItem::new( - path, - language_id, - version, - text, - ), - }); + if let Err(err) = + self.plugin_tx.send(PluginCatalogRpc::DidOpenTextDocument { + document: TextDocumentItem::new( + path, + language_id, + version, + text, + ), + }) + { + tracing::error!("{:?}", err); + } } Err(_) => { tracing::error!("Failed to parse URL from file path: {path:?}"); @@ -1226,7 +1310,9 @@ impl PluginCatalogRpcHandler { } }), }; - let _ = self.plugin_tx.send(rpc); + if let Err(err) = self.plugin_tx.send(rpc) { + tracing::error!("{:?}", err); + } } pub fn remove_volt(&self, volt: VoltMetadata) { @@ -1243,7 +1329,9 @@ impl PluginCatalogRpcHandler { } }), }; - let _ = self.plugin_tx.send(rpc); + if let Err(err) = self.plugin_tx.send(rpc) { + tracing::error!("{:?}", err); + } } pub fn reload_volt(&self, volt: VoltMetadata) -> Result<()> { @@ -1361,11 +1449,13 @@ impl PluginCatalogRpcHandler { reference: usize, f: impl FnOnce(Result, RpcError>) + Send + 'static, ) { - let _ = self.plugin_tx.send(PluginCatalogRpc::DapVariable { + if let Err(err) = self.plugin_tx.send(PluginCatalogRpc::DapVariable { dap_id, reference, f: Box::new(f), - }); + }) { + tracing::error!("{:?}", err); + } } pub fn dap_get_scopes( @@ -1377,11 +1467,13 @@ impl PluginCatalogRpcHandler { ) + Send + 'static, ) { - let _ = self.plugin_tx.send(PluginCatalogRpc::DapGetScopes { + if let Err(err) = self.plugin_tx.send(PluginCatalogRpc::DapGetScopes { dap_id, frame_id, f: Box::new(f), - }); + }) { + tracing::error!("{:?}", err); + } } pub fn register_debugger_type( @@ -1390,13 +1482,15 @@ impl PluginCatalogRpcHandler { program: String, args: Option>, ) { - let _ = self.catalog_notification( + if let Err(err) = self.catalog_notification( PluginCatalogNotification::RegisterDebuggerType { debugger_type, program, args, }, - ); + ) { + tracing::error!("{:?}", err); + } } } @@ -1457,7 +1551,9 @@ pub fn download_volt(volt: &VoltInfo) -> Result { let plugin_dir = Directory::plugins_directory() .ok_or_else(|| anyhow!("can't get plugin directory"))? .join(id.to_string()); - let _ = fs::remove_dir_all(&plugin_dir); + if let Err(err) = fs::remove_dir_all(&plugin_dir) { + tracing::error!("{:?}", err); + } fs::create_dir_all(&plugin_dir)?; if is_zstd { @@ -1490,7 +1586,11 @@ pub fn install_volt( let local_catalog_rpc = catalog_rpc.clone(); let local_meta = meta.clone(); - let _ = start_volt(workspace, configurations, local_catalog_rpc, local_meta); + if let Err(err) = + start_volt(workspace, configurations, local_catalog_rpc, local_meta) + { + tracing::error!("{:?}", err); + } let icon = volt_icon(&meta); catalog_rpc.core_rpc.volt_installed(meta, icon); Ok(()) @@ -1623,6 +1723,10 @@ fn client_capabilities() -> ClientCapabilities { call_hierarchy: Some(CallHierarchyClientCapabilities { dynamic_registration: Some(true), }), + document_symbol: Some(DocumentSymbolClientCapabilities { + hierarchical_document_symbol_support: Some(true), + ..Default::default() + }), ..Default::default() }), window: Some(WindowClientCapabilities { diff --git a/lapce-proxy/src/plugin/psp.rs b/lapce-proxy/src/plugin/psp.rs index 7bf54e22af..205aa72f48 100644 --- a/lapce-proxy/src/plugin/psp.rs +++ b/lapce-proxy/src/plugin/psp.rs @@ -30,21 +30,23 @@ use lsp_types::{ }, request::{ CallHierarchyIncomingCalls, CallHierarchyPrepare, CodeActionRequest, - CodeActionResolveRequest, CodeLensRequest, Completion, - DocumentSymbolRequest, Formatting, GotoDefinition, GotoTypeDefinition, - HoverRequest, Initialize, InlayHintRequest, InlineCompletionRequest, - PrepareRenameRequest, References, RegisterCapability, Rename, - ResolveCompletionItem, SelectionRangeRequest, SemanticTokensFullRequest, - SignatureHelpRequest, WorkDoneProgressCreate, WorkspaceSymbolRequest, + CodeActionResolveRequest, CodeLensRequest, CodeLensResolve, Completion, + DocumentSymbolRequest, Formatting, GotoDefinition, GotoImplementation, + GotoTypeDefinition, HoverRequest, Initialize, InlayHintRequest, + InlineCompletionRequest, PrepareRenameRequest, References, + RegisterCapability, Rename, ResolveCompletionItem, SelectionRangeRequest, + SemanticTokensFullRequest, SignatureHelpRequest, WorkDoneProgressCreate, + WorkspaceSymbolRequest, }, CodeActionProviderCapability, DidChangeTextDocumentParams, DidSaveTextDocumentParams, DocumentSelector, HoverProviderCapability, - InitializeResult, LogMessageParams, MessageType, OneOf, ProgressParams, - PublishDiagnosticsParams, Range, Registration, RegistrationParams, - SemanticTokens, SemanticTokensLegend, SemanticTokensServerCapabilities, - ServerCapabilities, ShowMessageParams, TextDocumentContentChangeEvent, - TextDocumentIdentifier, TextDocumentSaveRegistrationOptions, - TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncSaveOptions, + ImplementationProviderCapability, InitializeResult, LogMessageParams, + MessageType, OneOf, ProgressParams, PublishDiagnosticsParams, Range, + Registration, RegistrationParams, SemanticTokens, SemanticTokensLegend, + SemanticTokensServerCapabilities, ServerCapabilities, ShowMessageParams, + TextDocumentContentChangeEvent, TextDocumentIdentifier, + TextDocumentSaveRegistrationOptions, TextDocumentSyncCapability, + TextDocumentSyncKind, TextDocumentSyncSaveOptions, VersionedTextDocumentIdentifier, }; use parking_lot::Mutex; @@ -72,7 +74,9 @@ impl ResponseHandler { pub fn invoke(self, result: Result) { match self { ResponseHandler::Chan(tx) => { - let _ = tx.send(result); + if let Err(err) = tx.send(result) { + tracing::error!("{:?}", err); + } } ResponseHandler::Callback(f) => f.call(result), } @@ -189,18 +193,24 @@ impl ResponseSender { code: 0, message: e.to_string(), }); - let _ = self.tx.send(result); + if let Err(err) = self.tx.send(result) { + tracing::error!("{:?}", err); + } } pub fn send_null(&self) { - let _ = self.tx.send(Ok(Value::Null)); + if let Err(err) = self.tx.send(Ok(Value::Null)) { + tracing::error!("{:?}", err); + } } pub fn send_err(&self, code: i64, message: impl Into) { - let _ = self.tx.send(Err(RpcError { + if let Err(err) = self.tx.send(Err(RpcError { code, message: message.into(), - })); + })) { + tracing::error!("{:?}", err); + } } } @@ -308,11 +318,15 @@ impl PluginServerRpcHandler { } fn send_server_rpc(&self, msg: JsonRpc) { - let _ = self.io_tx.send(msg); + if let Err(err) = self.io_tx.send(msg) { + tracing::error!("{:?}", err); + } } pub fn handle_rpc(&self, rpc: PluginServerRpc) { - let _ = self.rpc_tx.send(rpc); + if let Err(err) = self.rpc_tx.send(rpc) { + tracing::error!("{:?}", err); + } } /// Send a notification. @@ -329,12 +343,14 @@ impl PluginServerRpcHandler { let method = method.into(); if check { - let _ = self.rpc_tx.send(PluginServerRpc::ServerNotification { + if let Err(err) = self.rpc_tx.send(PluginServerRpc::ServerNotification { method, params, language_id, path, - }); + }) { + tracing::error!("{:?}", err); + } } else { self.send_server_notification(&method, params); } @@ -402,14 +418,16 @@ impl PluginServerRpcHandler { let id = self.id.fetch_add(1, Ordering::Relaxed); let params = Params::from(serde_json::to_value(params).unwrap()); if check { - let _ = self.rpc_tx.send(PluginServerRpc::ServerRequest { + if let Err(err) = self.rpc_tx.send(PluginServerRpc::ServerRequest { id: Id::Num(id as i64), method, params, language_id, path, rh, - }); + }) { + tracing::error!("{:?}", err); + } } else { self.send_server_request(Id::Num(id as i64), &method, params, rh); } @@ -749,6 +767,20 @@ impl PluginHostHandler { OneOf::Right(_) => true, }) .unwrap_or(false), + GotoImplementation::METHOD => self + .server_capabilities + .implementation_provider + .as_ref() + .map(|r| match r { + ImplementationProviderCapability::Simple(is_capable) => { + *is_capable + } + ImplementationProviderCapability::Options(_) => { + // todo + false + } + }) + .unwrap_or(false), CodeActionRequest::METHOD => self .server_capabilities .code_action_provider @@ -796,6 +828,12 @@ impl PluginHostHandler { CodeLensRequest::METHOD => { self.server_capabilities.code_lens_provider.is_some() } + CodeLensResolve::METHOD => self + .server_capabilities + .code_lens_provider + .as_ref() + .and_then(|x| x.resolve_provider) + .unwrap_or(false), CallHierarchyPrepare::METHOD => { self.server_capabilities.call_hierarchy_provider.is_some() } @@ -846,7 +884,9 @@ impl PluginHostHandler { fn register_capabilities(&mut self, registrations: Vec) { for registration in registrations { - let _ = self.register_capability(registration); + if let Err(err) = self.register_capability(registration) { + tracing::error!("{:?}", err); + } } } @@ -946,7 +986,7 @@ impl PluginHostHandler { self.spawned_lsp .insert(plugin_id, SpawnedLspInfo { resp: Some(resp) }); thread::spawn(move || { - let _ = LspClient::start( + if let Err(err) = LspClient::start( catalog_rpc, params.document_selector, workspace, @@ -958,7 +998,9 @@ impl PluginHostHandler { params.server_uri, params.server_args, params.options, - ); + ) { + tracing::error!("{:?}", err); + } }); } SendLspNotification::METHOD => { @@ -1053,7 +1095,7 @@ impl PluginHostHandler { let volt_id = self.volt_id.clone(); let volt_display_name = self.volt_display_name.clone(); thread::spawn(move || { - let _ = LspClient::start( + if let Err(err) = LspClient::start( catalog_rpc, params.document_selector, workspace, @@ -1065,7 +1107,9 @@ impl PluginHostHandler { params.server_uri, params.server_args, params.options, - ); + ) { + tracing::error!("{:?}", err); + } }); } PublishDiagnostics::METHOD => { diff --git a/lapce-proxy/src/plugin/wasi.rs b/lapce-proxy/src/plugin/wasi.rs index 741d44a71f..227be496a4 100644 --- a/lapce-proxy/src/plugin/wasi.rs +++ b/lapce-proxy/src/plugin/wasi.rs @@ -129,7 +129,9 @@ impl PluginServerHandler for Plugin { params: Params, from: String, ) { - let _ = self.host.handle_notification(method, params, from); + if let Err(err) = self.host.handle_notification(method, params, from) { + tracing::error!("{:?}", err); + } } fn handle_host_request( @@ -220,8 +222,8 @@ impl Plugin { None, None, false, - move |value| { - if let Ok(value) = value { + move |value| match value { + Ok(value) => { if let Ok(result) = serde_json::from_value(value) { server_rpc.handle_rpc(PluginServerRpc::Handler( PluginHandlerNotification::InitializeResult(result), @@ -235,6 +237,9 @@ impl Plugin { ); } } + Err(err) => { + tracing::error!("{:?}", err); + } }, ); } @@ -260,7 +265,9 @@ pub fn load_all_volts( Some(meta) }) .collect(); - let _ = plugin_rpc.unactivated_volts(volts); + if let Err(err) = plugin_rpc.unactivated_volts(volts) { + tracing::error!("{:?}", err); + } } /// Find all installed volts. @@ -502,7 +509,10 @@ pub fn start_volt( handle_plugin_server_message(&local_rpc, &msg, &volt_name) { if let Ok(msg) = serde_json::to_string(&resp) { - let _ = writeln!(local_stdin.write().unwrap(), "{msg}"); + if let Err(err) = writeln!(local_stdin.write().unwrap(), "{msg}") + { + tracing::error!("{:?}", err); + } } } } @@ -535,9 +545,13 @@ pub fn start_volt( break; } if let Ok(msg) = serde_json::to_string(&msg) { - let _ = writeln!(stdin.write().unwrap(), "{msg}"); + if let Err(err) = writeln!(stdin.write().unwrap(), "{msg}") { + tracing::error!("{:?}", err); + } + } + if let Err(err) = handle_rpc.call(&mut store, ()) { + tracing::error!("{:?}", err); } - let _ = handle_rpc.call(&mut store, ()); } } if let Some(id) = exist_id { diff --git a/lapce-proxy/src/terminal.rs b/lapce-proxy/src/terminal.rs index e7c996964c..84ab5f8b41 100644 --- a/lapce-proxy/src/terminal.rs +++ b/lapce-proxy/src/terminal.rs @@ -1,4 +1,3 @@ -use std::time::Duration; use std::{ borrow::Cow, collections::VecDeque, @@ -6,6 +5,7 @@ use std::{ num::NonZeroUsize, path::PathBuf, sync::Arc, + time::Duration, }; use alacritty_terminal::{ @@ -45,8 +45,12 @@ impl TerminalSender { } pub fn send(&self, msg: Msg) { - let _ = self.tx.send(msg); - let _ = self.poller.notify(); + if let Err(err) = self.tx.send(msg) { + tracing::error!("{:?}", err); + } + if let Err(err) = self.poller.notify() { + tracing::error!("{:?}", err); + } } } @@ -116,6 +120,7 @@ impl Terminal { polling::Events::with_capacity(NonZeroUsize::new(1024).unwrap()); let timeout = Some(Duration::from_secs(6)); + let mut exit_code = None; 'event_loop: loop { events.clear(); if let Err(err) = self.poller.wait(&mut events, timeout) { @@ -133,10 +138,13 @@ impl Terminal { for event in events.iter() { match event.key { PTY_CHILD_EVENT_TOKEN => { - if let Some(tty::ChildEvent::Exited(_)) = + if let Some(tty::ChildEvent::Exited(exited_code)) = self.pty.next_child_event() { - let _ = self.pty_read(&core_rpc, &mut buf); + if let Err(err) = self.pty_read(&core_rpc, &mut buf) { + tracing::error!("{:?}", err); + } + exit_code = exited_code; break 'event_loop; } } @@ -192,8 +200,10 @@ impl Terminal { .unwrap(); } } - core_rpc.terminal_process_stopped(self.term_id); - let _ = self.pty.deregister(&self.poller); + core_rpc.terminal_process_stopped(self.term_id, exit_code); + if let Err(err) = self.pty.deregister(&self.poller) { + tracing::error!("{:?}", err); + } } /// Drain the channel. @@ -270,9 +280,14 @@ impl Terminal { fn workdir(profile: &TerminalProfile) -> Option { if let Some(cwd) = &profile.workdir { - if let Ok(cwd) = cwd.to_file_path() { - if cwd.exists() { - return Some(cwd); + match cwd.to_file_path() { + Ok(cwd) => { + if cwd.exists() { + return Some(cwd); + } + } + Err(err) => { + tracing::error!("{:?}", err); } } } diff --git a/lapce-proxy/src/watcher.rs b/lapce-proxy/src/watcher.rs index 6d2713375c..88a5fdfbe1 100644 --- a/lapce-proxy/src/watcher.rs +++ b/lapce-proxy/src/watcher.rs @@ -145,7 +145,9 @@ impl FileWatcher { let mode = mode_from_bool(w.recursive); if !state.watchees.iter().any(|w2| w.path == w2.path) { - let _ = self.inner.watch(&w.path, mode); + if let Err(err) = self.inner.watch(&w.path, mode) { + tracing::error!("{:?}", err); + } } state.watchees.push(w); @@ -165,7 +167,9 @@ impl FileWatcher { if let Some(idx) = idx { let removed = state.watchees.remove(idx); if !state.watchees.iter().any(|w| w.path == removed.path) { - let _ = self.inner.unwatch(&removed.path); + if let Err(err) = self.inner.unwatch(&removed.path) { + tracing::error!("{:?}", err); + } } //TODO: Ideally we would be tracking what paths we're watching with // some prefix-tree-like structure, which would let us keep track @@ -189,7 +193,9 @@ impl FileWatcher { .collect::>(); for (path, mode) in to_add { - let _ = self.inner.watch(&path, mode); + if let Err(err) = self.inner.watch(&path, mode) { + tracing::error!("{:?}", err); + } } } } diff --git a/lapce-rpc/src/core.rs b/lapce-rpc/src/core.rs index f18d3d9911..8c8f206f50 100644 --- a/lapce-rpc/src/core.rs +++ b/lapce-rpc/src/core.rs @@ -112,6 +112,7 @@ pub enum CoreNotification { }, TerminalProcessStopped { term_id: TermId, + exit_code: Option, }, RunInTerminal { config: RunDebugConfig, @@ -202,7 +203,9 @@ impl CoreRpcHandler { ) { let tx = { self.pending.lock().remove(&id) }; if let Some(tx) = tx { - let _ = tx.send(response); + if let Err(err) = tx.send(response) { + tracing::error!("{:?}", err); + } } } @@ -213,7 +216,9 @@ impl CoreRpcHandler { let mut pending = self.pending.lock(); pending.insert(id, tx); } - let _ = self.tx.send(CoreRpc::Request(id, request)); + if let Err(err) = self.tx.send(CoreRpc::Request(id, request)) { + tracing::error!("{:?}", err); + } rx.recv().unwrap_or_else(|_| { Err(RpcError { code: 0, @@ -223,11 +228,16 @@ impl CoreRpcHandler { } pub fn shutdown(&self) { - let _ = self.tx.send(CoreRpc::Shutdown); + if let Err(err) = self.tx.send(CoreRpc::Shutdown) { + tracing::error!("{:?}", err); + } } pub fn notification(&self, notification: CoreNotification) { - let _ = self.tx.send(CoreRpc::Notification(Box::new(notification))); + if let Err(err) = self.tx.send(CoreRpc::Notification(Box::new(notification))) + { + tracing::error!("{:?}", err); + } } pub fn workspace_file_change(&self) { @@ -328,8 +338,11 @@ impl CoreRpcHandler { }); } - pub fn terminal_process_stopped(&self, term_id: TermId) { - self.notification(CoreNotification::TerminalProcessStopped { term_id }); + pub fn terminal_process_stopped(&self, term_id: TermId, exit_code: Option) { + self.notification(CoreNotification::TerminalProcessStopped { + term_id, + exit_code, + }); } pub fn terminal_launch_failed(&self, term_id: TermId, error: String) { diff --git a/lapce-rpc/src/dap_types.rs b/lapce-rpc/src/dap_types.rs index bb4851fc51..dd0b0c0de8 100644 --- a/lapce-rpc/src/dap_types.rs +++ b/lapce-rpc/src/dap_types.rs @@ -57,6 +57,27 @@ pub struct RunDebugConfig { pub debug_command: Option>, #[serde(skip)] pub dap_id: DapId, + #[serde(default)] + pub tracing_output: bool, + #[serde(default)] + pub config_source: ConfigSource, +} + +#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)] +pub enum ConfigSource { + #[default] + Palette, + RunInTerminal, + RustCodeLens, +} +impl ConfigSource { + pub fn from_palette(&self) -> bool { + *self == Self::Palette + } + + pub fn from_rust_code_lens(&self) -> bool { + *self == Self::RustCodeLens + } } pub trait Request { @@ -74,6 +95,7 @@ pub struct DapRequest { #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] pub struct DapResponse { + pub seq: u64, pub request_seq: u64, pub success: bool, pub command: String, diff --git a/lapce-rpc/src/file_line.rs b/lapce-rpc/src/file_line.rs new file mode 100644 index 0000000000..5b52734596 --- /dev/null +++ b/lapce-rpc/src/file_line.rs @@ -0,0 +1,11 @@ +use std::path::PathBuf; + +use lsp_types::Position; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FileLine { + pub path: PathBuf, + pub position: Position, + pub content: String, +} diff --git a/lapce-rpc/src/lib.rs b/lapce-rpc/src/lib.rs index 8a4c1acaa3..89c8f510f5 100644 --- a/lapce-rpc/src/lib.rs +++ b/lapce-rpc/src/lib.rs @@ -5,6 +5,7 @@ pub mod core; pub mod counter; pub mod dap_types; pub mod file; +pub mod file_line; mod parse; pub mod plugin; pub mod proxy; diff --git a/lapce-rpc/src/proxy.rs b/lapce-rpc/src/proxy.rs index 09c813d9f6..237d640ee4 100644 --- a/lapce-rpc/src/proxy.rs +++ b/lapce-rpc/src/proxy.rs @@ -11,12 +11,12 @@ use crossbeam_channel::{Receiver, Sender}; use indexmap::IndexMap; use lapce_xi_rope::RopeDelta; use lsp_types::{ - request::GotoTypeDefinitionResponse, CallHierarchyIncomingCall, - CallHierarchyItem, CodeAction, CodeActionResponse, CodeLens, CompletionItem, - Diagnostic, DocumentSymbolResponse, GotoDefinitionResponse, Hover, InlayHint, - InlineCompletionResponse, InlineCompletionTriggerKind, Location, Position, - PrepareRenameResponse, SelectionRange, SymbolInformation, TextDocumentItem, - TextEdit, WorkspaceEdit, + request::{GotoImplementationResponse, GotoTypeDefinitionResponse}, + CallHierarchyIncomingCall, CallHierarchyItem, CodeAction, CodeActionResponse, + CodeLens, CompletionItem, Diagnostic, DocumentSymbolResponse, + GotoDefinitionResponse, Hover, InlayHint, InlineCompletionResponse, + InlineCompletionTriggerKind, Location, Position, PrepareRenameResponse, + SelectionRange, SymbolInformation, TextDocumentItem, TextEdit, WorkspaceEdit, }; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; @@ -26,6 +26,7 @@ use crate::{ buffer::BufferId, dap_types::{self, DapId, RunDebugConfig, SourceBreakpoint, ThreadId}, file::{FileNodeItem, PathObject}, + file_line::FileLine, plugin::{PluginId, VoltInfo, VoltMetadata}, source_control::FileDiff, style::SemanticStyles, @@ -100,6 +101,10 @@ pub enum ProxyRequest { path: PathBuf, position: Position, }, + GotoImplementation { + path: PathBuf, + position: Position, + }, GetDefinition { request_id: usize, path: PathBuf, @@ -146,6 +151,10 @@ pub enum ProxyRequest { GetCodeLens { path: PathBuf, }, + GetCodeLensResolve { + code_lens: CodeLens, + path: PathBuf, + }, GetDocumentSymbols { path: PathBuf, }, @@ -205,6 +214,9 @@ pub enum ProxyRequest { dap_id: DapId, frame_id: usize, }, + ReferencesResolve { + items: Vec, + }, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -388,6 +400,14 @@ pub enum ProxyResponse { plugin_id: PluginId, resp: Option>, }, + GetCodeLensResolveResponse { + plugin_id: PluginId, + resp: CodeLens, + }, + GotoImplementationResponse { + plugin_id: PluginId, + resp: Option, + }, GetFilesResponse { items: Vec, }, @@ -435,6 +455,9 @@ pub enum ProxyResponse { }, Success {}, SaveResponse {}, + ReferencesResolveResponse { + items: Vec, + }, } pub type ProxyMessage = RpcMessage; @@ -458,7 +481,9 @@ impl ResponseHandler { match self { ResponseHandler::Callback(f) => f(result), ResponseHandler::Chan(tx) => { - let _ = tx.send(result); + if let Err(err) = tx.send(result) { + tracing::error!("{:?}", err); + } } } } @@ -517,7 +542,9 @@ impl ProxyRpcHandler { self.pending.lock().insert(id, rh); - let _ = self.tx.send(ProxyRpc::Request(id, request)); + if let Err(err) = self.tx.send(ProxyRpc::Request(id, request)) { + tracing::error!("{:?}", err); + } } fn request(&self, request: ProxyRequest) -> Result { @@ -551,7 +578,9 @@ impl ProxyRpcHandler { } pub fn notification(&self, notification: ProxyNotification) { - let _ = self.tx.send(ProxyRpc::Notification(notification)); + if let Err(err) = self.tx.send(ProxyRpc::Notification(notification)) { + tracing::error!("{:?}", err); + } } pub fn git_init(&self) { @@ -588,7 +617,9 @@ impl ProxyRpcHandler { pub fn shutdown(&self) { self.notification(ProxyNotification::Shutdown {}); - let _ = self.tx.send(ProxyRpc::Shutdown); + if let Err(err) = self.tx.send(ProxyRpc::Shutdown) { + tracing::error!("{:?}", err); + } } pub fn initialize( @@ -903,6 +934,23 @@ impl ProxyRpcHandler { self.request_async(ProxyRequest::GetReferences { path, position }, f); } + pub fn references_resolve( + &self, + items: Vec, + f: impl ProxyCallback + 'static, + ) { + self.request_async(ProxyRequest::ReferencesResolve { items }, f); + } + + pub fn go_to_implementation( + &self, + path: PathBuf, + position: Position, + f: impl ProxyCallback + 'static, + ) { + self.request_async(ProxyRequest::GotoImplementation { path, position }, f); + } + pub fn get_code_actions( &self, path: PathBuf, @@ -924,6 +972,15 @@ impl ProxyRpcHandler { self.request_async(ProxyRequest::GetCodeLens { path }, f); } + pub fn get_code_lens_resolve( + &self, + code_lens: CodeLens, + path: PathBuf, + f: impl ProxyCallback + 'static, + ) { + self.request_async(ProxyRequest::GetCodeLensResolve { code_lens, path }, f); + } + pub fn get_document_formatting( &self, path: PathBuf, diff --git a/lapce.spec b/lapce.spec index 0a6761d063..767eedce40 100644 --- a/lapce.spec +++ b/lapce.spec @@ -1,5 +1,5 @@ Name: lapce-git -Version: 0.4.0.{{{ git_dir_version }}} +Version: 0.4.2.{{{ git_dir_version }}} Release: 1 Summary: Lightning-fast and Powerful Code Editor written in Rust License: Apache-2.0