Support for --_interactive mode of universal-ctags.
For running the tests, a universal-ctags version of
5d82ee6e92ee9cdda5cc9dcee91646dd678b4b42 or newer is needed.
Change-Id: I9aa13237d1045e5169a49bd76aab9a398c4a0c36
diff --git a/ctags/json.go b/ctags/json.go
new file mode 100644
index 0000000..5136521
--- /dev/null
+++ b/ctags/json.go
@@ -0,0 +1,161 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ctags
+
+import (
+ "bufio"
+ "encoding/json"
+ "io"
+ "os"
+ "os/exec"
+ "runtime"
+ "sync"
+)
+
+type ctagsProcess struct {
+ cmd *exec.Cmd
+ in io.WriteCloser
+ out *bufio.Scanner
+ outPipe io.ReadCloser
+
+ procErrMu sync.Mutex
+ procErr error
+}
+
+var bin = "universal-ctags"
+
+func newProcess() (*ctagsProcess, error) {
+ opt := "default"
+ if runtime.GOOS == "linux" {
+ opt = "sandbox"
+ }
+
+ cmd := exec.Command(bin, "--_interactive="+opt, "--fields=*")
+ in, err := cmd.StdinPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ out, err := cmd.StdoutPipe()
+ if err != nil {
+ in.Close()
+ return nil, err
+ }
+ cmd.Stderr = os.Stderr
+ proc := ctagsProcess{
+ cmd: cmd,
+ in: in,
+ out: bufio.NewScanner(out),
+ outPipe: out,
+ }
+
+ if err := cmd.Start(); err != nil {
+ return nil, err
+ }
+
+ var init reply
+ if err := proc.read(&init); err != nil {
+ return nil, err
+ }
+
+ return &proc, nil
+}
+
+func (p *ctagsProcess) Close() {
+ p.cmd.Process.Kill()
+ p.outPipe.Close()
+ p.in.Close()
+}
+
+func (p *ctagsProcess) read(rep *reply) error {
+ if !p.out.Scan() {
+ // capture exit error.
+ err := p.cmd.Wait()
+ p.outPipe.Close()
+ p.in.Close()
+ return err
+ }
+ return json.Unmarshal(p.out.Bytes(), rep)
+}
+
+func (p *ctagsProcess) post(req *request, content []byte) error {
+ body, err := json.Marshal(req)
+ if err != nil {
+ return err
+ }
+ body = append(body, '\n')
+ if _, err = p.in.Write(body); err != nil {
+ return err
+ }
+ _, err = p.in.Write(content)
+
+ return err
+}
+
+type request struct {
+ Command string `json:"command"`
+ Filename string `json:"filename"`
+ Size int `json:"size"`
+}
+
+type reply struct {
+ // Init
+ Typ string `json:"_type"`
+ Name string `json:"name"`
+ Version string `json:"version"`
+
+ // completed
+ Command string `json:"command"`
+
+ Path string `json:"path"`
+ Pattern string `json:"pattern"`
+ Language string `json:"language"`
+ Line int `json:"line"`
+ Kind string `json:"kind"`
+}
+
+func (p *ctagsProcess) Process(name string, content []byte) ([]*Entry, error) {
+ req := request{
+ Command: "generate-tags",
+ Size: len(content),
+ Filename: name,
+ }
+
+ if err := p.post(&req, content); err != nil {
+ return nil, err
+ }
+
+ var es []*Entry
+ for {
+ var rep reply
+ if err := p.read(&rep); err != nil {
+ return nil, err
+ }
+ if rep.Typ == "completed" {
+ break
+ }
+
+ e := Entry{
+ Sym: rep.Name,
+ Path: rep.Path,
+ Line: rep.Line,
+ Kind: rep.Kind,
+ }
+
+ es = append(es, &e)
+ }
+
+ return es, nil
+}
diff --git a/ctags/json_test.go b/ctags/json_test.go
new file mode 100644
index 0000000..f42febe
--- /dev/null
+++ b/ctags/json_test.go
@@ -0,0 +1,94 @@
+// Copyright 2017 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ctags
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestJSON(t *testing.T) {
+ p, err := newProcess()
+ if err != nil {
+ t.Fatal("newProcess", err)
+ }
+
+ defer p.Close()
+
+ java := `
+package io.zoekt;
+import java.util.concurrent.Future;
+class Back implements Future extends Frob {
+ public static int BLA = 1;
+ public int member;
+ public Back() {
+ member = 2;
+ }
+ public int method() {
+ member++;
+ }
+}
+`
+ name := "io/zoekt/Back.java"
+ got, err := p.Process(name, []byte(java))
+ if err != nil {
+ t.Errorf("Process: %v", err)
+ }
+
+ want := []*Entry{
+ {
+ Sym: "io.zoekt",
+ Kind: "package",
+ Path: "io/zoekt/Back.java",
+ Line: 2,
+ },
+ {
+ Sym: "Back",
+ Path: "io/zoekt/Back.java",
+ Line: 4,
+ Kind: "class",
+ },
+
+ {
+ Sym: "BLA",
+ Path: "io/zoekt/Back.java",
+ Line: 5,
+ Kind: "field",
+ },
+ {
+ Sym: "member",
+ Path: "io/zoekt/Back.java",
+ Line: 6,
+ Kind: "field",
+ },
+ {
+ Sym: "Back",
+ Path: "io/zoekt/Back.java",
+ Line: 7,
+ Kind: "method",
+ },
+ {
+ Sym: "method",
+ Path: "io/zoekt/Back.java",
+ Line: 10, Kind: "method",
+ },
+ }
+
+ for i := range want {
+ if !reflect.DeepEqual(got[i], want[i]) {
+ t.Fatalf("got %#v, want %#v", got[i], want[i])
+ }
+ }
+}
diff --git a/ctags/parse.go b/ctags/parse.go
index 3d8b9ec..b8783b1 100644
--- a/ctags/parse.go
+++ b/ctags/parse.go
@@ -28,10 +28,7 @@
Parent string
ParentType string
- FileLimited bool
- Access string
- Implementation bool
- Inherits []string
+ FileLimited bool
}
// Parse parses a single line of exuberant "ctags -n" output.