Skip to content

Conversation

justinmk
Copy link
Member

@justinmk justinmk commented Mar 14, 2016

This PR improves paste behavior and performance.

Tasks:

  • implement as callback instead of pre/post autocmds
  • Indicate paste start/stop as parameter to handler?
  • generalize string_to_array() ?
  • API: nvim_put [WIP] nvim_put #6819
  • API: nvim_paste
  • redo (dot-repeat)
  • 'nomodifiable' buffer
  • documentation

TODO (future):

@marvim marvim added the WIP label Mar 14, 2016
@justinmk justinmk force-pushed the paste branch 2 times, most recently from c9b85ab to 8898ee5 Compare March 14, 2016 03:41
@@ -3435,11 +3435,11 @@ getchar([expr]) *getchar()*
:endfunction
<
You may also receive synthetic characters, such as
|<CursorHold>|. Often you will want to ignore this and get
|<LeftMouse>|. Often you will want to ignore this and get

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

This comment was marked as resolved.

@tarruda
Copy link
Member

tarruda commented Mar 14, 2016

@justinmk I'm not sure I understand the goal behind this PR, is it to decouple the logic of automatically toggling paste with a special key?

If so, I don't see much gain here: We still have bracketed paste parsing hardcoded into the TUI, only the set paste logic will be moved to vimscript(even so, what would be the purpose of allowing autocmds to be attached to paste?). But I do see a worthly goal in this:

I am wondering if it would be a good idea to provide some internal generic mechanism for placing K_EVENT in the input stream and associating it with a callback.

If you could implement this, then yes, we could completely decouple bracketed paste(and possibly many other terminal sequences) from the tui. But it should not be K_EVENT, since it is already used for another purpose(and no, we can't place arbitrary events so they are processed at specific times during input processing).

However, this seems to overlap with @bfredl's work on #4419 :

map <command> <Paste> if &paste | set nopaste | else | set paste

Maybe it would be even better if we simply supported user-defined synthetic keys:

map <command> <User:SOME_KEY_ID> if &paste | set nopaste | else | set paste

The question is: How can a plugin tell the UI to put such synthetic keys into the input buffer? Right now we can't, but the TUI will soon allow configuration, so a bracketed paste plugin could be summarized to something like this:

let g:tui_keys_override = {
\  '\x1b[200~': '<User:Paste>'
\  '\x1b[201~': '<User:Paste>'
\}
imap <command> <User:Paste> if &paste | set nopaste | else | set paste

That is, the plugin asks the tui to translate bracketed pastes into <user:paste>, and maps it to a code that toggles the paste option.

src/nvim/main.c Outdated
@@ -328,6 +326,9 @@ int main(int argc, char **argv)
"'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, '')})");
#undef PROTO

do_cmdline_cmd("autocmd PastePre * set paste|if mode()!~#'c\\|t'|call feedkeys('\x1C\xEi','n')|endif");
do_cmdline_cmd("autocmd PastePost * set nopaste|if mode()!~#'c\\|t'|call feedkeys('\x1C\xE','n')|endif");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

\xE looks weird, better write \x0E.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though no, it is better to write this as \"\\<C-\\>\\<C-n>\".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I would also suggest to create autocmd group __Builtin for all built-in autocommands (currently only this one and term://* BufReadCmd), so that they are easy to identify, making user aware of what he sees and, possibly, changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex is better written as stridx('ct', mode()) == -1 or mode() !~# '[ct]'.

@justinmk

This comment has been minimized.

@bfredl

This comment has been minimized.

@justinmk

This comment has been minimized.

@bfredl

This comment has been minimized.

@ZyX-I
Copy link
Contributor

ZyX-I commented Mar 14, 2016

@justinmk Bracketed paste is terminal-UI-specific thing. GUIs do not have anything like this, all they have are events from user: clipboard and mouse. There is no “external paste”, there are things like "+p, :put and events which map to this. In any case clipboard contents is received in one piece, it is not a stream like {paste start}{contents}{paste end}.

@ZyX-I
Copy link
Contributor

ZyX-I commented Mar 14, 2016

