aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term/src')
-rw-r--r--helix-term/src/application.rs8
-rw-r--r--helix-term/src/commands.rs146
-rw-r--r--helix-term/src/job.rs14
-rw-r--r--helix-term/src/keymap.rs19
-rw-r--r--helix-term/src/ui/completion.rs2
-rw-r--r--helix-term/src/ui/editor.rs45
-rw-r--r--helix-term/src/ui/markdown.rs21
-rw-r--r--helix-term/src/ui/menu.rs2
-rw-r--r--helix-term/src/ui/picker.rs23
-rw-r--r--helix-term/src/ui/prompt.rs2
10 files changed, 178 insertions, 104 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index c7202feb..c376aefa 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -358,12 +358,8 @@ impl Application {
// trigger textDocument/didOpen for docs that are already open
for doc in docs {
- // TODO: extract and share with editor.open
- let language_id = doc
- .language()
- .and_then(|s| s.split('.').last()) // source.rust
- .map(ToOwned::to_owned)
- .unwrap_or_default();
+ let language_id =
+ doc.language_id().map(ToOwned::to_owned).unwrap_or_default();
tokio::spawn(language_server.text_document_did_open(
doc.url().unwrap(),
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 5c26a5b2..6123b7d2 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -11,6 +11,7 @@ use helix_core::{
object, pos_at_coords,
regex::{self, Regex, RegexBuilder},
search, selection, shellwords, surround, textobject,
+ tree_sitter::Node,
unicode::width::UnicodeWidthChar,
LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril,
Transaction,
@@ -173,7 +174,7 @@ macro_rules! static_commands {
impl MappableCommand {
pub fn execute(&self, cx: &mut Context) {
match &self {
- MappableCommand::Typable { name, args, doc: _ } => {
+ Self::Typable { name, args, doc: _ } => {
let args: Vec<Cow<str>> = args.iter().map(Cow::from).collect();
if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) {
let mut cx = compositor::Context {
@@ -186,21 +187,21 @@ impl MappableCommand {
}
}
}
- MappableCommand::Static { fun, .. } => (fun)(cx),
+ Self::Static { fun, .. } => (fun)(cx),
}
}
pub fn name(&self) -> &str {
match &self {
- MappableCommand::Typable { name, .. } => name,
- MappableCommand::Static { name, .. } => name,
+ Self::Typable { name, .. } => name,
+ Self::Static { name, .. } => name,
}
}
pub fn doc(&self) -> &str {
match &self {
- MappableCommand::Typable { doc, .. } => doc,
- MappableCommand::Static { doc, .. } => doc,
+ Self::Typable { doc, .. } => doc,
+ Self::Static { doc, .. } => doc,
}
}
@@ -363,6 +364,8 @@ impl MappableCommand {
rotate_selection_contents_backward, "Rotate selections contents backward",
expand_selection, "Expand selection to parent syntax node",
shrink_selection, "Shrink selection to previously expanded syntax node",
+ select_next_sibling, "Select the next sibling in the syntax tree",
+ select_prev_sibling, "Select the previous sibling in the syntax tree",
jump_forward, "Jump forward on jumplist",
jump_backward, "Jump backward on jumplist",
save_selection, "Save the current selection to the jumplist",
@@ -2278,7 +2281,7 @@ pub mod cmd {
force: bool,
) -> anyhow::Result<()> {
let mut errors = String::new();
-
+ let jobs = &mut cx.jobs;
// save all documents
for doc in &mut cx.editor.documents.values_mut() {
if doc.path().is_none() {
@@ -2286,9 +2289,23 @@ pub mod cmd {
continue;
}
- // TODO: handle error.
- let handle = doc.save();
- cx.jobs.add(Job::new(handle).wait_before_exiting());
+ if !doc.is_modified() {
+ continue;
+ }
+
+ let fmt = doc.auto_format().map(|fmt| {
+ let shared = fmt.shared();
+ let callback = make_format_callback(
+ doc.id(),
+ doc.version(),
+ Modified::SetUnmodified,
+ shared.clone(),
+ );
+ jobs.callback(callback);
+ shared
+ });
+ let future = doc.format_and_save(fmt);
+ jobs.add(Job::new(future).wait_before_exiting());
}
if quit {
@@ -2748,6 +2765,46 @@ pub mod cmd {
Ok(())
}
+ fn tree_sitter_subtree(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ _event: PromptEvent,
+ ) -> anyhow::Result<()> {
+ let (view, doc) = current!(cx.editor);
+
+ if let Some(syntax) = doc.syntax() {
+ let primary_selection = doc.selection(view.id).primary();
+ let text = doc.text();
+ let from = text.char_to_byte(primary_selection.from());
+ let to = text.char_to_byte(primary_selection.to());
+ if let Some(selected_node) = syntax
+ .tree()
+ .root_node()
+ .descendant_for_byte_range(from, to)
+ {
+ let contents = format!("```tsq\n{}\n```", selected_node.to_sexp());
+
+ let callback = async move {
+ let call: job::Callback =
+ Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
+ let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
+ let popup = Popup::new("hover", contents);
+ if let Some(doc_popup) = compositor.find_id("hover") {
+ *doc_popup = popup;
+ } else {
+ compositor.push(Box::new(popup));
+ }
+ });
+ Ok(call)
+ };
+
+ cx.jobs.callback(callback);
+ }
+ }
+
+ Ok(())
+ }
+
pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
TypableCommand {
name: "quit",
@@ -3064,6 +3121,13 @@ pub mod cmd {
fun: sort_reverse,
completer: None,
},
+ TypableCommand {
+ name: "tree-sitter-subtree",
+ aliases: &["ts-subtree"],
+ doc: "Display tree sitter subtree under cursor, primarily for debugging queries.",
+ fun: tree_sitter_subtree,
+ completer: None,
+ },
];
pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> =
@@ -3239,8 +3303,8 @@ fn buffer_picker(cx: &mut Context) {
.map(|(_, doc)| new_meta(doc))
.collect(),
BufferMeta::format,
- |editor: &mut Editor, meta, _action| {
- editor.switch(meta.id, Action::Replace);
+ |editor: &mut Editor, meta, action| {
+ editor.switch(meta.id, action);
},
|editor, meta| {
let doc = &editor.documents.get(&meta.id)?;
@@ -3479,11 +3543,9 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
match op {
ResourceOp::Create(op) => {
let path = op.uri.to_file_path().unwrap();
- let ignore_if_exists = if let Some(options) = &op.options {
+ let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
- } else {
- false
- };
+ });
if ignore_if_exists && path.exists() {
Ok(())
} else {
@@ -3493,11 +3555,12 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
ResourceOp::Delete(op) => {
let path = op.uri.to_file_path().unwrap();
if path.is_dir() {
- let recursive = if let Some(options) = &op.options {
- options.recursive.unwrap_or(false)
- } else {
- false
- };
+ let recursive = op
+ .options
+ .as_ref()
+ .and_then(|options| options.recursive)
+ .unwrap_or(false);
+
if recursive {
fs::remove_dir_all(&path)
} else {
@@ -3512,11 +3575,9 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
ResourceOp::Rename(op) => {
let from = op.old_uri.to_file_path().unwrap();
let to = op.new_uri.to_file_path().unwrap();
- let ignore_if_exists = if let Some(options) = &op.options {
+ let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
!options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
- } else {
- false
- };
+ });
if ignore_if_exists && to.exists() {
Ok(())
} else {
@@ -5388,8 +5449,8 @@ fn hover(cx: &mut Context) {
// skip if contents empty
let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
- let popup = Popup::new("documentation", contents);
- if let Some(doc_popup) = compositor.find_id("documentation") {
+ let popup = Popup::new("hover", contents);
+ if let Some(doc_popup) = compositor.find_id("hover") {
*doc_popup = popup;
} else {
compositor.push(Box::new(popup));
@@ -5490,7 +5551,7 @@ fn expand_selection(cx: &mut Context) {
// save current selection so it can be restored using shrink_selection
view.object_selections.push(current_selection.clone());
- let selection = object::expand_selection(syntax, text, current_selection);
+ let selection = object::expand_selection(syntax, text, current_selection.clone());
doc.set_selection(view.id, selection);
}
};
@@ -5516,7 +5577,26 @@ fn shrink_selection(cx: &mut Context) {
// if not previous selection, shrink to first child
if let Some(syntax) = doc.syntax() {
let text = doc.text().slice(..);
- let selection = object::shrink_selection(syntax, text, current_selection);
+ let selection = object::shrink_selection(syntax, text, current_selection.clone());
+ doc.set_selection(view.id, selection);
+ }
+ };
+ motion(cx.editor);
+ cx.editor.last_motion = Some(Motion(Box::new(motion)));
+}
+
+fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F)
+where
+ F: Fn(Node) -> Option<Node>,
+{
+ let motion = |editor: &mut Editor| {
+ let (view, doc) = current!(editor);
+
+ if let Some(syntax) = doc.syntax() {
+ let text = doc.text().slice(..);
+ let current_selection = doc.selection(view.id);
+ let selection =
+ object::select_sibling(syntax, text, current_selection.clone(), sibling_fn);
doc.set_selection(view.id, selection);
}
};
@@ -5524,6 +5604,14 @@ fn shrink_selection(cx: &mut Context) {
cx.editor.last_motion = Some(Motion(Box::new(motion)));
}
+fn select_next_sibling(cx: &mut Context) {
+ select_sibling_impl(cx, &|node| Node::next_sibling(&node))
+}
+
+fn select_prev_sibling(cx: &mut Context) {
+ select_sibling_impl(cx, &|node| Node::prev_sibling(&node))
+}
+
fn match_brackets(cx: &mut Context) {
let (view, doc) = current!(cx.editor);
diff --git a/helix-term/src/job.rs b/helix-term/src/job.rs
index 4fa38174..f5a0a425 100644
--- a/helix-term/src/job.rs
+++ b/helix-term/src/job.rs
@@ -22,8 +22,8 @@ pub struct Jobs {
}
impl Job {
- pub fn new<F: Future<Output = anyhow::Result<()>> + Send + 'static>(f: F) -> Job {
- Job {
+ pub fn new<F: Future<Output = anyhow::Result<()>> + Send + 'static>(f: F) -> Self {
+ Self {
future: f.map(|r| r.map(|()| None)).boxed(),
wait: false,
}
@@ -31,22 +31,22 @@ impl Job {
pub fn with_callback<F: Future<Output = anyhow::Result<Callback>> + Send + 'static>(
f: F,
- ) -> Job {
- Job {
+ ) -> Self {
+ Self {
future: f.map(|r| r.map(Some)).boxed(),
wait: false,
}
}
- pub fn wait_before_exiting(mut self) -> Job {
+ pub fn wait_before_exiting(mut self) -> Self {
self.wait = true;
self
}
}
impl Jobs {
- pub fn new() -> Jobs {
- Jobs::default()
+ pub fn new() -> Self {
+ Self::default()
}
pub fn spawn<F: Future<Output = anyhow::Result<()>> + Send + 'static>(&mut self, f: F) {
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 49f8469a..e5990d72 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -344,7 +344,7 @@ pub struct Keymap {
impl Keymap {
pub fn new(root: KeyTrie) -> Self {
- Keymap {
+ Self {
root,
state: Vec::new(),
sticky: None,
@@ -368,7 +368,7 @@ impl Keymap {
/// key cancels pending keystrokes. If there are no pending keystrokes but a
/// sticky node is in use, it will be cleared.
pub fn get(&mut self, key: KeyEvent) -> KeymapResult {
- if let key!(Esc) = key {
+ if key!(Esc) == key {
if !self.state.is_empty() {
return KeymapResult::new(
// Note that Esc is not included here
@@ -477,7 +477,7 @@ impl DerefMut for Keymaps {
}
impl Default for Keymaps {
- fn default() -> Keymaps {
+ fn default() -> Self {
let normal = keymap!({ "Normal mode"
"h" | "left" => move_char_left,
"j" | "down" => move_line_down,
@@ -552,6 +552,11 @@ impl Default for Keymaps {
"S" => split_selection,
";" => collapse_selection,
"A-;" => flip_selections,
+ "A-k" => expand_selection,
+ "A-j" => shrink_selection,
+ "A-h" => select_prev_sibling,
+ "A-l" => select_next_sibling,
+
"%" => select_all,
"x" => extend_line,
"X" => extend_to_line_bounds,
@@ -569,13 +574,11 @@ impl Default for Keymaps {
"d" => goto_prev_diag,
"D" => goto_first_diag,
"space" => add_newline_above,
- "o" => shrink_selection,
},
"]" => { "Right bracket"
"d" => goto_next_diag,
"D" => goto_last_diag,
"space" => add_newline_below,
- "o" => expand_selection,
},
"/" => search,
@@ -751,8 +754,10 @@ impl Default for Keymaps {
"del" => delete_char_forward,
"C-d" => delete_char_forward,
"ret" => insert_newline,
+ "C-j" => insert_newline,
"tab" => insert_tab,
"C-w" => delete_word_backward,
+ "A-backspace" => delete_word_backward,
"A-d" => delete_word_forward,
"left" => move_char_left,
@@ -767,6 +772,8 @@ impl Default for Keymaps {
"A-left" => move_prev_word_end,
"A-f" => move_next_word_start,
"A-right" => move_next_word_start,
+ "A-<" => goto_file_start,
+ "A->" => goto_file_end,
"pageup" => page_up,
"pagedown" => page_down,
"home" => goto_line_start,
@@ -780,7 +787,7 @@ impl Default for Keymaps {
"C-x" => completion,
"C-r" => insert_register,
});
- Keymaps(hashmap!(
+ Self(hashmap!(
Mode::Normal => Keymap::new(normal),
Mode::Select => Keymap::new(select),
Mode::Insert => Keymap::new(insert),
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 274330c0..c9ed3b4a 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -158,7 +158,7 @@ impl Completion {
let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
None
} else {
- Completion::resolve_completion_item(doc, item.clone())
+ Self::resolve_completion_item(doc, item.clone())
.and_then(|item| item.additional_text_edits)
};
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs
index 5b7e9075..040e746d 100644
--- a/helix-term/src/ui/editor.rs
+++ b/helix-term/src/ui/editor.rs
@@ -8,7 +8,9 @@ use crate::{
use helix_core::{
coords_at_pos, encoding,
- graphemes::{ensure_grapheme_boundary_next, next_grapheme_boundary, prev_grapheme_boundary},
+ graphemes::{
+ ensure_grapheme_boundary_next_byte, next_grapheme_boundary, prev_grapheme_boundary,
+ },
movement::Direction,
syntax::{self, HighlightEvent},
unicode::segmentation::UnicodeSegmentation,
@@ -69,13 +71,12 @@ impl EditorView {
surface: &mut Surface,
theme: &Theme,
is_focused: bool,
- loader: &syntax::Loader,
config: &helix_view::editor::Config,
) {
let inner = view.inner_area();
let area = view.area;
- let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme, loader);
+ let highlights = Self::doc_syntax_highlights(doc, view.offset, inner.height, theme);
let highlights = syntax::merge(highlights, Self::doc_diagnostics_highlights(doc, theme));
let highlights: Box<dyn Iterator<Item = HighlightEvent>> = if is_focused {
Box::new(syntax::merge(
@@ -98,8 +99,7 @@ impl EditorView {
let x = area.right();
let border_style = theme.get("ui.window");
for y in area.top()..area.bottom() {
- surface
- .get_mut(x, y)
+ surface[(x, y)]
.set_symbol(tui::symbols::line::VERTICAL)
//.set_symbol(" ")
.set_style(border_style);
@@ -123,8 +123,7 @@ impl EditorView {
doc: &'doc Document,
offset: Position,
height: u16,
- theme: &Theme,
- loader: &syntax::Loader,
+ _theme: &Theme,
) -> Box<dyn Iterator<Item = HighlightEvent> + 'doc> {
let text = doc.text().slice(..);
let last_line = std::cmp::min(
@@ -144,25 +143,8 @@ impl EditorView {
// TODO: range doesn't actually restrict source, just highlight range
let highlights = match doc.syntax() {
Some(syntax) => {
- let scopes = theme.scopes();
syntax
- .highlight_iter(text.slice(..), Some(range), None, |language| {
- loader.language_configuration_for_injection_string(language)
- .and_then(|language_config| {
- let config = language_config.highlight_config(scopes)?;
- let config_ref = config.as_ref();
- // SAFETY: the referenced `HighlightConfiguration` behind
- // the `Arc` is guaranteed to remain valid throughout the
- // duration of the highlight.
- let config_ref = unsafe {
- std::mem::transmute::<
- _,
- &'static syntax::HighlightConfiguration,
- >(config_ref)
- };
- Some(config_ref)
- })
- })
+ .highlight_iter(text.slice(..), Some(range), None)
.map(|event| event.unwrap())
.collect() // TODO: we collect here to avoid holding the lock, fix later
}
@@ -175,8 +157,8 @@ impl EditorView {
.map(move |event| match event {
// convert byte offsets to char offset
HighlightEvent::Source { start, end } => {
- let start = ensure_grapheme_boundary_next(text, text.byte_to_char(start));
- let end = ensure_grapheme_boundary_next(text, text.byte_to_char(end));
+ let start = text.byte_to_char(ensure_grapheme_boundary_next_byte(text, start));
+ let end = text.byte_to_char(ensure_grapheme_boundary_next_byte(text, end));
HighlightEvent::Source { start, end }
}
event => event,
@@ -306,6 +288,10 @@ impl EditorView {
let text_style = theme.get("ui.text");
+ // It's slightly more efficient to produce a full RopeSlice from the Rope, then slice that a bunch
+ // of times than it is to always call Rope::slice/get_slice (it will internally always hit RSEnum::Light).
+ let text = text.slice(..);
+
'outer: for event in highlights {
match event {
HighlightEvent::HighlightStart(span) => {
@@ -411,8 +397,7 @@ impl EditorView {
.add_modifier(Modifier::DIM)
});
- surface
- .get_mut(viewport.x + pos.col as u16, viewport.y + pos.row as u16)
+ surface[(viewport.x + pos.col as u16, viewport.y + pos.row as u16)]
.set_style(style);
}
}
@@ -1090,7 +1075,6 @@ impl Component for EditorView {
for (view, is_focused) in cx.editor.tree.views() {
let doc = cx.editor.document(view.doc).unwrap();
- let loader = &cx.editor.syn_loader;
self.render_view(
doc,
view,
@@ -1098,7 +1082,6 @@ impl Component for EditorView {
surface,
&cx.editor.theme,
is_focused,
- loader,
&cx.editor.config,
);
}
diff --git a/helix-term/src/ui/markdown.rs b/helix-term/src/ui/markdown.rs
index 46657fb9..00da2c11 100644
--- a/helix-term/src/ui/markdown.rs
+++ b/helix-term/src/ui/markdown.rs
@@ -38,7 +38,7 @@ impl Markdown {
fn parse<'a>(
contents: &'a str,
theme: Option<&Theme>,
- loader: &syntax::Loader,
+ loader: Arc<syntax::Loader>,
) -> tui::text::Text<'a> {
// // also 2021-03-04T16:33:58.553 helix_lsp::transport [INFO] <- {"contents":{"kind":"markdown","value":"\n```rust\ncore::num\n```\n\n```rust\npub const fn saturating_sub(self, rhs:Self) ->Self\n```\n\n---\n\n```rust\n```"},"range":{"end":{"character":61,"line":101},"start":{"character":47,"line":101}}}
// let text = "\n```rust\ncore::iter::traits::iterator::Iterator\n```\n\n```rust\nfn collect<B: FromIterator<Self::Item>>(self) -> B\nwhere\n Self: Sized,\n```\n\n---\n\nTransforms an iterator into a collection.\n\n`collect()` can take anything iterable, and turn it into a relevant\ncollection. This is one of the more powerful methods in the standard\nlibrary, used in a variety of contexts.\n\nThe most basic pattern in which `collect()` is used is to turn one\ncollection into another. You take a collection, call [`iter`](https://doc.rust-lang.org/nightly/core/iter/traits/iterator/trait.Iterator.html) on it,\ndo a bunch of transformations, and then `collect()` at the end.\n\n`collect()` can also create instances of types that are not typical\ncollections. For example, a [`String`](https://doc.rust-lang.org/nightly/core/iter/std/string/struct.String.html) can be built from [`char`](type@char)s,\nand an iterator of [`Result<T, E>`](https://doc.rust-lang.org/nightly/core/result/enum.Result.html) items can be collected\ninto `Result<Collection<T>, E>`. See the examples below for more.\n\nBecause `collect()` is so general, it can cause problems with type\ninference. As such, `collect()` is one of the few times you'll see\nthe syntax affectionately known as the 'turbofish': `::<>`. This\nhelps the inference algorithm understand specifically which collection\nyou're trying to collect into.\n\n# Examples\n\nBasic usage:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled: Vec<i32> = a.iter()\n .map(|&x| x * 2)\n .collect();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nNote that we needed the `: Vec<i32>` on the left-hand side. This is because\nwe could collect into, for example, a [`VecDeque<T>`](https://doc.rust-lang.org/nightly/core/iter/std/collections/struct.VecDeque.html) instead:\n\n```rust\nuse std::collections::VecDeque;\n\nlet a = [1, 2, 3];\n\nlet doubled: VecDeque<i32> = a.iter().map(|&x| x * 2).collect();\n\nassert_eq!(2, doubled[0]);\nassert_eq!(4, doubled[1]);\nassert_eq!(6, doubled[2]);\n```\n\nUsing the 'turbofish' instead of annotating `doubled`:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled = a.iter().map(|x| x * 2).collect::<Vec<i32>>();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nBecause `collect()` only cares about what you're collecting into, you can\nstill use a partial type hint, `_`, with the turbofish:\n\n```rust\nlet a = [1, 2, 3];\n\nlet doubled = a.iter().map(|x| x * 2).collect::<Vec<_>>();\n\nassert_eq!(vec![2, 4, 6], doubled);\n```\n\nUsing `collect()` to make a [`String`](https://doc.rust-lang.org/nightly/core/iter/std/string/struct.String.html):\n\n```rust\nlet chars = ['g', 'd', 'k', 'k', 'n'];\n\nlet hello: String = chars.iter()\n .map(|&x| x as u8)\n .map(|x| (x + 1) as char)\n .collect();\n\nassert_eq!(\"hello\", hello);\n```\n\nIf you have a list of [`Result<T, E>`](https://doc.rust-lang.org/nightly/core/result/enum.Result.html)s, you can use `collect()` to\nsee if any of them failed:\n\n```rust\nlet results = [Ok(1), Err(\"nope\"), Ok(3), Err(\"bad\")];\n\nlet result: Result<Vec<_>, &str> = results.iter().cloned().collect();\n\n// gives us the first error\nassert_eq!(Err(\"nope\"), result);\n\nlet results = [Ok(1), Ok(3)];\n\nlet result: Result<Vec<_>, &str> = results.iter().cloned().collect();\n\n// gives us the list of answers\nassert_eq!(Ok(vec![1, 3]), result);\n```";
@@ -77,7 +77,9 @@ fn parse<'a>(
Event::End(tag) => {
tags.pop();
match tag {
- Tag::Heading(_) | Tag::Paragraph | Tag::CodeBlock(CodeBlockKind::Fenced(_)) => {
+ Tag::Heading(_, _, _)
+ | Tag::Paragraph
+ | Tag::CodeBlock(CodeBlockKind::Fenced(_)) => {
// whenever code block or paragraph closes, new line
let spans = std::mem::take(&mut spans);
if !spans.is_empty() {
@@ -96,14 +98,13 @@ fn parse<'a>(
let syntax = loader
.language_configuration_for_injection_string(language)
.and_then(|config| config.highlight_config(theme.scopes()))
- .map(|config| Syntax::new(&rope, config));
+ .map(|config| Syntax::new(&rope, config, loader.clone()));
if let Some(syntax) = syntax {
// if we have a syntax available, highlight_iter and generate spans
let mut highlights = Vec::new();
- for event in syntax.highlight_iter(rope.slice(..), None, None, |_| None)
- {
+ for event in syntax.highlight_iter(rope.slice(..), None, None) {
match event.unwrap() {
HighlightEvent::HighlightStart(span) => {
highlights.push(span);
@@ -158,7 +159,7 @@ fn parse<'a>(
lines.push(Spans::from(span));
}
}
- } else if let Some(Tag::Heading(_)) = tags.last() {
+ } else if let Some(Tag::Heading(_, _, _)) = tags.last() {
let mut span = to_span(text);
span.style = heading_style;
spans.push(span);
@@ -209,7 +210,11 @@ impl Component for Markdown {
fn render(&mut self, area: Rect, surface: &mut Surface, cx: &mut Context) {
use tui::widgets::{Paragraph, Widget, Wrap};
- let text = parse(&self.contents, Some(&cx.editor.theme), &self.config_loader);
+ let text = parse(
+ &self.contents,
+ Some(&cx.editor.theme),
+ self.config_loader.clone(),
+ );
let par = Paragraph::new(text)
.wrap(Wrap { trim: false })
@@ -227,7 +232,7 @@ impl Component for Markdown {
if padding >= viewport.1 || padding >= viewport.0 {
return None;
}
- let contents = parse(&self.contents, None, &self.config_loader);
+ let contents = parse(&self.contents, None, self.config_loader.clone());
// TODO: account for tab width
let max_text_width = (viewport.0 - padding).min(120);
let mut text_width = 0;
diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs
index 69053db3..9758732c 100644
--- a/helix-term/src/ui/menu.rs
+++ b/helix-term/src/ui/menu.rs
@@ -301,7 +301,7 @@ impl<T: Item + 'static> Component for Menu<T> {
let is_marked = i >= scroll_line && i < scroll_line + scroll_height;
if is_marked {
- let cell = surface.get_mut(area.x + area.width - 2, area.y + i as u16);
+ let cell = &mut surface[(area.x + area.width - 2, area.y + i as u16)];
cell.set_symbol("▐ ");
// cell.set_style(selected);
// cell.set_style(if is_marked { selected } else { style });
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index 1ef94df0..e9692809 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -139,7 +139,7 @@ impl<T> FilePicker<T> {
(size, _) if size > MAX_FILE_SIZE_FOR_PREVIEW => CachedPreview::LargeFile,
_ => {
// TODO: enable syntax highlighting; blocked by async rendering
- Document::open(path, None, Some(&editor.theme), None)
+ Document::open(path, None, None)
.map(|doc| CachedPreview::Document(Box::new(doc)))
.unwrap_or(CachedPreview::NotFound)
}
@@ -159,6 +159,7 @@ impl<T: 'static> Component for FilePicker<T> {
// |picker | | |
// | | | |
// +---------+ +---------+
+
let render_preview = area.width > MIN_SCREEN_WIDTH_FOR_PREVIEW;
let area = inner_rect(area);
// -- Render the frame:
@@ -220,13 +221,8 @@ impl<T: 'static> Component for FilePicker<T> {
let offset = Position::new(first_line, 0);
- let highlights = EditorView::doc_syntax_highlights(
- doc,
- offset,
- area.height,
- &cx.editor.theme,
- &cx.editor.syn_loader,
- );
+ let highlights =
+ EditorView::doc_syntax_highlights(doc, offset, area.height, &cx.editor.theme);
EditorView::render_text_highlights(
doc,
offset,
@@ -492,10 +488,9 @@ impl<T: 'static> Component for Picker<T> {
let sep_style = Style::default().fg(Color::Rgb(90, 89, 119));
let borders = BorderType::line_symbols(BorderType::Plain);
for x in inner.left()..inner.right() {
- surface
- .get_mut(x, inner.y + 1)
- .set_symbol(borders.horizontal)
- .set_style(sep_style);
+ if let Some(cell) = surface.get_mut(x, inner.y + 1) {
+ cell.set_symbol(borders.horizontal).set_style(sep_style);
+ }
}
// -- Render the contents:
@@ -505,7 +500,7 @@ impl<T: 'static> Component for Picker<T> {
let selected = cx.editor.theme.get("ui.text.focus");
let rows = inner.height;
- let offset = self.cursor / (rows as usize) * (rows as usize);
+ let offset = self.cursor / std::cmp::max(1, (rows as usize) * (rows as usize));
let files = self.matches.iter().skip(offset).map(|(index, _score)| {
(index, self.options.get(*index).unwrap()) // get_unchecked
@@ -513,7 +508,7 @@ impl<T: 'static> Component for Picker<T> {
for (i, (_index, option)) in files.take(rows as usize).enumerate() {
if i == (self.cursor - offset) {
- surface.set_string(inner.x - 2, inner.y + i as u16, ">", selected);
+ surface.set_string(inner.x.saturating_sub(2), inner.y + i as u16, ">", selected);
}
surface.set_string_truncated(
diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs
index 0202de23..4c4fef26 100644
--- a/helix-term/src/ui/prompt.rs
+++ b/helix-term/src/ui/prompt.rs
@@ -330,7 +330,7 @@ impl Prompt {
.max(BASE_WIDTH);
let cols = std::cmp::max(1, area.width / max_len);
- let col_width = (area.width - (cols)) / cols;
+ let col_width = (area.width.saturating_sub(cols)) / cols;
let height = ((self.completion.len() as u16 + cols - 1) / cols)
.min(10) // at most 10 rows (or less)