mirror of https://github.com/Hilbis/Hilbish
Compare commits
7 Commits
1febe66f84
...
bd4e0df7b3
Author | SHA1 | Date |
---|---|---|
TorchedSammy | bd4e0df7b3 | |
TorchedSammy | ebec585690 | |
TorchedSammy | ff4609e432 | |
TorchedSammy | ef3e7d92bc | |
TorchedSammy | d6338fc021 | |
TorchedSammy | 3eaeb6a5da | |
TorchedSammy | 8b547f2af0 |
|
@ -76,6 +76,7 @@ disables commands being added to history.
|
||||||
random errors introduced with the new Lua runtime (see [#197])
|
random errors introduced with the new Lua runtime (see [#197])
|
||||||
- `bait.release(name, catcher)` removes `handler` for the named `event`
|
- `bait.release(name, catcher)` removes `handler` for the named `event`
|
||||||
- `exec`, `clear` and `cat` builtin commands
|
- `exec`, `clear` and `cat` builtin commands
|
||||||
|
- `hilbish.cancel` hook
|
||||||
|
|
||||||
[#197]: https://github.com/Rosettea/Hilbish/issues/197
|
[#197]: https://github.com/Rosettea/Hilbish/issues/197
|
||||||
|
|
||||||
|
@ -151,6 +152,12 @@ an error of missing format variable
|
||||||
- Fix an error with sh syntax in aliases
|
- Fix an error with sh syntax in aliases
|
||||||
- Prompt now works with east asian characters (CJK)
|
- Prompt now works with east asian characters (CJK)
|
||||||
- Set back the prompt to normal after exiting the continue prompt with ctrl-d
|
- Set back the prompt to normal after exiting the continue prompt with ctrl-d
|
||||||
|
- Users can now tab complete files with spaces while quoted or with escaped spaces.
|
||||||
|
This means a query of `Files\ to\ ` with file names of `Files to tab complete` and `Files to complete`
|
||||||
|
will result in the files being completed.
|
||||||
|
- Fixed grid menu display if cell width ends up being the width of the terminal
|
||||||
|
- Cut off item names in grid menu if its longer than cell width
|
||||||
|
- Fix completion search menu disappearing
|
||||||
|
|
||||||
## [2.0.0-rc1] - 2022-09-14
|
## [2.0.0-rc1] - 2022-09-14
|
||||||
This is a pre-release version of Hilbish for testing. To see the changelog,
|
This is a pre-release version of Hilbish for testing. To see the changelog,
|
||||||
|
|
67
complete.go
67
complete.go
|
@ -11,15 +11,49 @@ import (
|
||||||
rt "github.com/arnodel/golua/runtime"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func splitQuote(str string) []string {
|
var charEscapeMap = []string{
|
||||||
|
"\"", "\\\"",
|
||||||
|
"'", "\\'",
|
||||||
|
"`", "\\`",
|
||||||
|
" ", "\\ ",
|
||||||
|
"(", "\\(",
|
||||||
|
")", "\\)",
|
||||||
|
"[", "\\[",
|
||||||
|
"]", "\\]",
|
||||||
|
"$", "\\$",
|
||||||
|
"&", "\\&",
|
||||||
|
"*", "\\*",
|
||||||
|
">", "\\>",
|
||||||
|
"<", "\\<",
|
||||||
|
"|", "\\|",
|
||||||
|
}
|
||||||
|
var charEscapeMapInvert = invert(charEscapeMap)
|
||||||
|
var escapeReplaer = strings.NewReplacer(charEscapeMap...)
|
||||||
|
var escapeInvertReplaer = strings.NewReplacer(charEscapeMapInvert...)
|
||||||
|
|
||||||
|
func invert(m []string) []string {
|
||||||
|
newM := make([]string, len(charEscapeMap))
|
||||||
|
for i := range m {
|
||||||
|
if (i + 1) % 2 == 0 {
|
||||||
|
newM[i] = m[i - 1]
|
||||||
|
newM[i - 1] = m[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newM
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitForFile(str string) []string {
|
||||||
split := []string{}
|
split := []string{}
|
||||||
sb := &strings.Builder{}
|
sb := &strings.Builder{}
|
||||||
quoted := false
|
quoted := false
|
||||||
|
|
||||||
for _, r := range str {
|
for i, r := range str {
|
||||||
if r == '"' {
|
if r == '"' {
|
||||||
quoted = !quoted
|
quoted = !quoted
|
||||||
sb.WriteRune(r)
|
sb.WriteRune(r)
|
||||||
|
} else if r == ' ' && str[i - 1] == '\\' {
|
||||||
|
sb.WriteRune(r)
|
||||||
} else if !quoted && r == ' ' {
|
} else if !quoted && r == ' ' {
|
||||||
split = append(split, sb.String())
|
split = append(split, sb.String())
|
||||||
sb.Reset()
|
sb.Reset()
|
||||||
|
@ -39,12 +73,15 @@ func splitQuote(str string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
q := splitQuote(ctx)
|
q := splitForFile(ctx)
|
||||||
|
|
||||||
return matchPath(q[len(q) - 1])
|
return matchPath(q[len(q) - 1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
|
q := splitForFile(ctx)
|
||||||
|
query = q[len(q) - 1]
|
||||||
|
|
||||||
var completions []string
|
var completions []string
|
||||||
|
|
||||||
prefixes := []string{"./", "../", "/", "~/"}
|
prefixes := []string{"./", "../", "/", "~/"}
|
||||||
|
@ -54,7 +91,7 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
if len(fileCompletions) != 0 {
|
if len(fileCompletions) != 0 {
|
||||||
for _, f := range fileCompletions {
|
for _, f := range fileCompletions {
|
||||||
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
fullPath, _ := filepath.Abs(util.ExpandHome(query + strings.TrimPrefix(f, filePref)))
|
||||||
if err := findExecutable(fullPath, false, true); err != nil {
|
if err := findExecutable(escapeInvertReplaer.Replace(fullPath), false, true); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
completions = append(completions, f)
|
completions = append(completions, f)
|
||||||
|
@ -66,7 +103,6 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
|
|
||||||
// filter out executables, but in path
|
// filter out executables, but in path
|
||||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||||
// print dir to stderr for debugging
|
|
||||||
// search for an executable which matches our query string
|
// search for an executable which matches our query string
|
||||||
if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil {
|
if matches, err := filepath.Glob(filepath.Join(dir, query + "*")); err == nil {
|
||||||
// get basename from matches
|
// get basename from matches
|
||||||
|
@ -102,6 +138,7 @@ func matchPath(query string) ([]string, string) {
|
||||||
var entries []string
|
var entries []string
|
||||||
var baseName string
|
var baseName string
|
||||||
|
|
||||||
|
query = escapeInvertReplaer.Replace(query)
|
||||||
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
|
path, _ := filepath.Abs(util.ExpandHome(filepath.Dir(query)))
|
||||||
if string(query) == "" {
|
if string(query) == "" {
|
||||||
// filepath base below would give us "."
|
// filepath base below would give us "."
|
||||||
|
@ -129,25 +166,7 @@ func matchPath(query string) ([]string, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func escapeFilename(fname string) string {
|
func escapeFilename(fname string) string {
|
||||||
args := []string{
|
return escapeReplaer.Replace(fname)
|
||||||
"\"", "\\\"",
|
|
||||||
"'", "\\'",
|
|
||||||
"`", "\\`",
|
|
||||||
" ", "\\ ",
|
|
||||||
"(", "\\(",
|
|
||||||
")", "\\)",
|
|
||||||
"[", "\\[",
|
|
||||||
"]", "\\]",
|
|
||||||
"$", "\\$",
|
|
||||||
"&", "\\&",
|
|
||||||
"*", "\\*",
|
|
||||||
">", "\\>",
|
|
||||||
"<", "\\<",
|
|
||||||
"|", "\\|",
|
|
||||||
}
|
|
||||||
|
|
||||||
r := strings.NewReplacer(args...)
|
|
||||||
return r.Replace(fname)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func completionLoader(rtm *rt.Runtime) *rt.Table {
|
func completionLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
|
|
|
@ -5,3 +5,5 @@
|
||||||
|
|
||||||
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
|
+ `hilbish.vimAction` -> actionName, args > Sent when the user does a "vim action," being something
|
||||||
like yanking or pasting text. See `doc vim-mode actions` for more info.
|
like yanking or pasting text. See `doc vim-mode actions` for more info.
|
||||||
|
|
||||||
|
+ `hilbish.cancel` > Sent when the user cancels their input with Ctrl-C.
|
||||||
|
|
7
main.go
7
main.go
|
@ -181,11 +181,14 @@ input:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != readline.CtrlC {
|
if err == readline.CtrlC {
|
||||||
|
fmt.Println("^C")
|
||||||
|
hooks.Emit("hilbish.cancel")
|
||||||
|
} else {
|
||||||
// If we get a completely random error, print
|
// If we get a completely random error, print
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
fmt.Println("^C")
|
// TODO: Halt if any other error occurs
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var priv bool
|
var priv bool
|
||||||
|
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
"github.com/rivo/uniseg"
|
||||||
|
)
|
||||||
|
|
||||||
// initGrid - Grid display details. Called each time we want to be sure to have
|
// initGrid - Grid display details. Called each time we want to be sure to have
|
||||||
// a working completion group either immediately, or later on. Generally defered.
|
// a working completion group either immediately, or later on. Generally defered.
|
||||||
|
@ -13,8 +14,8 @@ func (g *CompletionGroup) initGrid(rl *Instance) {
|
||||||
// Compute size of each completion item box
|
// Compute size of each completion item box
|
||||||
tcMaxLength := 1
|
tcMaxLength := 1
|
||||||
for i := range g.Suggestions {
|
for i := range g.Suggestions {
|
||||||
if len(g.Suggestions[i]) > tcMaxLength {
|
if uniseg.GraphemeClusterCount(g.Suggestions[i]) > tcMaxLength {
|
||||||
tcMaxLength = len([]rune(g.Suggestions[i]))
|
tcMaxLength = uniseg.GraphemeClusterCount(g.Suggestions[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +104,7 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
||||||
rl.tcUsedY++
|
rl.tcUsedY++
|
||||||
}
|
}
|
||||||
|
|
||||||
cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 2)
|
cellWidth := strconv.Itoa((GetTermWidth() / g.tcMaxX) - 4)
|
||||||
x := 0
|
x := 0
|
||||||
y := 1
|
y := 1
|
||||||
|
|
||||||
|
@ -124,7 +125,15 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
||||||
comp += seqInvert
|
comp += seqInvert
|
||||||
}
|
}
|
||||||
|
|
||||||
comp += fmt.Sprintf("%-"+cellWidth+"s %s", fmtEscape(g.Suggestions[i]), seqReset)
|
sugg := g.Suggestions[i]
|
||||||
|
if len(sugg) > GetTermWidth() {
|
||||||
|
sugg = sugg[:GetTermWidth() - 4] + "..."
|
||||||
|
}
|
||||||
|
formatStr := "%-"+cellWidth+"s%s "
|
||||||
|
if g.tcMaxX == 1 {
|
||||||
|
formatStr = "%s%s"
|
||||||
|
}
|
||||||
|
comp += fmt.Sprintf(formatStr, fmtEscape(sugg), seqReset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always add a newline to the group if the end if not punctuated with one
|
// Always add a newline to the group if the end if not punctuated with one
|
||||||
|
|
|
@ -238,7 +238,9 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
|
|
||||||
// Normal completion search does only refresh the search pattern and the comps
|
// Normal completion search does only refresh the search pattern and the comps
|
||||||
if rl.modeTabFind || rl.modeAutoFind {
|
if rl.modeTabFind || rl.modeAutoFind {
|
||||||
|
rl.resetVirtualComp(false)
|
||||||
rl.backspaceTabFind()
|
rl.backspaceTabFind()
|
||||||
|
rl.renderHelpers()
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
} else {
|
} else {
|
||||||
// Always cancel any virtual completion
|
// Always cancel any virtual completion
|
||||||
|
@ -331,6 +333,8 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
|
|
||||||
rl.modeTabFind = true
|
rl.modeTabFind = true
|
||||||
rl.updateTabFind([]rune{})
|
rl.updateTabFind([]rune{})
|
||||||
|
rl.updateVirtualComp()
|
||||||
|
rl.renderHelpers()
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
|
|
||||||
// Tab Completion & Completion Search ---------------------------------------------------------------
|
// Tab Completion & Completion Search ---------------------------------------------------------------
|
||||||
|
@ -484,7 +488,10 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
if string(r[:i]) != seqShiftTab &&
|
if string(r[:i]) != seqShiftTab &&
|
||||||
string(r[:i]) != seqForwards && string(r[:i]) != seqBackwards &&
|
string(r[:i]) != seqForwards && string(r[:i]) != seqBackwards &&
|
||||||
string(r[:i]) != seqUp && string(r[:i]) != seqDown {
|
string(r[:i]) != seqUp && string(r[:i]) != seqDown {
|
||||||
rl.resetVirtualComp(false)
|
// basically only applies except on 1st ctrl r open
|
||||||
|
// so if we have not explicitly selected something
|
||||||
|
// (tabCompletionSelect is false) drop virtual completion
|
||||||
|
rl.resetVirtualComp(!rl.tabCompletionSelect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,7 +524,9 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
if rl.modeAutoFind || rl.modeTabFind {
|
if rl.modeAutoFind || rl.modeTabFind {
|
||||||
rl.resetVirtualComp(false)
|
rl.resetVirtualComp(false)
|
||||||
rl.updateTabFind(r[:i])
|
rl.updateTabFind(r[:i])
|
||||||
|
rl.renderHelpers()
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
|
continue
|
||||||
} else {
|
} else {
|
||||||
rl.resetVirtualComp(false)
|
rl.resetVirtualComp(false)
|
||||||
rl.editorInput(r[:i])
|
rl.editorInput(r[:i])
|
||||||
|
@ -604,6 +613,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
case string(charEscape):
|
case string(charEscape):
|
||||||
switch {
|
switch {
|
||||||
case rl.modeAutoFind:
|
case rl.modeAutoFind:
|
||||||
|
rl.resetVirtualComp(true)
|
||||||
rl.resetTabFind()
|
rl.resetTabFind()
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
rl.resetTabCompletion()
|
rl.resetTabCompletion()
|
||||||
|
@ -611,6 +621,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
|
|
||||||
case rl.modeTabFind:
|
case rl.modeTabFind:
|
||||||
|
rl.resetVirtualComp(true)
|
||||||
rl.resetTabFind()
|
rl.resetTabFind()
|
||||||
rl.resetTabCompletion()
|
rl.resetTabCompletion()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue