diff options
-rw-r--r-- | Cargo.lock | 16 | ||||
-rw-r--r-- | helix-core/src/lib.rs | 1 | ||||
-rw-r--r-- | helix-core/src/path.rs | 92 | ||||
-rw-r--r-- | helix-lsp/src/transport.rs | 20 | ||||
-rw-r--r-- | helix-term/Cargo.toml | 2 | ||||
-rw-r--r-- | helix-term/src/commands.rs | 75 | ||||
-rw-r--r-- | helix-term/src/ui/editor.rs | 30 | ||||
-rw-r--r-- | helix-term/src/ui/info.rs | 8 | ||||
-rw-r--r-- | helix-term/src/ui/markdown.rs | 9 | ||||
-rw-r--r-- | helix-term/src/ui/menu.rs | 8 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 2 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 76 | ||||
-rw-r--r-- | helix-tui/Cargo.toml | 2 | ||||
-rw-r--r-- | helix-view/Cargo.toml | 2 | ||||
-rw-r--r-- | helix-view/src/clipboard.rs | 2 | ||||
-rw-r--r-- | helix-view/src/document.rs | 97 | ||||
-rw-r--r-- | helix-view/src/editor.rs | 2 | ||||
-rw-r--r-- | helix-view/src/graphics.rs | 87 | ||||
-rw-r--r-- | helix-view/src/view.rs | 7 | ||||
-rw-r--r-- | runtime/queries/protobuf/highlights.scm | 2 | ||||
-rw-r--r-- | runtime/themes/monokai.toml | 83 | ||||
-rw-r--r-- | theme.toml | 1 |
22 files changed, 433 insertions, 191 deletions
@@ -19,9 +19,9 @@ checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" [[package]] name = "arc-swap" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e906254e445520903e7fc9da4f709886c84ae4bc4ddaf0e093188d66df4dc820" +checksum = "34a23efe54373080cf871532e2d01076be41c4c896d32ef63af1b2dded924b03" [[package]] name = "autocfg" @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" +checksum = "486d44227f71a1ef39554c0dc47e44b9f4139927c75043312690c3f476d1d788" dependencies = [ "bitflags", "crossterm_winapi", @@ -810,18 +810,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.127" +version = "1.0.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" +checksum = "d1f72836d2aa753853178eda473a3b9d8e4eefdaf20523b919677e6de489f8f1" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.127" +version = "1.0.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" +checksum = "e57ae87ad533d9a56427558b516d0adac283614e347abf85b0dc0cbbf0a249f3" dependencies = [ "proc-macro2", "quote", diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs index 2823959f..be01c302 100644 --- a/helix-core/src/lib.rs +++ b/helix-core/src/lib.rs @@ -11,6 +11,7 @@ pub mod macros; pub mod match_brackets; pub mod movement; pub mod object; +pub mod path; mod position; pub mod register; pub mod search; diff --git a/helix-core/src/path.rs b/helix-core/src/path.rs new file mode 100644 index 00000000..6c37cfa1 --- /dev/null +++ b/helix-core/src/path.rs @@ -0,0 +1,92 @@ +use std::path::{Component, Path, PathBuf}; + +/// Replaces users home directory from `path` with tilde `~` if the directory +/// is available, otherwise returns the path unchanged. +pub fn fold_home_dir(path: &Path) -> PathBuf { + if let Ok(home) = super::home_dir() { + if path.starts_with(&home) { + // it's ok to unwrap, the path starts with home dir + return PathBuf::from("~").join(path.strip_prefix(&home).unwrap()); + } + } + + path.to_path_buf() +} + +/// Expands tilde `~` into users home directory if avilable, otherwise returns the path +/// unchanged. The tilde will only be expanded when present as the first component of the path +/// and only slash follows it. +pub fn expand_tilde(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + if let Some(Component::Normal(c)) = components.peek() { + if c == &"~" { + if let Ok(home) = super::home_dir() { + // it's ok to unwrap, the path starts with `~` + return home.join(path.strip_prefix("~").unwrap()); + } + } + } + + path.to_path_buf() +} + +/// Normalize a path, removing things like `.` and `..`. +/// +/// CAUTION: This does not resolve symlinks (unlike +/// [`std::fs::canonicalize`]). This may cause incorrect or surprising +/// behavior at times. This should be used carefully. Unfortunately, +/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often +/// fail, or on Windows returns annoying device paths. This is a problem Cargo +/// needs to improve on. +/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81> +pub fn get_normalized_path(path: &Path) -> PathBuf { + let path = expand_tilde(path); + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} + +/// Returns the canonical, absolute form of a path with all intermediate components normalized. +/// +/// This function is used instead of `std::fs::canonicalize` because we don't want to verify +/// here if the path exists, just normalize it's components. +pub fn get_canonicalized_path(path: &Path) -> std::io::Result<PathBuf> { + let path = if path.is_relative() { + std::env::current_dir().map(|current_dir| current_dir.join(path))? + } else { + path.to_path_buf() + }; + + Ok(get_normalized_path(path.as_path())) +} + +pub fn get_relative_path(path: &Path) -> PathBuf { + let path = if path.is_absolute() { + let cwdir = std::env::current_dir().expect("couldn't determine current directory"); + path.strip_prefix(cwdir).unwrap_or(path) + } else { + path + }; + fold_home_dir(path) +} diff --git a/helix-lsp/src/transport.rs b/helix-lsp/src/transport.rs index 0710b449..5be694c8 100644 --- a/helix-lsp/src/transport.rs +++ b/helix-lsp/src/transport.rs @@ -222,10 +222,13 @@ impl Transport { loop { match Self::recv_server_message(&mut server_stdout, &mut recv_buffer).await { Ok(msg) => { - transport - .process_server_message(&client_tx, msg) - .await - .unwrap(); + match transport.process_server_message(&client_tx, msg).await { + Ok(_) => {} + Err(err) => { + error!("err: <- {:?}", err); + break; + } + }; } Err(err) => { error!("err: <- {:?}", err); @@ -254,10 +257,15 @@ impl Transport { mut client_rx: UnboundedReceiver<Payload>, ) { while let Some(msg) = client_rx.recv().await { - transport + match transport .send_payload_to_server(&mut server_stdin, msg) .await - .unwrap() + { + Ok(_) => {} + Err(err) => { + error!("err: <- {:?}", err); + } + } } } } diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index c0b2cfe5..6e9c0daf 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -32,7 +32,7 @@ once_cell = "1.8" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } num_cpus = "1" tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } -crossterm = { version = "0.20", features = ["event-stream"] } +crossterm = { version = "0.21", features = ["event-stream"] } signal-hook = "0.3" tokio-stream = "0.1" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index ecdd2e6d..6ba244ee 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2006,7 +2006,24 @@ mod cmd { Ok(()) } - fn debug_set_logpoint( + fn vsplit( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let (_, doc) = current!(cx.editor); + let id = doc.id(); + + if let Some(path) = args.get(0) { + cx.editor.open(path.into(), Action::VerticalSplit)?; + } else { + cx.editor.switch(id, Action::VerticalSplit); + } + + Ok(()) + } + + fn hsplit( cx: &mut compositor::Context, args: &[&str], _event: PromptEvent, @@ -2051,6 +2068,24 @@ mod cmd { _ => Some(args.remove(0)), }; dap_start_impl(&mut cx.editor, name, address, Some(args)); + + Ok(()) + } + + fn debug_set_logpoint( + cx: &mut compositor::Context, + args: &[&str], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let (_, doc) = current!(cx.editor); + let id = doc.id(); + + if let Some(path) = args.get(0) { + cx.editor.open(path.into(), Action::HorizontalSplit)?; + } else { + cx.editor.switch(id, Action::HorizontalSplit); + } + Ok(()) } @@ -2327,6 +2362,20 @@ mod cmd { doc: "Make current breakpoint a log point.", fun: debug_set_logpoint, completer: None, + }, + TypableCommand { + name: "vsplit", + alias: Some("vsp"), + doc: "Open the file in a vertical split.", + fun: vsplit, + completer: Some(completers::filename), + }, + TypableCommand { + name: "hsplit", + alias: Some("sp"), + doc: "Open the file in a horizontal split.", + fun: hsplit, + completer: Some(completers::filename), } ]; @@ -2429,16 +2478,16 @@ fn buffer_picker(cx: &mut Context) { cx.editor .documents .iter() - .map(|(id, doc)| (id, doc.relative_path())) + .map(|(id, doc)| (id, doc.path().cloned())) .collect(), move |(id, path): &(DocumentId, Option<PathBuf>)| { - // format_fn + let path = path.as_deref().map(helix_core::path::get_relative_path); match path.as_ref().and_then(|path| path.to_str()) { Some(path) => { if *id == current { - format!("{} (*)", path).into() + format!("{} (*)", &path).into() } else { - path.into() + path.to_owned().into() } } None => "[scratch buffer]".into(), @@ -2454,7 +2503,7 @@ fn buffer_picker(cx: &mut Context) { .selection(view_id) .primary() .cursor_line(doc.text().slice(..)); - Some((path.clone()?, Some(line))) + Some((path.clone()?, Some((line, line)))) }, ); cx.push_layer(Box::new(picker)); @@ -2519,13 +2568,18 @@ fn symbol_picker(cx: &mut Context) { if let Some(range) = lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding) { - doc.set_selection(view.id, Selection::single(range.anchor, range.head)); + // we flip the range so that the cursor sits on the start of the symbol + // (for example start of the function). + doc.set_selection(view.id, Selection::single(range.head, range.anchor)); align_view(doc, view, Align::Center); } }, move |_editor, symbol| { let path = symbol.location.uri.to_file_path().unwrap(); - let line = Some(symbol.location.range.start.line as usize); + let line = Some(( + symbol.location.range.start.line as usize, + symbol.location.range.end.line as usize, + )); Some((path, line)) }, ); @@ -2958,7 +3012,10 @@ fn goto_impl( }, |_editor, location| { let path = location.uri.to_file_path().unwrap(); - let line = Some(location.range.start.line as usize); + let line = Some(( + location.range.start.line as usize, + location.range.end.line as usize, + )); Some((path, line)) }, ); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 272c8ac1..92a631ed 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -112,13 +112,11 @@ impl EditorView { self.render_diagnostics(doc, view, inner, surface, theme, debugger); - let area = Rect::new( - view.area.x, - view.area.y + view.area.height.saturating_sub(1), - view.area.width, - 1, - ); - self.render_statusline(doc, view, area, surface, theme, is_focused); + let statusline_area = view + .area + .clip_top(view.area.height.saturating_sub(1)) + .clip_bottom(1); // -1 from bottom to remove commandline + self.render_statusline(doc, view, statusline_area, surface, theme, is_focused); } /// Get syntax highlights for a document in a view represented by the first line @@ -201,7 +199,9 @@ impl EditorView { .find_scope_index("diagnostic") .or_else(|| theme.find_scope_index("ui.cursor")) .or_else(|| theme.find_scope_index("ui.selection")) - .expect("no selection scope found!"); + .expect( + "at least one of the following scopes must be defined in the theme: `diagnostic`, `ui.cursor`, or `ui.selection`", + ); doc.diagnostics() .iter() @@ -226,7 +226,7 @@ impl EditorView { let selection_scope = theme .find_scope_index("ui.selection") - .expect("no selection scope found!"); + .expect("could not find `ui.selection` scope in the theme!"); let base_cursor_scope = theme .find_scope_index("ui.cursor") .unwrap_or(selection_scope); @@ -598,12 +598,7 @@ impl EditorView { let width = 80.min(viewport.width); let height = 15.min(viewport.height); paragraph.render( - Rect::new( - viewport.right() - width, - viewport.y as u16 + 1, - width, - height, - ), + Rect::new(viewport.right() - width, viewport.y + 1, width, height), surface, ); } @@ -642,7 +637,7 @@ impl EditorView { theme.get("ui.statusline.inactive") }; // statusline - surface.set_style(Rect::new(viewport.x, viewport.y, viewport.width, 1), style); + surface.set_style(viewport.with_height(1), style); if is_focused { surface.set_string(viewport.x + 1, viewport.y, mode, style); } @@ -1071,8 +1066,7 @@ impl Component for EditorView { surface.set_style(area, cx.editor.theme.get("ui.background")); // if the terminal size suddenly changed, we need to trigger a resize - cx.editor - .resize(Rect::new(area.x, area.y, area.width, area.height - 1)); // - 1 to account for commandline + cx.editor.resize(area.clip_bottom(1)); // -1 from bottom for commandline for (view, is_focused) in cx.editor.tree.views() { let doc = cx.editor.document(view.doc).unwrap(); diff --git a/helix-term/src/ui/info.rs b/helix-term/src/ui/info.rs index 75d978da..55b0e65d 100644 --- a/helix-term/src/ui/info.rs +++ b/helix-term/src/ui/info.rs @@ -6,8 +6,12 @@ use tui::widgets::{Block, Borders, Paragraph, Widget}; impl Component for Info { fn render(&mut self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { - let text_style = cx.editor.theme.get("ui.text.focus"); - let popup_style = text_style.patch(cx.editor.theme.get("ui.popup")); + let get_theme = |style, fallback| { + let theme = &cx.editor.theme; + theme.try_get(style).unwrap_or_else(|| theme.get(fallback)) + }; + let text_style = get_theme("ui.info.text", "ui.text"); + let popup_style = text_style.patch(get_theme("ui.info", "ui.popup")); // Calculate the area of the terminal to modify. Because we want to // render at the bottom right, we use the viewport's width and height diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs index b7e29f21..28542cdc 100644 --- a/helix-term/src/ui/markdown.rs +++ b/helix-term/src/ui/markdown.rs @@ -13,7 +13,7 @@ use helix_core::{ Rope, }; use helix_view::{ - graphics::{Color, Rect, Style}, + graphics::{Color, Margin, Rect, Style}, Theme, }; @@ -207,8 +207,11 @@ impl Component for Markdown { .wrap(Wrap { trim: false }) .scroll((cx.scroll.unwrap_or_default() as u16, 0)); - let area = Rect::new(area.x + 1, area.y + 1, area.width - 2, area.height - 2); - par.render(area, surface); + let margin = Margin { + vertical: 1, + horizontal: 1, + }; + par.render(area.inner(&margin), surface); } fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 3e63db35..a56cf19b 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -304,14 +304,6 @@ impl<T: Item + 'static> Component for Menu<T> { }, ); - // // TODO: set bg for the whole row if selected - // if line == self.cursor { - // surface.set_style( - // Rect::new(area.x, area.y + i as u16, area.width - 1, 1), - // selected, - // ) - // } - for (i, _) in (scroll..(scroll + win_height).min(len)).enumerate() { let is_marked = i >= scroll_line && i < scroll_line + scroll_height; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 390f1a66..e4871312 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -208,7 +208,7 @@ pub mod completers { use std::path::Path; let is_tilde = input.starts_with('~') && input.len() == 1; - let path = helix_view::document::expand_tilde(Path::new(input)); + let path = helix_core::path::expand_tilde(Path::new(input)); let (dir, file_name) = if input.ends_with('/') { (path, None) diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 2cc25c24..ef2c434c 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -17,16 +17,15 @@ use std::{borrow::Cow, collections::HashMap, path::PathBuf}; use crate::ui::{Prompt, PromptEvent}; use helix_core::Position; use helix_view::{ - document::canonicalize_path, editor::Action, - graphics::{Color, CursorKind, Rect, Style}, + graphics::{Color, CursorKind, Margin, Rect, Style}, Document, Editor, }; pub const MIN_SCREEN_WIDTH_FOR_PREVIEW: u16 = 80; /// File path and line number (used to align and highlight a line) -type FileLocation = (PathBuf, Option<usize>); +type FileLocation = (PathBuf, Option<(usize, usize)>); pub struct FilePicker<T> { picker: Picker<T>, @@ -54,7 +53,11 @@ impl<T> FilePicker<T> { self.picker .selection() .and_then(|current| (self.file_fn)(editor, current)) - .and_then(|(path, line)| canonicalize_path(&path).ok().zip(Some(line))) + .and_then(|(path, line)| { + helix_core::path::get_canonicalized_path(&path) + .ok() + .zip(Some(line)) + }) } fn calculate_preview(&mut self, editor: &Editor) { @@ -90,34 +93,40 @@ impl<T: 'static> Component for FilePicker<T> { area.width }; - let picker_area = Rect::new(area.x, area.y, picker_width, area.height); + let picker_area = area.with_width(picker_width); self.picker.render(picker_area, surface, cx); if !render_preview { return; } - let preview_area = Rect::new(area.x + picker_width, area.y, area.width / 2, area.height); + let preview_area = area.clip_left(picker_width); // don't like this but the lifetime sucks let block = Block::default().borders(Borders::ALL); // calculate the inner area inside the box - let mut inner = block.inner(preview_area); + let inner = block.inner(preview_area); // 1 column gap on either side - inner.x += 1; - inner.width = inner.width.saturating_sub(2); + let margin = Margin { + vertical: 0, + horizontal: 1, + }; + let inner = inner.inner(&margin); block.render(preview_area, surface); - if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, line)| { + if let Some((doc, line)) = self.current_file(cx.editor).and_then(|(path, range)| { cx.editor .document_by_path(&path) .or_else(|| self.preview_cache.get(&path)) - .zip(Some(line)) + .zip(Some(range)) }) { // align to middle - let first_line = line.unwrap_or(0).saturating_sub(inner.height as usize / 2); + let first_line = line + .map(|(start, _)| start) + .unwrap_or(0) + .saturating_sub(inner.height as usize / 2); let offset = Position::new(first_line, 0); let highlights = EditorView::doc_syntax_highlights( @@ -137,12 +146,21 @@ impl<T: 'static> Component for FilePicker<T> { ); // highlight the line - if let Some(line) = line { - for x in inner.left()..inner.right() { - surface - .get_mut(x, inner.y + line.saturating_sub(first_line) as u16) - .set_style(cx.editor.theme.get("ui.selection")); - } + if let Some((start, end)) = line { + let offset = start.saturating_sub(first_line) as u16; + surface.set_style( + Rect::new( + inner.x, + inner.y + offset, + inner.width, + (end.saturating_sub(start) as u16 + 1) + .min(inner.height.saturating_sub(offset)), + ), + cx.editor + .theme + .try_get("ui.highlight") + .unwrap_or_else(|| cx.editor.theme.get("ui.selection")), + ); } } } @@ -282,15 +300,11 @@ impl<T> Picker<T> { // - score all the names in relation to input fn inner_rect(area: Rect) -> Rect { - let padding_vertical = area.height * 10 / 100; - let padding_horizontal = area.width * 10 / 100; - - Rect::new( - area.x + padding_horizontal, - area.y + padding_vertical, - area.width - padding_horizontal * 2, - area.height - padding_vertical * 2, - ) + let margin = Margin { + vertical: area.height * 10 / 100, + horizontal: area.width * 10 / 100, + }; + area.inner(&margin) } impl<T: 'static> Component for Picker<T> { @@ -410,7 +424,7 @@ impl<T: 'static> Component for Picker<T> { // -- Render the input bar: - let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1); + let area = inner.clip_left(1).with_height(1); let count = format!("{}/{}", self.matches.len(), self.options.len()); surface.set_stringn( @@ -434,8 +448,8 @@ impl<T: 'static> Component for Picker<T> { } // -- Render the contents: - // subtract the area of the prompt (-2) and current item marker " > " (-3) - let inner = Rect::new(inner.x + 3, inner.y + 2, inner.width - 3, inner.height - 2); + // subtract area of prompt from top and current item marker " > " from left + let inner = inner.clip_top(2).clip_left(3); let selected = cx.editor.theme.get("ui.text.focus"); @@ -474,7 +488,7 @@ impl<T: 'static> Component for Picker<T> { let inner = block.inner(area); // prompt area - let area = Rect::new(inner.x + 1, inner.y, inner.width - 1, 1); + let area = inner.clip_left(1).with_height(1); self.prompt.cursor(area, editor) } diff --git a/helix-tui/Cargo.toml b/helix-tui/Cargo.toml index 5f22b7c8..80a772a4 100644 --- a/helix-tui/Cargo.toml +++ b/helix-tui/Cargo.toml @@ -19,7 +19,7 @@ default = ["crossterm"] bitflags = "1.3" cassowary = "0.3" unicode-segmentation = "1.8" -crossterm = { version = "0.20", optional = true } +crossterm = { version = "0.21", optional = true } serde = { version = "1", "optional" = true, features = ["derive"]} helix-view = { version = "0.4", path = "../helix-view", features = ["term"] } helix-core = { version = "0.4", path = "../helix-core" } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index b3991584..1f55a36b 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -19,7 +19,7 @@ anyhow = "1" helix-core = { version = "0.4", path = "../helix-core" } helix-lsp = { version = "0.4", path = "../helix-lsp"} helix-dap = { version = "0.4", path = "../helix-dap"} -crossterm = { version = "0.20", optional = true } +crossterm = { version = "0.21", optional = true } # Conversion traits once_cell = "1.8" diff --git a/helix-view/src/clipboard.rs b/helix-view/src/clipboard.rs index 9dd86da7..a11224ac 100644 --- a/helix-view/src/clipboard.rs +++ b/helix-view/src/clipboard.rs @@ -84,7 +84,7 @@ pub fn get_clipboard_provider() -> Box<dyn ClipboardProvider> { // FIXME: check performance of is_exit_success command_provider! { paste => "xsel", "-o", "-b"; - copy => "xsel", "--nodetach", "-i", "-b"; + copy => "xsel", "-i", "-b"; primary_paste => "xsel", "-o"; primary_copy => "xsel", "-i"; } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index ff0c8bf4..a238644a 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -4,7 +4,7 @@ use std::cell::Cell; use std::collections::HashMap; use std::fmt::Display; use std::future::Future; -use std::path::{Component, Path, PathBuf}; +use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; @@ -317,87 +317,6 @@ where } } -/// Expands tilde `~` into users home directory if avilable, otherwise returns the path -/// unchanged. The tilde will only be expanded when present as the first component of the path -/// and only slash follows it. -pub fn expand_tilde(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - if let Some(Component::Normal(c)) = components.peek() { - if c == &"~" { - if let Ok(home) = helix_core::home_dir() { - // it's ok to unwrap, the path starts with `~` - return home.join(path.strip_prefix("~").unwrap()); - } - } - } - - path.to_path_buf() -} - -/// Replaces users home directory from `path` with tilde `~` if the directory -/// is available, otherwise returns the path unchanged. -pub fn fold_home_dir(path: &Path) -> PathBuf { - if let Ok(home) = helix_core::home_dir() { - if path.starts_with(&home) { - // it's ok to unwrap, the path starts with home dir - return PathBuf::from("~").join(path.strip_prefix(&home).unwrap()); - } - } - - path.to_path_buf() -} - -/// Normalize a path, removing things like `.` and `..`. -/// -/// CAUTION: This does not resolve symlinks (unlike -/// [`std::fs::canonicalize`]). This may cause incorrect or surprising -/// behavior at times. This should be used carefully. Unfortunately, -/// [`std::fs::canonicalize`] can be hard to use correctly, since it can often -/// fail, or on Windows returns annoying device paths. This is a problem Cargo -/// needs to improve on. -/// Copied from cargo: <https://github.com/rust-lang/cargo/blob/070e459c2d8b79c5b2ac5218064e7603329c92ae/crates/cargo-util/src/paths.rs#L81> -pub fn normalize_path(path: &Path) -> PathBuf { - let path = expand_tilde(path); - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - ret -} - -/// Returns the canonical, absolute form of a path with all intermediate components normalized. -/// -/// This function is used instead of `std::fs::canonicalize` because we don't want to verify -/// here if the path exists, just normalize it's components. -pub fn canonicalize_path(path: &Path) -> std::io::Result<PathBuf> { - let path = if path.is_relative() { - std::env::current_dir().map(|current_dir| current_dir.join(path))? - } else { - path.to_path_buf() - }; - - Ok(normalize_path(&path)) -} - use helix_lsp::lsp; use url::Url; @@ -631,7 +550,7 @@ impl Document { } pub fn set_path(&mut self, path: &Path) -> Result<(), std::io::Error> { - let path = canonicalize_path(path)?; + let path = helix_core::path::get_canonicalized_path(path)?; // if parent doesn't exist we still want to open the document // and error out when document is saved @@ -936,15 +855,9 @@ impl Document { } pub fn relative_path(&self) -> Option<PathBuf> { - let cwdir = std::env::current_dir().expect("couldn't determine current directory"); - - self.path.as_ref().map(|path| { - let mut path = path.as_path(); - if path.is_absolute() { - path = path.strip_prefix(cwdir).unwrap_or(path) - }; - fold_home_dir(path) - }) + self.path + .as_deref() + .map(helix_core::path::get_relative_path) } // pub fn slice<R>(&self, range: R) -> RopeSlice where R: RangeBounds { diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 17319327..295dfc0e 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -238,7 +238,7 @@ impl Editor { } pub fn open(&mut self, path: PathBuf, action: Action) -> Result<DocumentId, Error> { - let path = crate::document::canonicalize_path(&path)?; + let path = helix_core::path::get_canonicalized_path(&path)?; let id = self .documents() diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index 9a7a86c3..66013ee5 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -24,7 +24,7 @@ pub struct Margin { }
/// A simple rectangle used in the computation of the layout and to give widgets an hint about the
-/// area they are supposed to render to.
+/// area they are supposed to render to. (x, y) = (0, 0) is at the top left corner of the screen.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Rect {
pub x: u16,
@@ -92,6 +92,57 @@ impl Rect { self.y.saturating_add(self.height)
}
+ // Returns a new Rect with width reduced from the left side.
+ // This changes the `x` coordinate and clamps it to the right
+ // edge of the original Rect.
+ pub fn clip_left(self, width: u16) -> Rect {
+ let width = std::cmp::min(width, self.width);
+ Rect {
+ x: self.x.saturating_add(width),
+ width: self.width.saturating_sub(width),
+ ..self
+ }
+ }
+
+ // Returns a new Rect with width reduced from the right side.
+ // This does _not_ change the `x` coordinate.
+ pub fn clip_right(self, width: u16) -> Rect {
+ Rect {
+ width: self.width.saturating_sub(width),
+ ..self
+ }
+ }
+
+ // Returns a new Rect with height reduced from the top.
+ // This changes the `y` coordinate and clamps it to the bottom
+ // edge of the original Rect.
+ pub fn clip_top(self, height: u16) -> Rect {
+ let height = std::cmp::min(height, self.height);
+ Rect {
+ y: self.y.saturating_add(height),
+ height: self.height.saturating_sub(height),
+ ..self
+ }
+ }
+
+ // Returns a new Rect with height reduced from the bottom.
+ // This does _not_ change the `y` coordinate.
+ pub fn clip_bottom(self, height: u16) -> Rect {
+ Rect {
+ height: self.height.saturating_sub(height),
+ ..self
+ }
+ }
+
+ pub fn with_height(self, height: u16) -> Rect {
+ // new height may make area > u16::max_value, so use new()
+ Self::new(self.x, self.y, self.width, height)
+ }
+
+ pub fn with_width(self, width: u16) -> Rect {
+ Self::new(self.x, self.y, width, self.height)
+ }
+
pub fn inner(self, margin: &Margin) -> Rect {
if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
Rect::default()
@@ -495,6 +546,40 @@ mod tests { assert_eq!(rect.height, 100);
}
+ #[test]
+ fn test_rect_chop_from_left() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(10, 0, 10, 30), rect.clip_left(10));
+ assert_eq!(
+ Rect::new(20, 0, 0, 30),
+ rect.clip_left(40),
+ "x should be clamped to original width if new width is bigger"
+ );
+ }
+
+ #[test]
+ fn test_rect_chop_from_right() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(0, 0, 10, 30), rect.clip_right(10));
+ }
+
+ #[test]
+ fn test_rect_chop_from_top() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(0, 10, 20, 20), rect.clip_top(10));
+ assert_eq!(
+ Rect::new(0, 30, 20, 0),
+ rect.clip_top(50),
+ "y should be clamped to original height if new height is bigger"
+ );
+ }
+
+ #[test]
+ fn test_rect_chop_from_bottom() {
+ let rect = Rect::new(0, 0, 20, 30);
+ assert_eq!(Rect::new(0, 0, 20, 20), rect.clip_bottom(10));
+ }
+
fn styles() -> Vec<Style> {
vec![
Style::default(),
diff --git a/helix-view/src/view.rs b/helix-view/src/view.rs index b707c092..01f18c71 100644 --- a/helix-view/src/view.rs +++ b/helix-view/src/view.rs @@ -83,12 +83,7 @@ impl View { pub fn inner_area(&self) -> Rect { // TODO: not ideal const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter - Rect::new( - self.area.x + OFFSET, - self.area.y, - self.area.width - OFFSET, - self.area.height.saturating_sub(1), // -1 for statusline - ) + self.area.clip_left(OFFSET).clip_bottom(1) // -1 for statusline } pub fn ensure_cursor_in_view(&mut self, doc: &Document, scrolloff: usize) { diff --git a/runtime/queries/protobuf/highlights.scm b/runtime/queries/protobuf/highlights.scm index 4597dd5a..cd021be1 100644 --- a/runtime/queries/protobuf/highlights.scm +++ b/runtime/queries/protobuf/highlights.scm @@ -14,6 +14,7 @@ "to" "stream" "extend" + "optional" ] @keyword [ @@ -23,7 +24,6 @@ [ (mapName) - (oneofName) (enumName) (messageName) (extendName) diff --git a/runtime/themes/monokai.toml b/runtime/themes/monokai.toml new file mode 100644 index 00000000..2407591a --- /dev/null +++ b/runtime/themes/monokai.toml @@ -0,0 +1,83 @@ +# Author: Shafkath Shuhan <shafkathshuhannyc@gmail.com> + +"namespace" = { fg = "type" } +"module" = { fg = "type" } + +"type" = { fg = "type" } +"type.builtin" = { fg = "#66D9EF" } +"type.enum.variant" = { fg = "text" } +"constructor" = { fg = "text" } +"property" = { fg = "variable" } + +"keyword" = { fg = "keyword" } +"keyword.directive" = { fg = "keyword" } +"keyword.control" = { fg = "keyword" } +"label" = { fg = "keyword" } + +"special" = { fg = "keyword" } +"operator" = { fg = "text" } + +"punctuation" = { fg = "text" } +"punctuation.delimiter" = { fg = "text" } + +"variable" = { fg = "variable" } +"variable.parameter" = { fg = "#fd971f" } +"variable.builtin" = { fg = "keyword" } +"constant" = { fg = "variable" } +"constant.builtin" = { fg = "#ae81ff" } + +"function" = { fg = "fn_declaration" } +"function.builtin" = { fg = "fn_declaration" } +"function.macro" = { fg = "keyword" } +"attribute" = { fg = "fn_declaration" } + +"comment" = { fg = "#88846F" } + +"string" = { fg = "#e6db74" } +"number" = { fg = "#ae81ff" } +"escape" = { fg = "#ae81ff" } + +"ui.background" = { fg = "text", bg = "background" } + +"ui.window" = { bg = "widget" } +"ui.popup" = { bg = "widget" } +"ui.help" = { bg = "widget" } +"ui.menu.selected" = { bg = "widget" } + +"ui.cursor" = { fg = "cursor", modifiers = ["reversed"] } +"ui.cursor.primary" = { fg = "cursor", modifiers = ["reversed"] } +"ui.cursor.match" = { fg = "#888888", modifiers = ["reversed"] } + +"ui.selection" = { bg = "#878b91" } +"ui.selection.primary" = { bg = "#575b61" } + +"ui.linenr" = { fg = "#90908a" } +"ui.linenr.selected" = { fg = "#c2c2bf" } + +"ui.statusline" = { fg = "active_text", bg = "#75715e" } +"ui.statusline.inactive" = { fg = "active_text", bg = "#75715e" } + +"ui.text" = { fg = "text", bg = "background" } +"ui.text.focus" = { fg = "active_text" } + +"warning" = { fg = "#cca700" } +"error" = { fg = "#f48771" } +"info" = { fg = "#75beff" } +"hint" = { fg = "#eeeeeeb3" } + +diagnostic = { modifiers = ["underlined"] } + +[palette] +type = "#A6E22E" +keyword = "#F92672" +regex = "#CE9178" +special = "#C586C0" +variable = "#F8F8F2" +fn_declaration = "#A6E22E" + +background = "#272822" +text = "#f8f8f2" +active_text = "#ffffff" +cursor = "#a6a6a6" +inactive_cursor = "#878b91" +widget = "#1e1f1c" @@ -51,6 +51,7 @@ module = "#ff0000" "ui.cursor.insert" = { bg = "white" } "ui.cursor.match" = { fg = "#212121", bg = "#6C6999" } "ui.cursor" = { modifiers = ["reversed"] } +"ui.highlight" = { bg = "bossanova" } "ui.menu.selected" = { fg = "revolver", bg = "white" } |