aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/Cargo.toml2
-rw-r--r--helix-term/src/commands.rs75
-rw-r--r--helix-term/src/ui/editor.rs30
-rw-r--r--helix-term/src/ui/info.rs8
-rw-r--r--helix-term/src/ui/markdown.rs9
-rw-r--r--helix-term/src/ui/menu.rs8
-rw-r--r--helix-term/src/ui/mod.rs2
-rw-r--r--helix-term/src/ui/picker.rs76
8 files changed, 137 insertions, 73 deletions
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)
}