From 8bbffa09a27e8f564a05f3eb5b275eee60ecd82e Mon Sep 17 00:00:00 2001 From: Redhawk18 Date: Thu, 18 Jul 2024 12:03:31 -0400 Subject: [PATCH] bump iced to 0.13.0-dev --- Cargo.toml | 10 +- gui/src/buffer.rs | 3 +- gui/src/lib.rs | 143 +++---- gui/src/style.rs | 16 +- gui/src/widgets/mod.rs | 2 - gui/src/widgets/pane_grid.rs | 15 +- gui/src/widgets/tab_bar.rs | 15 +- gui/src/widgets/text_editor.rs | 725 -------------------------------- lsp/Cargo.toml | 1 - lsp/src/commands/initialize.rs | 2 +- lsp/src/commands/shutdown.rs | 2 +- lsp/src/commands/synchronize.rs | 7 +- lsp/src/lib.rs | 11 +- 13 files changed, 117 insertions(+), 835 deletions(-) delete mode 100644 gui/src/widgets/text_editor.rs diff --git a/Cargo.toml b/Cargo.toml index 4e295cf..77eb651 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ kuiper_gui.workspace = true tokio.workspace = true pretty_env_logger.workspace = true -[dev.dependencies] +[dev-dependencies] console-subscriber = "0.3.0" [workspace] @@ -31,9 +31,8 @@ kuiper_lsp = { path = "lsp" } async-lsp = { version = "0.2", features = ["tokio"] } dark-light = "1.0" -iced = { version = "0.12", features = ["advanced", "tokio"] } -iced_aw = { version = "0.8", features = ["icons", "menu","tab_bar"], default-features = false } -iced_runtime = "0.12" +iced = { version = "0.13.0-dev", features = ["advanced", "tokio"] } +iced_aw = { version = "0.9", features = ["icons", "tab_bar"], default-features = false } jsonrpc = "0.17.0" log = "0.4" pattern_code = "0.1" @@ -46,4 +45,5 @@ tokio-util = { version = "0.7", features = ["compat"] } tower = "0.4" [patch.crates-io] -iced_aw = { git = 'https://github.com/iced-rs/iced_aw.git', rev = '99c12f1' } +iced = { git = 'https://github.com/iced-rs/iced.git' } +iced_aw = { git = 'https://github.com/iced-rs/iced_aw.git' } diff --git a/gui/src/buffer.rs b/gui/src/buffer.rs index 7941f61..df00196 100644 --- a/gui/src/buffer.rs +++ b/gui/src/buffer.rs @@ -1,5 +1,4 @@ -use crate::widgets::Content; - +use iced::widget::text_editor::Content; use std::path::PathBuf; #[derive(Debug)] diff --git a/gui/src/lib.rs b/gui/src/lib.rs index dc1a03e..6354629 100644 --- a/gui/src/lib.rs +++ b/gui/src/lib.rs @@ -6,7 +6,7 @@ mod widgets; use buffer::{Buffer, FileBuffer}; pub use messages::{Button, LanguageServer, Message, PaneGrid, Tab, TextEditor, Widgets}; -use widgets::{menu_bar, pane_grid, Content}; +use widgets::{menu_bar, pane_grid}; use kuiper_lsp::{ client::LSPClient, @@ -14,18 +14,21 @@ use kuiper_lsp::{ }; use iced::{ - executor, font, + application, font, widget::{ column, pane_grid::{DragEvent, Pane, ResizeEvent, State}, + text_editor::Content, }, - Application, Command, Element, Settings, Theme, + Element, Task, Theme, }; use slotmap::{DefaultKey, SlotMap}; use std::path::PathBuf; pub fn start_gui() -> iced::Result { - Kuiper::run(Settings::default()) + application(Kuiper::title, Kuiper::update, Kuiper::view) + .theme(Kuiper::theme) + .run_with(Kuiper::new) } pub struct Kuiper { @@ -50,60 +53,7 @@ pub struct PaneState { } impl Kuiper { - pub(crate) fn get_panestate(&self) -> &PaneState { - self.panes.data.get(self.panes.active).unwrap() - } - - pub(crate) fn get_mut_panestate(&mut self) -> &mut PaneState { - self.panes.data.get_mut(self.panes.active).unwrap() - } - - pub(crate) fn get_buffer(&self) -> Option<&Buffer> { - let panestate = self.get_panestate(); - match panestate.data.get(panestate.active_tab_index) { - Some(key) => Some(self.data.get(*key).unwrap()), - None => None, - } - } - - pub(crate) fn get_mut_buffer(&mut self) -> Option<&mut Buffer> { - let panestate = self.get_panestate(); - match panestate.get_active_key() { - Some(key) => Some(self.data.get_mut(*key).unwrap()), - None => None, - } - } - - pub(crate) fn insert_buffer(&mut self, buffer: Buffer) { - let key = self.data.insert(buffer); - self.get_mut_panestate().data.push(key); - } -} - -impl PaneState { - pub fn get_active_key(&self) -> Option<&DefaultKey> { - self.data.get(self.active_tab_index) - } - - pub fn get_data<'a>(&'a self, map: &'a SlotMap) -> Vec<&Buffer> { - self.data.iter().map(|key| map.get(*key).unwrap()).collect() - } - - pub fn with_key(key: &DefaultKey) -> Self { - PaneState { - data: vec![*key], - ..Default::default() - } - } -} - -impl Application for Kuiper { - type Executor = executor::Default; - type Message = Message; - type Theme = Theme; - type Flags = (); - - fn new(_flags: Self::Flags) -> (Self, Command) { + fn new() -> (Self, Task) { ( Kuiper { data: SlotMap::default(), @@ -111,7 +61,7 @@ impl Application for Kuiper { panes: Panes::default(), workspace_folder: None, }, - Command::batch(vec![ + Task::batch(vec![ font::load(iced_aw::BOOTSTRAP_FONT_BYTES).map(Message::FontLoaded), font::load(iced_aw::NERD_FONT_BYTES).map(Message::FontLoaded), ]), @@ -122,14 +72,14 @@ impl Application for Kuiper { String::from("Kuiper") } - fn update(&mut self, message: Message) -> Command { + fn update(&mut self, message: Message) -> Task { match message { Message::FontLoaded(_) => {} Message::LanguageServer(lsp) => match lsp { LanguageServer::Initalize(result) => { let Ok(server) = result else { log::error!("Error initializing language server"); - return Command::none(); + return Task::none(); }; self.lsp_client = Some(LSPClient::new(server)); @@ -141,13 +91,13 @@ impl Application for Kuiper { Widgets::Button(button) => match button { Button::NewFile => self.insert_buffer(Buffer::File(FileBuffer::default())), Button::OpenFile => { - return Command::perform(file_dialog::open_file(), |x| { + return Task::perform(file_dialog::open_file(), |x| { Message::Widgets(Widgets::Button(Button::OpenedFile(x))) }); } Button::OpenedFile(result) => { let Ok(file) = result else { - return Command::none(); + return Task::none(); }; self.insert_buffer(Buffer::File(FileBuffer { @@ -155,18 +105,18 @@ impl Application for Kuiper { content: Content::with_text(&file.1), })); - return Command::perform(initialize(), |x| { + return Task::perform(initialize(), |x| { Message::LanguageServer(LanguageServer::Initalize(x)) }); } Button::OpenFolder => { - return Command::perform(file_dialog::open_folder(), |x| { + return Task::perform(file_dialog::open_folder(), |x| { Message::Widgets(Widgets::Button(Button::OpenedFolder(x))) }) } Button::OpenedFolder(result) => { let Ok(folder) = result else { - return Command::none(); + return Task::none(); }; self.workspace_folder = Some(folder); @@ -174,12 +124,12 @@ impl Application for Kuiper { Button::Save => { let Some(buffer) = self.get_buffer() else { log::warn!("No file open to save"); - return Command::none(); + return Task::none(); }; match buffer { Buffer::File(file_buffer) => { - return Command::perform( + return Task::perform( file_dialog::save_file( file_buffer.path.clone(), file_buffer.content.text(), @@ -193,12 +143,12 @@ impl Application for Kuiper { Button::SaveAs => { let Some(buffer) = self.get_buffer() else { log::warn!("No file open to save"); - return Command::none(); + return Task::none(); }; match buffer { Buffer::File(file_buffer) => { - return Command::perform( + return Task::perform( file_dialog::save_file_with_dialog( file_buffer.path.clone(), file_buffer.content.text(), @@ -209,7 +159,8 @@ impl Application for Kuiper { } } Button::SavedAs(_) => {} - Button::Quit => return iced::window::close(iced::window::Id::MAIN), + // Button::Quit => return iced::window::close(0), + Button::Quit => todo!(), }, Widgets::PaneGrid(pane_grid) => match pane_grid { PaneGrid::PaneClicked(pane) => self.panes.active = pane, @@ -262,7 +213,7 @@ impl Application for Kuiper { Buffer::File(buffer) => { buffer.content.perform(action); let path = buffer.path.clone().unwrap(); - return Command::perform( + return Task::perform( synchronize(path, self.lsp_client.clone().unwrap().socket), |x| Message::LanguageServer(LanguageServer::Syncronize(x)), ); @@ -273,7 +224,7 @@ impl Application for Kuiper { }, } - Command::none() + Task::none() } fn view(&self) -> Element { @@ -285,6 +236,52 @@ impl Application for Kuiper { fn theme(&self) -> Theme { Theme::GruvboxDark } + + pub(crate) fn get_buffer(&self) -> Option<&Buffer> { + let panestate = self.get_panestate(); + match panestate.data.get(panestate.active_tab_index) { + Some(key) => Some(self.data.get(*key).unwrap()), + None => None, + } + } + + pub(crate) fn get_mut_buffer(&mut self) -> Option<&mut Buffer> { + let panestate = self.get_panestate(); + match panestate.get_active_key() { + Some(key) => Some(self.data.get_mut(*key).unwrap()), + None => None, + } + } + + pub(crate) fn get_panestate(&self) -> &PaneState { + self.panes.data.get(self.panes.active).unwrap() + } + + pub(crate) fn get_mut_panestate(&mut self) -> &mut PaneState { + self.panes.data.get_mut(self.panes.active).unwrap() + } + + pub(crate) fn insert_buffer(&mut self, buffer: Buffer) { + let key = self.data.insert(buffer); + self.get_mut_panestate().data.push(key); + } +} + +impl PaneState { + pub fn get_active_key(&self) -> Option<&DefaultKey> { + self.data.get(self.active_tab_index) + } + + pub fn get_data<'a>(&self, map: &'a SlotMap) -> Vec<&'a Buffer> { + self.data.iter().map(|key| map.get(*key).unwrap()).collect() + } + + pub fn with_key(key: &DefaultKey) -> Self { + PaneState { + data: vec![*key], + ..Default::default() + } + } } impl Default for Panes { diff --git a/gui/src/style.rs b/gui/src/style.rs index c06a8d6..766ec7e 100644 --- a/gui/src/style.rs +++ b/gui/src/style.rs @@ -4,10 +4,10 @@ use iced::{Border, Theme}; const WIDTH: f32 = 2.0; const RADIUS: f32 = 4.0; -pub fn pane_active(theme: &Theme) -> container::Appearance { +pub fn pane_active(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { background: Some(palette.background.base.color.into()), border: Border { color: palette.background.strong.color, @@ -18,10 +18,10 @@ pub fn pane_active(theme: &Theme) -> container::Appearance { } } -pub fn pane_inactive(theme: &Theme) -> container::Appearance { +pub fn pane_inactive(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { background: Some(palette.background.base.color.into()), border: Border { color: palette.background.strong.color, @@ -32,20 +32,20 @@ pub fn pane_inactive(theme: &Theme) -> container::Appearance { } } -pub fn title_bar_active(theme: &Theme) -> container::Appearance { +pub fn title_bar_active(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { text_color: Some(palette.background.strong.text), background: Some(palette.background.strong.color.into()), ..Default::default() } } -pub fn title_bar_inactive(theme: &Theme) -> container::Appearance { +pub fn title_bar_inactive(theme: &Theme) -> container::Style { let palette = theme.extended_palette(); - container::Appearance { + container::Style { text_color: Some(palette.primary.strong.text), background: Some(palette.primary.strong.color.into()), ..Default::default() diff --git a/gui/src/widgets/mod.rs b/gui/src/widgets/mod.rs index d37e6f4..4a58a23 100644 --- a/gui/src/widgets/mod.rs +++ b/gui/src/widgets/mod.rs @@ -1,9 +1,7 @@ mod menu_bar; mod pane_grid; mod tab_bar; -mod text_editor; pub(crate) use menu_bar::menu_bar; pub(crate) use pane_grid::pane_grid; pub(crate) use tab_bar::tab_bar; -pub(crate) use text_editor::{Content, TextEditor}; diff --git a/gui/src/widgets/pane_grid.rs b/gui/src/widgets/pane_grid.rs index b28a095..062bcf1 100644 --- a/gui/src/widgets/pane_grid.rs +++ b/gui/src/widgets/pane_grid.rs @@ -15,15 +15,18 @@ use iced::{ use iced_aw::core::icons::nerd::{icon_to_text, Nerd}; use slotmap::{DefaultKey, SlotMap}; -pub(crate) fn pane_grid<'a>( +pub fn pane_grid<'a>( panes: &'a Panes, map: &'a SlotMap, ) -> PaneGrid<'a, Message, Theme, Renderer> { - PaneGrid::new(&panes.data, |pane, state, _is_maximized| { - let active = panes.active != pane; + let active = panes.active; + + PaneGrid::new(&panes.data, move |pane, state, _is_maximized| { + let active = active != pane; // currently this is fine **if** we want all gui elements to be tabs - Content::new(tab_bar(state.active_tab_index, &state.get_data(map))) + let buffers = state.get_data(&map); + Content::new(tab_bar(state.active_tab_index, &buffers)) .style(if active { pane_active } else { pane_inactive }) .title_bar(title_bar(active, pane)) }) @@ -35,7 +38,7 @@ pub(crate) fn pane_grid<'a>( .spacing(15) } -fn title_bar(active: bool, pane: Pane) -> TitleBar<'static, Message, Theme, Renderer> { +fn title_bar<'a>(active: bool, pane: Pane) -> TitleBar<'a, Message, Theme, Renderer> { TitleBar::new( Element::from( row!( @@ -45,7 +48,7 @@ fn title_bar(active: bool, pane: Pane) -> TitleBar<'static, Message, Theme, Rend .on_press(crate::PaneGrid::PaneSplit(Axis::Vertical, pane)), button(icon_to_text(Nerd::X)).on_press(crate::PaneGrid::PaneClosed(pane)), ) - .align_items(Alignment::Center), + .align_y(Alignment::Center), ) .map(|x| Message::Widgets(Widgets::PaneGrid(x))), ) diff --git a/gui/src/widgets/tab_bar.rs b/gui/src/widgets/tab_bar.rs index 52b7ecd..40bfbaa 100644 --- a/gui/src/widgets/tab_bar.rs +++ b/gui/src/widgets/tab_bar.rs @@ -1,7 +1,7 @@ -use crate::{widgets::TextEditor, Buffer, Message, Tab, Widgets}; +use crate::{Buffer, Message, Tab, Widgets}; use iced::{ - widget::{column, Column}, + widget::{column, Column, TextEditor}, Element, Length, Renderer, Theme, }; use iced_aw::{ @@ -10,18 +10,17 @@ use iced_aw::{ }; use std::path::PathBuf; -pub(crate) fn tab_bar<'a>( - active: usize, - data: &[&'a Buffer], -) -> Column<'a, Message, Theme, Renderer> { +pub fn tab_bar<'a>(active: usize, data: &[&'a Buffer]) -> Column<'a, Message, Theme, Renderer> { if data.is_empty() { column!() } else { - column!(head(active, data), body(active, data)).padding(1) + let head = head(active, data); + let body = body(active, data); + column!(head, body).padding(1) } } -fn head(active: usize, data: &[&Buffer]) -> TabBar { +fn head<'a>(active: usize, data: &[&Buffer]) -> TabBar<'a, Message, usize, Theme, Renderer> { let mut tab_bar = TabBar::new(|x| Message::Widgets(Widgets::Tab(Tab::TabSelected(x)))) .on_close(|x| Message::Widgets(Widgets::Tab(Tab::TabClosed(x)))); diff --git a/gui/src/widgets/text_editor.rs b/gui/src/widgets/text_editor.rs deleted file mode 100644 index 0287b47..0000000 --- a/gui/src/widgets/text_editor.rs +++ /dev/null @@ -1,725 +0,0 @@ -//! Directly copied from iced, keep updated with upstream changes - -//! Display a multi-line text input for text editing. -use iced::{ - advanced::{ - clipboard::{self, Clipboard}, - layout::{self, Layout}, - mouse, renderer, - text::{ - self, - editor::{Cursor, Editor as _}, - highlighter::{self, Highlighter}, - LineHeight, - }, - widget::{self, Widget}, - Shell, - }, - event::{self, Event}, - keyboard::{self, key}, - Element, Length, Padding, Pixels, Rectangle, Size, Vector, -}; - -use std::cell::RefCell; -use std::fmt; -use std::ops::DerefMut; -use std::sync::Arc; - -pub use iced::widget::text_editor::StyleSheet; -pub use text::editor::{Action, Edit, Motion}; - -/// A multi-line text input. -#[allow(missing_debug_implementations)] -pub struct TextEditor<'a, Highlighter, Message, Theme = iced::Theme, Renderer = iced::Renderer> -where - Highlighter: text::Highlighter, - Theme: StyleSheet, - Renderer: text::Renderer, -{ - content: &'a Content, - font: Option, - text_size: Option, - line_height: LineHeight, - width: Length, - height: Length, - padding: Padding, - style: Theme::Style, - on_edit: Option Message + 'a>>, - highlighter_settings: Highlighter::Settings, - highlighter_format: fn(&Highlighter::Highlight, &Theme) -> highlighter::Format, -} - -impl<'a, Message, Theme, Renderer> TextEditor<'a, highlighter::PlainText, Message, Theme, Renderer> -where - Theme: StyleSheet, - Renderer: text::Renderer, -{ - /// Creates new [`TextEditor`] with the given [`Content`]. - pub fn new(content: &'a Content) -> Self { - Self { - content, - font: None, - text_size: None, - line_height: LineHeight::default(), - width: Length::Fill, - height: Length::Shrink, - padding: Padding::new(5.0), - style: Default::default(), - on_edit: None, - highlighter_settings: (), - highlighter_format: |_highlight, _theme| highlighter::Format::default(), - } - } -} - -impl<'a, Highlighter, Message, Theme, Renderer> - TextEditor<'a, Highlighter, Message, Theme, Renderer> -where - Highlighter: text::Highlighter, - Theme: StyleSheet, - Renderer: text::Renderer, -{ - /// Sets the height of the [`TextEditor`]. - pub fn height(mut self, height: impl Into) -> Self { - self.height = height.into(); - self - } - - /// Sets the message that should be produced when some action is performed in - /// the [`TextEditor`]. - /// - /// If this method is not called, the [`TextEditor`] will be disabled. - pub fn on_action(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self { - self.on_edit = Some(Box::new(on_edit)); - self - } - - /// Sets the [`Font`] of the [`TextEditor`]. - /// - /// [`Font`]: text::Renderer::Font - pub fn font(mut self, font: impl Into) -> Self { - self.font = Some(font.into()); - self - } - - /// Sets the [`Padding`] of the [`TextEditor`]. - pub fn padding(mut self, padding: impl Into) -> Self { - self.padding = padding.into(); - self - } - - /// Highlights the [`TextEditor`] with the given [`Highlighter`] and - /// a strategy to turn its highlights into some text format. - pub fn highlight( - self, - settings: H::Settings, - to_format: fn(&H::Highlight, &Theme) -> highlighter::Format, - ) -> TextEditor<'a, H, Message, Theme, Renderer> { - TextEditor { - content: self.content, - font: self.font, - text_size: self.text_size, - line_height: self.line_height, - width: self.width, - height: self.height, - padding: self.padding, - style: self.style, - on_edit: self.on_edit, - highlighter_settings: settings, - highlighter_format: to_format, - } - } - - /// Sets the style of the [`TextEditor`]. - pub fn style(mut self, style: impl Into) -> Self { - self.style = style.into(); - self - } -} - -/// The content of a [`TextEditor`]. -pub struct Content(RefCell>) -where - R: text::Renderer; - -struct Internal -where - R: iced::advanced::text::Renderer, -{ - editor: R::Editor, - is_dirty: bool, -} - -impl Content -where - R: iced::advanced::text::Renderer, -{ - /// Creates an empty [`Content`]. - pub fn new() -> Self { - Self::with_text("") - } - - /// Creates a [`Content`] with the given text. - pub fn with_text(text: &str) -> Self { - Self(RefCell::new(Internal { - editor: R::Editor::with_text(text), - is_dirty: true, - })) - } - - /// Performs an [`Action`] on the [`Content`]. - pub fn perform(&mut self, action: Action) { - let internal = self.0.get_mut(); - - internal.editor.perform(action); - internal.is_dirty = true; - } - - /// Returns the amount of lines of the [`Content`]. - pub fn line_count(&self) -> usize { - self.0.borrow().editor.line_count() - } - - /// Returns the text of the line at the given index, if it exists. - pub fn line(&self, index: usize) -> Option + '_> { - std::cell::Ref::filter_map(self.0.borrow(), |internal| internal.editor.line(index)).ok() - } - - /// Returns an iterator of the text of the lines in the [`Content`]. - pub fn lines(&self) -> impl Iterator + '_> { - struct Lines<'a, Renderer: text::Renderer> { - internal: std::cell::Ref<'a, Internal>, - current: usize, - } - - impl<'a, Renderer: text::Renderer> Iterator for Lines<'a, Renderer> { - type Item = std::cell::Ref<'a, str>; - - fn next(&mut self) -> Option { - let line = - std::cell::Ref::filter_map(std::cell::Ref::clone(&self.internal), |internal| { - internal.editor.line(self.current) - }) - .ok()?; - - self.current += 1; - - Some(line) - } - } - - Lines { - internal: self.0.borrow(), - current: 0, - } - } - - /// Returns the text of the [`Content`]. - /// - /// Lines are joined with `'\n'`. - pub fn text(&self) -> String { - let mut text = self - .lines() - .enumerate() - .fold(String::new(), |mut contents, (i, line)| { - if i > 0 { - contents.push('\n'); - } - - contents.push_str(&line); - - contents - }); - - if !text.ends_with('\n') { - text.push('\n'); - } - - text - } - - /// Returns the selected text of the [`Content`]. - pub fn selection(&self) -> Option { - self.0.borrow().editor.selection() - } - - /// Returns the current cursor position of the [`Content`]. - pub fn cursor_position(&self) -> (usize, usize) { - self.0.borrow().editor.cursor_position() - } -} - -impl Default for Content -where - Renderer: iced::advanced::text::Renderer, -{ - fn default() -> Self { - Self::new() - } -} - -impl fmt::Debug for Content -where - Renderer: iced::advanced::text::Renderer, - Renderer::Editor: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let internal = self.0.borrow(); - - f.debug_struct("Content") - .field("editor", &internal.editor) - .field("is_dirty", &internal.is_dirty) - .finish() - } -} - -struct State { - is_focused: bool, - last_click: Option, - drag_click: Option, - partial_scroll: f32, - highlighter: RefCell, - highlighter_settings: Highlighter::Settings, - highlighter_format_address: usize, -} - -impl<'a, Highlighter, Message, Theme, Renderer> Widget - for TextEditor<'a, Highlighter, Message, Theme, Renderer> -where - Highlighter: text::Highlighter, - Theme: StyleSheet, - Renderer: text::Renderer, -{ - fn tag(&self) -> widget::tree::Tag { - widget::tree::Tag::of::>() - } - - fn state(&self) -> widget::tree::State { - widget::tree::State::new(State { - is_focused: false, - last_click: None, - drag_click: None, - partial_scroll: 0.0, - highlighter: RefCell::new(Highlighter::new(&self.highlighter_settings)), - highlighter_settings: self.highlighter_settings.clone(), - highlighter_format_address: self.highlighter_format as usize, - }) - } - - fn size(&self) -> Size { - Size { - width: self.width, - height: self.height, - } - } - - fn layout( - &self, - tree: &mut widget::Tree, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let mut internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_mut::>(); - - if state.highlighter_format_address != self.highlighter_format as usize { - state.highlighter.borrow_mut().change_line(0); - - state.highlighter_format_address = self.highlighter_format as usize; - } - - if state.highlighter_settings != self.highlighter_settings { - state - .highlighter - .borrow_mut() - .update(&self.highlighter_settings); - - state.highlighter_settings = self.highlighter_settings.clone(); - } - - let limits = limits.height(self.height); - - internal.editor.update( - limits.shrink(self.padding).max(), - self.font.unwrap_or_else(|| renderer.default_font()), - self.text_size.unwrap_or_else(|| renderer.default_size()), - self.line_height, - state.highlighter.borrow_mut().deref_mut(), - ); - - match self.height { - Length::Fill | Length::FillPortion(_) | Length::Fixed(_) => { - layout::Node::new(limits.max()) - } - Length::Shrink => { - let min_bounds = internal.editor.min_bounds(); - - layout::Node::new( - limits - .height(min_bounds.height) - .max() - .expand(Size::new(0.0, self.padding.vertical())), - ) - } - } - } - - fn on_event( - &mut self, - tree: &mut widget::Tree, - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - _renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - _viewport: &Rectangle, - ) -> event::Status { - let Some(on_edit) = self.on_edit.as_ref() else { - return event::Status::Ignored; - }; - - let state = tree.state.downcast_mut::>(); - - let Some(update) = Update::from_event(event, state, layout.bounds(), self.padding, cursor) - else { - return event::Status::Ignored; - }; - - match update { - Update::Click(click) => { - let action = match click.kind() { - mouse::click::Kind::Single => Action::Click(click.position()), - mouse::click::Kind::Double => Action::SelectWord, - mouse::click::Kind::Triple => Action::SelectLine, - }; - - state.is_focused = true; - state.last_click = Some(click); - state.drag_click = Some(click.kind()); - - shell.publish(on_edit(action)); - } - Update::Scroll(lines) => { - let lines = lines + state.partial_scroll; - state.partial_scroll = lines.fract(); - - shell.publish(on_edit(Action::Scroll { - lines: lines as i32, - })); - } - Update::Unfocus => { - state.is_focused = false; - state.drag_click = None; - } - Update::Release => { - state.drag_click = None; - } - Update::Action(action) => { - shell.publish(on_edit(action)); - } - Update::Copy => { - if let Some(selection) = self.content.selection() { - clipboard.write(clipboard::Kind::Standard, selection); - } - } - Update::Cut => { - if let Some(selection) = self.content.selection() { - clipboard.write(clipboard::Kind::Standard, selection); - shell.publish(on_edit(Action::Edit(Edit::Delete))); - } - } - Update::Paste => { - if let Some(contents) = clipboard.read(clipboard::Kind::Standard) { - shell.publish(on_edit(Action::Edit(Edit::Paste(Arc::new(contents))))); - } - } - } - - event::Status::Captured - } - - fn draw( - &self, - tree: &widget::Tree, - renderer: &mut Renderer, - theme: &Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor: mouse::Cursor, - viewport: &Rectangle, - ) { - let bounds = layout.bounds(); - - let mut internal = self.content.0.borrow_mut(); - let state = tree.state.downcast_ref::>(); - - internal.editor.highlight( - self.font.unwrap_or_else(|| renderer.default_font()), - state.highlighter.borrow_mut().deref_mut(), - |highlight| (self.highlighter_format)(highlight, theme), - ); - - let is_disabled = self.on_edit.is_none(); - let is_mouse_over = cursor.is_over(bounds); - - let appearance = if is_disabled { - theme.disabled(&self.style) - } else if state.is_focused { - theme.focused(&self.style) - } else if is_mouse_over { - theme.hovered(&self.style) - } else { - theme.active(&self.style) - }; - - renderer.fill_quad( - renderer::Quad { - bounds, - border: appearance.border, - ..renderer::Quad::default() - }, - appearance.background, - ); - - renderer.fill_editor( - &internal.editor, - bounds.position() + Vector::new(self.padding.left, self.padding.top), - style.text_color, - *viewport, - ); - - let translation = Vector::new(bounds.x + self.padding.left, bounds.y + self.padding.top); - - if state.is_focused { - match internal.editor.cursor() { - Cursor::Caret(position) => { - let position = position + translation; - - if bounds.contains(position) { - renderer.fill_quad( - renderer::Quad { - bounds: Rectangle { - x: position.x, - y: position.y, - width: 1.0, - height: self - .line_height - .to_absolute( - self.text_size - .unwrap_or_else(|| renderer.default_size()), - ) - .into(), - }, - ..renderer::Quad::default() - }, - theme.value_color(&self.style), - ); - } - } - Cursor::Selection(ranges) => { - for range in ranges - .into_iter() - .filter_map(|range| bounds.intersection(&(range + translation))) - { - renderer.fill_quad( - renderer::Quad { - bounds: range, - ..renderer::Quad::default() - }, - theme.selection_color(&self.style), - ); - } - } - } - } - } - - fn mouse_interaction( - &self, - _state: &widget::Tree, - layout: Layout<'_>, - cursor: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &Renderer, - ) -> mouse::Interaction { - let is_disabled = self.on_edit.is_none(); - - if cursor.is_over(layout.bounds()) { - if is_disabled { - mouse::Interaction::NotAllowed - } else { - mouse::Interaction::Text - } - } else { - mouse::Interaction::default() - } - } -} - -impl<'a, Highlighter, Message, Theme, Renderer> - From> - for Element<'a, Message, Theme, Renderer> -where - Highlighter: text::Highlighter, - Message: 'a, - Theme: StyleSheet + 'a, - Renderer: text::Renderer, -{ - fn from(text_editor: TextEditor<'a, Highlighter, Message, Theme, Renderer>) -> Self { - Self::new(text_editor) - } -} - -enum Update { - Click(mouse::Click), - Scroll(f32), - Unfocus, - Release, - Action(Action), - Copy, - Cut, - Paste, -} - -impl Update { - fn from_event( - event: Event, - state: &State, - bounds: Rectangle, - padding: Padding, - cursor: mouse::Cursor, - ) -> Option { - let action = |action| Some(Update::Action(action)); - let edit = |edit| action(Action::Edit(edit)); - - match event { - Event::Mouse(event) => match event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - if let Some(cursor_position) = cursor.position_in(bounds) { - let cursor_position = - cursor_position - Vector::new(padding.top, padding.left); - - let click = mouse::Click::new(cursor_position, state.last_click); - - Some(Update::Click(click)) - } else if state.is_focused { - Some(Update::Unfocus) - } else { - None - } - } - mouse::Event::ButtonReleased(mouse::Button::Left) => Some(Update::Release), - mouse::Event::CursorMoved { .. } => match state.drag_click { - Some(mouse::click::Kind::Single) => { - let cursor_position = - cursor.position_in(bounds)? - Vector::new(padding.top, padding.left); - - action(Action::Drag(cursor_position)) - } - _ => None, - }, - mouse::Event::WheelScrolled { delta } if cursor.is_over(bounds) => { - Some(Update::Scroll(match delta { - mouse::ScrollDelta::Lines { y, .. } => { - if y.abs() > 0.0 { - y.signum() * -(y.abs() * 4.0).max(1.0) - } else { - 0.0 - } - } - mouse::ScrollDelta::Pixels { y, .. } => -y / 4.0, - })) - } - _ => None, - }, - Event::Keyboard(event) => match event { - keyboard::Event::KeyPressed { - key, - modifiers, - text, - .. - } if state.is_focused => { - match key.as_ref() { - keyboard::Key::Named(key::Named::Enter) => { - return edit(Edit::Enter); - } - keyboard::Key::Named(key::Named::Backspace) => { - return edit(Edit::Backspace); - } - keyboard::Key::Named(key::Named::Delete) => { - return edit(Edit::Delete); - } - keyboard::Key::Named(key::Named::Escape) => { - return Some(Self::Unfocus); - } - keyboard::Key::Character("c") if modifiers.command() => { - return Some(Self::Copy); - } - keyboard::Key::Character("x") if modifiers.command() => { - return Some(Self::Cut); - } - keyboard::Key::Character("v") - if modifiers.command() && !modifiers.alt() => - { - return Some(Self::Paste); - } - _ => {} - } - - if let Some(text) = text { - if let Some(c) = text.chars().find(|c| !c.is_control()) { - return edit(Edit::Insert(c)); - } - } - - if let keyboard::Key::Named(named_key) = key.as_ref() { - if let Some(motion) = motion(named_key) { - let motion = if platform::is_jump_modifier_pressed(modifiers) { - motion.widen() - } else { - motion - }; - - return action(if modifiers.shift() { - Action::Select(motion) - } else { - Action::Move(motion) - }); - } - } - - None - } - _ => None, - }, - _ => None, - } - } -} - -fn motion(key: key::Named) -> Option { - match key { - key::Named::ArrowLeft => Some(Motion::Left), - key::Named::ArrowRight => Some(Motion::Right), - key::Named::ArrowUp => Some(Motion::Up), - key::Named::ArrowDown => Some(Motion::Down), - key::Named::Home => Some(Motion::Home), - key::Named::End => Some(Motion::End), - key::Named::PageUp => Some(Motion::PageUp), - key::Named::PageDown => Some(Motion::PageDown), - _ => None, - } -} - -mod platform { - use iced::keyboard; - - pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool { - if cfg!(target_os = "macos") { - modifiers.alt() - } else { - modifiers.control() - } - } -} diff --git a/lsp/Cargo.toml b/lsp/Cargo.toml index 0e3b2b3..5a4c98d 100644 --- a/lsp/Cargo.toml +++ b/lsp/Cargo.toml @@ -9,7 +9,6 @@ license.workspace = true [dependencies] async-lsp.workspace = true -iced_runtime.workspace = true log.workspace = true snafu.workspace = true tokio.workspace = true diff --git a/lsp/src/commands/initialize.rs b/lsp/src/commands/initialize.rs index fda4b9a..ef5b6f6 100644 --- a/lsp/src/commands/initialize.rs +++ b/lsp/src/commands/initialize.rs @@ -29,7 +29,7 @@ impl LanguageClient for ClientState { type NotifyResult = ControlFlow>; } -pub async fn initialize() -> Result { +pub async fn initialize() -> Result { let root_dir = Path::new(TEST_ROOT) .canonicalize() .expect("test root should be valid"); diff --git a/lsp/src/commands/shutdown.rs b/lsp/src/commands/shutdown.rs index fb16780..8bf4a16 100644 --- a/lsp/src/commands/shutdown.rs +++ b/lsp/src/commands/shutdown.rs @@ -2,7 +2,7 @@ use async_lsp::{LanguageServer, ServerSocket}; struct Stop; -pub async fn shutdown(mut server: ServerSocket) -> Result<(), async_lsp::Error> { +pub async fn shutdown(mut server: ServerSocket) -> Result<(), crate::Error> { server.shutdown(()).await.unwrap_err(); server.exit(()).unwrap_err(); server.emit(Stop).unwrap_err(); diff --git a/lsp/src/commands/synchronize.rs b/lsp/src/commands/synchronize.rs index 361b30c..48fa217 100644 --- a/lsp/src/commands/synchronize.rs +++ b/lsp/src/commands/synchronize.rs @@ -1,13 +1,16 @@ +use crate::LspSnafu; + use async_lsp::{ lsp_types::{DidOpenTextDocumentParams, TextDocumentItem, Url}, LanguageServer, ServerSocket, }; +use snafu::ResultExt; use std::path::PathBuf; pub async fn synchronize( path: PathBuf, mut server: ServerSocket, -) -> Result { +) -> Result { //TODO add let text = tokio::fs::read_to_string(path.clone()).await.unwrap(); let uri = Url::from_file_path(path).unwrap(); @@ -21,6 +24,6 @@ pub async fn synchronize( }, }) { Ok(_) => Ok(server.clone()), - Err(e) => Err(e), + Err(e) => Err(e).context(LspSnafu), } } diff --git a/lsp/src/lib.rs b/lsp/src/lib.rs index 0daa4f3..6d4cda7 100644 --- a/lsp/src/lib.rs +++ b/lsp/src/lib.rs @@ -1,4 +1,13 @@ pub mod client; pub mod commands; -pub use async_lsp::Error; +use snafu::Snafu; +use std::sync::Arc; + +#[derive(Debug, Clone, Snafu)] +pub enum Error { + Lsp { + #[snafu(source(from(async_lsp::Error, Arc::new)))] + source: Arc, + }, +}