diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 00000000..1c8e8a5a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,13 @@ +--- +name: Enhancement +about: Suggest an improvement +title: '' +labels: C-enhancement +assignees: '' +--- + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 9a88cb4a..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -name: Feature request -about: Suggest a new feature or improvement -title: '' -labels: C-enhancement -assignees: '' ---- - - - -#### Describe your feature request - diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b24cdb8c..2a1950df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -131,3 +131,23 @@ jobs: || (echo "Run 'cargo xtask docgen', commit the changes and push again" \ && exit 1) + queries: + name: Tree-sitter queries + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Install stable toolchain + uses: helix-editor/rust-toolchain@v1 + with: + profile: minimal + override: true + + - uses: Swatinem/rust-cache@v1 + + - name: Generate docs + uses: actions-rs/cargo@v1 + with: + command: xtask + args: query-check diff --git a/CHANGELOG.md b/CHANGELOG.md index 693a2a5f..73d37a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,272 @@ +# 22.08.1 (2022-09-01) + +This is a patch release that fixes a panic caused by closing splits or buffers. ([#3633](https://github.com/helix-editor/helix/pull/3633)) + +# 22.08 (2022-08-31) + +A big _thank you_ to our contributors! This release had 87 contributors. + +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](https://github.com/helix-editor/helix/compare/22.05..22.08). + +Breaking changes: + +- Special keymap names for `+`, `;` and `%` have been replaced with those literal characters ([#2677](https://github.com/helix-editor/helix/pull/2677), [#3556](https://github.com/helix-editor/helix/pull/3556)) +- `A-Left` and `A-Right` have become `C-Left` and `C-Right` for word-wise motion ([#2500](https://github.com/helix-editor/helix/pull/2500)) +- The `catppuccin` theme's name has been corrected from `catpuccin` ([#2713](https://github.com/helix-editor/helix/pull/2713)) +- `catppuccin` has been replaced by its variants, `catppuccin_frappe`, `catppuccin_latte`, `catppuccin_macchiato`, `catppuccin_mocha` ([#3281](https://github.com/helix-editor/helix/pull/3281)) +- `C-n` and `C-p` have been removed from the default insert mode keymap ([#3340](https://github.com/helix-editor/helix/pull/3340)) +- The `extend_line` command has been replaced with `extend_line_below` and a new `extend_line` command now exists ([#3046](https://github.com/helix-editor/helix/pull/3046)) + +Features: + +- Add an integration testing harness ([#2359](https://github.com/helix-editor/helix/pull/2359)) +- Indent guides ([#1796](https://github.com/helix-editor/helix/pull/1796), [906259c](https://github.com/helix-editor/helix/commit/906259c)) +- Cursorline ([#2170](https://github.com/helix-editor/helix/pull/2170), [fde9e03](https://github.com/helix-editor/helix/commit/fde9e03)) +- Select all instances of the symbol under the cursor (`h`) ([#2738](https://github.com/helix-editor/helix/pull/2738)) +- A picker for document and workspace LSP diagnostics (`g`/`G`) ([#2013](https://github.com/helix-editor/helix/pull/2013), [#2984](https://github.com/helix-editor/helix/pull/2984)) +- Allow styling the mode indicator per-mode ([#2676](https://github.com/helix-editor/helix/pull/2676)) +- Live preview for the theme picker ([#1798](https://github.com/helix-editor/helix/pull/1798)) +- Configurable statusline ([#2434](https://github.com/helix-editor/helix/pull/2434)) +- LSP SignatureHelp ([#1755](https://github.com/helix-editor/helix/pull/1755), [a8b123f](https://github.com/helix-editor/helix/commit/a8b123f)) +- A picker for the jumplist ([#3033](https://github.com/helix-editor/helix/pull/3033)) +- Configurable external formatter binaries ([#2942](https://github.com/helix-editor/helix/pull/2942)) +- Bracketed paste support ([#3233](https://github.com/helix-editor/helix/pull/3233), [12ddd03](https://github.com/helix-editor/helix/commit/12ddd03)) + +Commands: + +- `:insert-output` and `:append-output` which insert/append output from a shell command ([#2589](https://github.com/helix-editor/helix/pull/2589)) +- The `t` textobject (`]t`/`[t`/`mit`/`mat`) for navigating tests ([#2807](https://github.com/helix-editor/helix/pull/2807)) +- `C-Backspace` and `C-Delete` for word-wise deletion in prompts and pickers ([#2500](https://github.com/helix-editor/helix/pull/2500)) +- `A-Delete` for forward word-wise deletion in insert mode ([#2500](https://github.com/helix-editor/helix/pull/2500)) +- `C-t` for toggling the preview pane in pickers ([#3021](https://github.com/helix-editor/helix/pull/3021)) +- `extend_line` now extends in the direction of the cursor ([#3046](https://github.com/helix-editor/helix/pull/3046)) + +Usability improvements and fixes: + +- Fix tree-sitter parser builds on illumos ([#2602](https://github.com/helix-editor/helix/pull/2602)) +- Remove empty stratch buffer from jumplists when removing ([5ed6223](https://github.com/helix-editor/helix/commit/5ed6223)) +- Fix panic on undo after `shell_append_output` ([#2625](https://github.com/helix-editor/helix/pull/2625)) +- Sort LSP edits by start range ([3d91c99](https://github.com/helix-editor/helix/commit/3d91c99)) +- Be more defensive about LSP URI conversions ([6de6a3e](https://github.com/helix-editor/helix/commit/6de6a3e), [378f438](https://github.com/helix-editor/helix/commit/378f438)) +- Ignore SendErrors when grammar builds fail ([#2641](https://github.com/helix-editor/helix/pull/2641)) +- Append `set_line_ending` to document history ([#2649](https://github.com/helix-editor/helix/pull/2649)) +- Use last prompt entry when empty ([b14c258](https://github.com/helix-editor/helix/commit/b14c258), [#2870](https://github.com/helix-editor/helix/pull/2870)) +- Do not add extra line breaks in markdown lists ([#2689](https://github.com/helix-editor/helix/pull/2689)) +- Disable dialyzer by default for ElixirLS ([#2710](https://github.com/helix-editor/helix/pull/2710)) +- Refactor textobject node capture ([#2741](https://github.com/helix-editor/helix/pull/2741)) +- Prevent re-selecting the same range with `expand_selection` ([#2760](https://github.com/helix-editor/helix/pull/2760)) +- Introduce `keyword.storage` highlight scope ([#2731](https://github.com/helix-editor/helix/pull/2731)) +- Handle symlinks more consistently ([#2718](https://github.com/helix-editor/helix/pull/2718)) +- Improve markdown list rendering ([#2687](https://github.com/helix-editor/helix/pull/2687)) +- Update auto-pairs and idle-timout settings when the config is reloaded ([#2736](https://github.com/helix-editor/helix/pull/2736)) +- Fix panic on closing last buffer ([#2658](https://github.com/helix-editor/helix/pull/2658)) +- Prevent modifying jumplist until jumping to a reference ([#2670](https://github.com/helix-editor/helix/pull/2670)) +- Ensure `:quit` and `:quit!` take no arguments ([#2654](https://github.com/helix-editor/helix/pull/2654)) +- Fix crash due to cycles when replaying macros ([#2647](https://github.com/helix-editor/helix/pull/2647)) +- Pass LSP FormattingOptions ([#2635](https://github.com/helix-editor/helix/pull/2635)) +- Prevent showing colors when the health-check is piped ([#2836](https://github.com/helix-editor/helix/pull/2836)) +- Use character indexing for mouse selection ([#2839](https://github.com/helix-editor/helix/pull/2839)) +- Display the highest severity diagnostic for a line in the gutter ([#2835](https://github.com/helix-editor/helix/pull/2835)) +- Default the ruler color to red background ([#2669](https://github.com/helix-editor/helix/pull/2669)) +- Make `move_vertically` aware of tabs and wide characters ([#2620](https://github.com/helix-editor/helix/pull/2620)) +- Enable shellwords for Windows ([#2767](https://github.com/helix-editor/helix/pull/2767)) +- Add history suggestions to global search ([#2717](https://github.com/helix-editor/helix/pull/2717)) +- Fix the scrollbar's length proportional to total menu items ([#2860](https://github.com/helix-editor/helix/pull/2860)) +- Reset terminal modifiers for diagnostic text ([#2861](https://github.com/helix-editor/helix/pull/2861), [#2900](https://github.com/helix-editor/helix/pull/2900)) +- Redetect indents and line-endings after a Language Server replaces the document ([#2778](https://github.com/helix-editor/helix/pull/2778)) +- Check selection's visible width when copying on mouse click ([#2711](https://github.com/helix-editor/helix/pull/2711)) +- Fix edge-case in tree-sitter `expand_selection` command ([#2877](https://github.com/helix-editor/helix/pull/2877)) +- Add a single-width left margin for the completion popup ([#2728](https://github.com/helix-editor/helix/pull/2728)) +- Right-align the scrollbar in the completion popup ([#2754](https://github.com/helix-editor/helix/pull/2754)) +- Fix recursive macro crash and empty macro lockout ([#2902](https://github.com/helix-editor/helix/pull/2902)) +- Fix backwards character deletion on other whitespaces ([#2855](https://github.com/helix-editor/helix/pull/2855)) +- Add search and space/backspace bindings to view modes ([#2803](https://github.com/helix-editor/helix/pull/2803)) +- Add `--vsplit` and `--hsplit` CLI arguments for opening in splits ([#2773](https://github.com/helix-editor/helix/pull/2773), [#3073](https://github.com/helix-editor/helix/pull/3073)) +- Sort themes, languages and files inputs by score and name ([#2675](https://github.com/helix-editor/helix/pull/2675)) +- Highlight entire rows in ([#2939](https://github.com/helix-editor/helix/pull/2939)) +- Fix backwards selection duplication widening bug ([#2945](https://github.com/helix-editor/helix/pull/2945), [#3024](https://github.com/helix-editor/helix/pull/3024)) +- Skip serializing Option type DAP fields ([44f5963](https://github.com/helix-editor/helix/commit/44f5963)) +- Fix required `cwd` field in DAP `RunTerminalArguments` type ([85411be](https://github.com/helix-editor/helix/commit/85411be), [#3240](https://github.com/helix-editor/helix/pull/3240)) +- Add LSP `workspace/applyEdit` to client capabilities ([#3012](https://github.com/helix-editor/helix/pull/3012)) +- Respect count for repeating motion ([#3057](https://github.com/helix-editor/helix/pull/3057)) +- Respect count for selecting next/previous match ([#3056](https://github.com/helix-editor/helix/pull/3056)) +- Respect count for tree-sitter motions ([#3058](https://github.com/helix-editor/helix/pull/3058)) +- Make gutters padding optional ([#2996](https://github.com/helix-editor/helix/pull/2996)) +- Support pre-filling prompts ([#2459](https://github.com/helix-editor/helix/pull/2459), [#3259](https://github.com/helix-editor/helix/pull/3259)) +- Add statusline element to display file line-endings ([#3113](https://github.com/helix-editor/helix/pull/3113)) +- Keep jump and file history when using `:split` ([#3031](https://github.com/helix-editor/helix/pull/3031), [#3160](https://github.com/helix-editor/helix/pull/3160)) +- Make tree-sitter query `; inherits ` feature imperative ([#2470](https://github.com/helix-editor/helix/pull/2470)) +- Indent with tabs by default ([#3095](https://github.com/helix-editor/helix/pull/3095)) +- Fix non-msvc grammar compilation on Windows ([#3190](https://github.com/helix-editor/helix/pull/3190)) +- Add spacer element to the statusline ([#3165](https://github.com/helix-editor/helix/pull/3165), [255c173](https://github.com/helix-editor/helix/commit/255c173)) +- Make gutters padding automatic ([#3163](https://github.com/helix-editor/helix/pull/3163)) +- Add `code` for LSP `Diagnostic` type ([#3096](https://github.com/helix-editor/helix/pull/3096)) +- Add position percentage to the statusline ([#3168](https://github.com/helix-editor/helix/pull/3168)) +- Add a configurable and themable statusline separator string ([#3175](https://github.com/helix-editor/helix/pull/3175)) +- Use OR of all selections when `search_selection` acts on multiple selections ([#3138](https://github.com/helix-editor/helix/pull/3138)) +- Add clipboard information to logs and the healthcheck ([#3271](https://github.com/helix-editor/helix/pull/3271)) +- Fix align selection behavior on tabs ([#3276](https://github.com/helix-editor/helix/pull/3276)) +- Fix terminal cursor shape reset ([#3289](https://github.com/helix-editor/helix/pull/3289)) +- Add an `injection.include-unnamed-children` predicate to injections queries ([#3129](https://github.com/helix-editor/helix/pull/3129)) +- Add a `-c`/`--config` CLI flag for specifying config file location ([#2666](https://github.com/helix-editor/helix/pull/2666)) +- Detect indent-style in `:set-language` command ([#3330](https://github.com/helix-editor/helix/pull/3330)) +- Fix non-deterministic highlighting ([#3275](https://github.com/helix-editor/helix/pull/3275)) +- Avoid setting the stdin handle when not necessary ([#3248](https://github.com/helix-editor/helix/pull/3248), [#3379](https://github.com/helix-editor/helix/pull/3379)) +- Fix indent guide styling ([#3324](https://github.com/helix-editor/helix/pull/3324)) +- Fix tab highlight when tab is partially visible ([#3313](https://github.com/helix-editor/helix/pull/3313)) +- Add completion for nested settings ([#3183](https://github.com/helix-editor/helix/pull/3183)) +- Advertise WorkspaceSymbolClientCapabilities LSP client capability ([#3361](https://github.com/helix-editor/helix/pull/3361)) +- Remove duplicate entries from the theme picker ([#3439](https://github.com/helix-editor/helix/pull/3439)) +- Shorted output for grammar fetching and building ([#3396](https://github.com/helix-editor/helix/pull/3396)) +- Add a `tabpad` option for visible tab padding whitespace characters ([#3458](https://github.com/helix-editor/helix/pull/3458)) +- Make DAP external terminal provider configurable ([cb7615e](https://github.com/helix-editor/helix/commit/cb7615e)) +- Use health checkmark character with shorter width ([#3505](https://github.com/helix-editor/helix/pull/3505)) +- Reset document mode to normal on view focus loss ([e4c9d40](https://github.com/helix-editor/helix/commit/e4c9d40)) +- Render indented code-blocks in markdown ([#3503](https://github.com/helix-editor/helix/pull/3503)) +- Add WezTerm to DAP terminal provider defaults ([#3588](https://github.com/helix-editor/helix/pull/3588)) +- Derive `Document` language name from `languages.toml` `name` key ([#3338](https://github.com/helix-editor/helix/pull/3338)) +- Fix process spawning error handling ([#3349](https://github.com/helix-editor/helix/pull/3349)) +- Don't resolve links for `:o` completion ([8a4fbf6](https://github.com/helix-editor/helix/commit/8a4fbf6)) +- Recalculate completion after pasting into prompt ([e77b7d1](https://github.com/helix-editor/helix/commit/e77b7d1)) +- Fix extra selections with regex anchors ([#3598](https://github.com/helix-editor/helix/pull/3598)) +- Move mode transition logic to `handle_keymap_event` ([#2634](https://github.com/helix-editor/helix/pull/2634)) +- Add documents to view history when using the jumplist ([#3593](https://github.com/helix-editor/helix/pull/3593)) +- Prevent panic when loading tree-sitter queries ([fa1dc7e](https://github.com/helix-editor/helix/commit/fa1dc7e)) +- Discard LSP publishDiagnostic when LS is not initialized ([#3403](https://github.com/helix-editor/helix/pull/3403)) +- Refactor tree-sitter textobject motions as repeatable motions ([#3264](https://github.com/helix-editor/helix/pull/3264)) +- Avoid command execution hooks on closed docs ([#3613](https://github.com/helix-editor/helix/pull/3613)) +- Share `restore_term` code between panic and normal exits ([#2612](https://github.com/helix-editor/helix/pull/2612)) +- Show clipboard info in `--health` output ([#2947](https://github.com/helix-editor/helix/pull/2947)) +- Recalculate completion when going through prompt history ([#3193](https://github.com/helix-editor/helix/pull/3193)) + +Themes: + +- Update `tokyonight` and `tokyonight_storm` themes ([#2606](https://github.com/helix-editor/helix/pull/2606)) +- Update `solarized_light` themes ([#2626](https://github.com/helix-editor/helix/pull/2626)) +- Fix `catpuccin` `ui.popup` theme ([#2644](https://github.com/helix-editor/helix/pull/2644)) +- Update selection style of `night_owl` ([#2668](https://github.com/helix-editor/helix/pull/2668)) +- Fix spelling of `catppuccin` theme ([#2713](https://github.com/helix-editor/helix/pull/2713)) +- Update `base16_default`'s `ui.menu` ([#2794](https://github.com/helix-editor/helix/pull/2794)) +- Add `noctis_bordo` ([#2830](https://github.com/helix-editor/helix/pull/2830)) +- Add `acme` ([#2876](https://github.com/helix-editor/helix/pull/2876)) +- Add `meliora` ([#2884](https://github.com/helix-editor/helix/pull/2884), [#2890](https://github.com/helix-editor/helix/pull/2890)) +- Add cursorline scopes to various themes ([33d287a](https://github.com/helix-editor/helix/commit/33d287a), [#2892](https://github.com/helix-editor/helix/pull/2892), [#2915](https://github.com/helix-editor/helix/pull/2915), [#2916](https://github.com/helix-editor/helix/pull/2916), [#2918](https://github.com/helix-editor/helix/pull/2918), [#2927](https://github.com/helix-editor/helix/pull/2927), [#2925](https://github.com/helix-editor/helix/pull/2925), [#2938](https://github.com/helix-editor/helix/pull/2938), [#2962](https://github.com/helix-editor/helix/pull/2962), [#3054](https://github.com/helix-editor/helix/pull/3054)) +- Add mode colors to various themes ([#2926](https://github.com/helix-editor/helix/pull/2926), [#2933](https://github.com/helix-editor/helix/pull/2933), [#2929](https://github.com/helix-editor/helix/pull/2929), [#3098](https://github.com/helix-editor/helix/pull/3098), [#3104](https://github.com/helix-editor/helix/pull/3104), [#3128](https://github.com/helix-editor/helix/pull/3128), [#3135](https://github.com/helix-editor/helix/pull/3135), [#3200](https://github.com/helix-editor/helix/pull/3200)) +- Add `nord_light` ([#2908](https://github.com/helix-editor/helix/pull/2908)) +- Update `night_owl` ([#2929](https://github.com/helix-editor/helix/pull/2929)) +- Update `autumn` ([2e70985](https://github.com/helix-editor/helix/commit/2e70985), [936ed3a](https://github.com/helix-editor/helix/commit/936ed3a)) +- Update `one_dark` ([#3011](https://github.com/helix-editor/helix/pull/3011)) +- Add `noctis` ([#3043](https://github.com/helix-editor/helix/pull/3043), [#3128](https://github.com/helix-editor/helix/pull/3128)) +- Update `boo_berry` ([#3191](https://github.com/helix-editor/helix/pull/3191)) +- Update `monokai` ([#3131](https://github.com/helix-editor/helix/pull/3131)) +- Add `ayu_dark`, `ayu_light`, `ayu_mirage` ([#3184](https://github.com/helix-editor/helix/pull/3184)) +- Update `onelight` ([#3226](https://github.com/helix-editor/helix/pull/3226)) +- Add `base16_transparent` ([#3216](https://github.com/helix-editor/helix/pull/3216), [b565fff](https://github.com/helix-editor/helix/commit/b565fff)) +- Add `flatwhite` ([#3236](https://github.com/helix-editor/helix/pull/3236)) +- Update `dark_plus` ([#3302](https://github.com/helix-editor/helix/pull/3302)) +- Add `doom_acario_dark` ([#3308](https://github.com/helix-editor/helix/pull/3308), [#3539](https://github.com/helix-editor/helix/pull/3539)) +- Add `rose_pine_moon` ([#3229](https://github.com/helix-editor/helix/pull/3229)) +- Update `spacebones_light` ([#3342](https://github.com/helix-editor/helix/pull/3342)) +- Fix typos in themes ([8deaebd](https://github.com/helix-editor/helix/commit/8deaebd), [#3412](https://github.com/helix-editor/helix/pull/3412)) +- Add `emacs` ([#3410](https://github.com/helix-editor/helix/pull/3410)) +- Add `papercolor-light` ([#3426](https://github.com/helix-editor/helix/pull/3426), [#3470](https://github.com/helix-editor/helix/pull/3470), [#3585](https://github.com/helix-editor/helix/pull/3585)) +- Add `penumbra+` ([#3398](https://github.com/helix-editor/helix/pull/3398)) +- Add `fleetish` ([#3591](https://github.com/helix-editor/helix/pull/3591), [#3607](https://github.com/helix-editor/helix/pull/3607)) +- Add `sonokai` ([#3595](https://github.com/helix-editor/helix/pull/3595)) +- Update all themes for theme lints ([#3587](https://github.com/helix-editor/helix/pull/3587)) + +LSP: + +- V ([#2526](https://github.com/helix-editor/helix/pull/2526)) +- Prisma ([#2703](https://github.com/helix-editor/helix/pull/2703)) +- Clojure ([#2780](https://github.com/helix-editor/helix/pull/2780)) +- WGSL ([#2872](https://github.com/helix-editor/helix/pull/2872)) +- Elvish ([#2948](https://github.com/helix-editor/helix/pull/2948)) +- Idris ([#2971](https://github.com/helix-editor/helix/pull/2971)) +- Fortran ([#3025](https://github.com/helix-editor/helix/pull/3025)) +- Gleam ([#3139](https://github.com/helix-editor/helix/pull/3139)) +- Odin ([#3214](https://github.com/helix-editor/helix/pull/3214)) + +New languages: + +- V ([#2526](https://github.com/helix-editor/helix/pull/2526)) +- EDoc ([#2640](https://github.com/helix-editor/helix/pull/2640)) +- JSDoc ([#2650](https://github.com/helix-editor/helix/pull/2650)) +- OpenSCAD ([#2680](https://github.com/helix-editor/helix/pull/2680)) +- Prisma ([#2703](https://github.com/helix-editor/helix/pull/2703)) +- Clojure ([#2780](https://github.com/helix-editor/helix/pull/2780)) +- Starlark ([#2903](https://github.com/helix-editor/helix/pull/2903)) +- Elvish ([#2948](https://github.com/helix-editor/helix/pull/2948)) +- Fortran ([#3025](https://github.com/helix-editor/helix/pull/3025)) +- Ungrammar ([#3048](https://github.com/helix-editor/helix/pull/3048)) +- SCSS ([#3074](https://github.com/helix-editor/helix/pull/3074)) +- Go Template ([#3091](https://github.com/helix-editor/helix/pull/3091)) +- Graphviz dot ([#3241](https://github.com/helix-editor/helix/pull/3241)) +- Cue ([#3262](https://github.com/helix-editor/helix/pull/3262)) +- Slint ([#3355](https://github.com/helix-editor/helix/pull/3355)) +- Beancount ([#3297](https://github.com/helix-editor/helix/pull/3297)) +- Taskwarrior ([#3468](https://github.com/helix-editor/helix/pull/3468)) +- xit ([#3521](https://github.com/helix-editor/helix/pull/3521)) +- ESDL ([#3526](https://github.com/helix-editor/helix/pull/3526)) +- Awk ([#3528](https://github.com/helix-editor/helix/pull/3528), [#3535](https://github.com/helix-editor/helix/pull/3535)) +- Pascal ([#3542](https://github.com/helix-editor/helix/pull/3542)) + +Updated languages and queries: + +- Nix ([#2472](https://github.com/helix-editor/helix/pull/2472)) +- Elixir ([#2619](https://github.com/helix-editor/helix/pull/2619)) +- CPON ([#2643](https://github.com/helix-editor/helix/pull/2643)) +- Textobjects queries for Erlang, Elixir, Gleam ([#2661](https://github.com/helix-editor/helix/pull/2661)) +- Capture rust closures as function textobjects ([4a27e2d](https://github.com/helix-editor/helix/commit/4a27e2d)) +- Heex ([#2800](https://github.com/helix-editor/helix/pull/2800), [#3170](https://github.com/helix-editor/helix/pull/3170)) +- Add `<<=` operator highlighting for Rust ([#2805](https://github.com/helix-editor/helix/pull/2805)) +- Fix comment injection in JavaScript/TypeScript ([#2763](https://github.com/helix-editor/helix/pull/2763)) +- Nickel ([#2859](https://github.com/helix-editor/helix/pull/2859)) +- Add `Rakefile` and `Gemfile` to Ruby file-types ([#2875](https://github.com/helix-editor/helix/pull/2875)) +- Erlang ([#2910](https://github.com/helix-editor/helix/pull/2910), [ac669ad](https://github.com/helix-editor/helix/commit/ac669ad)) +- Markdown ([#2910](https://github.com/helix-editor/helix/pull/2910), [#3108](https://github.com/helix-editor/helix/pull/3108), [#3400](https://github.com/helix-editor/helix/pull/3400)) +- Bash ([#2910](https://github.com/helix-editor/helix/pull/2910)) +- Rust ([#2910](https://github.com/helix-editor/helix/pull/2910), [#3397](https://github.com/helix-editor/helix/pull/3397)) +- Edoc ([#2910](https://github.com/helix-editor/helix/pull/2910)) +- HTML ([#2910](https://github.com/helix-editor/helix/pull/2910)) +- Make ([#2910](https://github.com/helix-editor/helix/pull/2910)) +- TSQ ([#2910](https://github.com/helix-editor/helix/pull/2910), [#2960](https://github.com/helix-editor/helix/pull/2960)) +- git-commit ([#2910](https://github.com/helix-editor/helix/pull/2910)) +- Use default fallback for Python indents ([9ae70cc](https://github.com/helix-editor/helix/commit/9ae70cc)) +- Add Haskell LSP roots ([#2954](https://github.com/helix-editor/helix/pull/2954)) +- Ledger ([#2936](https://github.com/helix-editor/helix/pull/2936), [#2988](https://github.com/helix-editor/helix/pull/2988)) +- Nickel ([#2987](https://github.com/helix-editor/helix/pull/2987)) +- JavaScript/TypeScript ([#2961](https://github.com/helix-editor/helix/pull/2961), [#3219](https://github.com/helix-editor/helix/pull/3219), [#3213](https://github.com/helix-editor/helix/pull/3213), [#3280](https://github.com/helix-editor/helix/pull/3280), [#3301](https://github.com/helix-editor/helix/pull/3301)) +- GLSL ([#3051](https://github.com/helix-editor/helix/pull/3051)) +- Fix locals tracking in Rust ([#3027](https://github.com/helix-editor/helix/pull/3027), [#3212](https://github.com/helix-editor/helix/pull/3212), [#3345](https://github.com/helix-editor/helix/pull/3345)) +- Verilog ([#3158](https://github.com/helix-editor/helix/pull/3158)) +- Ruby ([#3173](https://github.com/helix-editor/helix/pull/3173), [#3527](https://github.com/helix-editor/helix/pull/3527)) +- Svelte ([#3147](https://github.com/helix-editor/helix/pull/3147)) +- Add Elixir and HEEx comment textobjects ([#3179](https://github.com/helix-editor/helix/pull/3179)) +- Python ([#3103](https://github.com/helix-editor/helix/pull/3103), [#3201](https://github.com/helix-editor/helix/pull/3201), [#3284](https://github.com/helix-editor/helix/pull/3284)) +- PHP ([#3317](https://github.com/helix-editor/helix/pull/3317)) +- Latex ([#3370](https://github.com/helix-editor/helix/pull/3370)) +- Clojure ([#3387](https://github.com/helix-editor/helix/pull/3387)) +- Swift ([#3461](https://github.com/helix-editor/helix/pull/3461)) +- C# ([#3480](https://github.com/helix-editor/helix/pull/3480), [#3494](https://github.com/helix-editor/helix/pull/3494)) +- Org ([#3489](https://github.com/helix-editor/helix/pull/3489)) +- Elm ([#3497](https://github.com/helix-editor/helix/pull/3497)) +- Dart ([#3419](https://github.com/helix-editor/helix/pull/3419)) +- Julia ([#3507](https://github.com/helix-editor/helix/pull/3507)) +- Fix Rust textobjects ([#3590](https://github.com/helix-editor/helix/pull/3590)) +- C ([00d88e5](https://github.com/helix-editor/helix/commit/00d88e5)) +- Update Rust ([0ef0ef9](https://github.com/helix-editor/helix/commit/0ef0ef9)) + +Packaging: + +- Add `rust-analyzer` to Nix flake devShell ([#2739](https://github.com/helix-editor/helix/pull/2739)) +- Add cachix information to the Nix flake ([#2999](https://github.com/helix-editor/helix/pull/2999)) +- Pass makeWrapperArgs to wrapProgram in the Nix flake ([#3003](https://github.com/helix-editor/helix/pull/3003)) +- Add a way to override which grammars are built by Nix ([#3141](https://github.com/helix-editor/helix/pull/3141)) +- Add a GitHub actions release for `aarch64-macos` ([#3137](https://github.com/helix-editor/helix/pull/3137)) +- Add shell auto-completions for Elvish ([#3331](https://github.com/helix-editor/helix/pull/3331)) + # 22.05 (2022-05-28) An even bigger shout out than usual to all the contributors - we had a whopping diff --git a/Cargo.lock b/Cargo.lock index 0277cc49..b2225f88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,18 +13,18 @@ dependencies = [ [[package]] name = "android_system_properties" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +checksum = "a26fa4d7e3f2eebadf743988fc8aec9fa9a9e82611acafd77c1462ed6262440a" [[package]] name = "arc-swap" @@ -57,9 +57,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "bytecount" @@ -199,9 +199,9 @@ dependencies = [ [[package]] name = "either" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "encoding_rs" @@ -278,15 +278,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -295,15 +295,15 @@ dependencies = [ [[package]] name = "futures-task" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-core", "futures-task", @@ -550,13 +550,14 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.44" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf7d67cf4a22adc5be66e75ebdf769b3f2ea032041437a7061f97a63dad4b" +checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" dependencies = [ "android_system_properties", "core-foundation-sys", "js-sys", + "once_cell", "wasm-bindgen", "winapi", ] @@ -628,9 +629,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.127" +version = "0.2.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" [[package]] name = "libloading" @@ -644,9 +645,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg", "scopeguard", @@ -663,9 +664,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.93.0" +version = "0.93.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70c74e2173b2b31f8655d33724b4b45ac13f439386f66290f539c22b144c2212" +checksum = "a3bcfee315dde785ba887edb540b08765fd7df75a7d948844be6bf5712246734" dependencies = [ "bitflags", "serde", @@ -688,9 +689,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -1061,9 +1062,9 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -1125,18 +1126,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" dependencies = [ "proc-macro2", "quote", @@ -1387,13 +1388,13 @@ checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static", "libc", + "once_cell", ] [[package]] @@ -1475,6 +1476,7 @@ name = "xtask" version = "0.6.0" dependencies = [ "helix-core", + "helix-loader", "helix-term", "toml", ] diff --git a/README.md b/README.md index 1e8a10e6..0d3dab4b 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,9 @@ for a language. ## MacOS -Helix can be installed on MacOS through homebrew via: +Helix can be installed on MacOS through homebrew: ``` -brew tap helix-editor/helix brew install helix ``` diff --git a/VERSION b/VERSION index bb7635c7..b9ed4c22 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -22.05 \ No newline at end of file +22.08.1 \ No newline at end of file diff --git a/book/src/configuration.md b/book/src/configuration.md index 25c7bb2c..5b3f6570 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -49,6 +49,7 @@ You may also specify a file to use for configuration with the `-c` or | `auto-info` | Whether to display infoboxes | `true` | | `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative. | `false` | | `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file. | `[]` | +| `bufferline` | Renders a line at the top of the editor displaying open buffers. Can be `always`, `never` or `multiple` (only shown if more than one buffer is in use) | `never` | | `color-modes` | Whether to color the mode indicator with different colors depending on the mode itself | `false` | ### `[editor.statusline]` Section diff --git a/book/src/generated/lang-support.md b/book/src/generated/lang-support.md index 8d8275ab..467e4c5e 100644 --- a/book/src/generated/lang-support.md +++ b/book/src/generated/lang-support.md @@ -4,7 +4,7 @@ | bash | ✓ | | | `bash-language-server` | | beancount | ✓ | | | | | c | ✓ | ✓ | ✓ | `clangd` | -| c-sharp | ✓ | | | `OmniSharp` | +| c-sharp | ✓ | ✓ | | `OmniSharp` | | cairo | ✓ | | | | | clojure | ✓ | | | `clojure-lsp` | | cmake | ✓ | ✓ | ✓ | `cmake-language-server` | @@ -14,7 +14,7 @@ | css | ✓ | | | `vscode-css-language-server` | | cue | ✓ | | | `cuelsp` | | dart | ✓ | | ✓ | `dart` | -| devicetree | ✓ | | ✓ | | +| devicetree | ✓ | | | | | dockerfile | ✓ | | | `docker-langserver` | | dot | ✓ | | | `dot-language-server` | | edoc | ✓ | | | | @@ -28,7 +28,7 @@ | esdl | ✓ | | | | | fish | ✓ | ✓ | ✓ | | | fortran | ✓ | | ✓ | `fortls` | -| gdscript | ✓ | | ✓ | | +| gdscript | ✓ | | | | | git-attributes | ✓ | | | | | git-commit | ✓ | | | | | git-config | ✓ | | | | @@ -42,7 +42,7 @@ | gotmpl | ✓ | | | `gopls` | | gowork | ✓ | | | `gopls` | | graphql | ✓ | | | | -| hare | ✓ | | ✓ | | +| hare | ✓ | | | | | haskell | ✓ | | | `haskell-language-server-wrapper` | | hcl | ✓ | | ✓ | `terraform-ls` | | heex | ✓ | ✓ | | | @@ -69,7 +69,7 @@ | meson | ✓ | | ✓ | | | mint | | | | `mint` | | nickel | ✓ | | ✓ | `nls` | -| nix | ✓ | | ✓ | `rnix-lsp` | +| nix | ✓ | | | `rnix-lsp` | | nu | ✓ | | | | | ocaml | ✓ | | ✓ | `ocamllsp` | | ocaml-interface | ✓ | | | `ocamllsp` | @@ -99,7 +99,7 @@ | sql | ✓ | | | | | sshclientconfig | ✓ | | | | | starlark | ✓ | ✓ | | | -| svelte | ✓ | | ✓ | `svelteserver` | +| svelte | ✓ | | | `svelteserver` | | swift | ✓ | | | `sourcekit-lsp` | | tablegen | ✓ | ✓ | ✓ | | | task | ✓ | | | | diff --git a/book/src/install.md b/book/src/install.md index b3109dd9..497dd0a4 100644 --- a/book/src/install.md +++ b/book/src/install.md @@ -6,10 +6,9 @@ We provide pre-built binaries on the [GitHub Releases page](https://github.com/h ## OSX -A Homebrew tap is available: +Helix is available in homebrew-core: ``` -brew tap helix-editor/helix brew install helix ``` diff --git a/book/src/keymap.md b/book/src/keymap.md index 1fd20bed..d2c0496c 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -125,7 +125,7 @@ | `Alt-(` | Rotate selection contents backward | `rotate_selection_contents_backward` | | `Alt-)` | Rotate selection contents forward | `rotate_selection_contents_forward` | | `%` | Select entire file | `select_all` | -| `x` | Select current line, if already selected, extend to next line | `extend_line` | +| `x` | Select current line, if already selected, extend to next line | `extend_line_below` | | `X` | Extend selection to line bounds (line-wise selection) | `extend_to_line_bounds` | | `Alt-x` | Shrink selection to line bounds (line-wise selection) | `shrink_to_line_bounds` | | `J` | Join lines inside selection | `join_selections` | diff --git a/docs/releases.md b/docs/releases.md index 0608a201..ec0b7270 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -23,7 +23,7 @@ we'll use `` as a placeholder for the tag being published. * 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 +[homebrew formula]: https://github.com/Homebrew/homebrew-core/blob/master/Formula/helix.rb ## Changelog Curation diff --git a/helix-core/src/selection.rs b/helix-core/src/selection.rs index 59bd736e..3463c1d3 100644 --- a/helix-core/src/selection.rs +++ b/helix-core/src/selection.rs @@ -659,7 +659,13 @@ pub fn select_on_matches( let start = text.byte_to_char(start_byte + mat.start()); let end = text.byte_to_char(start_byte + mat.end()); - result.push(Range::new(start, end)); + + let range = Range::new(start, end); + // Make sure the match is not right outside of the selection. + // These invalid matches can come from using RegEx anchors like `^`, `$` + if range != Range::point(sel.to()) { + result.push(range); + } } } @@ -929,6 +935,76 @@ mod test { assert_eq!(Range::new(6, 5).min_width_1(s), Range::new(6, 5)); } + #[test] + fn test_select_on_matches() { + use crate::regex::{Regex, RegexBuilder}; + + let r = Rope::from_str("Nobody expects the Spanish inquisition"); + let s = r.slice(..); + + let selection = Selection::single(0, r.len_chars()); + assert_eq!( + select_on_matches(s, &selection, &Regex::new(r"[A-Z][a-z]*").unwrap()), + Some(Selection::new( + smallvec![Range::new(0, 6), Range::new(19, 26)], + 0 + )) + ); + + let r = Rope::from_str("This\nString\n\ncontains multiple\nlines"); + let s = r.slice(..); + + let start_of_line = RegexBuilder::new(r"^").multi_line(true).build().unwrap(); + let end_of_line = RegexBuilder::new(r"$").multi_line(true).build().unwrap(); + + // line without ending + assert_eq!( + select_on_matches(s, &Selection::single(0, 4), &start_of_line), + Some(Selection::single(0, 0)) + ); + assert_eq!( + select_on_matches(s, &Selection::single(0, 4), &end_of_line), + None + ); + // line with ending + assert_eq!( + select_on_matches(s, &Selection::single(0, 5), &start_of_line), + Some(Selection::single(0, 0)) + ); + assert_eq!( + select_on_matches(s, &Selection::single(0, 5), &end_of_line), + Some(Selection::single(4, 4)) + ); + // line with start of next line + assert_eq!( + select_on_matches(s, &Selection::single(0, 6), &start_of_line), + Some(Selection::new( + smallvec![Range::point(0), Range::point(5)], + 0 + )) + ); + assert_eq!( + select_on_matches(s, &Selection::single(0, 6), &end_of_line), + Some(Selection::single(4, 4)) + ); + + // multiple lines + assert_eq!( + select_on_matches( + s, + &Selection::single(0, s.len_chars()), + &RegexBuilder::new(r"^[a-z ]*$") + .multi_line(true) + .build() + .unwrap() + ), + Some(Selection::new( + smallvec![Range::point(12), Range::new(13, 30), Range::new(31, 36)], + 0 + )) + ); + } + #[test] fn test_line_range() { let r = Rope::from_str("\r\nHi\r\nthere!"); diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 99922d37..6ec56bde 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -334,7 +334,7 @@ impl TextObjectQuery { } } -fn read_query(language: &str, filename: &str) -> String { +pub fn read_query(language: &str, filename: &str) -> String { static INHERITS_REGEX: Lazy = Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()-]+)\s*").unwrap()); @@ -374,7 +374,8 @@ impl LanguageConfiguration { &injections_query, &locals_query, ) - .unwrap_or_else(|query_error| panic!("Could not parse queries for language {:?}. Are your grammars out of sync? Try running 'hx --grammar fetch' and 'hx --grammar build'. This query could not be parsed: {:?}", self.language_id, query_error)); + .map_err(|err| log::error!("Could not parse queries for language {:?}. Are your grammars out of sync? Try running 'hx --grammar fetch' and 'hx --grammar build'. This query could not be parsed: {:?}", self.language_id, err)) + .ok()?; config.configure(scopes); Some(Arc::new(config)) @@ -399,28 +400,15 @@ impl LanguageConfiguration { pub fn indent_query(&self) -> Option<&Query> { self.indent_query - .get_or_init(|| { - let lang_name = self.language_id.to_ascii_lowercase(); - let query_text = read_query(&lang_name, "indents.scm"); - if query_text.is_empty() { - return None; - } - let lang = self.highlight_config.get()?.as_ref()?.language; - Query::new(lang, &query_text).ok() - }) + .get_or_init(|| self.load_query("indents.scm")) .as_ref() } pub fn textobject_query(&self) -> Option<&TextObjectQuery> { self.textobject_query - .get_or_init(|| -> Option { - let lang_name = self.language_id.to_ascii_lowercase(); - let query_text = read_query(&lang_name, "textobjects.scm"); - let lang = self.highlight_config.get()?.as_ref()?.language; - let query = Query::new(lang, &query_text) - .map_err(|e| log::error!("Failed to parse textobjects.scm queries: {}", e)) - .ok()?; - Some(TextObjectQuery { query }) + .get_or_init(|| { + self.load_query("textobjects.scm") + .map(|query| TextObjectQuery { query }) }) .as_ref() } @@ -428,6 +416,18 @@ impl LanguageConfiguration { pub fn scope(&self) -> &str { &self.scope } + + fn load_query(&self, kind: &str) -> Option { + let lang_name = self.language_id.to_ascii_lowercase(); + let query_text = read_query(&lang_name, kind); + if query_text.is_empty() { + return None; + } + let lang = self.highlight_config.get()?.as_ref()?.language; + Query::new(lang, &query_text) + .map_err(|e| log::error!("Failed to parse {} queries for {}: {}", kind, lang_name, e)) + .ok() + } } // Expose loader as Lazy<> global since it's always static? diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 21be7db0..9e79e7c9 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -29,7 +29,10 @@ use std::{ use anyhow::{Context, Error}; use crossterm::{ - event::{DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent}, + event::{ + DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture, + Event as CrosstermEvent, + }, execute, terminal, tty::IsTty, }; @@ -82,6 +85,22 @@ fn setup_integration_logging() { .apply(); } +fn restore_term() -> Result<(), Error> { + let mut stdout = stdout(); + // reset cursor shape + write!(stdout, "\x1B[0 q")?; + // Ignore errors on disabling, this might trigger on windows if we call + // disable without calling enable previously + let _ = execute!(stdout, DisableMouseCapture); + execute!( + stdout, + DisableBracketedPaste, + terminal::LeaveAlternateScreen + )?; + terminal::disable_raw_mode()?; + Ok(()) +} + impl Application { pub fn new(args: Args, config: Config) -> Result { #[cfg(feature = "integration")] @@ -386,7 +405,7 @@ impl Application { match signal { signal::SIGTSTP => { self.compositor.save_cursor(); - self.restore_term().unwrap(); + restore_term().unwrap(); low_level::emulate_default_handler(signal::SIGTSTP).unwrap(); } signal::SIGCONT => { @@ -425,14 +444,13 @@ impl Application { scroll: None, }; // Handle key events - let should_redraw = match event { - Ok(CrosstermEvent::Resize(width, height)) => { + let should_redraw = match event.unwrap() { + CrosstermEvent::Resize(width, height) => { self.compositor.resize(width, height); self.compositor - .handle_event(Event::Resize(width, height), &mut cx) + .handle_event(&Event::Resize(width, height), &mut cx) } - Ok(event) => self.compositor.handle_event(event.into(), &mut cx), - Err(x) => panic!("{}", x), + event => self.compositor.handle_event(&event.into(), &mut cx), }; if should_redraw && !self.editor.should_close() { @@ -510,7 +528,12 @@ impl Application { use helix_core::diagnostic::{Diagnostic, Range, Severity::*}; use lsp::DiagnosticSeverity; - let language_server = doc.language_server().unwrap(); + let language_server = if let Some(language_server) = doc.language_server() { + language_server + } else { + log::warn!("Discarding diagnostic because language server is not initialized: {:?}", diagnostic); + return None; + }; // TODO: convert inside server let start = if let Some(start) = lsp_pos_to_pos( @@ -788,7 +811,7 @@ impl Application { async fn claim_term(&mut self) -> Result<(), Error> { terminal::enable_raw_mode()?; let mut stdout = stdout(); - execute!(stdout, terminal::EnterAlternateScreen)?; + execute!(stdout, terminal::EnterAlternateScreen, EnableBracketedPaste)?; execute!(stdout, terminal::Clear(terminal::ClearType::All))?; if self.config.load().editor.mouse { execute!(stdout, EnableMouseCapture)?; @@ -796,18 +819,6 @@ impl Application { Ok(()) } - fn restore_term(&mut self) -> Result<(), Error> { - let mut stdout = stdout(); - // reset cursor shape - write!(stdout, "\x1B[0 q")?; - // Ignore errors on disabling, this might trigger on windows if we call - // disable without calling enable previously - let _ = execute!(stdout, DisableMouseCapture); - execute!(stdout, terminal::LeaveAlternateScreen)?; - terminal::disable_raw_mode()?; - Ok(()) - } - pub async fn run(&mut self, input_stream: &mut S) -> Result where S: Stream> + Unpin, @@ -819,16 +830,14 @@ impl Application { std::panic::set_hook(Box::new(move |info| { // We can't handle errors properly inside this closure. And it's // probably not a good idea to `unwrap()` inside a panic handler. - // So we just ignore the `Result`s. - let _ = execute!(std::io::stdout(), DisableMouseCapture); - let _ = execute!(std::io::stdout(), terminal::LeaveAlternateScreen); - let _ = terminal::disable_raw_mode(); + // So we just ignore the `Result`. + let _ = restore_term(); hook(info); })); self.event_loop(input_stream).await; self.close().await?; - self.restore_term()?; + restore_term()?; Ok(self.editor.exit_code) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index a036407c..585f9f2f 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -247,7 +247,8 @@ impl MappableCommand { extend_search_prev, "Add previous search match to selection", search_selection, "Use current selection as search pattern", global_search, "Global search in workspace folder", - extend_line, "Select current line, if already selected, extend to next line", + extend_line, "Select current line, if already selected, extend to another line based on the anchor", + extend_line_below, "Select current line, if already selected, extend to next line", extend_line_above, "Select current line, if already selected, extend to previous line", extend_to_line_bounds, "Extend selection to line bounds", shrink_to_line_bounds, "Shrink selection to line bounds", @@ -588,7 +589,7 @@ fn goto_line_end(cx: &mut Context) { goto_line_end_impl( view, doc, - if doc.mode == Mode::Select { + if cx.editor.mode == Mode::Select { Movement::Extend } else { Movement::Move @@ -618,7 +619,7 @@ fn goto_line_end_newline(cx: &mut Context) { goto_line_end_newline_impl( view, doc, - if doc.mode == Mode::Select { + if cx.editor.mode == Mode::Select { Movement::Extend } else { Movement::Move @@ -649,7 +650,7 @@ fn goto_line_start(cx: &mut Context) { goto_line_start_impl( view, doc, - if doc.mode == Mode::Select { + if cx.editor.mode == Mode::Select { Movement::Extend } else { Movement::Move @@ -754,7 +755,7 @@ fn goto_first_nonwhitespace(cx: &mut Context) { if let Some(pos) = find_first_non_whitespace_char(text.line(line)) { let pos = pos + text.line_to_char(line); - range.put_cursor(text, pos, doc.mode == Mode::Select) + range.put_cursor(text, pos, cx.editor.mode == Mode::Select) } else { range } @@ -954,7 +955,7 @@ where let motion = move |editor: &mut Editor| { let (view, doc) = current!(editor); let text = doc.text().slice(..); - let behavior = if doc.mode == Mode::Select { + let behavior = if editor.mode == Mode::Select { Movement::Extend } else { Movement::Move @@ -987,7 +988,7 @@ fn goto_file_start(cx: &mut Context) { let selection = doc .selection(view.id) .clone() - .transform(|range| range.put_cursor(text, 0, doc.mode == Mode::Select)); + .transform(|range| range.put_cursor(text, 0, cx.editor.mode == Mode::Select)); push_jump(view, doc); doc.set_selection(view.id, selection); } @@ -1000,7 +1001,7 @@ fn goto_file_end(cx: &mut Context) { let selection = doc .selection(view.id) .clone() - .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + .transform(|range| range.put_cursor(text, pos, cx.editor.mode == Mode::Select)); push_jump(view, doc); doc.set_selection(view.id, selection); } @@ -1377,7 +1378,7 @@ pub fn scroll(cx: &mut Context, offset: usize, direction: Direction) { if line != cursor.row { let head = pos_at_coords(text, Position::new(line, cursor.col), true); // this func will properly truncate to line end - let anchor = if doc.mode == Mode::Select { + let anchor = if cx.editor.mode == Mode::Select { range.anchor } else { head @@ -1948,6 +1949,15 @@ enum Extend { } fn extend_line(cx: &mut Context) { + let (view, doc) = current_ref!(cx.editor); + let extend = match doc.selection(view.id).primary().direction() { + Direction::Forward => Extend::Below, + Direction::Backward => Extend::Above, + }; + extend_line_impl(cx, extend); +} + +fn extend_line_below(cx: &mut Context) { extend_line_impl(cx, Extend::Below); } @@ -1963,20 +1973,32 @@ fn extend_line_impl(cx: &mut Context, extend: Extend) { let selection = doc.selection(view.id).clone().transform(|range| { let (start_line, end_line) = range.line_range(text.slice(..)); - let start = text.line_to_char(start_line); - let end = text.line_to_char((end_line + count).min(text.len_lines())); + let start = text.line_to_char(match extend { + Extend::Above => start_line.saturating_sub(count), + Extend::Below => start_line, + }); + let end = text.line_to_char( + match extend { + Extend::Above => end_line + 1, // the start of next line + Extend::Below => end_line + count, + } + .min(text.len_lines()), + ); // extend to previous/next line if current line is selected let (anchor, head) = if range.from() == start && range.to() == end { match extend { - Extend::Above => (end, text.line_to_char(start_line.saturating_sub(1))), + Extend::Above => (end, text.line_to_char(start_line.saturating_sub(count + 1))), Extend::Below => ( start, text.line_to_char((end_line + count + 1).min(text.len_lines())), ), } } else { - (start, end) + match extend { + Extend::Above => (end, start), + Extend::Below => (start, end), + } }; Range::new(anchor, head) @@ -2079,7 +2101,7 @@ fn delete_selection_impl(cx: &mut Context, op: Operation) { exit_select_mode(cx); } Operation::Change => { - enter_insert_mode(doc); + enter_insert_mode(cx); } } } @@ -2148,14 +2170,14 @@ fn ensure_selections_forward(cx: &mut Context) { doc.set_selection(view.id, selection); } -fn enter_insert_mode(doc: &mut Document) { - doc.mode = Mode::Insert; +fn enter_insert_mode(cx: &mut Context) { + cx.editor.mode = Mode::Insert; } // inserts at the start of each selection fn insert_mode(cx: &mut Context) { + enter_insert_mode(cx); let (view, doc) = current!(cx.editor); - enter_insert_mode(doc); log::trace!( "entering insert mode with sel: {:?}, text: {:?}", @@ -2173,8 +2195,8 @@ fn insert_mode(cx: &mut Context) { // inserts at the end of each selection fn append_mode(cx: &mut Context) { + enter_insert_mode(cx); let (view, doc) = current!(cx.editor); - enter_insert_mode(doc); doc.restore_cursor = true; let text = doc.text().slice(..); @@ -2439,9 +2461,9 @@ impl ui::menu::Item for MappableCommand { pub fn command_palette(cx: &mut Context) { cx.callback = Some(Box::new( move |compositor: &mut Compositor, cx: &mut compositor::Context| { - let doc = doc_mut!(cx.editor); - let keymap = - compositor.find::().unwrap().keymaps.map()[&doc.mode].reverse_map(); + let keymap = compositor.find::().unwrap().keymaps.map() + [&cx.editor.mode] + .reverse_map(); let mut commands: Vec = MappableCommand::STATIC_COMMAND_LIST.into(); commands.extend(typed::TYPABLE_COMMAND_LIST.iter().map(|cmd| { @@ -2482,14 +2504,13 @@ fn last_picker(cx: &mut Context) { // I inserts at the first nonwhitespace character of each line with a selection fn prepend_to_line(cx: &mut Context) { goto_first_nonwhitespace(cx); - let doc = doc_mut!(cx.editor); - enter_insert_mode(doc); + enter_insert_mode(cx); } // A inserts at the end of each line with a selection fn append_to_line(cx: &mut Context) { + enter_insert_mode(cx); let (view, doc) = current!(cx.editor); - enter_insert_mode(doc); let selection = doc.selection(view.id).clone().transform(|range| { let text = doc.text().slice(..); @@ -2545,8 +2566,8 @@ pub enum Open { fn open(cx: &mut Context, open: Open) { let count = cx.count(); + enter_insert_mode(cx); let (view, doc) = current!(cx.editor); - enter_insert_mode(doc); let text = doc.text().slice(..); let contents = doc.text(); @@ -2624,13 +2645,12 @@ fn open_above(cx: &mut Context) { } fn normal_mode(cx: &mut Context) { - let (view, doc) = current!(cx.editor); - - if doc.mode == Mode::Normal { + if cx.editor.mode == Mode::Normal { return; } - doc.mode = Mode::Normal; + cx.editor.mode = Mode::Normal; + let (view, doc) = current!(cx.editor); try_restore_indent(doc, view.id); @@ -2708,7 +2728,7 @@ fn goto_line_impl(editor: &mut Editor, count: Option) { let selection = doc .selection(view.id) .clone() - .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + .transform(|range| range.put_cursor(text, pos, editor.mode == Mode::Select)); push_jump(view, doc); doc.set_selection(view.id, selection); @@ -2728,7 +2748,7 @@ fn goto_last_line(cx: &mut Context) { let selection = doc .selection(view.id) .clone() - .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + .transform(|range| range.put_cursor(text, pos, cx.editor.mode == Mode::Select)); push_jump(view, doc); doc.set_selection(view.id, selection); @@ -2751,7 +2771,7 @@ fn goto_last_modification(cx: &mut Context) { let selection = doc .selection(view.id) .clone() - .transform(|range| range.put_cursor(text, pos, doc.mode == Mode::Select)); + .transform(|range| range.put_cursor(text, pos, cx.editor.mode == Mode::Select)); doc.set_selection(view.id, selection); } } @@ -2788,13 +2808,12 @@ fn select_mode(cx: &mut Context) { }); doc.set_selection(view.id, selection); - doc_mut!(cx.editor).mode = Mode::Select; + cx.editor.mode = Mode::Select; } fn exit_select_mode(cx: &mut Context) { - let doc = doc_mut!(cx.editor); - if doc.mode == Mode::Select { - doc.mode = Mode::Normal; + if cx.editor.mode == Mode::Select { + cx.editor.mode = Mode::Normal; } } @@ -3409,13 +3428,7 @@ enum Paste { Cursor, } -fn paste_impl( - values: &[String], - doc: &mut Document, - view: &View, - action: Paste, - count: usize, -) -> Option { +fn paste_impl(values: &[String], doc: &mut Document, view: &View, action: Paste, count: usize) { let repeat = std::iter::repeat( values .last() @@ -3458,8 +3471,17 @@ fn paste_impl( }; (pos, pos, values.next()) }); + doc.apply(&transaction, view.id); +} - Some(transaction) +pub(crate) fn paste_bracketed_value(cx: &mut Context, contents: String) { + let count = cx.count(); + let paste = match cx.editor.mode { + Mode::Insert | Mode::Select => Paste::Cursor, + Mode::Normal => Paste::Before, + }; + let (view, doc) = current!(cx.editor); + paste_impl(&[contents], doc, view, paste, count); } fn paste_clipboard_impl( @@ -3469,18 +3491,11 @@ fn paste_clipboard_impl( count: usize, ) -> anyhow::Result<()> { let (view, doc) = current!(editor); - - match editor - .clipboard_provider - .get_contents(clipboard_type) - .map(|contents| paste_impl(&[contents], doc, view, action, count)) - { - Ok(Some(transaction)) => { - doc.apply(&transaction, view.id); - doc.append_changes_to_history(view.id); + match editor.clipboard_provider.get_contents(clipboard_type) { + Ok(contents) => { + paste_impl(&[contents], doc, view, action, count); Ok(()) } - Ok(None) => Ok(()), Err(e) => Err(e.context("Couldn't get system clipboard contents")), } } @@ -3593,11 +3608,8 @@ fn paste(cx: &mut Context, pos: Paste) { let (view, doc) = current!(cx.editor); let registers = &mut cx.editor.registers; - if let Some(transaction) = registers - .read(reg_name) - .and_then(|values| paste_impl(values, doc, view, pos, count)) - { - doc.apply(&transaction, view.id); + if let Some(values) = registers.read(reg_name) { + paste_impl(values, doc, view, pos, count); } } @@ -3863,8 +3875,7 @@ pub fn completion(cx: &mut Context) { cx.callback( future, move |editor, compositor, response: Option| { - let doc = doc!(editor); - if doc.mode() != Mode::Insert { + if editor.mode != Mode::Insert { // we're not in insert mode anymore return; } @@ -4071,7 +4082,7 @@ fn match_brackets(cx: &mut Context) { if let Some(pos) = match_brackets::find_matching_bracket_fuzzy(syntax, doc.text(), range.cursor(text)) { - range.put_cursor(text, pos, doc.mode == Mode::Select) + range.put_cursor(text, pos, cx.editor.mode == Mode::Select) } else { range } @@ -4085,13 +4096,18 @@ fn match_brackets(cx: &mut Context) { fn jump_forward(cx: &mut Context) { let count = cx.count(); let view = view_mut!(cx.editor); + let doc_id = view.doc; if let Some((id, selection)) = view.jumps.forward(count) { view.doc = *id; let selection = selection.clone(); let (view, doc) = current!(cx.editor); // refetch doc - doc.set_selection(view.id, selection); + if doc.id() != doc_id { + view.add_to_history(doc_id); + } + + doc.set_selection(view.id, selection); align_view(doc, view, Align::Center); }; } @@ -4099,13 +4115,18 @@ fn jump_forward(cx: &mut Context) { fn jump_backward(cx: &mut Context) { let count = cx.count(); let (view, doc) = current!(cx.editor); + let doc_id = doc.id(); if let Some((id, selection)) = view.jumps.backward(view.id, doc, count) { view.doc = *id; let selection = selection.clone(); let (view, doc) = current!(cx.editor); // refetch doc - doc.set_selection(view.id, selection); + if doc.id() != doc_id { + view.add_to_history(doc_id); + } + + doc.set_selection(view.id, selection); align_view(doc, view, Align::Center); }; } @@ -4266,26 +4287,33 @@ fn scroll_down(cx: &mut Context) { scroll(cx, cx.count(), Direction::Forward); } -fn goto_ts_object_impl(cx: &mut Context, object: &str, direction: Direction) { +fn goto_ts_object_impl(cx: &mut Context, object: &'static str, direction: Direction) { let count = cx.count(); - let (view, doc) = current!(cx.editor); - let text = doc.text().slice(..); - let range = doc.selection(view.id).primary(); + let motion = move |editor: &mut Editor| { + let (view, doc) = current!(editor); + if let Some((lang_config, syntax)) = doc.language_config().zip(doc.syntax()) { + let text = doc.text().slice(..); + let root = syntax.tree().root_node(); - let new_range = match doc.language_config().zip(doc.syntax()) { - Some((lang_config, syntax)) => movement::goto_treesitter_object( - text, - range, - object, - direction, - syntax.tree().root_node(), - lang_config, - count, - ), - None => range, - }; + let selection = doc.selection(view.id).clone().transform(|range| { + movement::goto_treesitter_object( + text, + range, + object, + direction, + root, + lang_config, + count, + ) + }); - doc.set_selection(view.id, Selection::single(new_range.anchor, new_range.head)); + doc.set_selection(view.id, selection); + } else { + editor.set_status("Syntax-tree is not available in current buffer"); + } + }; + motion(cx.editor); + cx.editor.last_motion = Some(Motion(Box::new(motion))); } fn goto_next_function(cx: &mut Context) { @@ -4621,8 +4649,18 @@ fn shell_impl( } let output = process.wait_with_output()?; - if !output.stderr.is_empty() { - log::error!("Shell error: {}", String::from_utf8_lossy(&output.stderr)); + if !output.status.success() { + if !output.stderr.is_empty() { + let err = String::from_utf8_lossy(&output.stderr).to_string(); + log::error!("Shell error: {}", err); + bail!("Shell error: {}", err); + } + bail!("Shell command failed"); + } else if !output.stderr.is_empty() { + log::debug!( + "Command printed to stderr: {}", + String::from_utf8_lossy(&output.stderr).to_string() + ); } let str = std::str::from_utf8(&output.stdout) @@ -4889,7 +4927,7 @@ fn replay_macro(cx: &mut Context) { cx.callback = Some(Box::new(move |compositor, cx| { for _ in 0..count { for &key in keys.iter() { - compositor.handle_event(compositor::Event::Key(key), cx); + compositor.handle_event(&compositor::Event::Key(key), cx); } } // The macro under replay is cleared at the end of the callback, not in the diff --git a/helix-term/src/commands/dap.rs b/helix-term/src/commands/dap.rs index 1c780c1f..12a3fbc7 100644 --- a/helix-term/src/commands/dap.rs +++ b/helix-term/src/commands/dap.rs @@ -582,7 +582,7 @@ pub fn dap_edit_condition(cx: &mut Context) { None => return, }; let callback = Box::pin(async move { - let call: Callback = Box::new(move |_editor, compositor| { + let call: Callback = Box::new(move |editor, compositor| { let mut prompt = Prompt::new( "condition:".into(), None, @@ -607,7 +607,7 @@ pub fn dap_edit_condition(cx: &mut Context) { }, ); if let Some(condition) = breakpoint.condition { - prompt.insert_str(&condition) + prompt.insert_str(&condition, editor) } compositor.push(Box::new(prompt)); }); @@ -624,7 +624,7 @@ pub fn dap_edit_log(cx: &mut Context) { None => return, }; let callback = Box::pin(async move { - let call: Callback = Box::new(move |_editor, compositor| { + let call: Callback = Box::new(move |editor, compositor| { let mut prompt = Prompt::new( "log-message:".into(), None, @@ -648,7 +648,7 @@ pub fn dap_edit_log(cx: &mut Context) { }, ); if let Some(log_message) = breakpoint.log_message { - prompt.insert_str(&log_message); + prompt.insert_str(&log_message, editor); } compositor.push(Box::new(prompt)); }); diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index 38507e4d..61eed638 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -863,10 +863,7 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) { } }; let doc = doc!(editor); - let language = doc - .language() - .and_then(|scope| scope.strip_prefix("source.")) - .unwrap_or(""); + let language = doc.language_name().unwrap_or(""); let signature = match response .signatures diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index bda38c59..c0898dae 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -29,7 +29,7 @@ pub struct Context<'a> { pub trait Component: Any + AnyComponent { /// Process input events, return true if handled. - fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult { + fn handle_event(&mut self, _event: &Event, _ctx: &mut Context) -> EventResult { EventResult::Ignored(None) } // , args: () @@ -157,10 +157,10 @@ impl Compositor { Some(self.layers.remove(idx)) } - pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool { + pub fn handle_event(&mut self, event: &Event, cx: &mut Context) -> bool { // If it is a key event and a macro is being recorded, push the key event to the recording. if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) { - keys.push(key); + keys.push(*key); } let mut callbacks = Vec::new(); diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index 4a266e48..ac9f06fc 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -83,6 +83,33 @@ pub fn general() -> std::io::Result<()> { Ok(()) } +pub fn clipboard() -> std::io::Result<()> { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + let board = get_clipboard_provider(); + match board.name().as_ref() { + "none" => { + writeln!( + stdout, + "{}", + "System clipboard provider: Not installed".red() + )?; + writeln!( + stdout, + " {}", + "For troubleshooting system clipboard issues, refer".red() + )?; + writeln!(stdout, " {}", + "https://github.com/helix-editor/helix/wiki/Troubleshooting#copypaste-fromto-system-clipboard-not-working" + .red().underlined())?; + } + name => writeln!(stdout, "System clipboard provider: {}", name)?, + } + + Ok(()) +} + pub fn languages_all() -> std::io::Result<()> { let stdout = std::io::stdout(); let mut stdout = stdout.lock(); @@ -281,13 +308,15 @@ fn probe_treesitter_feature(lang: &str, feature: TsFeature) -> std::io::Result<( pub fn print_health(health_arg: Option) -> std::io::Result<()> { match health_arg.as_deref() { - Some("all") => languages_all()?, - Some(lang) => language(lang.to_string())?, - None => { + Some("languages") => languages_all()?, + Some("clipboard") => clipboard()?, + None | Some("all") => { general()?; + clipboard()?; writeln!(std::io::stdout().lock())?; languages_all()?; } + Some(lang) => language(lang.to_string())?, } Ok(()) } diff --git a/helix-term/src/keymap/default.rs b/helix-term/src/keymap/default.rs index 71f0f154..9f3f6aad 100644 --- a/helix-term/src/keymap/default.rs +++ b/helix-term/src/keymap/default.rs @@ -85,7 +85,7 @@ pub fn default() -> HashMap { "A-n" | "A-right" => select_next_sibling, "%" => select_all, - "x" => extend_line, + "x" => extend_line_below, "X" => extend_to_line_bounds, "A-x" => shrink_to_line_bounds, diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 7f04f201..d21d3e77 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -61,8 +61,9 @@ ARGS: FLAGS: -h, --help Prints help information --tutor Loads the tutorial - --health [LANG] Checks for potential errors in editor setup - If given, checks for config errors in language LANG + --health [CATEGORY] Checks for potential errors in editor setup + CATEGORY can be a language or one of 'clipboard', 'languages' + or 'all'. 'all' is the default if not specified. -g, --grammar {{fetch|build}} Fetches or builds tree-sitter grammars listed in languages.toml -c, --config Specifies a file to use for configuration -v Increases logging verbosity each use for up to 3 times diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 6a743632..2d7d4f92 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -298,7 +298,7 @@ impl Completion { } impl Component for Completion { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { // let the Editor handle Esc instead if let Event::Key(KeyEvent { code: KeyCode::Esc, .. @@ -324,10 +324,7 @@ impl Component for Completion { // option.documentation let (view, doc) = current!(cx.editor); - let language = doc - .language() - .and_then(|scope| scope.strip_prefix("source.")) - .unwrap_or(""); + let language = doc.language_name().unwrap_or(""); let text = doc.text().slice(..); let cursor_pos = doc.selection(view.id).primary().cursor(text); let coords = helix_core::visual_coords_at_pos(text, cursor_pos, doc.tab_width()); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 07b27f59..c3efa5cf 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -16,14 +16,14 @@ use helix_core::{ LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ - document::Mode, + document::{Mode, SCRATCH_BUFFER_NAME}, editor::{CompleteAction, CursorShapeConfig}, graphics::{Color, CursorKind, Modifier, Rect, Style}, input::{KeyEvent, MouseButton, MouseEvent, MouseEventKind}, keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, }; -use std::borrow::Cow; +use std::{borrow::Cow, path::PathBuf}; use tui::buffer::Buffer as Surface; @@ -124,7 +124,13 @@ impl EditorView { let highlights: Box> = if is_focused { Box::new(syntax::merge( highlights, - Self::doc_selection_highlights(doc, view, theme, &editor.config().cursor_shape), + Self::doc_selection_highlights( + editor.mode(), + doc, + view, + theme, + &editor.config().cursor_shape, + ), )) } else { Box::new(highlights) @@ -299,6 +305,7 @@ impl EditorView { /// Get highlight spans for selections in a document view. pub fn doc_selection_highlights( + mode: Mode, doc: &Document, view: &View, theme: &Theme, @@ -308,7 +315,6 @@ impl EditorView { let selection = doc.selection(view.id); let primary_idx = selection.primary_index(); - let mode = doc.mode(); let cursorkind = cursor_shape_config.from_mode(mode); let cursor_is_block = cursorkind == CursorKind::Block; @@ -615,6 +621,59 @@ impl EditorView { } } + /// Render bufferline at the top + pub fn render_bufferline(editor: &Editor, viewport: Rect, surface: &mut Surface) { + let scratch = PathBuf::from(SCRATCH_BUFFER_NAME); // default filename to use for scratch buffer + surface.clear_with( + viewport, + editor + .theme + .try_get("ui.bufferline.background") + .unwrap_or_else(|| editor.theme.get("ui.statusline")), + ); + + let bufferline_active = editor + .theme + .try_get("ui.bufferline.active") + .unwrap_or_else(|| editor.theme.get("ui.statusline.active")); + + let bufferline_inactive = editor + .theme + .try_get("ui.bufferline") + .unwrap_or_else(|| editor.theme.get("ui.statusline.inactive")); + + let mut x = viewport.x; + let current_doc = view!(editor).doc; + + for doc in editor.documents() { + let fname = doc + .path() + .unwrap_or(&scratch) + .file_name() + .unwrap_or_default() + .to_str() + .unwrap_or_default(); + + let style = if current_doc == doc.id() { + bufferline_active + } else { + bufferline_inactive + }; + + let text = format!(" {}{} ", fname, if doc.is_modified() { "[+]" } else { "" }); + let used_width = viewport.x.saturating_sub(x); + let rem_width = surface.area.width.saturating_sub(used_width); + + x = surface + .set_stringn(x, viewport.y, text, rem_width as usize, style) + .0; + + if x >= surface.area.right() { + break; + } + } + } + pub fn render_gutter( editor: &Editor, doc: &Document, @@ -773,15 +832,50 @@ impl EditorView { cxt: &mut commands::Context, event: KeyEvent, ) -> Option { + let mut last_mode = mode; let key_result = self.keymaps.get(mode, event); cxt.editor.autoinfo = self.keymaps.sticky().map(|node| node.infobox()); + let mut execute_command = |command: &commands::MappableCommand| { + command.execute(cxt); + let current_mode = cxt.editor.mode(); + match (last_mode, current_mode) { + (Mode::Normal, Mode::Insert) => { + // HAXX: if we just entered insert mode from normal, clear key buf + // and record the command that got us into this mode. + + // how we entered insert mode is important, and we should track that so + // we can repeat the side effect. + self.last_insert.0 = command.clone(); + self.last_insert.1.clear(); + + commands::signature_help_impl(cxt, commands::SignatureHelpInvoked::Automatic); + } + (Mode::Insert, Mode::Normal) => { + // if exiting insert mode, remove completion + self.completion = None; + + // TODO: Use an on_mode_change hook to remove signature help + cxt.jobs.callback(async { + let call: job::Callback = Box::new(|_editor, compositor| { + compositor.remove(SignatureHelp::ID); + }); + Ok(call) + }); + } + _ => (), + } + last_mode = current_mode; + }; + match &key_result { - KeymapResult::Matched(command) => command.execute(cxt), + KeymapResult::Matched(command) => { + execute_command(command); + } KeymapResult::Pending(node) => cxt.editor.autoinfo = Some(node.infobox()), KeymapResult::MatchedSequence(commands) => { for command in commands { - command.execute(cxt); + execute_command(command); } } KeymapResult::NotFound | KeymapResult::Cancelled(_) => return Some(key_result), @@ -915,8 +1009,8 @@ impl EditorView { pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult { if self.completion.is_some() + || cx.editor.mode != Mode::Insert || !cx.editor.config().auto_completion - || doc!(cx.editor).mode != Mode::Insert { return EventResult::Ignored(None); } @@ -938,7 +1032,7 @@ impl EditorView { impl EditorView { fn handle_mouse_event( &mut self, - event: MouseEvent, + event: &MouseEvent, cxt: &mut commands::Context, ) -> EventResult { let config = cxt.editor.config(); @@ -948,7 +1042,7 @@ impl EditorView { column, modifiers, .. - } = event; + } = *event; let pos_and_view = |editor: &Editor, row, column| { editor.tree.views().find_map(|(view, _focus)| { @@ -1117,7 +1211,7 @@ impl EditorView { impl Component for EditorView { fn handle_event( &mut self, - event: Event, + event: &Event, context: &mut crate::compositor::Context, ) -> EventResult { if let Some(explore) = self.explorer.as_mut() { @@ -1135,6 +1229,24 @@ impl Component for EditorView { }; match event { + Event::Paste(contents) => { + cx.count = cx.editor.count; + commands::paste_bracketed_value(&mut cx, contents.clone()); + cx.editor.count = None; + + let config = cx.editor.config(); + let mode = cx.editor.mode(); + let (view, doc) = current!(cx.editor); + view.ensure_cursor_in_view(doc, config.scrolloff); + + // Store a history state if not in insert mode. Otherwise wait till we exit insert + // to include any edits to the paste in the history state. + if mode != Mode::Insert { + doc.append_changes_to_history(view.id); + } + + EventResult::Consumed(None) + } Event::Resize(_width, _height) => { // Ignore this event, we handle resizing just before rendering to screen. // Handling it here but not re-rendering will cause flashing @@ -1147,8 +1259,9 @@ impl Component for EditorView { // clear status cx.editor.status_msg = None; - let doc = doc!(cx.editor); - let mode = doc.mode(); + let mode = cx.editor.mode(); + let (view, _) = current!(cx.editor); + let focus = view.id; if let Some(on_next_key) = self.on_next_key.take() { // if there's a command waiting input, do that first @@ -1210,48 +1323,20 @@ impl Component for EditorView { if cx.editor.should_close() { return EventResult::Ignored(None); } - let config = cx.editor.config(); - let (view, doc) = current!(cx.editor); - view.ensure_cursor_in_view(doc, config.scrolloff); - - // Store a history state if not in insert mode. This also takes care of - // committing changes when leaving insert mode. - if doc.mode() != Mode::Insert { - doc.append_changes_to_history(view.id); - } - - // mode transitions - match (mode, doc.mode()) { - (Mode::Normal, Mode::Insert) => { - // HAXX: if we just entered insert mode from normal, clear key buf - // and record the command that got us into this mode. - - // how we entered insert mode is important, and we should track that so - // we can repeat the side effect. - - self.last_insert.0 = match self.keymaps.get(mode, key) { - KeymapResult::Matched(command) => command, - // FIXME: insert mode can only be entered through single KeyCodes - _ => unimplemented!(), - }; - self.last_insert.1.clear(); - commands::signature_help_impl( - &mut cx, - commands::SignatureHelpInvoked::Automatic, - ); - } - (Mode::Insert, Mode::Normal) => { - // if exiting insert mode, remove completion - self.completion = None; - // TODO: Use an on_mode_change hook to remove signature help - context.jobs.callback(async { - let call: job::Callback = Box::new(|_editor, compositor| { - compositor.remove(SignatureHelp::ID); - }); - Ok(call) - }); + // if the focused view still exists and wasn't closed + if cx.editor.tree.contains(focus) { + let config = cx.editor.config(); + let mode = cx.editor.mode(); + let view = cx.editor.tree.get_mut(focus); + let doc = cx.editor.documents.get_mut(&view.doc).unwrap(); + + view.ensure_cursor_in_view(doc, config.scrolloff); + + // Store a history state if not in insert mode. This also takes care of + // committing changes when leaving insert mode. + if mode != Mode::Insert { + doc.append_changes_to_history(view.id); } - _ => (), } EventResult::Consumed(callback) @@ -1266,8 +1351,22 @@ impl Component for EditorView { // clear with background color surface.set_style(area, cx.editor.theme.get("ui.background")); let config = cx.editor.config(); - // if the terminal size suddenly changed, we need to trigger a resize + + // check if bufferline should be rendered + use helix_view::editor::BufferLine; + let use_bufferline = match config.bufferline { + BufferLine::Always => true, + BufferLine::Multiple if cx.editor.documents.len() > 1 => true, + _ => false, + }; + + // -1 for commandline and -1 for bufferline let mut editor_area = area.clip_bottom(1); + if use_bufferline { + editor_area = editor_area.clip_top(1); + } + + // if the terminal size suddenly changed, we need to trigger a resize if self.explorer.is_some() && (config.explorer.is_embed()) { editor_area = editor_area.clip_left(config.explorer.column_width as u16 + 2); } @@ -1278,6 +1377,11 @@ impl Component for EditorView { explore.content.render(area, surface, cx); } } + cx.editor.resize(editor_area); + + if use_bufferline { + Self::render_bufferline(cx.editor, area.with_height(1), surface); + } for (view, is_focused) in cx.editor.tree.views() { let doc = cx.editor.document(view.doc).unwrap(); diff --git a/helix-term/src/ui/explore.rs b/helix-term/src/ui/explore.rs index d2978c8b..2f32adcd 100644 --- a/helix-term/src/ui/explore.rs +++ b/helix-term/src/ui/explore.rs @@ -244,7 +244,7 @@ pub struct Explorer { state: State, prompt: Option<(PromptAction, Prompt)>, #[allow(clippy::type_complexity)] - on_next_key: Option EventResult>>, + on_next_key: Option EventResult>>, #[allow(clippy::type_complexity)] repeat_motion: Option>, } @@ -552,24 +552,26 @@ impl Explorer { } } - fn handle_filter_event(&mut self, event: KeyEvent, cx: &mut Context) -> EventResult { + fn handle_filter_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult { let (action, mut prompt) = self.prompt.take().unwrap(); - match event.into() { + match event { key!(Tab) | key!(Down) | ctrl!('j') => { self.tree.clean_recycle(); return self .tree - .handle_event(Event::Key(event), cx, &mut self.state); + .handle_event(Event::Key(event.clone()), cx, &mut self.state); } key!(Enter) => { self.tree.clean_recycle(); return self .tree - .handle_event(Event::Key(event), cx, &mut self.state); + .handle_event(Event::Key(event.clone()), cx, &mut self.state); } key!(Esc) | ctrl!('c') => self.tree.restore_recycle(), _ => { - if let EventResult::Consumed(_) = prompt.handle_event(Event::Key(event), cx) { + if let EventResult::Consumed(_) = + prompt.handle_event(&Event::Key(event.clone()), cx) + { self.tree.filter(prompt.line(), cx, &mut self.state); } self.prompt = Some((action, prompt)); @@ -578,17 +580,17 @@ impl Explorer { EventResult::Consumed(None) } - fn handle_search_event(&mut self, event: KeyEvent, cx: &mut Context) -> EventResult { + fn handle_search_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult { let (action, mut prompt) = self.prompt.take().unwrap(); let search_next = match action { PromptAction::Search(search_next) => search_next, _ => return EventResult::Ignored(None), }; - match event.into() { + match event { key!(Tab) | key!(Down) | ctrl!('j') => { return self .tree - .handle_event(Event::Key(event), cx, &mut self.state) + .handle_event(Event::Key(event.clone()), cx, &mut self.state) } key!(Enter) => { let search_str = prompt.line().clone(); @@ -612,11 +614,13 @@ impl Explorer { } return self .tree - .handle_event(Event::Key(event), cx, &mut self.state); + .handle_event(Event::Key(event.clone()), cx, &mut self.state); } key!(Esc) | ctrl!('c') => self.tree.restore_view(), _ => { - if let EventResult::Consumed(_) = prompt.handle_event(Event::Key(event), cx) { + if let EventResult::Consumed(_) = + prompt.handle_event(&Event::Key(event.clone()), cx) + { if search_next { self.tree.search_next(cx, prompt.line(), &mut self.state); } else { @@ -629,7 +633,7 @@ impl Explorer { EventResult::Consumed(None) } - fn handle_prompt_event(&mut self, event: KeyEvent, cx: &mut Context) -> EventResult { + fn handle_prompt_event(&mut self, event: &KeyEvent, cx: &mut Context) -> EventResult { match &self.prompt { Some((PromptAction::Search(_), _)) => return self.handle_search_event(event, cx), Some((PromptAction::Filter, _)) => return self.handle_filter_event(event, cx), @@ -640,7 +644,7 @@ impl Explorer { _ => return EventResult::Ignored(None), }; let line = prompt.line(); - match (action, event.into()) { + match (action, event) { (PromptAction::Mkdir, key!(Enter)) => { if let Err(e) = self.new_path(line, true) { cx.editor.set_error(format!("{e}")) @@ -672,7 +676,7 @@ impl Explorer { } (_, key!(Esc) | ctrl!('c')) => {} _ => { - prompt.handle_event(Event::Key(event), cx); + prompt.handle_event(&Event::Key(event.clone()), cx); self.prompt = Some((action, prompt)); } } @@ -714,7 +718,7 @@ impl Explorer { impl Component for Explorer { /// Process input events, return true if handled. - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let key_event = match event { Event::Key(event) => event, Event::Resize(..) => return EventResult::Consumed(None), @@ -727,7 +731,7 @@ impl Component for Explorer { return on_next_key(cx, self, key_event); } - if let EventResult::Consumed(c) = self.handle_prompt_event(key_event, cx) { + if let EventResult::Consumed(c) = self.handle_prompt_event(&key_event, cx) { return EventResult::Consumed(c); } @@ -737,7 +741,7 @@ impl Component for Explorer { } }))); - match key_event.into() { + match key_event { key!(Esc) => self.unfocus(), ctrl!('c') => return close_fn, key!('n') => { @@ -768,7 +772,7 @@ impl Component for Explorer { key!('?') => self.new_search_prompt(false), key!('m') => { self.on_next_key = Some(Box::new(|_, explorer, event| { - match event.into() { + match event { key!('d') => explorer.new_mkdir_prompt(), key!('f') => explorer.new_create_file_prompt(), _ => return EventResult::Ignored(None), @@ -778,7 +782,7 @@ impl Component for Explorer { } key!('r') => { self.on_next_key = Some(Box::new(|cx, explorer, event| { - match event.into() { + match event { key!('d') => explorer.new_remove_dir_prompt(cx), key!('f') => explorer.new_remove_file_prompt(cx), _ => return EventResult::Ignored(None), @@ -788,7 +792,7 @@ impl Component for Explorer { } _ => { self.tree - .handle_event(Event::Key(key_event), cx, &mut self.state); + .handle_event(Event::Key(key_event.clone()), cx, &mut self.state); } } diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index ce51ecbc..1d247b1a 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -225,9 +225,9 @@ impl Menu { use super::PromptEvent as MenuEvent; impl Component for Menu { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let event = match event { - Event::Key(event) => event, + Event::Key(event) => *event, _ => return EventResult::Ignored(None), }; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 046ca26d..f5dea047 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -39,10 +39,10 @@ pub fn prompt( completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static, ) { - show_prompt( - cx, - Prompt::new(prompt, history_register, completion_fn, callback_fn), - ); + let mut prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn); + // Calculate the initial completion + prompt.recalculate_completion(cx.editor); + cx.push_layer(Box::new(prompt)); } pub fn prompt_with_input( @@ -53,15 +53,8 @@ pub fn prompt_with_input( completion_fn: impl FnMut(&Editor, &str) -> Vec + 'static, callback_fn: impl FnMut(&mut crate::compositor::Context, &str, PromptEvent) + 'static, ) { - show_prompt( - cx, - Prompt::new(prompt, history_register, completion_fn, callback_fn).with_line(input), - ); -} - -fn show_prompt(cx: &mut crate::commands::Context, mut prompt: Prompt) { - // Calculate initial completion - prompt.recalculate_completion(cx.editor); + let prompt = Prompt::new(prompt, history_register, completion_fn, callback_fn) + .with_line(input, cx.editor); cx.push_layer(Box::new(prompt)); } @@ -385,7 +378,7 @@ pub mod completers { } // TODO: we could return an iter/lazy thing so it can fetch as many as it needs. - fn filename_impl(editor: &Editor, input: &str, filter_fn: F) -> Vec + fn filename_impl(_editor: &Editor, input: &str, filter_fn: F) -> Vec where F: Fn(&ignore::DirEntry) -> FileMatch, { @@ -417,7 +410,7 @@ pub mod completers { let mut files: Vec<_> = WalkBuilder::new(&dir) .hidden(false) - .follow_links(editor.config().file_picker.follow_symlinks) + .follow_links(false) // We're scanning over depth 1 .max_depth(Some(1)) .build() .filter_map(|file| { diff --git a/helix-term/src/ui/overlay.rs b/helix-term/src/ui/overlay.rs index 1cd60be5..0b8a93ae 100644 --- a/helix-term/src/ui/overlay.rs +++ b/helix-term/src/ui/overlay.rs @@ -61,7 +61,7 @@ impl Component for Overlay { Some((width, height)) } - fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult { self.content.handle_event(event, ctx) } diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 169aeadd..24d3b288 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -260,7 +260,7 @@ impl Component for FilePicker { } } - fn handle_event(&mut self, event: Event, ctx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult { // TODO: keybinds for scrolling preview self.picker.handle_event(event, ctx) } @@ -470,12 +470,20 @@ impl Picker { self.filters .extend(self.matches.iter().map(|(index, _)| *index)); self.filters.sort_unstable(); // used for binary search later - self.prompt.clear(cx); + self.prompt.clear(cx.editor); } pub fn toggle_preview(&mut self) { self.show_preview = !self.show_preview; } + + fn prompt_handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { + if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) { + // TODO: recalculate only if pattern changed + self.score(); + } + EventResult::Consumed(None) + } } // process: @@ -489,9 +497,10 @@ impl Component for Picker { Some(viewport) } - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let key_event = match event { - Event::Key(event) => event, + Event::Key(event) => *event, + Event::Paste(..) => return self.prompt_handle_event(event, cx), Event::Resize(..) => return EventResult::Consumed(None), _ => return EventResult::Ignored(None), }; @@ -548,10 +557,7 @@ impl Component for Picker { self.toggle_preview(); } _ => { - if let EventResult::Consumed(_) = self.prompt.handle_event(event, cx) { - // TODO: recalculate only if pattern changed - self.score(); - } + self.prompt_handle_event(event, cx); } } diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index af8e53c5..3c140da4 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -138,9 +138,9 @@ impl Popup { } impl Component for Popup { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let key = match event { - Event::Key(event) => event, + Event::Key(event) => *event, Event::Resize(_, _) => { // TODO: calculate inner area, call component's handle_event with that area return EventResult::Ignored(None); diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 4cb38fb0..24fc8233 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -83,10 +83,11 @@ impl Prompt { } } - pub fn with_line(mut self, line: String) -> Self { + pub fn with_line(mut self, line: String, editor: &Editor) -> Self { let cursor = line.len(); self.line = line; self.cursor = cursor; + self.recalculate_completion(editor); self } @@ -95,6 +96,7 @@ impl Prompt { } pub fn recalculate_completion(&mut self, editor: &Editor) { + self.exit_selection(); self.completion = (self.completion_fn)(editor, &self.line); } @@ -213,12 +215,12 @@ impl Prompt { self.cursor = pos; } self.recalculate_completion(cx.editor); - self.exit_selection(); } - pub fn insert_str(&mut self, s: &str) { + pub fn insert_str(&mut self, s: &str, editor: &Editor) { self.line.insert_str(self.cursor, s); self.cursor += s.len(); + self.recalculate_completion(editor); } pub fn move_cursor(&mut self, movement: Movement) { @@ -234,65 +236,65 @@ impl Prompt { self.cursor = self.line.len(); } - pub fn delete_char_backwards(&mut self, cx: &Context) { + pub fn delete_char_backwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::BackwardChar(1)); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; - self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn delete_char_forwards(&mut self, cx: &Context) { + pub fn delete_char_forwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::ForwardChar(1)); self.line.replace_range(self.cursor..pos, ""); - self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn delete_word_backwards(&mut self, cx: &Context) { + pub fn delete_word_backwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::BackwardWord(1)); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; - self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn delete_word_forwards(&mut self, cx: &Context) { + pub fn delete_word_forwards(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::ForwardWord(1)); self.line.replace_range(self.cursor..pos, ""); - self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn kill_to_start_of_line(&mut self, cx: &Context) { + pub fn kill_to_start_of_line(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::StartOfLine); self.line.replace_range(pos..self.cursor, ""); self.cursor = pos; - self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn kill_to_end_of_line(&mut self, cx: &Context) { + pub fn kill_to_end_of_line(&mut self, editor: &Editor) { let pos = self.eval_movement(Movement::EndOfLine); self.line.replace_range(self.cursor..pos, ""); - self.exit_selection(); - self.recalculate_completion(cx.editor); + self.recalculate_completion(editor); } - pub fn clear(&mut self, cx: &Context) { + pub fn clear(&mut self, editor: &Editor) { self.line.clear(); self.cursor = 0; - self.recalculate_completion(cx.editor); - self.exit_selection(); + self.recalculate_completion(editor); } - pub fn change_history(&mut self, register: &[String], direction: CompletionDirection) { + pub fn change_history( + &mut self, + cx: &mut Context, + register: char, + direction: CompletionDirection, + ) { + let register = cx.editor.registers.get_mut(register).read(); + if register.is_empty() { return; } @@ -312,6 +314,7 @@ impl Prompt { self.history_pos = Some(index); self.move_end(); + self.recalculate_completion(cx.editor); } pub fn change_completion_selection(&mut self, direction: CompletionDirection) { @@ -466,9 +469,14 @@ impl Prompt { } impl Component for Prompt { - fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { + fn handle_event(&mut self, event: &Event, cx: &mut Context) -> EventResult { let event = match event { - Event::Key(event) => event, + Event::Paste(data) => { + self.insert_str(data, cx.editor); + self.recalculate_completion(cx.editor); + return EventResult::Consumed(None); + } + Event::Key(event) => *event, Event::Resize(..) => return EventResult::Consumed(None), _ => return EventResult::Ignored(None), }; @@ -489,16 +497,18 @@ impl Component for Prompt { ctrl!('f') | key!(Right) => self.move_cursor(Movement::ForwardChar(1)), ctrl!('e') | key!(End) => self.move_end(), ctrl!('a') | key!(Home) => self.move_start(), - ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => self.delete_word_backwards(cx), - alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx), - ctrl!('k') => self.kill_to_end_of_line(cx), - ctrl!('u') => self.kill_to_start_of_line(cx), + ctrl!('w') | alt!(Backspace) | ctrl!(Backspace) => { + self.delete_word_backwards(cx.editor) + } + alt!('d') | alt!(Delete) | ctrl!(Delete) => self.delete_word_forwards(cx.editor), + ctrl!('k') => self.kill_to_end_of_line(cx.editor), + ctrl!('u') => self.kill_to_start_of_line(cx.editor), ctrl!('h') | key!(Backspace) => { - self.delete_char_backwards(cx); + self.delete_char_backwards(cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } ctrl!('d') | key!(Delete) => { - self.delete_char_forwards(cx); + self.delete_char_forwards(cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } ctrl!('s') => { @@ -515,27 +525,32 @@ impl Component for Prompt { ); let line = text.slice(range.from()..range.to()).to_string(); if !line.is_empty() { - self.insert_str(line.as_str()); + self.insert_str(line.as_str(), cx.editor); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } key!(Enter) => { if self.selection.is_some() && self.line.ends_with(std::path::MAIN_SEPARATOR) { self.recalculate_completion(cx.editor); - self.exit_selection(); } else { + let last_item = self + .history_register + .and_then(|reg| cx.editor.registers.last(reg).cloned()) + .map(|entry| entry.into()) + .unwrap_or_else(|| Cow::from("")); + // handle executing with last command in history if nothing entered let input: Cow = if self.line.is_empty() { - // latest value in the register list - self.history_register - .and_then(|reg| cx.editor.registers.last(reg).cloned()) - .map(|entry| entry.into()) - .unwrap_or_else(|| Cow::from("")) + last_item } else { - if let Some(register) = self.history_register { + if last_item != self.line { // store in history - let register = cx.editor.registers.get_mut(register); - register.push(self.line.clone()); + if let Some(register) = self.history_register { + cx.editor + .registers + .get_mut(register) + .push(self.line.clone()); + }; } self.line.as_str().into() @@ -548,15 +563,13 @@ impl Component for Prompt { } ctrl!('p') | key!(Up) => { if let Some(register) = self.history_register { - let register = cx.editor.registers.get_mut(register); - self.change_history(register.read(), CompletionDirection::Backward); + self.change_history(cx, register, CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } ctrl!('n') | key!(Down) => { if let Some(register) = self.history_register { - let register = cx.editor.registers.get_mut(register); - self.change_history(register.read(), CompletionDirection::Forward); + self.change_history(cx, register, CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update); } } @@ -565,7 +578,6 @@ impl Component for Prompt { // if single completion candidate is a directory list content in completion if self.completion.len() == 1 && self.line.ends_with(std::path::MAIN_SEPARATOR) { self.recalculate_completion(cx.editor); - self.exit_selection(); } (self.callback_fn)(cx, &self.line, PromptEvent::Update) } @@ -597,8 +609,8 @@ impl Component for Prompt { .read(c) .and_then(|r| r.first()) .map_or("", |r| r.as_str()), + context.editor, ); - prompt.recalculate_completion(context.editor); })); (self.callback_fn)(cx, &self.line, PromptEvent::Update); return EventResult::Consumed(None); diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs index 75e5dbd7..365e1ca9 100644 --- a/helix-term/src/ui/statusline.rs +++ b/helix-term/src/ui/statusline.rs @@ -160,7 +160,7 @@ where format!( " {} ", if visible { - match context.doc.mode() { + match context.editor.mode() { Mode::Insert => "INS", Mode::Select => "SEL", Mode::Normal => "NOR", @@ -171,7 +171,7 @@ where } ), if visible && context.editor.config().color_modes { - match context.doc.mode() { + match context.editor.mode() { Mode::Insert => Some(context.editor.theme.get("ui.statusline.insert")), Mode::Select => Some(context.editor.theme.get("ui.statusline.select")), Mode::Normal => Some(context.editor.theme.get("ui.statusline.normal")), @@ -329,7 +329,7 @@ fn render_file_type(context: &mut RenderContext, write: F) where F: Fn(&mut RenderContext, String, Option