-
Notifications
You must be signed in to change notification settings - Fork 79
Use ergochat/readline #199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
vishesh92
wants to merge
4
commits into
main
Choose a base branch
from
update-readline-and-remove-vendors
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| <!-- | ||
| Licensed to the Apache Software Foundation (ASF) under one | ||
| or more contributor license agreements. See the NOTICE file | ||
| distributed with this work for additional information | ||
| regarding copyright ownership. The ASF licenses this file | ||
| to you under the Apache License, Version 2.0 (the | ||
| "License"); you may not use this file except in compliance | ||
| with the License. You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, | ||
| software distributed under the License is distributed on an | ||
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| KIND, either express or implied. See the License for the | ||
| specific language governing permissions and limitations | ||
| under the License. | ||
| --> | ||
|
|
||
| # Local patches to vendored dependencies | ||
|
|
||
| Some files under `vendor/` carry local changes that are **not** present in the | ||
| upstream release. Running `go mod vendor` reverts them silently — the build | ||
| still succeeds, but the resulting binary loses the fixes. Every local change | ||
| must therefore be recorded here. | ||
|
|
||
| After any `go mod vendor`, re-apply the patches: | ||
|
|
||
| make vendor-patch | ||
|
|
||
| which is equivalent to: | ||
|
|
||
| go mod vendor | ||
| git apply patches/*.patch | ||
|
|
||
| ## `ergochat-readline-v0.1.3.patch` | ||
|
|
||
| Applies to `github.com/ergochat/readline` v0.1.3. Three changes: | ||
|
|
||
| ### 1. `complete.go` — insert the value, display the detail | ||
|
|
||
| CloudMonkey builds completion candidates as `<value> (<detail>)` so the user | ||
| can see what a UUID refers to (see `cli/completer.go`). Upstream's | ||
| `AutoCompleter.Do` returns a single slice that is used both for display *and* | ||
| for insertion, so without this patch the ` (<detail>)` suffix is written into | ||
| the command line. | ||
|
|
||
| `writeRunes` strips everything from the ` … (` boundary before inserting, and | ||
| `truncateBufferAfterLastEqual` drops the partial text after the last `=` when | ||
| the candidate matched on its detail rather than on its value. | ||
|
|
||
| Originally added for #133 and #196 against `chzyer/readline`; forward-ported | ||
| when the library was switched to `ergochat/readline`. | ||
|
|
||
| ### 2. `complete.go` — redraw the prompt in `CompleteRefresh` | ||
|
|
||
| The `\033[J` erase issued while rendering the candidate grid can clear the | ||
| prompt line, leaving the prompt blank until the next keystroke. The patch | ||
| redraws the prompt and buffer contents after the erase. | ||
|
|
||
| This is an upstream rendering bug, not a CloudMonkey-specific need. | ||
|
|
||
| ### 3. `operation.go` — bound the cursor position query | ||
|
|
||
| `Runes()` calls `getAndSetOffset(nil)` before printing every prompt, which | ||
| sends a DSR cursor position request (`ESC[6n`) and blocks on the reply with a | ||
| nil deadline — i.e. forever. Under a pty whose emulator never answers | ||
| (`expect`, `pexpect`, Ansible with a pty, some CI runners) `cmk` prints its | ||
| banner and then hangs with no prompt. | ||
|
|
||
| The patch passes a deadline of `dsrTimeout` (250ms, the constant upstream | ||
| already uses in `waitForDSR`). On expiry `GetCursorPosition` returns an error, | ||
| `getAndSetOffset` leaves the offset unchanged and the prompt is printed anyway. | ||
| A late CPR reply is still recognised and discarded by `consumeANSIEscape`, so | ||
| it is never mistaken for user input. | ||
|
|
||
| The timeout would otherwise be paid before *every* prompt, so the result is | ||
| latched: on `deadlineExceeded` the `dsrUnsupported` flag is set and no further | ||
| queries are made for the life of the session. A terminal that answers — even | ||
| one that takes 150ms to do so — never sets the flag and keeps full offset | ||
| tracking on every prompt. The tradeoff is that a terminal which misses one | ||
| query and then recovers stays latched off for the session; the consequence is | ||
| cosmetic (the prompt may overwrite pre-existing text on the line) and real | ||
| emulators answer CPR synchronously, so a one-off miss is not expected. | ||
|
|
||
| ## Upstream status | ||
|
|
||
| None of these are fixed in `ergochat/readline` v0.1.3, which has been the | ||
| latest release since September 2024. Changes 2 and 3 are general bugs and | ||
| should be reported upstream so this patch can shrink; change 1 needs an | ||
| upstream API that separates a candidate's display text from its insert text. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| --- a/vendor/github.com/ergochat/readline/complete.go | ||
| +++ b/vendor/github.com/ergochat/readline/complete.go | ||
| @@ -4,6 +4,7 @@ | ||
| "bufio" | ||
| "bytes" | ||
| "fmt" | ||
| + "strings" | ||
| "sync/atomic" | ||
|
|
||
| "github.com/ergochat/readline/internal/platform" | ||
| @@ -46,9 +47,38 @@ | ||
| } | ||
| } | ||
|
|
||
| +func (o *opCompleter) truncateBufferAfterLastEqual(completion []rune) { | ||
| + bufRunes := o.op.buf.Runes() | ||
| + for i := len(bufRunes) - 1; i >= 0; i-- { | ||
| + if bufRunes[i] == '=' { | ||
| + prefix := bufRunes[i+1:] // part after '=' in buffer | ||
| + if len(prefix) > 0 && len(completion) >= len(prefix) && string(completion[:len(prefix)]) == string(prefix) { | ||
| + o.op.buf.Set(bufRunes[:i+1]) // Keep content till '=' | ||
| + } | ||
| + break | ||
| + } | ||
| + } | ||
| +} | ||
| + | ||
| +func (o *opCompleter) writeRunes(candidate []rune) { | ||
| + selected := candidate | ||
| + spaceFound := false | ||
| + for idx, r := range candidate { | ||
| + if r == ' ' { | ||
| + spaceFound = true | ||
| + } | ||
| + if spaceFound && r == '(' { | ||
| + o.truncateBufferAfterLastEqual(candidate[idx+1:]) | ||
| + selected = candidate[:idx] | ||
| + break | ||
| + } | ||
| + } | ||
| + o.op.buf.WriteRunes(selected) | ||
| +} | ||
| + | ||
| func (o *opCompleter) doSelect() { | ||
| if len(o.candidate) == 1 { | ||
| - o.op.buf.WriteRunes(o.candidate[0]) | ||
| + o.writeRunes(o.candidate[0]) | ||
| o.ExitCompleteMode(false) | ||
| return | ||
| } | ||
| @@ -232,7 +262,7 @@ | ||
| if !o.IsInCompleteMode() { | ||
| if len(newLines) == 1 { | ||
| // not yet in complete mode but only 1 candidate so complete it | ||
| - buf.WriteRunes(newLines[0]) | ||
| + o.writeRunes(newLines[0]) | ||
| o.ExitCompleteMode(false) | ||
| return true | ||
| } | ||
| @@ -264,7 +294,7 @@ | ||
| switch r { | ||
| case CharEnter, CharCtrlJ: | ||
| next = false | ||
| - o.op.buf.WriteRunes(o.candidate[o.candidateChoice]) | ||
| + o.writeRunes(o.candidate[o.candidateChoice]) | ||
| o.ExitCompleteMode(false) | ||
| case CharLineStart: | ||
| o.lineStart() | ||
| @@ -459,6 +489,33 @@ | ||
|
|
||
| // wrote out choices over "lines", move back to cursor (positioned at index) | ||
| fmt.Fprintf(buf, "\033[%dA", lines) | ||
| + | ||
| + // Redraw the prompt and buffer since \033[J may have cleared them | ||
| + // Calculate which line the cursor is on (0-indexed, where 0 is the prompt line) | ||
| + cursorLine := o.op.buf.IdxLine(tWidth) | ||
| + | ||
| + // Move to the beginning of the prompt line (line 0) | ||
| + if cursorLine > 0 { | ||
| + fmt.Fprintf(buf, "\033[%dA", cursorLine) | ||
| + } | ||
| + // Move to column 1 to redraw from the start | ||
| + buf.WriteString("\033[1G") | ||
| + // Redraw prompt and buffer content | ||
| + cfg := o.op.GetConfig() | ||
| + buf.WriteString(cfg.Prompt) | ||
| + buf.WriteString("\x1b[0K") // Clear line from cursor right | ||
| + rs := o.op.buf.Runes() | ||
| + for _, e := range cfg.Painter(rs, o.op.buf.Pos()) { | ||
| + if e == '\t' { | ||
| + buf.WriteString(strings.Repeat(" ", runes.TabWidth)) | ||
| + } else { | ||
| + buf.WriteRune(e) | ||
| + } | ||
| + } | ||
| + // Now position cursor correctly - move back down to the cursor line | ||
| + if cursorLine > 0 { | ||
| + fmt.Fprintf(buf, "\033[%dB", cursorLine) | ||
| + } | ||
| buf.Write(o.op.buf.getBackspaceSequence()) | ||
| buf.Flush() | ||
| } | ||
| --- a/vendor/github.com/ergochat/readline/operation.go | ||
| +++ b/vendor/github.com/ergochat/readline/operation.go | ||
| @@ -5,6 +5,7 @@ | ||
| "io" | ||
| "sync" | ||
| "sync/atomic" | ||
| + "time" | ||
|
|
||
| "github.com/ergochat/readline/internal/platform" | ||
| "github.com/ergochat/readline/internal/runes" | ||
| @@ -23,6 +24,12 @@ | ||
|
|
||
| isPrompting bool // true when prompt written and waiting for input | ||
|
|
||
| + // dsrUnsupported is set once a cursor position query has timed out. The | ||
| + // terminal is then assumed not to implement DSR and no further queries | ||
| + // are made, so the timeout is paid at most once per session rather than | ||
| + // before every prompt. Read and written under m, via getAndSetOffset. | ||
| + dsrUnsupported bool | ||
| + | ||
| history *opHistory | ||
| search *opSearch | ||
| completer *opCompleter | ||
| @@ -419,7 +426,10 @@ | ||
| // Query cursor position before printing the prompt as there | ||
| // may be existing text on the same line that ideally we don't | ||
| // want to overwrite and cause prompt to jump left. | ||
| - o.getAndSetOffset(nil) | ||
| + // Bound the query: a pty that never answers the DSR request (expect, | ||
| + // pexpect, Ansible with a pty, some CI runners) would otherwise block | ||
| + // here forever and no prompt would ever be printed. | ||
| + o.getAndSetOffset(newDSRDeadline()) | ||
| o.buf.Print() // print prompt & buffer contents | ||
| // Prompt written safely, unlock until read completes and then | ||
| // lock again to unset. | ||
| @@ -440,7 +450,7 @@ | ||
| } | ||
|
|
||
| func (o *operation) getAndSetOffset(deadline chan struct{}) { | ||
| - if !o.GetConfig().isInteractive { | ||
| + if !o.GetConfig().isInteractive || o.dsrUnsupported { | ||
| return | ||
| } | ||
|
|
||
| @@ -451,11 +461,27 @@ | ||
| // TODO ??? | ||
| o.t.Write([]byte(" \b")) | ||
|
|
||
| - if offset, err := o.t.GetCursorPosition(deadline); err == nil { | ||
| + offset, err := o.t.GetCursorPosition(deadline) | ||
| + switch { | ||
| + case err == nil: | ||
| o.buf.SetOffset(offset) | ||
| + case errors.Is(err, deadlineExceeded): | ||
| + // The terminal did not answer in time; assume it never will and | ||
| + // stop querying, so the timeout is not paid before every prompt. | ||
| + o.dsrUnsupported = true | ||
| } | ||
| } | ||
|
|
||
| +// newDSRDeadline returns a channel that closes once dsrTimeout has elapsed, | ||
| +// bounding how long a cursor position query will wait for the terminal to | ||
| +// answer. On expiry GetCursorPosition returns an error, the offset is left | ||
| +// unchanged and the prompt is printed anyway. | ||
| +func newDSRDeadline() chan struct{} { | ||
| + deadline := make(chan struct{}) | ||
| + time.AfterFunc(dsrTimeout, func() { close(deadline) }) | ||
| + return deadline | ||
| +} | ||
| + | ||
| func (o *operation) GenPasswordConfig() *Config { | ||
| baseConfig := o.GetConfig() | ||
| return &Config{ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.