@justinmk <MiddleMouse>, <C-v>, etc are handled by the application itself and it is UI which initiates pasting upon receiving an appropriate event. There are conventions and toolkits which follow conventions by default, but they are not enforced and toolkits can be told to not follow them as well. With bracketed paste it is terminal which initiates pasting, which is an “external” application.

@bfredl

This comment has been minimized.

@ZyX-I

This comment has been minimized.

@justinmk

This comment has been minimized.

@bfredl

This comment has been minimized.

@ZyX-I
Copy link
Contributor

ZyX-I commented Mar 14, 2016

By the way, consider that user pressed <S-Insert>foo, which translated into {paste start}{inserted contents}{paste end}foo. Can I use Paste* events to get {inserted contents} without foo?

Also I saw that you test ignoring second {paste start} in {paste start}{paste start}. This is incorrect, I checked konsole using

echo $'\e[200~' | xclip -i
echo $'\e[?2004h' ; stty -echo ; cat -v

(with <MiddleMouse> after last <CR>) and got

^[[200~^[[200~
^[[201~

: no escaping. Spurious {paste start} should be part of the pasted text, not ignored. Double {paste end}/{paste end} without {paste start} should be ignored though I think.

@justinmk
Copy link
Member Author

Can I use Paste* events to get {inserted contents} without foo?

The text is not provided in any register or v: variable, but the buffer contents at time of PastePost should not contain foo. And you could compare this to buffer contents at time of PastePre.

Spurious {paste start} should be part of the pasted text, not ignored. Double {paste end}/{paste end} without {paste start} should be ignored though I think.

Thanks, I will fix it.

@tarruda
Copy link
Member

tarruda commented Mar 14, 2016

I don't think so. The toggle does not work. It's broken. The system needs to know "this is the start" and "this is the end". The state of 'paste' cannot be a factor in this, the user may already have it enabled.

The old code was sending to toggle on and another one later in the stream to toggle off. This is the cause of bugs we've had for 6 months.

Alright. What about your suggestion of adding a generic mechanism to allow execution of vimscript when a certain synthetic keys are processed? Assuming the TUI allows the user to override terminal code parsing(it will), a bracketed paste plugin could do this:

let g:tui_keys_override = {
\  '\x1b[200~': '<User:PasteEnable>'
\  '\x1b[201~': '<User:PasteDisable>'
\}

It seems like something that could work. Even FocusGain/FocusLost could work nicely on such infrastructure.

@ZyX-I
Copy link
Contributor

ZyX-I commented Mar 14, 2016

I also do not see tests for pasting while in all other modes (visual, select, operator-pending, replace, virtual replace).

@tarruda
Copy link
Member

tarruda commented Mar 14, 2016

Adapting my previous comment to @bfredl's idea, here's how bracketed paste would be implemented as a plugin:

let g:tui_keys_override = {
\  '\x1b[200~': '<Cmd>set paste<cr>'
\  '\x1b[201~': '<Cmd>set nopaste<cr>'
\}

@justinmk
Copy link
Member Author

Assuming the TUI allows the user to override terminal code parsing(it will), a bracketed paste plugin could do this:
let g:tui_keys_override

I suppose that would be fine if no one is convinced that PastePre/PastePost will be useful for GUIs (I gave use-cases above). But:

  • It will block this fix for a long time. Paste has been broken for more than 6 months.
  • It means we're back to asking users to fiddle with termcodes--even when the work was already done to abstract it for certain cases.
  • It won't apply to GUIs, so configuration of this behavior won't be consistent across UIs.

@bfredl
Copy link
Member

bfredl commented Mar 14, 2016

Hmm I wonder how to implement "Send paste sequences to :terminal" with that. Is the remapping done by the tui module or do you mean runtime will add those mappings to insert mode?

@ZyX-I
Copy link
Contributor

ZyX-I commented Mar 14, 2016

@bfredl Neither of suggested variants allow implementing “send paste sequences to :terminal”, because this needs hooking "*p. Though bracket pasting to terminal may be implemented based on PastePre/PastePost, this also needs getting terminal state.

This is "readfile()-style", see also ":help channel-lines".
Fixes strange behavior where sometimes the buffer contents of a series
of paste chunks (vim._paste) would be out-of-order.

Now the tui_spec.lua screen-tests are much more reliable. But they still
sometimes fail because of off-by-one cursor (caused by "typeahead race"
resulting in wrong mode; fixed later in this patch-series).
- Send `phase` parameter to the paste handler.
- Redraw at intervals and when paste terminates.
- Show "..." throbber during paste to indicate activity.
Workaround this failure:

    [  ERROR   ] test/functional/terminal/tui_spec.lua @ 192: TUI paste: exactly 64 bytes
    test/functional/helpers.lua:403:
    retry() attempts: 478
    test/functional/terminal/tui_spec.lua:201: Expected objects to be the same.
    Passed in:
    (table: 0x47cd77e8) {
     *[1] = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz endz' }
    Expected:
    (table: 0x47cd7830) {
     *[1] = 'zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz end' }

This happens because `curwin->w_cursor.col` is sometimes decremented at
the end of `do_put`... because the editor is in Normal-mode instead of
the expected Insert-mode.

Caused by "typeahead race" (neovim#10826): there may be queued input in the
main thread not yet processed, thus the editor mode (`State` global)
will be "wrong" during paste. Example: input "i" followed immediately by
a paste sequence:

    i<start-paste>...<stop-paste>
    ^
     "i" does not get processed in time, so the editor is in
     Normal-mode instead of Insert-mode while handling the paste.

Attempted workarounds:
- vim.api.nvim_feedkeys('','x',false) in vim._paste()
- exec_normal() in tinput_wait_enqueue()
- LOOP_PROCESS_EVENTS(&main_loop,…,0) in tinput_wait_enqueue()

ref neovim#10826
- Normal-mode redo idiom(?): prepend "i" and append ESC.
- Insert-mode only needs AppendToRedobuffLit().
- Cmdline-mode: only paste the first line.
HACK: The cursor does not get repositioned after the paste completes.
Scheduling a dummy event seems to fix it.

Test case:
0. Revert this commit.
1. Paste some text in Normal-mode.
2. Notice the cursor is still in the cmdline area.
- Show error only once per "paste stream".
- Drain remaining chunks until phase=3.
- Lay groundwork for "cancel".
- Constrain semantics of "cancel" to mean "client must stop"; it is
  unrelated to presence of error(s).
- nvim_paste(): Marshal through luaeval() instead of nvim_execute_lua()
  because the latter seems to hide some errors.
- Handle 'nomodifiable' in `nvim_put()` explicitly.
- Require explicit `false` from `vim.paste()` in order to "cancel",
  otherwise assume true ("continue").
@justinmk
Copy link
Member Author

justinmk commented Aug 27, 2019

Resolution/Summary

vim-patch:8.1.1198
vim-patch:8.1.0224
vim-patch:8.0.1299
vim-patch:8.0.0569
vim-patch:8.0.0303
vim-patch:8.0.0296
vim-patch:8.0.0244
vim-patch:8.0.0238
vim-patch:8.0.0232
vim-patch:8.0.0231
vim-patch:8.0.0230
vim-patch:8.0.0210

Notes:

  • cursor is hidden after paste completes. Workaround: send dummy event 4344ac1:
    loop_schedule_deferred(&main_loop, event_create(x_dummy_event, 0));
    

Locked to keep the summary visible. You can always chat or open a ticket if you have new information/topics to discuss.

@justinmk justinmk force-pushed the paste branch 2 times, most recently from 99a1753 to fee2e1c Compare August 27, 2019 22:13
- Introduce TRY_WRAP() until we have an *architectural* solution.
  - TODO: bfredl idea: prepare error-handling at "top level" (nv_event).
- nvim_paste(): Revert luaeval() hack (see parent commit).
  - With TRY_WRAP() in nvim_put(), 'nomodifiable' error now correctly
    "bubbles up".
@justinmk justinmk merged commit 82d52b2 into neovim:master Aug 27, 2019
@justinmk justinmk deleted the paste branch August 27, 2019 23:56
@neovim neovim locked as resolved and limited conversation to collaborators Aug 27, 2019
@justinmk justinmk added the performance performance, latency, cpu/memory usage label Sep 5, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api libnvim, Nvim RPC API performance performance, latency, cpu/memory usage
Projects
None yet
9 participants