diff --git a/book/src/configuration.md b/book/src/configuration.md index c209dc3de249..4eab4a48aa78 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -62,7 +62,7 @@ Statusline elements can be defined as follows: [editor.statusline] left = ["mode", "spinner"] center = ["file-name"] -right = ["diagnostics", "selections", "position", "file-encoding", "file-type"] +right = ["diagnostics", "selections", "position", "file-encoding", "file-line-ending", "file-type"] ``` The following elements can be configured: @@ -73,6 +73,7 @@ The following elements can be configured: | `spinner` | A progress spinner indicating LSP activity | | `file-name` | The path/name of the opened file | | `file-encoding` | The encoding of the opened file if it differs from UTF-8 | +| `file-line-ending` | The file line endings (CRLF or LF) | | `file-type` | The type of the opened file | | `diagnostics` | The number of warnings and/or errors | | `selections` | The number of active selections | diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index aecf80acb305..21371c9334f1 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -30,7 +30,7 @@ | git-diff | ✓ | | | | | git-ignore | ✓ | | | | | git-rebase | ✓ | | | | -| gleam | ✓ | ✓ | | | +| gleam | ✓ | ✓ | | `gleam` | | glsl | ✓ | ✓ | ✓ | | | go | ✓ | ✓ | ✓ | `gopls` | | gomod | ✓ | | | `gopls` | diff --git a/book/src/keymap.md b/book/src/keymap.md index abb6f3022d59..9acbd3b6b49c 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -238,6 +238,7 @@ This layer is a kludge of mappings, mostly pickers. | ----- | ----------- | ------- | | `f` | Open file picker | `file_picker` | | `b` | Open buffer picker | `buffer_picker` | +| `j` | Open jumplist picker | `jumplist_picker` | | `k` | Show documentation for item under cursor in a [popup](#popup) (**LSP**) | `hover` | | `s` | Open document symbol picker (**LSP**) | `symbol_picker` | | `S` | Open workspace symbol picker (**LSP**) | `workspace_symbol_picker` | diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 000000000000..0608a201c048 --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,59 @@ +## Checklist + +Helix releases are versioned in the Calendar Versioning scheme: +`YY.0M(.MICRO)`, for example `22.05` for May of 2022. In these instructions +we'll use `` as a placeholder for the tag being published. + +* Merge the changelog PR +* Tag and push + * `git tag -s -m "" -a && git push` + * Make sure to switch to master and pull first +* Edit the `VERSION` file and change the date to the next planned release + * Releases are planned to happen every two months, so `22.05` would change to `22.07` +* Wait for the Release CI to finish + * It will automatically turn the git tag into a GitHub release when it uploads artifacts +* Edit the new release + * Use `` as the title + * Link to the changelog and release notes +* Merge the release notes PR +* Download the macos and linux binaries and update the `sha256`s in the [homebrew formula] + * Use `sha256sum` on the downloaded `.tar.xz` files to determine the hash +* Link to the release notes in this-week-in-rust + * [Example PR](https://github.com/rust-lang/this-week-in-rust/pull/3300) +* Post to reddit + * [Example post](https://www.reddit.com/r/rust/comments/uzp5ze/helix_editor_2205_released/) + +[homebrew formula]: https://github.com/helix-editor/homebrew-helix/blob/master/Formula/helix.rb + +## Changelog Curation + +The changelog is currently created manually by reading through commits in the +log since the last release. GitHub's compare view is a nice way to approach +this. For example when creating the 22.07 release notes, this compare link +may be used + +``` +https://github.com/helix-editor/helix/compare/22.05...master +``` + +Either side of the triple-dot may be replaced with an exact revision, so if +you wish to incrementally compile the changelog, you can tackle a weeks worth +or so, record the revision where you stopped, and use that as a starting point +next week: + +``` +https://github.com/helix-editor/helix/compare/7706a4a0d8b67b943c31d0c5f7b00d357b5d838d...master +``` + +A work-in-progress commit for a changelog might look like +[this example](https://github.com/helix-editor/helix/commit/831adfd4c709ca16b248799bfef19698d5175e55). + +Not every PR or commit needs a blurb in the changelog. Each release section +tends to have a blurb that links to a GitHub comparison between release +versions for convenience: + +> As usual, the following is a summary of each of the changes since the last +> release. For the full log, check out the git log. + +Typically, small changes like dependencies or documentation updates, refactors, +or meta changes like GitHub Actions work are left out. \ No newline at end of file diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 8d7520c3d131..9011f835b907 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -328,29 +328,15 @@ fn read_query(language: &str, filename: &str) -> String { let query = load_runtime_file(language, filename).unwrap_or_default(); - // TODO: the collect() is not ideal - let inherits = INHERITS_REGEX - .captures_iter(&query) - .flat_map(|captures| { + // replaces all "; inherits (,)*" with the queries of the given language(s) + INHERITS_REGEX + .replace_all(&query, |captures: ®ex::Captures| { captures[1] .split(',') - .map(str::to_owned) - .collect::>() + .map(|language| format!("\n{}\n", read_query(language, filename))) + .collect::() }) - .collect::>(); - - if inherits.is_empty() { - return query; - } - - let mut queries = inherits - .iter() - .map(|language| read_query(language, filename)) - .collect::>(); - - queries.push(query); - - queries.concat() + .to_string() } impl LanguageConfiguration { diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 3ee75f6a392f..bd1bd8945029 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -264,6 +264,7 @@ impl MappableCommand { file_picker_in_current_directory, "Open file picker at current working directory", code_action, "Perform code action", buffer_picker, "Open buffer picker", + jumplist_picker, "Open jumplist picker", symbol_picker, "Open symbol picker", select_references_to_symbol_under_cursor, "Select symbol references", workspace_symbol_picker, "Open workspace symbol picker", @@ -2270,6 +2271,87 @@ fn buffer_picker(cx: &mut Context) { cx.push_layer(Box::new(overlayed(picker))); } +fn jumplist_picker(cx: &mut Context) { + struct JumpMeta { + id: DocumentId, + path: Option, + selection: Selection, + text: String, + is_current: bool, + } + + impl ui::menu::Item for JumpMeta { + type Data = (); + + fn label(&self, _data: &Self::Data) -> Spans { + let path = self + .path + .as_deref() + .map(helix_core::path::get_relative_path); + let path = match path.as_deref().and_then(Path::to_str) { + Some(path) => path, + None => SCRATCH_BUFFER_NAME, + }; + + let mut flags = Vec::new(); + if self.is_current { + flags.push("*"); + } + + let flag = if flags.is_empty() { + "".into() + } else { + format!(" ({})", flags.join("")) + }; + format!("{} {}{} {}", self.id, path, flag, self.text).into() + } + } + + let new_meta = |view: &View, doc_id: DocumentId, selection: Selection| { + let doc = &cx.editor.documents.get(&doc_id); + let text = doc.map_or("".into(), |d| { + selection + .fragments(d.text().slice(..)) + .map(Cow::into_owned) + .collect::>() + .join(" ") + }); + + JumpMeta { + id: doc_id, + path: doc.and_then(|d| d.path().cloned()), + selection, + text, + is_current: view.doc == doc_id, + } + }; + + let picker = FilePicker::new( + cx.editor + .tree + .views() + .flat_map(|(view, _)| { + view.jumps + .get() + .iter() + .map(|(doc_id, selection)| new_meta(view, *doc_id, selection.clone())) + }) + .collect(), + (), + |cx, meta, action| { + cx.editor.switch(meta.id, action); + let (view, doc) = current!(cx.editor); + doc.set_selection(view.id, meta.selection.clone()); + }, + |editor, meta| { + let doc = &editor.documents.get(&meta.id)?; + let line = meta.selection.primary().cursor_line(doc.text().slice(..)); + Some((meta.path.clone()?, Some((line, line)))) + }, + ); + cx.push_layer(Box::new(overlayed(picker))); +} + impl ui::menu::Item for MappableCommand { type Data = ReverseKeymap; @@ -2974,14 +3056,18 @@ pub mod insert { pub fn delete_char_backward(cx: &mut Context) { let count = cx.count(); - let (view, doc) = current!(cx.editor); + let (view, doc) = current_ref!(cx.editor); let text = doc.text().slice(..); let indent_unit = doc.indent_unit(); let tab_size = doc.tab_width(); + let auto_pairs = doc.auto_pairs(cx.editor); let transaction = Transaction::change_by_selection(doc.text(), doc.selection(view.id), |range| { let pos = range.cursor(text); + if pos == 0 { + return (pos, pos, None); + } let line_start_pos = text.line_to_char(range.cursor_line(text)); // consider to delete by indent level if all characters before `pos` are indent units. let fragment = Cow::from(text.slice(line_start_pos..pos)); @@ -3029,14 +3115,36 @@ pub mod insert { (start, pos, None) // delete! } } else { - // delete char - ( - graphemes::nth_prev_grapheme_boundary(text, pos, count), - pos, - None, - ) + match ( + text.get_char(pos.saturating_sub(1)), + text.get_char(pos), + auto_pairs, + ) { + (Some(_x), Some(_y), Some(ap)) + if range.is_single_grapheme(text) + && ap.get(_x).is_some() + && ap.get(_x).unwrap().close == _y => + // delete both autopaired characters + { + ( + graphemes::nth_prev_grapheme_boundary(text, pos, count), + graphemes::nth_next_grapheme_boundary(text, pos, count), + None, + ) + } + _ => + // delete 1 char + { + ( + graphemes::nth_prev_grapheme_boundary(text, pos, count), + pos, + None, + ) + } + } } }); + let (view, doc) = current!(cx.editor); doc.apply(&transaction, view.id); lsp::signature_help_impl(cx, SignatureHelpInvoked::Automatic); @@ -4001,13 +4109,11 @@ fn split(cx: &mut Context, action: Action) { let (view, doc) = current!(cx.editor); let id = doc.id(); let selection = doc.selection(view.id).clone(); - let offset = view.offset; cx.editor.switch(id, action); // match the selection in the previous view let (view, doc) = current!(cx.editor); - view.offset = offset; doc.set_selection(view.id, selection); } diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index fb03af44c9b9..d6db117e67e4 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -413,12 +413,11 @@ fn set_line_ending( // Attempt to parse argument as a line ending. let line_ending = match arg { - // We check for CR first because it shares a common prefix with CRLF. - #[cfg(feature = "unicode-lines")] - arg if arg.starts_with("cr") => CR, arg if arg.starts_with("crlf") => Crlf, arg if arg.starts_with("lf") => LF, #[cfg(feature = "unicode-lines")] + arg if arg.starts_with("cr") => CR, + #[cfg(feature = "unicode-lines")] arg if arg.starts_with("ff") => FF, #[cfg(feature = "unicode-lines")] arg if arg.starts_with("nel") => Nel, diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index b8c1a4007f23..f6fb6140b6d3 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -205,6 +205,7 @@ pub fn default() -> HashMap { "f" => file_picker, "F" => file_picker_in_current_directory, "b" => buffer_picker, + "j" => jumplist_picker, "s" => symbol_picker, "S" => workspace_symbol_picker, "g" => diagnostics_picker, diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 895043cd1154..85992c602164 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -138,6 +138,7 @@ where helix_view::editor::StatusLineElement::Spinner => render_lsp_spinner, helix_view::editor::StatusLineElement::FileName => render_file_name, helix_view::editor::StatusLineElement::FileEncoding => render_file_encoding, + helix_view::editor::StatusLineElement::FileLineEnding => render_file_line_ending, helix_view::editor::StatusLineElement::FileType => render_file_type, helix_view::editor::StatusLineElement::Diagnostics => render_diagnostics, helix_view::editor::StatusLineElement::Selections => render_selections, @@ -280,6 +281,31 @@ where } } +fn render_file_line_ending(context: &mut RenderContext, write: F) +where + F: Fn(&mut RenderContext, String, Option