Get zsh completion working apart from delegation
This commit is contained in:
parent
cbbda23e01
commit
2cc359ccc8
@ -3,5 +3,5 @@
|
|||||||
(( ${+commands[kitty]} )) || builtin return
|
(( ${+commands[kitty]} )) || builtin return
|
||||||
builtin local src cmd=${(F)words:0:$CURRENT}
|
builtin local src cmd=${(F)words:0:$CURRENT}
|
||||||
# Send all words up to the word the cursor is currently on.
|
# Send all words up to the word the cursor is currently on.
|
||||||
src=$(builtin command kitty +complete zsh "_matcher=$_matcher" <<<$cmd) || builtin return
|
src=$(builtin command kitty-tool __complete__ zsh "_matcher=$_matcher" <<<$cmd) || builtin return
|
||||||
builtin eval "$src"
|
builtin eval "$src"
|
||||||
|
|||||||
@ -269,6 +269,7 @@ func directory_completer(title string, relative_to relative_to) completion_func
|
|||||||
|
|
||||||
return func(completions *Completions, word string, arg_num int) {
|
return func(completions *Completions, word string, arg_num int) {
|
||||||
mg := completions.add_match_group(title)
|
mg := completions.add_match_group(title)
|
||||||
|
mg.NoTrailingSpace = true
|
||||||
mg.IsFiles = true
|
mg.IsFiles = true
|
||||||
complete_files(word, func(entry *FileEntry) {
|
complete_files(word, func(entry *FileEntry) {
|
||||||
if entry.mode.IsDir() {
|
if entry.mode.IsDir() {
|
||||||
|
|||||||
@ -55,6 +55,7 @@ func complete_kitty(completions *Completions, word string, arg_num int) {
|
|||||||
func complete_kitty_override(title string, names []string) completion_func {
|
func complete_kitty_override(title string, names []string) completion_func {
|
||||||
return func(completions *Completions, word string, arg_num int) {
|
return func(completions *Completions, word string, arg_num int) {
|
||||||
mg := completions.add_match_group(title)
|
mg := completions.add_match_group(title)
|
||||||
|
mg.NoTrailingSpace = true
|
||||||
for _, q := range names {
|
for _, q := range names {
|
||||||
if strings.HasPrefix(q, word) {
|
if strings.HasPrefix(q, word) {
|
||||||
mg.add_match(q + "=")
|
mg.add_match(q + "=")
|
||||||
@ -66,6 +67,7 @@ func complete_kitty_override(title string, names []string) completion_func {
|
|||||||
func complete_kitty_listen_on(completions *Completions, word string, arg_num int) {
|
func complete_kitty_listen_on(completions *Completions, word string, arg_num int) {
|
||||||
if !strings.Contains(word, ":") {
|
if !strings.Contains(word, ":") {
|
||||||
mg := completions.add_match_group("Address family")
|
mg := completions.add_match_group("Address family")
|
||||||
|
mg.NoTrailingSpace = true
|
||||||
for _, q := range []string{"unix:", "tcp:"} {
|
for _, q := range []string{"unix:", "tcp:"} {
|
||||||
if strings.HasPrefix(q, word) {
|
if strings.HasPrefix(q, word) {
|
||||||
mg.add_match(q)
|
mg.add_match(q)
|
||||||
|
|||||||
@ -43,7 +43,11 @@ func main(args []string) error {
|
|||||||
output_type = args[0]
|
output_type = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
shell_state := make(map[string]string, len(args))
|
n := len(args)
|
||||||
|
if n < 1 {
|
||||||
|
n = 1
|
||||||
|
}
|
||||||
|
shell_state := make(map[string]string, n)
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
k, v, found := utils.Cut(arg, "=")
|
k, v, found := utils.Cut(arg, "=")
|
||||||
if !found {
|
if !found {
|
||||||
|
|||||||
@ -55,12 +55,17 @@ func (self *Completions) add_options_group(options []*Option, word string) {
|
|||||||
if word == "-" {
|
if word == "-" {
|
||||||
group.Matches = append(group.Matches, &Match{Word: "--", Description: "End of options"})
|
group.Matches = append(group.Matches, &Match{Word: "--", Description: "End of options"})
|
||||||
for _, opt := range options {
|
for _, opt := range options {
|
||||||
|
has_single_letter_alias := false
|
||||||
for _, q := range opt.Aliases {
|
for _, q := range opt.Aliases {
|
||||||
if len(q) == 1 {
|
if len(q) == 1 {
|
||||||
group.add_match("-"+q, opt.Description)
|
group.add_match("-"+q, opt.Description)
|
||||||
|
has_single_letter_alias = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !has_single_letter_alias {
|
||||||
|
group.add_match("--"+opt.Aliases[0], opt.Description)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runes := []rune(word)
|
runes := []rune(word)
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
package completion
|
package completion
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"kitty/tools/utils"
|
||||||
|
"kitty/tools/wcswidth"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type Match struct {
|
type Match struct {
|
||||||
Word string `json:"word,omitempty"`
|
Word string `json:"word,omitempty"`
|
||||||
@ -28,6 +32,46 @@ func (self *MatchGroup) add_prefix_to_all_matches(prefix string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *MatchGroup) remove_prefix_from_all_matches(prefix string) {
|
||||||
|
for _, m := range self.Matches {
|
||||||
|
m.Word = m.Word[len(prefix):]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MatchGroup) has_descriptions() bool {
|
||||||
|
for _, m := range self.Matches {
|
||||||
|
if m.Description != "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MatchGroup) max_visual_word_length(limit int) int {
|
||||||
|
ans := 0
|
||||||
|
for _, m := range self.Matches {
|
||||||
|
if q := wcswidth.Stringwidth(m.Word); q > ans {
|
||||||
|
ans = q
|
||||||
|
if ans > limit {
|
||||||
|
return limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *MatchGroup) longest_common_prefix() string {
|
||||||
|
limit := len(self.Matches)
|
||||||
|
i := 0
|
||||||
|
return utils.LongestCommon(func() (string, bool) {
|
||||||
|
if i < limit {
|
||||||
|
i++
|
||||||
|
return self.Matches[i-1].Word, false
|
||||||
|
}
|
||||||
|
return "", true
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
type Completions struct {
|
type Completions struct {
|
||||||
Groups []*MatchGroup `json:"groups,omitempty"`
|
Groups []*MatchGroup `json:"groups,omitempty"`
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,11 @@ package completion
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"kitty/tools/cli/markup"
|
||||||
|
"kitty/tools/tty"
|
||||||
|
"kitty/tools/utils"
|
||||||
|
"kitty/tools/wcswidth"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -12,8 +17,11 @@ var _ = fmt.Print
|
|||||||
|
|
||||||
func zsh_input_parser(data []byte, shell_state map[string]string) ([][]string, error) {
|
func zsh_input_parser(data []byte, shell_state map[string]string) ([][]string, error) {
|
||||||
matcher := shell_state["_matcher"]
|
matcher := shell_state["_matcher"]
|
||||||
q := strings.Split(strings.ToLower(matcher), ":")[0][:1]
|
q := ""
|
||||||
if strings.Contains("lrbe", q) {
|
if matcher != "" {
|
||||||
|
q = strings.Split(strings.ToLower(matcher), ":")[0][:1]
|
||||||
|
}
|
||||||
|
if q != "" && strings.Contains("lrbe", q) {
|
||||||
// this is zsh anchor based matching
|
// this is zsh anchor based matching
|
||||||
// https://zsh.sourceforge.io/Doc/Release/Completion-Widgets.html#Completion-Matching-Control
|
// https://zsh.sourceforge.io/Doc/Release/Completion-Widgets.html#Completion-Matching-Control
|
||||||
// can be specified with matcher-list and some systems do it by default,
|
// can be specified with matcher-list and some systems do it by default,
|
||||||
@ -38,8 +46,98 @@ func zsh_input_parser(data []byte, shell_state map[string]string) ([][]string, e
|
|||||||
return [][]string{words}, nil
|
return [][]string{words}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fmt_desc(word, desc string, max_word_len int, f *markup.Context, screen_width int) string {
|
||||||
|
if desc == "" {
|
||||||
|
return word
|
||||||
|
}
|
||||||
|
line, _, _ := utils.Cut(strings.TrimSpace(desc), "\n")
|
||||||
|
desc = f.Prettify(line)
|
||||||
|
|
||||||
|
multiline := false
|
||||||
|
max_desc_len := screen_width - 2
|
||||||
|
word_len := wcswidth.Stringwidth(word)
|
||||||
|
if word_len > max_word_len {
|
||||||
|
multiline = true
|
||||||
|
} else {
|
||||||
|
word += strings.Repeat(" ", max_word_len-word_len)
|
||||||
|
max_desc_len = screen_width - max_word_len - 3
|
||||||
|
}
|
||||||
|
if wcswidth.Stringwidth(desc) > max_desc_len {
|
||||||
|
desc = wcswidth.TruncateToVisualLength(desc, max_desc_len-2) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
if multiline {
|
||||||
|
return word + "\n " + desc
|
||||||
|
}
|
||||||
|
return word + " " + desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(completions *Completions, f *markup.Context, screen_width int) ([]byte, error) {
|
||||||
|
cmd := strings.Builder{}
|
||||||
|
output := strings.Builder{}
|
||||||
|
for _, mg := range completions.Groups {
|
||||||
|
cmd.WriteString("compadd -U -J ")
|
||||||
|
cmd.WriteString(utils.QuoteStringForSH(mg.Title))
|
||||||
|
cmd.WriteString(" -X ")
|
||||||
|
cmd.WriteString(utils.QuoteStringForSH("%B" + mg.Title + "%b"))
|
||||||
|
if mg.NoTrailingSpace {
|
||||||
|
cmd.WriteString(" -S ''")
|
||||||
|
}
|
||||||
|
if mg.IsFiles {
|
||||||
|
cmd.WriteString(" -f")
|
||||||
|
if len(mg.Matches) > 1 {
|
||||||
|
lcp := mg.longest_common_prefix()
|
||||||
|
if strings.Contains(lcp, utils.Sep) {
|
||||||
|
lcp = strings.TrimRight(filepath.Dir(lcp), utils.Sep) + utils.Sep
|
||||||
|
cmd.WriteString(" -p ")
|
||||||
|
cmd.WriteString(utils.QuoteStringForSH(lcp))
|
||||||
|
mg.remove_prefix_from_all_matches(lcp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(mg.Matches) > 1 && strings.HasPrefix(mg.Matches[0].Word, "--") && strings.Contains(mg.Matches[0].Word, "=") {
|
||||||
|
lcp, _, _ := utils.Cut(mg.longest_common_prefix(), "=")
|
||||||
|
lcp += "="
|
||||||
|
if len(lcp) > 3 {
|
||||||
|
cmd.WriteString(" -p ")
|
||||||
|
cmd.WriteString(utils.QuoteStringForSH(lcp))
|
||||||
|
mg.remove_prefix_from_all_matches(lcp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mg.has_descriptions() {
|
||||||
|
fmt.Fprintln(&output, "compdescriptions=(")
|
||||||
|
limit := mg.max_visual_word_length(16)
|
||||||
|
for _, m := range mg.Matches {
|
||||||
|
fmt.Fprintln(&output, utils.QuoteStringForSH(fmt_desc(m.Word, m.Description, limit, f, screen_width)))
|
||||||
|
}
|
||||||
|
fmt.Fprintln(&output, ")")
|
||||||
|
cmd.WriteString(" -l -d compdescriptions")
|
||||||
|
}
|
||||||
|
cmd.WriteString(" --")
|
||||||
|
for _, m := range mg.Matches {
|
||||||
|
cmd.WriteString(" ")
|
||||||
|
cmd.WriteString(utils.QuoteStringForSH(m.Word))
|
||||||
|
}
|
||||||
|
fmt.Fprintln(&output, cmd.String(), ";")
|
||||||
|
}
|
||||||
|
return []byte(output.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
func zsh_output_serializer(completions []*Completions, shell_state map[string]string) ([]byte, error) {
|
func zsh_output_serializer(completions []*Completions, shell_state map[string]string) ([]byte, error) {
|
||||||
return nil, nil
|
var f *markup.Context
|
||||||
|
screen_width := 80
|
||||||
|
ctty, err := tty.OpenControllingTerm()
|
||||||
|
if err == nil {
|
||||||
|
sz, err := ctty.GetSize()
|
||||||
|
ctty.Close()
|
||||||
|
if err == nil {
|
||||||
|
screen_width = int(sz.Col)
|
||||||
|
f = markup.New(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
f = markup.New(false)
|
||||||
|
}
|
||||||
|
return serialize(completions[0], f, screen_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user