all repos

viye @ 896d55427eac536be9d99d6c941215e4bf165ac1

my shot at reimplementing xiki
4 files changed, 61 insertions(+), 41 deletions(-)
fix splitLeaf; add some tests; move some code around
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-08-26 15:25:57 +0300
Authored at: 2026-08-26 13:14:34 +0300
Change ID: tvlnquoyuwvnpswtpukvownsqlmkrwxv
Parent: cf8afd9
M internal/plumbing/ip/ip.go
···
        38
        38
         	}

      
        39
        39
         

      
        40
        40
         	if lerr == nil && perr != nil {

      
        41
        
        -		_, _ = buf.WriteString(pip)

      
        
        41
        +		_, _ = buf.WriteString(lip)

      
        42
        42
         	}

      
        43
        43
         

      
        44
        44
         	_ = buf.WriteByte('\n')

      
M internal/viye/viye.go
···
        5
        5
         	"fmt"

      
        6
        6
         	"io"

      
        7
        7
         	"os"

      
        8
        
        -	"strings"

      
        9
        8
         )

      
        10
        9
         

      
        11
        10
         const Version = "0.0.1-alpha"

      ···
        13
        12
         var ErrPlumbingNotFound = errors.New("tool not found")

      
        14
        13
         

      
        15
        14
         type Context struct {

      
        16
        
        -	Dir string // working directory

      
        17
        
        -

      
        18
        
        -	Cmd  string   // first word of the leaf path component

      
        19
        
        -	Args []string // remaining words of the leaf path component

      
        
        15
        +	Dir  string   // working directory

      
        20
        16
         	Path []string // ancestor chain [root, ..., target]

      
        21
        
        -	Body []string // body lines from : children (edited data for two-phase execution)

      
        
        17
        +	Cmd  string

      
        
        18
        +	Args []string

      
        
        19
        +	Body []string // body lines from : children

      
        22
        20
         

      
        23
        21
         	dispatch func(*Context) (string, error) // set by [Viye.dispatch] for tool chaining

      
        24
        22
         }

      ···
        47
        45
         		return nil

      
        48
        46
         	}

      
        49
        47
         	if args[1] == "help" {

      
        50
        
        -		return v.showHelp(out)

      
        
        48
        +		fmt.Fprintln(out, "| Available tools:")

      
        
        49
        +		for _, tool := range v.tools {

      
        
        50
        +			fmt.Fprintf(out, "| %s\n", tool.Name())

      
        
        51
        +		}

      
        
        52
        +		return nil

      
        51
        53
         	}

      
        52
        54
         	if args[1] == "version" {

      
        53
        
        -		return v.showVersion(out)

      
        
        55
        +		fmt.Fprint(out, "| viye version: "+Version)

      
        
        56
        +		return nil

      
        54
        57
         	}

      
        55
        58
         

      
        56
        59
         	path, body := splitArgs(args[1:])

      ···
        71
        74
         	return err

      
        72
        75
         }

      
        73
        76
         

      
        74
        
        -func (v *Viye) showHelp(out io.Writer) error {

      
        75
        
        -	fmt.Fprintln(out, "| Available tools:")

      
        76
        
        -	for _, tool := range v.tools {

      
        77
        
        -		fmt.Fprintf(out, "| %s\n", tool.Name())

      
        78
        
        -	}

      
        79
        
        -	return nil

      
        80
        
        -}

      
        81
        
        -

      
        82
        
        -func (v *Viye) showVersion(out io.Writer) error {

      
        83
        
        -	fmt.Fprint(out, "| viye version: "+Version)

      
        84
        
        -	return nil

      
        85
        
        -}

      
        86
        
        -

      
        87
        77
         // dispatch executes the path against registered tools, first-match-wins

      
        88
        78
         func (v *Viye) dispatch(ctx *Context) (string, error) {

      
        89
        79
         	if len(ctx.Path) == 0 {

      
        90
        80
         		return "", nil

      
        91
        81
         	}

      
        92
        
        -	ctx.Cmd, ctx.Args = splitLeaf(ctx.Path[len(ctx.Path)-1])

      
        
        82
        +	ctx.Cmd, ctx.Args = splitLeaf(ctx.Path)

      
        93
        83
         	ctx.dispatch = v.dispatch

      
        94
        84
         	for _, tool := range v.tools {

      
        95
        85
         		if tool.Match(ctx) {

      ···
        99
        89
         	return "", ErrPlumbingNotFound

      
        100
        90
         }

      
        101
        91
         

      
        102
        
        -// splitLeaf splits a leaf path component into command name and args.

      
        103
        
        -// For "$ go run ." returns ("$", []string{"go", "run", "."}).

      
        104
        
        -// For "ip" returns ("ip", nil).

      
        105
        
        -func splitLeaf(leaf string) (cmd string, args []string) {

      
        106
        
        -	parts := strings.Fields(leaf)

      
        107
        
        -	if len(parts) == 0 {

      
        
        92
        +// splitLeaf splits a path into command name and args.

      
        
        93
        +// ["get", "http://site.com"] → ("get", ["http://site.com"]).

      
        
        94
        +func splitLeaf(path []string) (cmd string, args []string) {

      
        
        95
        +	if len(path) == 0 {

      
        108
        96
         		return "", nil

      
        109
        97
         	}

      
        110
        
        -	return parts[0], parts[1:]

      
        
        98
        +	return path[0], path[1:]

      
        111
        99
         }

      
        112
        100
         

      
        113
        101
         // splitArgs builds path and body from args.

      
M internal/viye/viye_test.go
···
        36
        36
         

      
        37
        37
         func TestSplitLeaf(t *testing.T) {

      
        38
        38
         	for _, tt := range []struct {

      
        39
        
        -		leaf, cmd string

      
        40
        
        -		want      []string

      
        
        39
        +		path     []string

      
        
        40
        +		cmd      string

      
        
        41
        +		wantArgs []string

      
        41
        42
         	}{

      
        42
        
        -		{"mkdir dir", "mkdir", []string{"dir"}},

      
        43
        
        -		{"$ go run .", "$", []string{"go", "run", "."}},

      
        44
        
        -		{"ip", "ip", []string{}},

      
        
        43
        +		{[]string{"get", "http://site.com"}, "get", []string{"http://site.com"}},

      
        
        44
        +		{[]string{"$", "go", "run", "."}, "$", []string{"go", "run", "."}},

      
        
        45
        +		{[]string{"ip"}, "ip", []string{}},

      
        45
        46
         	} {

      
        46
        
        -		gotCmd, gotArgs := splitLeaf(tt.leaf)

      
        
        47
        +		gotCmd, gotArgs := splitLeaf(tt.path)

      
        47
        48
         		is.Equal(t, tt.cmd, gotCmd)

      
        48
        
        -		is.Equal(t, tt.want, gotArgs)

      
        
        49
        +		is.Equal(t, tt.wantArgs, gotArgs)

      
        49
        50
         	}

      
        50
        51
         }

      
        51
        52
         

      ···
        60
        61
         		is.Equal(t, tt.want, got)

      
        61
        62
         	}

      
        62
        63
         }

      
        
        64
        +

      
        
        65
        +func TestDispatch(t *testing.T) {

      
        
        66
        +	for _, tt := range []struct {

      
        
        67
        +		path     []string

      
        
        68
        +		wantCmd  string

      
        
        69
        +		wantArgs []string

      
        
        70
        +	}{

      
        
        71
        +		{[]string{"get", "http://site.com"}, "get", []string{"http://site.com"}},

      
        
        72
        +		{[]string{"ip"}, "ip", []string{}},

      
        
        73
        +		{[]string{"$", "go", "run", "."}, "$", []string{"go", "run", "."}},

      
        
        74
        +	} {

      
        
        75
        +		tool := &mockTool{cmd: tt.wantCmd}

      
        
        76
        +		v := &Viye{tools: []Tool{tool}}

      
        
        77
        +		ctx := &Context{Path: tt.path}

      
        
        78
        +		v.dispatch(ctx)

      
        
        79
        +		is.Equal(t, tt.wantCmd, ctx.Cmd)

      
        
        80
        +		is.Equal(t, tt.wantArgs, ctx.Args)

      
        
        81
        +	}

      
        
        82
        +}

      
        
        83
        +

      
        
        84
        +type mockTool struct {

      
        
        85
        +	name, cmd string

      
        
        86
        +	args      []string

      
        
        87
        +}

      
        
        88
        +

      
        
        89
        +func (m *mockTool) Name() string          { return m.name }

      
        
        90
        +func (m *mockTool) Match(c *Context) bool { return c.Cmd == m.cmd }

      
        
        91
        +func (m *mockTool) Execute(c *Context) (string, error) {

      
        
        92
        +	m.args = c.Args

      
        
        93
        +	return c.Cmd, nil

      
        
        94
        +}

      
M plugin/viye.lua
···
        29
        29
           local lnum = cmd_lnum

      
        30
        30
           while lnum do

      
        31
        31
             local l = vim.fn.getline(lnum)

      
        32
        
        -    table.insert(parts, 1, l:match("^%s*[+-] (.*)") or vim.trim(l))

      
        
        32
        +    local line = l:match("^%s*[+-] (.*)") or vim.trim(l)

      
        
        33
        +    local words = vim.split(line, "%s+")

      
        
        34
        +    for i = #words, 1, -1 do table.insert(parts, 1, words[i]) end

      
        33
        35
             lnum = parent_of(lnum)

      
        34
        36
           end

      
        35
        37
           return parts

      ···
        88
        90
           local first, last = children_of(cmd_lnum)

      
        89
        91
           local has_children = last ~= nil

      
        90
        92
           local on_cmd = cmd_lnum == lnum

      
        91
        
        -  if has_children and (on_cmd or line_type(line) == "data") then

      
        92
        
        -    if last then vim.cmd(string.format("%d,%ddelete _", first, last)) end

      
        93
        
        -    toggle_bullet(cmd_lnum)

      
        
        93
        +  if on_cmd and not has_children then

      
        94
        94
             return

      
        95
        95
           end

      
        96
        96
           local out = run(cmd_lnum)