4 files changed,
61 insertions(+),
41 deletions(-)
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/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)