src/ulsp/controller/scip/consume.go (141 lines of code) (raw):

package scip import ( "github.com/sourcegraph/scip/bindings/go/scip" "github.com/uber/scip-lsp/src/scip-lib/model" "go.lsp.dev/protocol" "go.lsp.dev/uri" ) // GetSymbolDataForFile returns the document symbols for a given file func (r *registryImpl) GetDocumentSymbolForFile(uri uri.URI) (*[]*SymbolData, error) { symbols := make([]*SymbolData, 0) doc, ok := r.Documents[uri] if !ok { return &symbols, nil } occs := doc.Document.Occurrences for _, occ := range occs { if occ.SymbolRoles&int32(scip.SymbolRole_Definition) > 0 { var symbolData *SymbolData if scip.IsLocalSymbol(occ.Symbol) { symbolData = doc.Locals[occ.Symbol] } else { symbolData, _ = r.GetSymbolDataForSymbol(occ.Symbol, doc.Package) } if symbolData == nil { continue } symbols = append(symbols, symbolData) } } return &symbols, nil } // GetSymbolForPosition returns the occurrence and symbol data for a given position func (r *registryImpl) GetSymbolForPosition(uri uri.URI, pos protocol.Position) (*model.Occurrence, *SymbolData, error) { doc := r.Documents[uri] if doc == nil { return nil, nil, nil } occ := GetOccurrenceForPosition(doc.Document.Occurrences, pos) if occ == nil { return nil, nil, nil } if scip.IsLocalSymbol(occ.Symbol) && len(doc.Locals) > 0 { // If locals are in the doc, they won't be in the package return occ, doc.Locals[occ.Symbol], nil } sd, err := r.GetSymbolDataForSymbol(occ.Symbol, doc.Package) return occ, sd, err } // GetSymbolDataForSymbol returns the symbol data for a given symbol func (r *registryImpl) GetSymbolDataForSymbol(symbol string, localPkg *PackageMeta) (*SymbolData, error) { if scip.IsLocalSymbol(symbol) { localPkg.mu.Lock() defer localPkg.mu.Unlock() return localPkg.SymbolData[symbol], nil } sy, err := model.ParseScipSymbol(symbol) if err != nil { return nil, err } pkg := r.Packages[PackageID(sy.Package.Name)] if pkg == nil { return nil, nil } pkg.mu.Lock() defer pkg.mu.Unlock() return pkg.SymbolData[symbol], nil } // GetFileInfo returns the FileInfo for a given URI func (r *registryImpl) GetFileInfo(fileURI uri.URI) *FileInfo { return r.Documents[fileURI] } // GetPackageInfo returns the Package for a given package ID func (r *registryImpl) GetPackageInfo(pkgID PackageID) *PackageMeta { found := make([]*PackageMeta, 0) for id, pkg := range r.Packages { if id == pkgID { found = append(found, pkg) } } return r.Packages[pkgID] } // GetOccurrencesForSymbol returns the occurrences for a given symbol and role // If role is -1, it will return all occurrences for the symbol func GetOccurrencesForSymbol(occurrences []*model.Occurrence, symbol string, role scip.SymbolRole) []*model.Occurrence { matchAll := true if role != -1 { matchAll = false } found := make([]*model.Occurrence, 0) for _, occ := range occurrences { if occ.Symbol == symbol && (matchAll || occ.SymbolRoles&int32(role) > 0) { found = append(found, occ) } } return found } // GetLocalSymbolInformation returns the symbol information for a given symbol func GetLocalSymbolInformation(symbols []*model.SymbolInformation, symbol string) *model.SymbolInformation { for _, sym := range symbols { if sym.Symbol == symbol { return sym } } return nil } // GetOccurrenceForPosition returns the occurrence for a given position func GetOccurrenceForPosition(occurrences []*model.Occurrence, pos protocol.Position) *model.Occurrence { // Since occurrences are sorted by their position in the document // we can use binary search to speed up the lookup low := 0 high := len(occurrences) - 1 for low <= high { mid := (low + high) / 2 occ := occurrences[mid] if IsMatchingPosition(occ, pos) { return occ } if IsRangeBefore(occ.Range, pos) { low = mid + 1 } else { high = mid - 1 } } return nil } // IsRangeBefore returns true if the range is before the position func IsRangeBefore(r []int32, pos protocol.Position) bool { endLine := int32(0) endChar := int32(0) if len(r) == 3 { endLine = r[0] endChar = r[2] } if len(r) == 4 { endLine = r[2] endChar = r[3] } return endLine < int32(pos.Line) || (endLine == int32(pos.Line) && endChar < int32(pos.Character)) } // IsMatchingPosition returns true if the occurrence matches the position func IsMatchingPosition(occ *model.Occurrence, pos protocol.Position) bool { rng, err := scip.NewRange(occ.Range) if err != nil { return false } if rng.IsSingleLine() { return rng.Start.Line == int32(pos.Line) && rng.Start.Character <= int32(pos.Character) && rng.End.Character >= int32(pos.Character) } // For multiline ranges we need a slightly more complex check: // 1. If the position is between the start and end line, we don't need to do a character check // 2. If the position is on the start line, we need to check if it's beyond the start of the range // 3. If the position is on the end line, we need to check if it's before the end of the range return (rng.Start.Line < int32(pos.Line) && rng.End.Line > int32(pos.Line)) || (rng.Start.Line == int32(pos.Line) && rng.Start.Character <= int32(pos.Character)) || (rng.End.Line == int32(pos.Line) && rng.End.Character >= int32(pos.Character)) }