Whenever a document is changed helix maps various positions like the
cursor or diagnostics through the `ChangeSet` applied to the document.
Currently, this mapping handles replacements as follows:
* Move position to the left for `Assoc::Before` (start of selection)
* Move position to the right for `Assoc::After` (end of selection)
However, when text is exactly replaced this can produce weird results
where the cursor is moved when it shouldn't. For example if `foo` is
selected and a separate cursor is placed on each character (`s.<ret>`)
and the text is replaced (for example `rx`) then the cursors are moved
to the side instead of remaining in place.
This change adds a special case to the mapping code of replacements:
If the deleted and inserted text have the same (char) length then
the position is returned as if the replacement doesn't exist.
only keep selections invariant under replacement
Keeping selections unchanged if they are inside an exact replacement
is intuitive. However, for diagnostics this is not desirable as
helix would otherwise fail to remove diagnostics if replacing parts
of the document.
Diagnostics are currently extended if text is inserted at their end. This is
desirable when inserting text after an identifier. For example consider:
let foo = 2;
--- unused variable
Renaming the identifier should extend the diagnostic:
let foobar = 2;
------ unused variable
This is currently implemented in helix but as a consequence adding whitespaces
or a type hint also extends the diagnostic:
let foo = 2;
-------- unused variable
let foo: Bar = 2;
-------- unused variable
In these cases the diagnostic should remain unchanged:
let foo = 2;
--- unused variable
let foo: Bar = 2;
--- unused variable
As a heuristic helix will now only extend diagnostics that end on a word char
if new chars are appended to the word (so not for punctuation/ whitespace).
The idea for this mapping was inspired for the word level tracking vscode uses
for many positions. While VSCode doesn't currently update diagnostics after
receiving publishDiagnostic it does use this system for inlay hints for example.
Similarly, the new association mechanism implemented here can be used for word
level tracking of inlay hints.
A similar mapping function is implemented for word starts. Together
these can be used to make a diagnostic stick to a word. If that word
is removed that diagnostic is automatically removed too. This is the exact
same behavior VSCode inlay hints eixibit.
Pascal and I discussed this and we think it's generally better to
take a 'RopeSlice' rather than a '&Rope'. The code block rendering
function in the markdown component module is a good example for how
this can be useful: we can remove an allocation of a rope and instead
directly turn a '&str' into a 'RopeSlice' which is very cheap.
A change to prefer 'RopeSlice' to '&Rope' whenever the rope isn't
modified would be nice, but it would be a very large diff (around 500+
500-). Starting off with just the syntax functions seems like a nice
middle-ground, and we can remove a Rope allocation because of it.
Co-authored-by: Pascal Kuthe <pascal.kuthe@semimod.de>
* correctly map unsorted positions
* Fix typo
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
---------
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
Currently, when forward deleting (`delete_char_forward` bound to `del`,
`delete_word_forward`, `kill_to_line_end`) the cursor is moved to the
left in append mode (or generally when the cursor is at the end of the
selection). For example in a document `|abc|def` (|indicates selection)
if enter append mode the cursor is moved to `c` and the selection
becomes: `|abcd|ef`. When deleting forward (`del`) `d` is deleted. The
expectation would be that the selection doesn't shrink so that `del`
again deletes `e` and then `f`. This would look as follows:
`|abcd|ef`
`|abce|f`
`|abcf|`
`|abc |`
This is inline with how other editors like kakoune work.
However, helix currently moves the selection backwards leading to the
following behavior:
`|abcd|ef`
`|abc|ef`
`|ab|ef`
`ef`
This means that `delete_char_forward` essentially acts like
`delete_char_backward` after deleting the first character in append
mode.
To fix the problem the cursor must be moved to the right while deleting
forward (first fix in this commit). Furthermore, when the EOF char is
reached a newline char must be inserted (just like when entering
appendmode) to prevent the cursor from moving to the right
Some deletion operations (especially those that use indentation)
can generate overlapping deletion ranges when using multiple cursors.
To fix that problem a new `Transaction::delete` and
`Transaction:delete_by_selection` function were added. These functions
merge overlapping deletion ranges instead of generating an invalid
transaction. This merging of changes is only possible for deletions
and not for other changes and therefore require its own function.
The function has been used in all commands that currently delete
text by using `Transaction::change_by_selection`.
This commit adds new functions to `Transaction` that allow creating
edits that might potentially overlap. Any change that overlaps
previous changes is ignored. Furthermore, a utility method is added
that also drops selections associated with dropped changes (for
transactions that are created from a selection).
This is needed to avoid crashes when applying multicursor
autocompletions, as the edit from a previous cursor may overlap
with the next cursor/edit.
* properly handle LSP position encoding
* add debug assertion to Transaction::change
* Apply suggestions from code review
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
---------
Co-authored-by: Michael Davis <mcarsondavis@gmail.com>
* Add auto pairs for same-char pairs
* Add unit tests for all existing functionality
* Add auto pairs for same-char pairs (quotes, etc). Account for
apostrophe in prose by requiring both sides of the cursor to be
non-pair chars or whitespace. This also incidentally will work for
avoiding a double single quote in lifetime annotations, at least until
<> is added
* Slight factor of moving the cursor transform of the selection to
inside the hooks. This will enable doing auto pairing with selections,
and fixing the bug where auto pairs destroy the selection.
Fixes#1014