blob: aa03f8087360639660005e7e3c2b6df90f1c7497 [file] [log] [blame]
// Copyright (C) 2016 The Android Open Source Project
//
// 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 langsvr
import (
"fmt"
"io"
"io/ioutil"
"strings"
"unicode/utf8"
"android.googlesource.com/platform/tools/gpu/framework/log"
"android.googlesource.com/platform/tools/gpu/framework/task"
"android.googlesource.com/platform/tools/gpu/tools/langsvr/protocol"
)
// InitConfig is returned by Server.Initialize().
type InitConfig struct {
// The language identifier for this language server.
LanguageID string
// CompletionTriggerCharacters is the list of characters that will trigger a
// completion suggestion.
CompletionTriggerCharacters []rune
// CompletionTriggerCharacters is the list of characters that will trigger a
// signature hint.
SignatureTriggerCharacters []rune
// Documents is a list of all the document paths that should be watched from
// initialization.
WorkspaceDocuments []string
}
// Server is the interface implemented by language servers.
type Server interface {
// Initialize is called when the server is first initialized by the client.
Initialize(ctx log.Context, rootPath string) (InitConfig, error)
// Shutdown is called to shutdown the server.
Shutdown(log.Context) error
// OnConfigChange is called when the configuration settings change.
OnConfigChange(log.Context, map[string]interface{}) error
// OnDocumentsAdded is called when new documents of interest are discovered.
OnDocumentsAdded(log.Context, []*Document) error
// OnDocumentsChanged is called when documents are changed.
OnDocumentsChanged(log.Context, []*Document) error
// OnDocumentsRemoved is called when documents are no longer monitored.
OnDocumentsRemoved(log.Context, []*Document) error
// OnDocumentSaved is called when an open document is saved.
OnDocumentSaved(log.Context, *Document) error
}
// HoverProvider is the interface implemented by servers that support hover
// information.
type HoverProvider interface {
// Hover returns a list of source code snippets and range for the given
// symbol at the specified position.
Hover(log.Context, *Document, Position) (SourceCodeList, Range, error)
}
// CompletionProvider is the interface implemented by servers that support
// completion information.
type CompletionProvider interface {
// Completions returns completion items at a given cursor position.
// Completion items are presented in the IntelliSense user interface.
Completions(log.Context, *Document, Position) (CompletionList, error)
}
// SignatureProvider is the interface implemented by servers that support
// callable signature information.
type SignatureProvider interface {
// Signatures returns the list of function signatures that are candidates
// at the given cursor position. The activeSig and activeParam are indices
// of the signature and parameter to highlight for the given cursor
// position.
Signatures(log.Context, *Document, Position) (sigs SignatureList, activeSig, activeParam int, err error)
}
// DefinitionProvider is the interface implemented by servers that support
// symbol definition information.
type DefinitionProvider interface {
// Definitions returns the list of definition locations for the given symbol
// in the specified document at position.
Definitions(log.Context, *Document, Position) ([]Location, error)
}
// ReferencesProvider is the interface implemented by servers that support
// symbol reference information.
type ReferencesProvider interface {
// References returns a list of references for the given symbol in the
// specified document at position.
References(log.Context, *Document, Position) ([]Location, error)
}
// HighlightsProvider is the interface implemented by servers that support
// symbol highlight information.
type HighlightsProvider interface {
// Highlights returns a list of highlights for the given symbol in the
// specified document at position.
Highlights(log.Context, *Document, Position) (HighlightList, error)
}
// SymbolsProvider is the interface implemented by servers that support
// document symbol information.
type SymbolsProvider interface {
// Symbols returns symbolic information about the specified document.
Symbols(log.Context, *Document) (SymbolList, error)
}
// WorkspaceSymbolsProvider is the interface implemented by servers that support
// whole-workspace symbol information.
type WorkspaceSymbolsProvider interface {
// WorkspaceSymbols returns all project-wide symbols.
WorkspaceSymbols(ctx log.Context) (SymbolList, error)
}
// CodeActionsProvider is the interface implemented by servers that support
// code actions.
type CodeActionsProvider interface {
// CodeActions compute commands for a given document and range.
// The request is triggered when the user moves the cursor into an problem
// marker in the editor or presses the lightbulb associated with a marker.
CodeActions(log.Context, *Document, Range, []Diagnostic) ([]Command, error)
}
// CodeLensProvider is the interface implemented by servers that support
// code lenes.
type CodeLensProvider interface {
// CodeLenses returns a list of CodeLens for the specified document.
CodeLenses(log.Context, *Document) ([]CodeLens, error)
}
// FormatProvider is the interface implemented by servers that support
// whole-document reformatting.
type FormatProvider interface {
// Format returns a list of edits required to format the entire document.
Format(ctx log.Context, doc *Document, opts FormattingOptions) (TextEditList, error)
}
// FormatRangeProvider is the interface implemented by servers that support
// document-range reformatting.
type FormatRangeProvider interface {
// FormatRange returns a list of edits required to format the specified
// range in the specified document.
FormatRange(ctx log.Context, doc *Document, rng Range, opts FormattingOptions) (TextEditList, error)
}
// FormatOnTypeProvider is the interface implemented by servers that support
// reformatting as-you-type.
type FormatOnTypeProvider interface {
// FormatOnType returns a list of edits required to format the code
// currently being written at pos, after char was typed.
FormatOnType(doc *Document, pos Position, char rune, opts FormattingOptions) (TextEditList, error)
}
// RenameProvider is the interface implemented by servers that support renaming
// symbols.
type RenameProvider interface {
// Rename is called to rename the symbol at pos with newName.
Rename(ctx log.Context, doc *Document, pos Position, newName string) (WorkspaceEdit, error)
}
// Connect creates a connection to between server and the client (code editor)
// communicating on stream.
func Connect(ctx log.Context, stream io.ReadWriter, server Server) error {
ctx, terminate := task.WithCancel(ctx)
conn := protocol.NewConnection(stream)
ls := &langsvr{
conn,
server,
make(map[string]*Document),
"",
terminate,
}
handler := log.GetHandler(ctx)
ctx = ctx.Handler(func(r log.Record) {
msg := fmt.Sprintf("%v", r.Message)
if ls.languageID != "" {
msg = fmt.Sprintf("%v: %v", ls.languageID, msg)
}
switch r.Severity {
case log.Severity_Emergency, log.Severity_Alert, log.Severity_Critical, log.Severity_Error:
conn.ShowMessage(protocol.ErrorType, msg)
case log.Severity_Warning:
conn.LogMessage(protocol.WarningType, msg)
case log.Severity_Notice, log.Severity_Info:
conn.LogMessage(protocol.InfoType, msg)
case log.Severity_Debug:
conn.LogMessage(protocol.LogType, msg)
}
handler(r)
})
return conn.Serve(ctx, ls)
}
// langsvr is an implementation of protocol.Server.
type langsvr struct {
conn *protocol.Connection
server Server
documents map[string]*Document // uri -> Document
languageID string
terminate func()
}
func runesToStrings(in []rune) []string {
out := make([]string, len(in))
for i, r := range in {
out[i] = string(r)
}
return out
}
func (s *langsvr) Initialize(ctx log.Context, processID int, rootPath string) (protocol.ServerCapabilities, error) {
ctx = ctx.Enter("Initialize")
cfg, err := s.server.Initialize(ctx, rootPath)
if err != nil {
return protocol.ServerCapabilities{}, err
}
s.languageID = cfg.LanguageID
added := make([]*Document, 0, len(cfg.WorkspaceDocuments))
for _, path := range cfg.WorkspaceDocuments {
ctx := ctx.S("path", path)
body, err := ioutil.ReadFile(path)
if err != nil {
ctx.Error().Log("Couldn't open workspace document")
continue
}
doc := s.newDocument(PathToURI(path), cfg.LanguageID, 0, NewBody(string(body)))
doc.watched = true
added = append(added, doc)
}
s.server.OnDocumentsAdded(ctx, added)
caps := protocol.ServerCapabilities{
TextDocumentSync: protocol.SyncIncremental,
}
_, caps.HoverProvider = s.server.(HoverProvider)
_, caps.DefinitionProvider = s.server.(DefinitionProvider)
_, caps.ReferencesProvider = s.server.(ReferencesProvider)
_, caps.DocumentHighlightProvider = s.server.(HighlightsProvider)
_, caps.DocumentSymbolProvider = s.server.(SymbolsProvider)
_, caps.WorkspaceSymbolProvider = s.server.(WorkspaceSymbolsProvider)
_, caps.CodeActionProvider = s.server.(CodeActionsProvider)
_, caps.DocumentFormattingProvider = s.server.(FormatProvider)
_, caps.DocumentRangeFormattingProvider = s.server.(FormatRangeProvider)
_, caps.RenameProvider = s.server.(RenameProvider)
if _, ok := s.server.(CompletionProvider); ok {
caps.CompletionProvider = protocol.CompletionOptions{
ResolveProvider: true,
TriggerCharacters: runesToStrings(cfg.CompletionTriggerCharacters),
}
}
if _, ok := s.server.(SignatureProvider); ok {
caps.SignatureHelpProvider = protocol.SignatureHelpOptions{
TriggerCharacters: runesToStrings(cfg.SignatureTriggerCharacters),
}
}
if _, ok := s.server.(CodeLensProvider); ok {
caps.CodeLensProvider = &protocol.CodeLensOptions{
ResolveProvider: true,
}
}
if _, ok := s.server.(FormatOnTypeProvider); ok {
caps.DocumentOnTypeFormattingProvider = &protocol.DocumentOnTypeFormattingOptions{
FirstTriggerCharacter: "",
MoreTriggerCharacter: []string{},
}
}
return caps, nil
}
func (s langsvr) Shutdown(ctx log.Context) error {
ctx = ctx.Enter("Shutdown")
return s.server.Shutdown(ctx)
}
func (s langsvr) Completion(ctx log.Context, uri protocol.TextDocumentIdentifier, position protocol.Position) (protocol.CompletionList, error) {
ctx = ctx.Enter("Completion")
cp, ok := s.server.(CompletionProvider)
if !ok {
return protocol.CompletionList{}, nil
}
doc, err := s.getDoc(uri.URI)
if err != nil {
return protocol.CompletionList{}, err
}
list, err := cp.Completions(ctx, doc, pos(position))
if err != nil {
return protocol.CompletionList{}, err
}
return list.toProtocol(), nil
}
func (s langsvr) CompletionItemResolve(ctx log.Context, item protocol.CompletionItem) (protocol.CompletionItem, error) {
ctx = ctx.Enter("CompletionItemResolve")
return item, nil
}
func (s langsvr) Hover(ctx log.Context, uri protocol.TextDocumentIdentifier, position protocol.Position) ([]protocol.MarkedString, *protocol.Range, error) {
ctx = ctx.Enter("Hover")
hp, ok := s.server.(HoverProvider)
if !ok {
return []protocol.MarkedString{}, nil, nil
}
doc, err := s.getDoc(uri.URI)
if err != nil {
return nil, nil, err
}
sources, rng, err := hp.Hover(ctx, doc, pos(position))
if err != nil {
return nil, nil, err
}
rngOut := rng.toProtocol()
return sources.toProtocol(), &rngOut, nil
}
func (s langsvr) SignatureHelp(ctx log.Context, uri protocol.TextDocumentIdentifier, position protocol.Position) ([]protocol.SignatureInformation, *int, *int, error) {
ctx = ctx.Enter("SignatureHelp")
sp, ok := s.server.(SignatureProvider)
if !ok {
return []protocol.SignatureInformation{}, nil, nil, nil
}
doc, err := s.getDoc(uri.URI)
if err != nil {
return nil, nil, nil, err
}
sigs, activeSig, activeParam, err := sp.Signatures(ctx, doc, pos(position))
if err != nil {
return nil, nil, nil, err
}
if len(sigs) > 0 {
return sigs.toProtocol(), &activeSig, &activeParam, nil
}
return []protocol.SignatureInformation{}, nil, nil, err
}
func (s langsvr) GotoDefinition(ctx log.Context, uri protocol.TextDocumentIdentifier, position protocol.Position) ([]protocol.Location, error) {
ctx = ctx.Enter("GotoDefinition")
dp, ok := s.server.(DefinitionProvider)
if !ok {
return []protocol.Location{}, nil
}
doc, err := s.getDoc(uri.URI)
if err != nil {
return nil, err
}
locations, err := dp.Definitions(ctx, doc, pos(position))
if err != nil {
return nil, err
}
out := make([]protocol.Location, len(locations))
for i, l := range locations {
out[i] = l.toProtocol()
}
return out, nil
}
func (s langsvr) FindReferences(ctx log.Context, uri protocol.TextDocumentIdentifier, position protocol.Position, includeDecl bool) ([]protocol.Location, error) {
ctx = ctx.Enter("FindReferences")
rp, ok := s.server.(ReferencesProvider)
if !ok {
return []protocol.Location{}, nil
}
doc, err := s.getDoc(uri.URI)
if err != nil {
return nil, err
}
references, err := rp.References(ctx, doc, pos(position))
if err != nil {
return nil, err
}
out := make([]protocol.Location, len(references))
for i, r := range references {
out[i] = r.toProtocol()
}
return out, nil
}
func (s langsvr) DocumentHighlights(ctx log.Context, uri protocol.TextDocumentIdentifier, position protocol.Position) ([]protocol.DocumentHighlight, error) {
ctx = ctx.Enter("DocumentHighlights")
hp, ok := s.server.(HighlightsProvider)
if !ok {
return []protocol.DocumentHighlight{}, nil
}
doc, err := s.getDoc(uri.URI)
if err != nil {
return nil, err
}
highlights, err := hp.Highlights(ctx, doc, pos(position))
return highlights.toProtocol(), err
}
func (s langsvr) DocumentSymbols(ctx log.Context, docID protocol.TextDocumentIdentifier) ([]protocol.SymbolInformation, error) {
ctx = ctx.Enter("DocumentSymbols")
sp, ok := s.server.(SymbolsProvider)
if !ok {
return []protocol.SymbolInformation{}, nil
}
doc, err := s.getDoc(docID.URI)
if err != nil {
return nil, err
}
symbols, err := sp.Symbols(ctx, doc)
if err != nil {
return nil, err
}
return symbols.toProtocol(), nil
}
func (s langsvr) WorkspaceSymbols(ctx log.Context, query string) ([]protocol.SymbolInformation, error) {
ctx = ctx.Enter("WorkspaceSymbols")
sp, ok := s.server.(WorkspaceSymbolsProvider)
if !ok {
return []protocol.SymbolInformation{}, nil
}
symbols, err := sp.WorkspaceSymbols(ctx)
if err != nil {
return nil, err
}
query = strings.ToLower(query)
symbols = symbols.Filter(func(s Symbol) bool { return strings.Contains(strings.ToLower(s.Name), query) })
return symbols.toProtocol(), nil
}
func (s langsvr) CodeAction(ctx log.Context, docID protocol.TextDocumentIdentifier, inRng protocol.Range, context protocol.CodeActionContext) ([]protocol.Command, error) {
ctx = ctx.Enter("CodeAction")
doc, err := s.getDoc(docID.URI)
if err != nil {
return nil, err
}
cp, ok := s.server.(CodeActionsProvider)
if !ok {
return []protocol.Command{}, nil
}
diagnostics := make([]Diagnostic, len(context.Diagnostics))
for i, diag := range context.Diagnostics {
diagnostics[i] = diagnostic(diag)
}
commands, err := cp.CodeActions(ctx, doc, rng(inRng), diagnostics)
if err != nil {
return nil, err
}
out := make([]protocol.Command, len(commands))
for i, cmd := range commands {
out[i] = cmd.toProtocol()
}
return out, nil
}
func (s langsvr) CodeLens(ctx log.Context, docID protocol.TextDocumentIdentifier) ([]protocol.CodeLens, error) {
ctx = ctx.Enter("CodeLens")
doc, err := s.getDoc(docID.URI)
if err != nil {
return nil, err
}
cp, ok := s.server.(CodeLensProvider)
if !ok {
return []protocol.CodeLens{}, nil
}
cls, err := cp.CodeLenses(ctx, doc)
if err != nil {
return nil, err
}
out := make([]protocol.CodeLens, len(cls))
for i, cl := range cls {
out[i] = protocol.CodeLens{
Range: cl.Range.toProtocol(),
Data: i,
}
}
return out, nil
}
func (s langsvr) CodeLensResolve(ctx log.Context, codelens protocol.CodeLens) (protocol.CodeLens, error) {
ctx = ctx.Enter("CodeLensResolve")
return codelens, nil
}
func (s langsvr) DocumentFormatting(ctx log.Context, item protocol.TextDocumentIdentifier, opts protocol.FormattingOptions) ([]protocol.TextEdit, error) {
ctx = ctx.Enter("DocumentFormatting")
fp, ok := s.server.(FormatProvider)
if !ok {
return []protocol.TextEdit{}, nil
}
doc, err := s.getDoc(item.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return []protocol.TextEdit{}, nil
}
edits, err := fp.Format(ctx, doc, FormattingOptions{
TabSize: opts.TabSize,
InsertSpaces: opts.InsertSpaces,
})
if err != nil {
return nil, err
}
return edits.toProtocol(), nil
}
func (s langsvr) DocumentRangeFormatting(ctx log.Context, item protocol.TextDocumentIdentifier, fmtrng protocol.Range, opts protocol.FormattingOptions) ([]protocol.TextEdit, error) {
ctx = ctx.Enter("DocumentRangeFormatting")
fp, ok := s.server.(FormatRangeProvider)
if !ok {
return []protocol.TextEdit{}, nil
}
doc, err := s.getDoc(item.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return []protocol.TextEdit{}, nil
}
edits, err := fp.FormatRange(ctx, doc, rng(fmtrng), FormattingOptions{
TabSize: opts.TabSize,
InsertSpaces: opts.InsertSpaces,
})
if err != nil {
return nil, err
}
return edits.toProtocol(), nil
}
func (s langsvr) DocumentOnTypeFormatting(ctx log.Context, item protocol.TextDocumentIdentifier, position protocol.Position, char string, opts protocol.FormattingOptions) ([]protocol.TextEdit, error) {
ctx = ctx.Enter("DocumentOnTypeFormatting")
fp, ok := s.server.(FormatOnTypeProvider)
if !ok {
return []protocol.TextEdit{}, nil
}
doc, err := s.getDoc(item.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return []protocol.TextEdit{}, nil
}
r, _ := utf8.DecodeRuneInString(char)
edits, err := fp.FormatOnType(doc, pos(position), r, FormattingOptions{
TabSize: opts.TabSize,
InsertSpaces: opts.InsertSpaces,
})
if err != nil {
return nil, err
}
return edits.toProtocol(), nil
}
func (s langsvr) Rename(ctx log.Context, item protocol.TextDocumentIdentifier, position protocol.Position, newName string) (protocol.WorkspaceEdit, error) {
ctx = ctx.Enter("Rename")
rp, ok := s.server.(RenameProvider)
if !ok {
return protocol.WorkspaceEdit{Changes: []protocol.TextEdit{}}, nil
}
doc, err := s.getDoc(item.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return protocol.WorkspaceEdit{Changes: []protocol.TextEdit{}}, nil
}
edits, err := rp.Rename(ctx, doc, pos(position), newName)
return edits.toProtocol(), nil
}
func (s langsvr) OnExit(ctx log.Context) error {
ctx = ctx.Enter("OnExit")
s.terminate()
return nil
}
func (s langsvr) OnChangeConfiguration(ctx log.Context, config map[string]interface{}) {
ctx = ctx.Enter("OnChangeConfiguration")
s.server.OnConfigChange(ctx, config)
}
func (s langsvr) OnOpenTextDocument(ctx log.Context, item protocol.TextDocumentItem) {
ctx = ctx.Enter("OnOpenTextDocument")
doc := s.documents[item.URI]
if doc == nil {
doc = s.newDocument(item.URI, item.LanguageID, item.Version, NewBody(item.Text))
s.server.OnDocumentsAdded(ctx, []*Document{doc})
}
doc.open = true
}
func (s langsvr) OnChangeTextDocument(ctx log.Context, item protocol.VersionedTextDocumentIdentifier, changes []protocol.TextDocumentContentChangeEvent) {
ctx = ctx.Enter("OnChangeTextDocument")
doc, err := s.getDoc(item.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return
}
body := doc.Body()
for _, change := range changes {
start := body.offset(change.Range.Start)
end := body.offset(change.Range.End)
change := []rune(change.Text)
runes := make([]rune, 0, len(body.Runes())-(end-start)+len(change))
runes = append(runes, body.Runes()[:start]...)
runes = append(runes, change...)
runes = append(runes, body.Runes()[end:]...)
body = NewBodyFromRunes(runes)
}
// Each document's body must be immutable.
// Make a copy, and replace the existing entry.
clone := *doc
doc = &clone
doc.body = body
doc.version = item.Version
s.documents[doc.uri] = doc
s.server.OnDocumentsChanged(ctx, []*Document{doc})
}
func (s langsvr) OnCloseTextDocument(ctx log.Context, docID protocol.TextDocumentIdentifier) {
ctx = ctx.Enter("OnCloseTextDocument")
doc, err := s.getDoc(docID.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return
}
doc.open = false
if !doc.watched {
s.server.OnDocumentsRemoved(ctx, []*Document{doc})
delete(s.documents, docID.URI)
}
}
func (s langsvr) OnSaveTextDocument(ctx log.Context, docID protocol.TextDocumentIdentifier) {
ctx = ctx.Enter("OnSaveTextDocument")
doc, err := s.getDoc(docID.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
return
}
s.server.OnDocumentSaved(ctx, doc)
}
func (s langsvr) OnChangeWatchedFiles(ctx log.Context, changes []protocol.FileEvent) {
ctx = ctx.Enter("OnChangeWatchedFiles")
created := make([]*Document, 0, len(changes))
for _, change := range changes {
if change.Type == protocol.Created {
path, err := URItoPath(change.URI)
if err != nil {
ctx.Fail(err, "Couldn't get path from change URI")
continue
}
body, err := ioutil.ReadFile(path)
if err != nil {
ctx.Fail(err, "Couldn't read created file")
continue
}
doc := s.newDocument(change.URI, s.languageID, 0, NewBody(string(body)))
doc.watched = true
created = append(created, doc)
}
}
if len(created) > 0 {
s.server.OnDocumentsAdded(ctx, created)
}
changed := make([]*Document, 0, len(changes))
for _, change := range changes {
if change.Type == protocol.Changed {
doc, err := s.getDoc(change.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
continue
}
if doc.open {
// Changes in open documents will be handled by OnChangeTextDocument.
continue
}
body, err := ioutil.ReadFile(doc.path)
if err != nil {
ctx.Fail(err, "Couldn't read changed file")
continue
}
doc.body = NewBody(string(body))
changed = append(changed, doc)
}
}
if len(changed) > 0 {
s.server.OnDocumentsChanged(ctx, changed)
}
deleted := make([]*Document, 0, len(changes))
for _, change := range changes {
if change.Type == protocol.Deleted {
doc, err := s.getDoc(change.URI)
if err != nil {
ctx.Fail(err, "Unknown document")
continue
}
doc.watched = false
if !doc.open {
deleted = append(deleted, doc)
delete(s.documents, change.URI)
}
}
}
if len(deleted) > 0 {
s.server.OnDocumentsRemoved(ctx, deleted)
}
}
func (s langsvr) getDoc(uri string) (*Document, error) {
doc, ok := s.documents[uri]
if !ok {
return nil, protocol.Error{Code: protocol.InvalidRequest, Message: "Unknown document ID"}
}
return doc, nil
}