├── README.md ├── debouncer.go ├── debouncer_test.go └── main.go /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://drone.io/github.com/s-urbaniak/agoc/status.png)](https://drone.io/github.com/s-urbaniak/agoc/latest) 2 | 3 | ## Go Autocompletion for the acme editor 4 | 5 | ### Usage 6 | Execute 'agoc' in the tag window of an existing .go file. A new window called "+agoc" will open. Whenever you type something in the original source window, a completion will be proposed in the "+agoc" window. 7 | 8 | ### Installation 9 | 10 | go get github.com/s-urbaniak/agoc 11 | 12 | ### Prerequisites 13 | You need to have gocode installed: 14 | 15 | go get github.com/nsf/gocode 16 | -------------------------------------------------------------------------------- /debouncer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type cancel struct{} 9 | 10 | func debouncer(offsets chan int, delay time.Duration) chan int { 11 | out := make(chan int) 12 | c := make(chan cancel) 13 | var wg sync.WaitGroup 14 | 15 | go func() { 16 | defer func() { 17 | close(c) 18 | wg.Wait() 19 | close(out) 20 | }() 21 | 22 | for o := range offsets { 23 | close(c) 24 | c = make(chan cancel) 25 | wg.Add(1) 26 | 27 | go func(o int, c chan cancel) { 28 | defer wg.Done() 29 | 30 | select { 31 | case <-time.After(delay): 32 | out <- o 33 | case <-c: 34 | } 35 | }(o, c) 36 | } 37 | }() 38 | 39 | return out 40 | } 41 | -------------------------------------------------------------------------------- /debouncer_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestDebouncer(t *testing.T) { 9 | o := make(chan int) 10 | d := debouncer(o, 100*time.Millisecond) 11 | 12 | o <- 1 13 | o <- 2 14 | time.Sleep(50 * time.Millisecond) 15 | o <- 3 16 | time.Sleep(50 * time.Millisecond) 17 | o <- 4 18 | time.Sleep(200 * time.Millisecond) 19 | 20 | if v := <-d; v != 4 { 21 | t.Errorf("expected 4, got %v\n", v) 22 | } 23 | 24 | o <- 5 25 | o <- 6 26 | time.Sleep(50 * time.Millisecond) 27 | o <- 7 28 | time.Sleep(200 * time.Millisecond) 29 | 30 | if v := <-d; v != 7 { 31 | t.Errorf("expected 7, got %v\n", v) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // agoc is a tool for code completion inside the acme editor 2 | package main 3 | 4 | import ( 5 | "io" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | acme9 "9fans.net/go/acme" 14 | "github.com/s-urbaniak/acme" 15 | ) 16 | 17 | type offset struct { 18 | id, offset int 19 | } 20 | 21 | func main() { 22 | log.SetFlags(log.LstdFlags | log.Lshortfile) 23 | 24 | agoc, err := agoc() 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | 29 | looper(agoc) 30 | } 31 | 32 | func looper(agoc *acme.Win) { 33 | logEvt := logEvtChan() 34 | opened := make(map[int]struct{}) 35 | offsetChan := make(chan offset) 36 | 37 | for { 38 | select { 39 | case o := <-offsetChan: 40 | complete(o, agoc) 41 | case evt := <-logEvt: 42 | switch evt.Op { 43 | case "del": 44 | delete(opened, evt.ID) 45 | case "focus": 46 | if _, ok := opened[evt.ID]; !ok { 47 | opened[evt.ID] = struct{}{} 48 | 49 | srcChan := src(evt.ID) 50 | id := evt.ID 51 | go func() { 52 | for o := range srcChan { 53 | offsetChan <- offset{id, o} 54 | } 55 | }() 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | func complete(o offset, agoc *acme.Win) { 63 | srcWin, err := acme.GetWin(o.id) 64 | if err != nil { 65 | log.Fatal(err) 66 | } 67 | 68 | cmd := exec.Command("gocode", "autocomplete", strconv.Itoa(o.offset)) 69 | stdin, err := cmd.StdinPipe() 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | 74 | stdout, err := cmd.StdoutPipe() 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | defer stdout.Close() 79 | 80 | if err := cmd.Start(); err != nil { 81 | log.Fatal(err) 82 | } 83 | 84 | go func() { 85 | defer func() { 86 | srcWin.CloseFiles() 87 | stdin.Close() 88 | }() 89 | 90 | _, err := io.Copy(stdin, srcWin.FileReadWriter("body")) 91 | if err != nil { 92 | log.Fatal(err) 93 | } 94 | }() 95 | 96 | agoc.ClearBody() 97 | 98 | _, err = io.Copy(agoc.FileReadWriter("body"), stdout) 99 | if err != nil { 100 | log.Fatal(err) 101 | } 102 | 103 | agoc.Fprintf("addr", "#0") 104 | agoc.Ctl("dot=addr") 105 | agoc.Ctl("show") 106 | agoc.Ctl("clean") 107 | 108 | err = cmd.Wait() 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | } 113 | 114 | func logEvtChan() <-chan acme9.LogEvent { 115 | acmeLog, err := acme9.Log() 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | 120 | events := make(chan acme9.LogEvent) 121 | 122 | go func() { 123 | for { 124 | evt, err := acmeLog.Read() 125 | if err != nil { 126 | log.Fatal(err) 127 | } 128 | 129 | if strings.HasSuffix(evt.Name, ".go") { 130 | events <- evt 131 | } 132 | } 133 | }() 134 | 135 | return events 136 | } 137 | 138 | func src(id int) <-chan int { 139 | offset := make(chan int) 140 | 141 | win, err := acme.GetWin(id) 142 | if err != nil { 143 | log.Fatal(err) 144 | } 145 | 146 | go func() { 147 | defer func() { 148 | win.CloseFiles() 149 | close(offset) 150 | }() 151 | 152 | var changed bool 153 | 154 | for { 155 | evt, err := win.ReadEvent() 156 | if err != nil { 157 | log.Fatal(err) 158 | } 159 | 160 | switch evt.C2 { 161 | case 'I', 'D': 162 | if !changed { 163 | tagb, err := win.ReadAll("tag") 164 | if err != nil { 165 | log.Fatal(err) 166 | } 167 | 168 | tag := string(tagb) 169 | if !strings.Contains(tag, "Put") { 170 | win.Write("tag", []byte(" Put")) 171 | } 172 | 173 | changed = true 174 | } 175 | 176 | err = win.Ctl("addr=dot") 177 | if err != nil { 178 | log.Fatal(err) 179 | } 180 | 181 | q0, _, err := win.ReadAddr() 182 | if err != nil { 183 | log.Fatal(err) 184 | } 185 | 186 | offset <- q0 187 | case 'x', 'X': 188 | evtText := string(evt.Text) 189 | 190 | switch { 191 | case evtText == "Put": 192 | changed = false 193 | case evtText == "Del": 194 | if changed { 195 | continue 196 | } 197 | fallthrough 198 | case evtText == "Del" || evtText == "Delete": 199 | win.Ctl("delete") 200 | win.WriteEvent(evt) 201 | return 202 | } 203 | } 204 | 205 | win.WriteEvent(evt) 206 | } 207 | }() 208 | 209 | return debouncer(offset, 300*time.Millisecond) 210 | } 211 | 212 | func agoc() (*acme.Win, error) { 213 | agoc, err := acme.New() 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | agoc.Name("+agoc") 219 | agoc.Ctl("clean") 220 | agoc.Fprintf("tag", "Get ") 221 | 222 | go func() { 223 | for { 224 | evt, err := agoc.ReadEvent() 225 | if err != nil { 226 | log.Fatal(err) 227 | } 228 | 229 | switch evt.C2 { 230 | case 'x', 'X': 231 | evtText := string(evt.Text) 232 | 233 | if evtText == "Del" || evtText == "Delete" { 234 | agoc.WriteEvent(evt) 235 | os.Exit(0) 236 | } 237 | } 238 | 239 | agoc.WriteEvent(evt) 240 | } 241 | }() 242 | 243 | return agoc, nil 244 | } 245 | --------------------------------------------------------------------------------