sublimelsp / LSP-typescript

TypeScript, JavaScript support for Sublime LSP plugin

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

organizeImports in lsp_code_actions_on_save is making Undo unusable

alexkuz opened this issue · comments

Describe the bug
When source.organizeImports is enabled in lsp_code_actions_on_save setting, I am unable to use Undo action after the TypeScript file is saved.
(this might be a bug in LSP itself)

To Reproduce
Steps to reproduce the behavior:

  1. Enable organizeImports:
    "lsp_code_actions_on_save": {
    	"source.organizeImports": true
    },
    
  2. Create a file file.ts:
    import './a';
    import './b';
    
    console.log(1);
    
  3. Press Ctrl+Z - nothing seemingly happens (unless I press it fast enough multiple times)

Environment (please complete the following information):

  • OS: Linux Mint
  • Sublime Text version: 4143
  • LSP version: 1.22.0
  • LSP-Typescript version: 2.3.0

Additional context
Here's how undo stack looks like when I save the file:

>>> [view.command_history(i) for i in range(0,20)]
[('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1380], [[1, 0], [2, 0], '', 1380]]}, 1), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0)]

and here's how it looks after pressing Ctrl+Z 5 times:

>>> [view.command_history(i) for i in range(0,20)]
[('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1370], [[1, 0], [2, 0], '', 1370]]}, 1), ('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1372], [[1, 0], [2, 0], '', 1372]]}, 1), ('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1374], [[1, 0], [2, 0], '', 1374]]}, 1), ('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1376], [[1, 0], [2, 0], '', 1376]]}, 1), ('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1378], [[1, 0], [2, 0], '', 1378]]}, 1), ('lsp_apply_document_edit', {'changes': [[[0, 0], [1, 0], "import './a';\nimport './b';\n", 1380], [[1, 0], [2, 0], '', 1380]]}, 1), ('', None, 1), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0), ('', None, 0)]

so it looks like organizeImports reapplies every time I press Ctrl+Z (maybe twice?) - even though the sorting hasn't changed.

It's a know issue with Typescript where it provides "organize imports" when imports are already organized. LSP keeps applying this command until it times out. That's why you and up with your undo stack containing many of the same edits repeated.

There are probably some ways to work around this issue but it would probably have to be done in the base LSP package.

One way (but not a cheap and easy one) would be to try to apply the change without actually changing the file and see if anything has actually changed. If not then we could stop processing that code action. But it wouldn't be trivial to implement.

Or maybe we shouldn't be repeatedly asking servers for code actions and instead only do that once.

I feel like this might miss some fixes though. Especially with eslint where auto-fixing one issue might introduce another. I suppose it would be good to check what VSCode really does.

@rchl thank you for explanation. As a workaround I'll comment that extra call on code actions completion. Seems to be good enough for me.

One way (but not a cheap and easy one) would be to try to apply the change without actually changing the file and see if anything has actually changed.

I dug in the code a little bit and maybe it's easier to store view version somewhere and to roll it back if file's content didn't change, instead of always calling changes_count() directly. Still need to compare the whole file before-after though, so yeah, it's not cheap.

It's not possible to drop the last item from the undo stack, if that's what you'r thinking when saying "roll back".

Also the change_count is controlled by ST and incremented automatically on each change done to the view. We are just reading its value with change_count(), not changing it.

No, sorry for not been very clear, what I meant is now it works like this:

[code_actions]
version = change_count()
...
[edit]
(apply edits)
...
[code_actions]
if (version != change_count()):
  run again

instead it could be something like:

[code_actions]
version = change_count()
...
[edit]
edit_version = change_count()
(apply edits)
if (new_file != old_file):
  edit_version = change_count()
...
[code_actions]
if (version != edit_version):
  run again

so still applying all the edits, just not always relying on edit counter
(I'm just not sure what's the proper way to handle edit_version here)

If we would be manually comparing the contents to see if the file has changed then we would not have to rely on change count at all. The problem is that this is a lot more expensive to do.