{"action":"created","issue":{"url":"https://api.github.com/repos/sindresorhus/pure/issues/143","labels_url":"https://api.github.com/repos/sindresorhus/pure/issues/143/labels{/name}","comments_url":"https://api.github.com/repos/sindresorhus/pure/issues/143/comments","events_url":"https://api.github.com/repos/sindresorhus/pure/issues/143/events","html_url":"https://github.com/sindresorhus/pure/issues/143","id":94671200,"number":143,"title":"Multiline ZLE buffer overwritten by preprompt update","user":{"login":"zmwangx","id":4149852,"avatar_url":"https://avatars.githubusercontent.com/u/4149852?v=3","gravatar_id":"","url":"https://api.github.com/users/zmwangx","html_url":"https://github.com/zmwangx","followers_url":"https://api.github.com/users/zmwangx/followers","following_url":"https://api.github.com/users/zmwangx/following{/other_user}","gists_url":"https://api.github.com/users/zmwangx/gists{/gist_id}","starred_url":"https://api.github.com/users/zmwangx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zmwangx/subscriptions","organizations_url":"https://api.github.com/users/zmwangx/orgs","repos_url":"https://api.github.com/users/zmwangx/repos","events_url":"https://api.github.com/users/zmwangx/events{/privacy}","received_events_url":"https://api.github.com/users/zmwangx/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":95,"created_at":"2015-07-13T08:55:37Z","updated_at":"2015-08-11T20:47:58Z","closed_at":null,"body":"A screencast should suffice to demonstrate this problem:\r\n\r\n[![asciicast](https://asciinema.org/a/645061r08iwz94hdxxtnfkmpz.png)](https://asciinema.org/a/645061r08iwz94hdxxtnfkmpz)\r\n\r\nThe problem is that the [`lines` variable in `prompt_pure_preprompt_render`](https://github.com/sindresorhus/pure/blob/5f5b990e2b1739ba359f08a33727c8bf107bf4f8/pure.zsh#L108) doesn't take into account current ZLE buffer lines (it only records the number of lines spanned by the preprompt), so if the buffer spans multiple lines, then the preprompt is updated in the wrong place.\r\n\r\n## Proposed fix\r\n\r\nA naive fix: ZLE has a read-only parameter `BUFFERLINES` available to ZLE widgets. We can thus register a ZLE widget, say `prompt_pure_record_buffer_lines`, that records `BUFFERLINES` in a global variable, say `prompt_pure_buffer_lines`. Then we can simply do `(( lines += prompt_pure_buffer_lines - 1 ))` to account for the buffer.\r\n\r\nHowever, there are two other technical challenges:\r\n\r\n1. The naive fix doesn't capture the case where the cursor is not at the end of the buffer. But this is easy to deal with. We can determine the absolute position of the cursor (from the beginning of the line) by summing the length of `PS1/PS2` (depending on `CONTEXT`) and the `CURSOR` parameter. (We already have a function `prompt_pure_string_length` to calculated the prompt-expanded length of a string.) Then we can determine the line where cursor is on by dividing by `COLUMNS`.\r\n\r\n2. The naive fix only works when the ZLE `CONTEXT` is `start`; if the `CONTEXT` is, say, `cont` (continuation of a command line), then the previous lines would have already been flushed from the ZLE buffer, so `BUFFERLINES` doesn't count those lines. As a concrete example, consider\r\n\r\n  ```\r\n> echo \"hello\r\ndquote> world\"_\r\n```\r\n\r\n  where `_` represents the cursor position. At that point, `BUFFERLINES` is 1, but we should count both lines in order to go back to the preprompt.\r\n\r\n  To deal with this scenario, the solution I came up with is to use an accumulator, say `prompt_pure_accumulated_buffer_lines`, which is cleared precmd and incremented by `BUFFERLINES` right before each `accept-line`. This would require replacing the default `accept-line` with a custom widget.\r\n\r\nI have an implementation of these ideas in my own prompt (the async model of which is largely based on pure). The relevant change set, which might be helpful, is here: https://github.com/zmwangx/prezto/compare/991dfe8...zmwangx:2ef0cfd."},"comment":{"url":"https://api.github.com/repos/sindresorhus/pure/issues/comments/130065833","html_url":"https://github.com/sindresorhus/pure/issues/143#issuecomment-130065833","issue_url":"https://api.github.com/repos/sindresorhus/pure/issues/143","id":130065833,"user":{"login":"zmwangx","id":4149852,"avatar_url":"https://avatars.githubusercontent.com/u/4149852?v=3","gravatar_id":"","url":"https://api.github.com/users/zmwangx","html_url":"https://github.com/zmwangx","followers_url":"https://api.github.com/users/zmwangx/followers","following_url":"https://api.github.com/users/zmwangx/following{/other_user}","gists_url":"https://api.github.com/users/zmwangx/gists{/gist_id}","starred_url":"https://api.github.com/users/zmwangx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zmwangx/subscriptions","organizations_url":"https://api.github.com/users/zmwangx/orgs","repos_url":"https://api.github.com/users/zmwangx/repos","events_url":"https://api.github.com/users/zmwangx/events{/privacy}","received_events_url":"https://api.github.com/users/zmwangx/received_events","type":"User","site_admin":false},"created_at":"2015-08-11T20:47:57Z","updated_at":"2015-08-11T20:47:57Z","body":"> I don't suppose you've come across something better\r\n\r\nNo I've not. [What I've been using](https://github.com/zmwangx/prezto/blob/bd6e370a8546959194b7d6bd57efeef2e02e48d3/modules/prompt/functions/prompt_zmwangx_setup#L196-L219) for the past three weeks in my prompt:\r\n\r\n```zsh\r\n# This function is used in comppostfuncs only, and sets the\r\n# prompt_zmwangx_preprompt_offscreen to true if it decides that the preprompt\r\n# will probably be pushed offscreen by completion listing.\r\n#\r\n# This function auto inserts itself into comppostfuncs.\r\nprompt_zmwangx_preprompt_offscreen_guard() {\r\n    setopt localoptions noshwordsplit noksharrays\r\n\r\n    # Compare the sum of the following three with the total number of lines available (i.e., LINES):\r\n    #\r\n    # * Potential number of lines occupied by preprompt;\r\n    # * Total number of lines occupied by the current command line (possibly multiline);\r\n    # * Potential number of lines occupied by completion listing.\r\n\r\n    # use prompt_zmwangx_last_preprompt_lines + 1 because occasionally the next preprompt will occupy one more line\r\n    local preprompt_lines=$(( prompt_zmwangx_last_preprompt_lines + 1 ))\r\n    local command_line_lines=$(( prompt_zmwangx_accumulated_buffer_lines + prompt_pure_ps_length / COLUMNS + BUFFERLINES ))\r\n    local completion_lines=$compstate[list_lines]\r\n\r\n    (( preprompt_lines + command_line_lines + completion_lines > LINES )) && prompt_zmwangx_preprompt_offscreen=true\r\n\r\n    # persist\r\n    comppostfuncs+=(prompt_zmwangx_preprompt_offscreen_guard)\r\n}\r\n```\r\n\r\nNo problems so far.\r\n\r\n> I'm also considering posting on the zsh-users/workers list\r\n\r\nPlease do."}}