diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dbdf09..54df633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v0.1.3 - 2020-12-09 + +### Added +- Event folding + +### Fixed +- Fixed bug where a completion request won't throw an error even if it found one + ## v0.1.2 - 2020-11-28 ### Fixed diff --git a/README.md b/README.md index d33bdc1..94066fa 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ stdio. Please note that this language server is currently not 100% stable, so bugs may appear here and there. +## Features + +- Hover information +- Diagnostics + - Event duplication checking + - Command usage validation + - Message box text overflow checking +- Completions +- Event folding + ## Getting Started Language clients should usually install the language server for you diff --git a/langserver/handlers/completion.go b/langserver/handlers/completion.go index c10acbf..1bce000 100644 --- a/langserver/handlers/completion.go +++ b/langserver/handlers/completion.go @@ -15,7 +15,7 @@ func TextDocumentCompletion(ctx context.Context, _ *jrpc2.Request) ([]lsp.Comple conf, err := lsctx.Config(ctx) if err != nil { - return []lsp.CompletionItem{}, nil + return []lsp.CompletionItem{}, err } completions := tsc.GetCompletions(conf) diff --git a/langserver/handlers/folding.go b/langserver/handlers/folding.go new file mode 100644 index 0000000..5d82a1e --- /dev/null +++ b/langserver/handlers/folding.go @@ -0,0 +1,52 @@ +package handlers + +import ( + "context" + + "github.com/creachadair/jrpc2" + "github.com/sourcegraph/go-lsp" + lsctx "pkg.nimblebun.works/tsc-language-server/langserver/context" + "pkg.nimblebun.works/tsc-language-server/langserver/filesystem/filehandler" + "pkg.nimblebun.works/tsc-language-server/tsc" +) + +// FoldingRangeParams is a (loosely reproduced) structure that contains the +// fields sent on a textDocument/foldingRange request. +type FoldingRangeParams struct { + TextDocument lsp.TextDocumentIdentifier `json:"textDocument"` +} + +// TextDocumentFoldingRange is the callback that runs on the +// "textDocument/foldingRange" method. +func (mh *MethodHandler) TextDocumentFoldingRange(ctx context.Context, req *jrpc2.Request) ([]tsc.FoldingRange, error) { + var params FoldingRangeParams + err := req.UnmarshalParams(jrpc2.NonStrict(¶ms)) + if err != nil { + return []tsc.FoldingRange{}, err + } + + fs, err := lsctx.FileSystem(ctx) + if err != nil { + return []tsc.FoldingRange{}, err + } + + handler := filehandler.FromDocumentURI(params.TextDocument.URI) + path, err := handler.FullPath() + if err != nil { + return []tsc.FoldingRange{}, err + } + + contents, err := fs.ReadFile(path) + if err != nil { + return []tsc.FoldingRange{}, err + } + + doc := lsp.TextDocumentItem{ + URI: params.TextDocument.URI, + LanguageID: "tsc", + Text: string(contents), + } + + ranges := tsc.GetFoldingRanges(doc) + return ranges, nil +} diff --git a/langserver/handlers/initialize.go b/langserver/handlers/initialize.go index 1441ed9..2aab1dc 100644 --- a/langserver/handlers/initialize.go +++ b/langserver/handlers/initialize.go @@ -7,10 +7,48 @@ import ( "github.com/sourcegraph/go-lsp" ) +// ServerCapabilities is a temporary workaround for the missing folding range +// provider field in Sourcegraph's go-lsp package. We will work on an in-house +// version of the LSP implementation in Go in the future. +type ServerCapabilities struct { + TextDocumentSync *lsp.TextDocumentSyncOptionsOrKind `json:"textDocumentSync,omitempty"` + HoverProvider bool `json:"hoverProvider,omitempty"` + FoldingRangeProvider bool `json:"foldingRangeProvider,omitempty"` + CompletionProvider *lsp.CompletionOptions `json:"completionProvider,omitempty"` + SignatureHelpProvider *lsp.SignatureHelpOptions `json:"signatureHelpProvider,omitempty"` + DefinitionProvider bool `json:"definitionProvider,omitempty"` + TypeDefinitionProvider bool `json:"typeDefinitionProvider,omitempty"` + ReferencesProvider bool `json:"referencesProvider,omitempty"` + DocumentHighlightProvider bool `json:"documentHighlightProvider,omitempty"` + DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"` + WorkspaceSymbolProvider bool `json:"workspaceSymbolProvider,omitempty"` + ImplementationProvider bool `json:"implementationProvider,omitempty"` + CodeActionProvider bool `json:"codeActionProvider,omitempty"` + CodeLensProvider *lsp.CodeLensOptions `json:"codeLensProvider,omitempty"` + DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"` + DocumentRangeFormattingProvider bool `json:"documentRangeFormattingProvider,omitempty"` + DocumentOnTypeFormattingProvider *lsp.DocumentOnTypeFormattingOptions `json:"documentOnTypeFormattingProvider,omitempty"` + RenameProvider bool `json:"renameProvider,omitempty"` + ExecuteCommandProvider *lsp.ExecuteCommandOptions `json:"executeCommandProvider,omitempty"` + SemanticHighlighting *lsp.SemanticHighlightingOptions `json:"semanticHighlighting,omitempty"` + XWorkspaceReferencesProvider bool `json:"xworkspaceReferencesProvider,omitempty"` + XDefinitionProvider bool `json:"xdefinitionProvider,omitempty"` + XWorkspaceSymbolByProperties bool `json:"xworkspaceSymbolByProperties,omitempty"` + + Experimental interface{} `json:"experimental,omitempty"` +} + +// InitializeResult is a temporary workaround for the missing folding range +// provider field in Sourcegraph's go-lsp package. We will work on an in-house +// version of the LSP implementation in Go in the future. +type InitializeResult struct { + Capabilities ServerCapabilities `json:"capabilities"` +} + // Initialize is the callback that runs on the "initialize" method -func (mh *MethodHandler) Initialize(ctx context.Context, _ *jrpc2.Request) (lsp.InitializeResult, error) { - result := lsp.InitializeResult{ - Capabilities: lsp.ServerCapabilities{ +func (mh *MethodHandler) Initialize(ctx context.Context, _ *jrpc2.Request) (InitializeResult, error) { + result := InitializeResult{ + Capabilities: ServerCapabilities{ TextDocumentSync: &lsp.TextDocumentSyncOptionsOrKind{ Options: &lsp.TextDocumentSyncOptions{ OpenClose: true, @@ -20,7 +58,8 @@ func (mh *MethodHandler) Initialize(ctx context.Context, _ *jrpc2.Request) (lsp. CompletionProvider: &lsp.CompletionOptions{ ResolveProvider: false, }, - HoverProvider: true, + HoverProvider: true, + FoldingRangeProvider: true, }, } diff --git a/langserver/handlers/service.go b/langserver/handlers/service.go index 5d1af89..a94768c 100644 --- a/langserver/handlers/service.go +++ b/langserver/handlers/service.go @@ -156,6 +156,17 @@ func (service *Service) Assigner() (jrpc2.Assigner, error) { return handle(ctx, req, mh.TextDocumentHover) }, + "textDocument/foldingRange": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { + err := sess.EnsureInitialized() + if err != nil { + return nil, err + } + + ctx = lsctx.WithFileSystem(ctx, fs) + + return handle(ctx, req, mh.TextDocumentFoldingRange) + }, + "tsc/setConfig": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { err := sess.EnsureInitialized() if err != nil { diff --git a/main.go b/main.go index c563216..cb87018 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ func main() { app := cli.NewApp() app.Name = "tsc-ls" app.Usage = "language Server for the TSC scripting language" - app.Version = "0.1.2" + app.Version = "0.1.3" app.Commands = []*cli.Command{ { diff --git a/tsc/completions_test.go b/tsc/completions_test.go index dbed06b..1a76f02 100644 --- a/tsc/completions_test.go +++ b/tsc/completions_test.go @@ -1,15 +1,16 @@ -package tsc +package tsc_test import ( "testing" "github.com/sourcegraph/go-lsp" "pkg.nimblebun.works/tsc-language-server/config" + "pkg.nimblebun.works/tsc-language-server/tsc" ) func TestGetCompletions(t *testing.T) { conf := config.New() - completions := GetCompletions(&conf) + completions := tsc.GetCompletions(&conf) definitionsLength := len(conf.GetTSCDefinitions()) completionsLength := len(completions) diff --git a/tsc/folding.go b/tsc/folding.go new file mode 100644 index 0000000..ba710bc --- /dev/null +++ b/tsc/folding.go @@ -0,0 +1,76 @@ +package tsc + +import ( + "github.com/sourcegraph/go-lsp" + "pkg.nimblebun.works/tsc-language-server/langserver/textdocument" + "pkg.nimblebun.works/tsc-language-server/utils" +) + +const ( + // FRKComment resolves to the "comment" folding range kind. + FRKComment = "comment" + + // FRKImports resolves to the "imports" folding range kind. + FRKImports = "imports" + + // FRKRegion resolves to the "region" folding range kind. + FRKRegion = "region" +) + +// FoldingRange defines a LSP-compatible folding range. +type FoldingRange struct { + StartLine int `json:"startLine"` + StartCharacter int `json:"startCharacter,omitempty"` + EndLine int `json:"endLine"` + EndCharacter int `json:"endCharacter"` + + Kind string `json:"kind,omitempty"` +} + +func createFoldingRange(start int, end int, document textdocument.TextDocument) FoldingRange { + startPos := document.PositionAt(start) + endPos := document.PositionAt(end) + + return FoldingRange{ + StartLine: startPos.Line, + StartCharacter: startPos.Character, + + EndLine: endPos.Line, + EndCharacter: endPos.Character, + + Kind: FRKRegion, + } +} + +// GetFoldingRanges will return all foldable ranges from a given document. +func GetFoldingRanges(textDocumentItem lsp.TextDocumentItem) []FoldingRange { + text := textDocumentItem.Text + + document := textdocument.From(textDocumentItem) + ranges := make([]FoldingRange, 0) + + start := -1 + end := -1 + + for idx, letter := range text { + if letter == '#' && IsEvent(utils.Substring(text, idx, 5)) { + if end-start > 4 { + foldingRange := createFoldingRange(start, end, document) + ranges = append(ranges, foldingRange) + } + + start = idx + } + + if letter != '\n' && letter != '\r' { + end = idx + } + } + + if end-start > 4 { + foldingRange := createFoldingRange(start, end, document) + ranges = append(ranges, foldingRange) + } + + return ranges +} diff --git a/tsc/folding_test.go b/tsc/folding_test.go new file mode 100644 index 0000000..d7280ac --- /dev/null +++ b/tsc/folding_test.go @@ -0,0 +1,107 @@ +package tsc_test + +import ( + "testing" + + "github.com/sourcegraph/go-lsp" + "pkg.nimblebun.works/tsc-language-server/tsc" +) + +func TestGetFoldingRanges(t *testing.T) { + dummyData := `#0098 + StartLine, got %v, want %v", idx, actual.StartLine, expected.StartLine) + } + + if actual.StartCharacter != expected.StartCharacter { + t.Errorf("GetFoldingRanges(doc) @ %d -> StartCharacter, got %v, want %v", idx, actual.StartCharacter, expected.StartCharacter) + } + + if actual.EndLine != expected.EndLine { + t.Errorf("GetFoldingRanges(doc) @ %d -> EndLine, got %v, want %v", idx, actual.EndLine, expected.EndLine) + } + + if actual.EndCharacter != expected.EndCharacter { + t.Errorf("GetFoldingRanges(doc) @ %d -> EndCharacter, got %v, want %v", idx, actual.EndCharacter, expected.EndCharacter) + } + } + }) +} diff --git a/tsc/hover_info_test.go b/tsc/hover_info_test.go index 6eba046..c46c862 100644 --- a/tsc/hover_info_test.go +++ b/tsc/hover_info_test.go @@ -1,9 +1,10 @@ -package tsc +package tsc_test import ( "testing" "pkg.nimblebun.works/tsc-language-server/config" + "pkg.nimblebun.works/tsc-language-server/tsc" ) func TestGetHoverInfo(t *testing.T) { @@ -41,7 +42,7 @@ Travel to map W, run event X, and move the PC to coordinates Y:Z. for _, testcase := range tests { t.Run(testcase.name, func(t *testing.T) { - got := GetHoverInfo(testcase.str, testcase.at, &c) + got := tsc.GetHoverInfo(testcase.str, testcase.at, &c) if got != testcase.want { t.Errorf( "GetHoverInfo(%s, %d, c) got %v, want %v", diff --git a/tsc/utils_test.go b/tsc/utils_test.go index f37b66d..a0a14fe 100644 --- a/tsc/utils_test.go +++ b/tsc/utils_test.go @@ -1,6 +1,10 @@ -package tsc +package tsc_test -import "testing" +import ( + "testing" + + "pkg.nimblebun.works/tsc-language-server/tsc" +) func TestIsEvent(t *testing.T) { var tests = []struct { @@ -16,7 +20,7 @@ func TestIsEvent(t *testing.T) { for _, testcase := range tests { t.Run(testcase.name, func(t *testing.T) { - got := IsEvent(testcase.str) + got := tsc.IsEvent(testcase.str) if got != testcase.want { t.Errorf( "IsEvent(%s) got %v, want %v", @@ -44,7 +48,7 @@ func TestIsValidArgument(t *testing.T) { for _, testcase := range tests { t.Run(testcase.name, func(t *testing.T) { - got := IsValidArgument(testcase.str) + got := tsc.IsValidArgument(testcase.str) if got != testcase.want { t.Errorf( "IsValidArgument(%s) got %v, want %v",