Commit 0f1aa3e3 authored by Kevin Lyda's avatar Kevin Lyda
Browse files

Fix completion; address sandbox issue

Should now complete for flags and abbreviated commands.  SSH FETCH
needs DNS access.
parent 4dabc764
Loading
Loading
Loading
Loading
+61 −18
Original line number Diff line number Diff line
@@ -41,6 +41,39 @@ func NewCompleter(ct CommandTable) Completer {
	}
}

// resolveCmd resolves a possibly-abbreviated command key to its full name.
// Returns "" if not found or ambiguous.
func (c Completer) resolveCmd(key string) string {
	upper := strings.ToUpper(key)
	var match string
	for _, cmd := range c.commands {
		if cmd == upper {
			return upper
		}
		if strings.HasPrefix(cmd, upper) {
			if match != "" {
				return "" // ambiguous
			}
			match = cmd
		}
	}
	return match
}

// commandWords extracts just the command word(s) from a string,
// stopping before any flag (token starting with /).
func commandWords(s string) string {
	parts := strings.Fields(s)
	result := []string{}
	for _, p := range parts {
		if strings.HasPrefix(p, "/") {
			break
		}
		result = append(result, strings.ToUpper(p))
	}
	return strings.Join(result, " ")
}

// Do return a list of possible completions.
func (c Completer) Do(line []rune, pos int) ([][]rune, int) {
	// Nothing typed in.
@@ -58,12 +91,14 @@ func (c Completer) Do(line []rune, pos int) ([][]rune, int) {

	// Check if the user is typing a flag (input contains a /).
	if idx := strings.LastIndex(input, "/"); idx >= 0 {
		// Extract the command part before the flag.
		cmdPart := strings.TrimSpace(input[:idx])
		// Extract the command part before the flag, stripping any earlier
		// flags so "dir /folders /desc" resolves to "DIRECTORY" not
		// "DIRECTORY /FOLDERS".
		cmdPart := commandWords(input[:idx])
		flagPart := strings.ToUpper(input[idx:])

		// Find matching command for flag lookup.
		cmdKey := strings.ToUpper(cmdPart)
		// Resolve abbreviated command then look up its flag table.
		if cmdKey := c.resolveCmd(cmdPart); cmdKey != "" {
			if ft, ok := c.flags[cmdKey]; ok {
				matches := ft.Completions(flagPart)
				newline := [][]rune{}
@@ -78,16 +113,24 @@ func (c Completer) Do(line []rune, pos int) ([][]rune, int) {
				return newline, pos
			}
		}
	}

	// Check if the user is typing an argument after a complete command.
	// Check if the user is typing an argument after a command.
	if spaceIdx := strings.LastIndex(input, " "); spaceIdx >= 0 {
		cmdPart := strings.TrimSpace(input[:spaceIdx])
		argPart := input[spaceIdx+1:]
		cmdKey := strings.ToUpper(cmdPart)
		argUpper := strings.ToUpper(argPart)

		if completer, ok := c.argCompleters[cmdKey]; ok {
			choices := completer()
		// Try direct lookup, then resolve abbreviation.
		cmdKey := strings.ToUpper(cmdPart)
		completerFn, ok := c.argCompleters[cmdKey]
		if !ok {
			if resolved := c.resolveCmd(cmdKey); resolved != "" {
				completerFn, ok = c.argCompleters[resolved]
			}
		}
		if ok {
			choices := completerFn()
			newline := [][]rune{}
			for _, choice := range choices {
				if strings.HasPrefix(strings.ToUpper(choice), argUpper) {
+2 −0
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ func InitSandbox() error {
		landlock.RODirs(roDirs...),
		// Allow outbound HTTPS for SSH FETCH from forges.
		landlock.ConnectTCP(443),
		landlock.ConnectTCP(53),
		landlock.ROFiles("/etc/resolv.conf", "/etc/hosts"),
	)
	if err != nil {
		return fmt.Errorf("failed to apply landlock sandbox: %w", err)
+3 −0
Original line number Diff line number Diff line
@@ -45,6 +45,9 @@ func InitSandbox() error {
		{"/dev/tty", "rw"},
		// Terminfo databases for tcell/readline.
		{"/usr/share/terminfo", "r"},
		// Needed for ssh fetch.
		{"/etc/resolv.conf", "r"},
		{"/etc/hosts", "r"},
	}

	for _, u := range unveils {