diff --git a/CHANGELOG.md b/CHANGELOG.md index e31fa27cad..e265fe071c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## Unreleased +### Features/Changes + +### 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..cec49f4946 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", @@ -2629,9 +2627,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -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", @@ -2978,7 +2976,7 @@ dependencies = [ [[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..da90228dbb 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" @@ -43,7 +43,7 @@ globset = { version = "0.4.14" } hashbrown = { version = "0.14.5", features = ["serde"] } im = { version = "15.0.0", features = ["serde"] } include_dir = { version = "0.7" } -indexmap = { version = "2.0", features = ["serde"] } +indexmap = { version = "2.5", features = ["serde"] } interprocess = { version = "1.2.1" } itertools = { version = "0.12.1" } notify = { version = "5.2.0", features = ["serde"] } @@ -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/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..024e5ff39e 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, }, @@ -1055,9 +1053,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 +2259,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 +2794,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 +2817,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 +2846,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 +2934,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 { @@ -3617,7 +3622,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 +3662,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 { 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/command.rs b/lapce-app/src/command.rs index 1ea715861c..1d6aa0ed27 100644 --- a/lapce-app/src/command.rs +++ b/lapce-app/src/command.rs @@ -187,9 +187,27 @@ pub enum LapceWorkbenchCommand { #[strum(message = "Show Call Hierarchy")] ShowCallHierarchy, - #[strum(serialize = "reveal_in_file_tree")] - #[strum(message = "Reveal in File Tree")] - RevealInFileTree, + #[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")] @@ -562,6 +580,10 @@ pub enum LapceWorkbenchCommand { #[strum(serialize = "quit")] #[strum(message = "Quit Editor")] Quit, + + #[strum(serialize = "go_to_location")] + #[strum(message = "Go to Location")] + GoToLocation, } #[derive(Clone, Debug)] 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..7c70e30340 100644 --- a/lapce-app/src/config.rs +++ b/lapce-app/src/config.rs @@ -655,6 +655,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/icon.rs b/lapce-app/src/config/icon.rs index a53812e88b..61a17bd421 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,7 @@ 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 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/db.rs b/lapce-app/src/db.rs index 1ed885931f..05bb1cf0ab 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}; 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..1b5a1d4df8 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::{ @@ -187,6 +191,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 +209,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 +243,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 +452,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 +462,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 +769,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 +1049,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 +1158,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() { @@ -2640,23 +2734,28 @@ impl EditorData { let is_file = doc.content.with_untracked(|content| content.is_file()); let mut menu = Menu::new(""); - let cmds = if is_file { + let mut 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)), + 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::RevealInFileTree, - )), Some(CommandKind::Workbench( LapceWorkbenchCommand::PaletteCommand, )), @@ -2672,6 +2771,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 +3050,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 +3159,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 +3205,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 +3406,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 +3615,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 +3633,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..2c695e387f 100644 --- a/lapce-app/src/file_explorer/data.rs +++ b/lapce-app/src/file_explorer/data.rs @@ -12,7 +12,7 @@ use floem::{ ext_event::create_ext_action, keyboard::Modifiers, menu::{Menu, MenuItem}, - reactive::{RwSignal, Scope}, + reactive::{RwSignal, Scope, SignalGet, SignalUpdate, SignalWith}, views::editor::text::SystemClipboard, }; use globset::Glob; @@ -22,7 +22,10 @@ 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, }; @@ -51,6 +54,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 +135,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 @@ -448,7 +453,8 @@ 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))); } } } @@ -540,21 +546,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..5b0b77bce3 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,28 +375,46 @@ 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) - }) + } }); // 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(); + move |_| { + click_data.click(&click_path); + select.update(|x| *x = Some(kind.clone())); + } }) .on_double_click(move |_| { double_click_data.double_click(&double_click_path) @@ -419,9 +437,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 +447,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..78a6753cdf 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()), @@ -259,6 +259,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 +298,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 +330,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 +345,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 +379,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 +469,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..2ce5aaa0ae 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,32 @@ 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, .. } = self { + if *repeat { + 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 +58,6 @@ impl KeyInput { Key::Dead(_) => KeyMapKey::Physical(*physical), } } - } + }) } } 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..8bf27d4af3 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; @@ -640,6 +640,7 @@ impl MainSplitData { }); } doc.get_code_lens(); + doc.get_document_symbol(); (doc, true) } } @@ -2914,6 +2915,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..92ddb6fa2a 100644 --- a/lapce-app/src/palette.rs +++ b/lapce-app/src/palette.rs @@ -15,7 +15,10 @@ 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 itertools::Itertools; use lapce_core::{ @@ -25,7 +28,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; @@ -646,42 +649,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 +661,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 +1532,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 +1541,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); } } 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..f984856909 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}; @@ -38,6 +40,10 @@ pub fn default_panel_order() -> PanelOrder { PanelKind::CallHierarchy ], ); + 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/kind.rs b/lapce-app/src/panel/kind.rs index 671571ebbf..0f29f5c241 100644 --- a/lapce-app/src/panel/kind.rs +++ b/lapce-app/src/panel/kind.rs @@ -16,6 +16,7 @@ pub enum PanelKind { Problem, Debug, CallHierarchy, + DocumentSymbol, } impl PanelKind { @@ -28,7 +29,8 @@ 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, } } diff --git a/lapce-app/src/panel/mod.rs b/lapce-app/src/panel/mod.rs index a539e9f9f3..2398e4f5bb 100644 --- a/lapce-app/src/panel/mod.rs +++ b/lapce-app/src/panel/mod.rs @@ -1,6 +1,7 @@ pub mod call_hierarchy_view; pub mod data; pub mod debug_view; +pub mod document_symbol; pub mod global_search_view; pub mod kind; pub mod plugin_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/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..050f29b113 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,9 @@ 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, + }, window_tab::{DragContent, WindowTabData}, }; @@ -489,6 +493,9 @@ fn panel_view( show_hierarchy_panel(window_tab_data.clone(), position) .into_any() } + PanelKind::DocumentSymbol => { + symbol_panel(window_tab_data.clone(), position).into_any() + } }; view.style(|s| s.size_pct(100.0, 100.0)) }, @@ -533,18 +540,18 @@ 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", }; + 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..03a4c7ae14 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::{ diff --git a/lapce-app/src/proxy/remote.rs b/lapce-app/src/proxy/remote.rs index 73a487451a..919583e3ce 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() @@ -267,10 +270,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 +286,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..a7c60b2054 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}, views::editor::text::SystemClipboard, }; use lapce_core::{ diff --git a/lapce-app/src/terminal/panel.rs b/lapce-app/src/terminal/panel.rs index 817a1ca356..c71951a045 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::{ @@ -220,19 +220,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; - }); + let new_active = info.active.min(info.tabs.len().saturating_sub(1)); + info.active = new_active; + close_tab + }) + .flatten() + { + close_tab + .into_iter() + .for_each(|x| self.common.proxy.terminal_close(x.1.term_id)); + } self.update_debug_active_term(); } @@ -356,7 +377,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,7 +402,8 @@ 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 run_debug.mode == RunDebugMode::Debug { 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..287c79e636 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, diff --git a/lapce-app/src/window.rs b/lapce-app/src/window.rs index 9750ceb718..84bc83c553 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, }; diff --git a/lapce-app/src/window_tab.rs b/lapce-app/src/window_tab.rs index ed487a9542..839fac200c 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; @@ -684,7 +688,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 { @@ -1373,7 +1382,7 @@ impl WindowTabData { Quit => { floem::quit_app(); } - RevealInFileTree => { + RevealInPanel => { if let Some(editor_data) = self.main_split.active_editor.get_untracked() { @@ -1387,6 +1396,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 +1447,65 @@ impl WindowTabData { editor_data.call_hierarchy(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(), + }; + 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, + } }); + } + } + } } @@ -1948,17 +2060,18 @@ 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 } => { + CoreNotification::TerminalProcessStopped { term_id, exit_code } => { let _ = self .common .term_tx .send((*term_id, TermEvent::CloseTerminal)); - self.terminal.terminal_stopped(term_id); + self.terminal.terminal_stopped(term_id, *exit_code); if self .terminal .tab_info @@ -2412,7 +2525,8 @@ impl WindowTabData { | PanelKind::Plugin | PanelKind::Problem | PanelKind::Debug - | PanelKind::CallHierarchy => { + | PanelKind::CallHierarchy + | PanelKind::DocumentSymbol => { // Some panels don't accept focus (yet). Fall back to visibility check // in those cases. self.panel.is_panel_visible(&kind) diff --git a/lapce-core/src/language.rs b/lapce-core/src/language.rs index 53c9ae4991..292f66d9ee 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 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-proxy/src/dispatch.rs b/lapce-proxy/src/dispatch.rs index d27e397afe..e076798448 100644 --- a/lapce-proxy/src/dispatch.rs +++ b/lapce-proxy/src/dispatch.rs @@ -177,12 +177,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()); } @@ -1073,6 +1073,22 @@ 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); + }, + ); + } } } } diff --git a/lapce-proxy/src/plugin/dap.rs b/lapce-proxy/src/plugin/dap.rs index ba22379048..7c71bce928 100644 --- a/lapce-proxy/src/plugin/dap.rs +++ b/lapce-proxy/src/plugin/dap.rs @@ -112,6 +112,7 @@ 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())?; @@ -128,6 +129,7 @@ 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) => { @@ -220,7 +222,11 @@ impl DapClient { } } // 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 = @@ -313,10 +319,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), @@ -449,7 +451,9 @@ 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(), diff --git a/lapce-proxy/src/plugin/mod.rs b/lapce-proxy/src/plugin/mod.rs index 009a899047..ca0d6d0a35 100644 --- a/lapce-proxy/src/plugin/mod.rs +++ b/lapce-proxy/src/plugin/mod.rs @@ -34,7 +34,7 @@ use lapce_xi_rope::{Rope, RopeDelta}; use lsp_types::{ request::{ CallHierarchyIncomingCalls, CallHierarchyPrepare, CodeActionRequest, - CodeActionResolveRequest, CodeLensRequest, Completion, + CodeActionResolveRequest, CodeLensRequest, CodeLensResolve, Completion, DocumentSymbolRequest, Formatting, GotoDefinition, GotoTypeDefinition, GotoTypeDefinitionParams, GotoTypeDefinitionResponse, HoverRequest, InlayHintRequest, InlineCompletionRequest, PrepareRenameRequest, References, @@ -49,10 +49,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, @@ -739,6 +739,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, @@ -1623,6 +1642,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..c264f12717 100644 --- a/lapce-proxy/src/plugin/psp.rs +++ b/lapce-proxy/src/plugin/psp.rs @@ -30,7 +30,7 @@ use lsp_types::{ }, request::{ CallHierarchyIncomingCalls, CallHierarchyPrepare, CodeActionRequest, - CodeActionResolveRequest, CodeLensRequest, Completion, + CodeActionResolveRequest, CodeLensRequest, CodeLensResolve, Completion, DocumentSymbolRequest, Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest, Initialize, InlayHintRequest, InlineCompletionRequest, PrepareRenameRequest, References, RegisterCapability, Rename, @@ -796,6 +796,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() } diff --git a/lapce-proxy/src/terminal.rs b/lapce-proxy/src/terminal.rs index e7c996964c..202eb48fa2 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::{ @@ -116,6 +116,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 +134,11 @@ 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); + exit_code = exited_code; break 'event_loop; } } @@ -192,7 +194,7 @@ impl Terminal { .unwrap(); } } - core_rpc.terminal_process_stopped(self.term_id); + core_rpc.terminal_process_stopped(self.term_id, exit_code); let _ = self.pty.deregister(&self.poller); } diff --git a/lapce-rpc/src/core.rs b/lapce-rpc/src/core.rs index f18d3d9911..d52b74019e 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, @@ -328,8 +329,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..ba26ef2804 100644 --- a/lapce-rpc/src/dap_types.rs +++ b/lapce-rpc/src/dap_types.rs @@ -74,6 +74,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/proxy.rs b/lapce-rpc/src/proxy.rs index 09c813d9f6..50473ebedd 100644 --- a/lapce-rpc/src/proxy.rs +++ b/lapce-rpc/src/proxy.rs @@ -146,6 +146,10 @@ pub enum ProxyRequest { GetCodeLens { path: PathBuf, }, + GetCodeLensResolve { + code_lens: CodeLens, + path: PathBuf, + }, GetDocumentSymbols { path: PathBuf, }, @@ -388,6 +392,10 @@ pub enum ProxyResponse { plugin_id: PluginId, resp: Option>, }, + GetCodeLensResolveResponse { + plugin_id: PluginId, + resp: CodeLens, + }, GetFilesResponse { items: Vec, }, @@ -924,6 +932,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