fork of https://github.com/sourcegraph/zoekt
1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package ctags
16
17import (
18 "fmt"
19 "log"
20 "os"
21 "time"
22
23 goctags "github.com/sourcegraph/go-ctags"
24)
25
26type Entry = goctags.Entry
27
28// CTagsParser wraps go-ctags and delegates to the right process (like universal-ctags or scip-ctags).
29// It is only safe for single-threaded use. This wrapper also enforces a timeout on parsing a single
30// document, which is important since documents can occasionally hang universal-ctags.
31// documents which hang universal-ctags.
32type CTagsParser struct {
33 bins ParserBinMap
34 parsers map[CTagsParserType]goctags.Parser
35}
36
37// parseTimeout is how long we wait for a response for parsing a single file
38// in ctags. 1 minute is a very conservative timeout which we should only hit
39// if ctags hangs.
40const parseTimeout = time.Minute
41
42func NewCTagsParser(bins ParserBinMap) CTagsParser {
43 return CTagsParser{bins: bins, parsers: make(map[CTagsParserType]goctags.Parser)}
44}
45
46type parseResult struct {
47 entries []*Entry
48 err error
49}
50
51func (lp *CTagsParser) Parse(name string, content []byte, typ CTagsParserType) ([]*Entry, error) {
52 if lp.parsers[typ] == nil {
53 parser, err := lp.newParserProcess(typ)
54 if parser == nil || err != nil {
55 return nil, err
56 }
57 lp.parsers[typ] = parser
58 }
59
60 deadline := time.NewTimer(parseTimeout)
61 defer deadline.Stop()
62
63 parser := lp.parsers[typ]
64 recv := make(chan parseResult, 1)
65 go func() {
66 entry, err := parser.Parse(name, content)
67 recv <- parseResult{entries: entry, err: err}
68 }()
69
70 select {
71 case resp := <-recv:
72 return resp.entries, resp.err
73 case <-deadline.C:
74 // Error out since ctags hanging is a sign something bad is happening.
75 return nil, fmt.Errorf("ctags timedout after %s parsing %s", parseTimeout, name)
76 }
77}
78
79func (lp *CTagsParser) newParserProcess(typ CTagsParserType) (goctags.Parser, error) {
80 bin := lp.bins[typ]
81 if bin == "" {
82 // This happens if CTagsMustSucceed is false and we didn't find the binary
83 return nil, nil
84 }
85
86 opts := goctags.Options{Bin: bin}
87 parserType := ParserToString(typ)
88 if debug {
89 opts.Info = log.New(os.Stderr, "CTAGS ("+parserType+") INF: ", log.LstdFlags)
90 opts.Debug = log.New(os.Stderr, "CTAGS ("+parserType+") DBG: ", log.LstdFlags)
91 }
92 return goctags.New(opts)
93}
94
95func (lp *CTagsParser) Close() {
96 for _, parser := range lp.parsers {
97 parser.Close()
98 }
99}