fork of https://github.com/sourcegraph/zoekt
0

Configure Feed

Select the types of activity you want to include in your feed.

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 "bytes" 19 "fmt" 20 "log" 21 "os" 22 "os/exec" 23 "strings" 24 "sync" 25 "time" 26 27 goctags "github.com/sourcegraph/go-ctags" 28) 29 30const debug = false 31 32type Parser = goctags.Parser 33type Entry = goctags.Entry 34 35type parseReq struct { 36 Name string 37 Content []byte 38} 39 40type parseResp struct { 41 Entries []*Entry 42 Err error 43} 44 45type lockedParser struct { 46 mu sync.Mutex 47 opts goctags.Options 48 p Parser 49 send chan<- parseReq 50 recv <-chan parseResp 51} 52 53// parseTimeout is how long we wait for a response for parsing a single file 54// in ctags. 1 minute is a very conservative timeout which we should only hit 55// if ctags hangs. 56const parseTimeout = time.Minute 57 58// Parse wraps go-ctags Parse. It lazily starts the process and adds a timeout 59// around parse requests. Additionally it serializes access to the parsing 60// process. The timeout is important since we occasionally come across 61// documents which hang universal-ctags. 62func (lp *lockedParser) Parse(name string, content []byte) ([]*Entry, error) { 63 lp.mu.Lock() 64 defer lp.mu.Unlock() 65 66 if lp.p == nil { 67 p, err := goctags.New(lp.opts) 68 if err != nil { 69 return nil, err 70 } 71 send := make(chan parseReq) 72 // buf of 1 so we avoid blocking sends in the parser if we exit early. 73 recv := make(chan parseResp, 1) 74 75 go func() { 76 defer close(recv) 77 for req := range send { 78 entries, err := p.Parse(req.Name, req.Content) 79 recv <- parseResp{Entries: entries, Err: err} 80 } 81 }() 82 83 lp.p = p 84 lp.send = send 85 lp.recv = recv 86 } 87 88 lp.send <- parseReq{Name: name, Content: content} 89 90 deadline := time.NewTimer(parseTimeout) 91 defer deadline.Stop() 92 93 select { 94 case resp := <-lp.recv: 95 return resp.Entries, resp.Err 96 case <-deadline.C: 97 // Error out since ctags hanging is a sign something bad is happening. 98 lp.close() 99 return nil, fmt.Errorf("ctags timedout after %s parsing %s", parseTimeout, name) 100 } 101} 102 103func (lp *lockedParser) Close() { 104 lp.mu.Lock() 105 defer lp.mu.Unlock() 106 lp.close() 107} 108 109// close assumes lp.mu is held. 110func (lp *lockedParser) close() { 111 if lp.p == nil { 112 return 113 } 114 115 lp.p.Close() 116 lp.p = nil 117 close(lp.send) 118 lp.send = nil 119 lp.recv = nil 120} 121 122// NewParser creates a parser that is implemented by the given 123// universal-ctags binary. The parser is safe for concurrent use. 124func NewParser(parserType CTagsParserType, bin string) (Parser, error) { 125 if err := checkBinary(parserType, bin); err != nil { 126 return nil, err 127 } 128 129 opts := goctags.Options{ 130 Bin: bin, 131 } 132 if debug { 133 opts.Info = log.New(os.Stderr, "CTAGS INF: ", log.LstdFlags) 134 opts.Debug = log.New(os.Stderr, "CTAGS DBG: ", log.LstdFlags) 135 } 136 return &lockedParser{ 137 opts: opts, 138 }, nil 139} 140 141// checkBinary does checks on bin to ensure we can correctly use the binary 142// for symbols. It is more user friendly to fail early in this case. 143func checkBinary(typ CTagsParserType, bin string) error { 144 switch typ { 145 case UniversalCTags: 146 helpOutput, err := exec.Command(bin, "--help").CombinedOutput() 147 if err != nil { 148 return fmt.Errorf("failed to check if %s is universal-ctags: %w\n--help output:\n%s", bin, err, string(helpOutput)) 149 } 150 if !bytes.Contains(helpOutput, []byte("+interactive")) { 151 return fmt.Errorf("ctags binary is not universal-ctags or is not compiled with +interactive feature: bin=%s", bin) 152 } 153 154 case ScipCTags: 155 if !strings.Contains(bin, "scip-ctags") { 156 return fmt.Errorf("only supports scip-ctags, not %s", bin) 157 } 158 } 159 160 return nil 161}