Skip to content

Commit

Permalink
feat: language-server formatting of files.
Browse files Browse the repository at this point in the history
Signed-off-by: Tiago Natel <[email protected]>
  • Loading branch information
i4ki committed May 8, 2024
1 parent a06cacb commit 38ab7d9
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 3 deletions.
55 changes: 52 additions & 3 deletions ls/ls.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package tmls
import (
"context"
"encoding/json"
"fmt"
stdfmt "fmt"
"os"
"path/filepath"
"sort"
Expand All @@ -18,6 +18,7 @@ import (
"github.com/terramate-io/terramate/config"
"github.com/terramate-io/terramate/errors"
"github.com/terramate-io/terramate/hcl"
"github.com/terramate-io/terramate/hcl/fmt"
"go.lsp.dev/jsonrpc2"
lsp "go.lsp.dev/protocol"
"go.lsp.dev/uri"
Expand Down Expand Up @@ -68,6 +69,7 @@ func (s *Server) buildHandlers() {
lsp.MethodTextDocumentDidChange: s.handleDocumentChange,
lsp.MethodTextDocumentDidSave: s.handleDocumentSaved,
lsp.MethodTextDocumentCompletion: s.handleCompletion,
lsp.MethodTextDocumentFormatting: s.handleFormatting,

// commands
MethodExecuteCommand: s.handleExecuteCommand,
Expand Down Expand Up @@ -116,7 +118,8 @@ func (s *Server) handleInitialize(
s.workspace = string(uri.New(params.RootURI).Filename())
err := reply(ctx, lsp.InitializeResult{
Capabilities: lsp.ServerCapabilities{
CompletionProvider: &lsp.CompletionOptions{},
DocumentFormattingProvider: true,
CompletionProvider: &lsp.CompletionOptions{},

// if we support `goto` definition.
DefinitionProvider: false,
Expand Down Expand Up @@ -197,7 +200,7 @@ func (s *Server) handleDocumentChange(
}

if len(params.ContentChanges) != 1 {
err := fmt.Errorf("expected content changes = 1, got = %d", len(params.ContentChanges))
err := stdfmt.Errorf("expected content changes = 1, got = %d", len(params.ContentChanges))
log.Error().Err(err).Send()
return err
}
Expand Down Expand Up @@ -300,6 +303,52 @@ func (s *Server) handleCompletion(
return reply(ctx, nil, nil)
}

func (s *Server) handleFormatting(
ctx context.Context,
reply jsonrpc2.Replier,
r jsonrpc2.Request,
log zerolog.Logger,
) error {
var params lsp.CompletionParams
if err := json.Unmarshal(r.Params(), &params); err != nil {
log.Error().Err(err).Msg("failed to unmarshal params")
return jsonrpc2.ErrParse
}
log.Debug().Str("params", string(r.Params()))
fname := params.TextDocument.URI.Filename()
content, err := os.ReadFile(fname)
if err != nil {
log.Error().Err(err).Msg("failed to read file for formatting")
return reply(ctx, nil, err)
}
formatted, err := fmt.Format(string(content), fname)
if err != nil {
log.Error().Err(err).Msg("failed to format file")
return reply(ctx, nil, err)
}
log.Info().Msgf("formatted:'%s'", formatted)
oldlines := strings.Split(string(content), "\n")
return reply(ctx, []lsp.TextEdit{
// remove old content
{
NewText: "",
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Character: 0,
},
End: lsp.Position{
Line: uint32(len(oldlines) - 1),
Character: uint32(len(oldlines[len(oldlines)-1])),
},
},
},
{
NewText: formatted,
},
}, nil)
}

func (s *Server) sendDiagnostics(ctx context.Context, uri lsp.URI, diags []lsp.Diagnostic) {
err := s.conn.Notify(ctx, lsp.MethodTextDocumentPublishDiagnostics, lsp.PublishDiagnosticsParams{
URI: uri,
Expand Down
37 changes: 37 additions & 0 deletions ls/ls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package tmls_test
import (
"encoding/json"
"fmt"
"path"
"path/filepath"
"sort"
"testing"
Expand Down Expand Up @@ -45,6 +46,42 @@ func TestDocumentOpen(t *testing.T) {
params.URI.Filename())
}

func TestDocumentFormat(t *testing.T) {
t.Parallel()
f := lstest.Setup(t)

stack := f.Sandbox.CreateStack("stack")
f.Editor.CheckInitialize(f.Sandbox.RootDir())

fileContent := " stack {\n }"
stack.CreateFile(stackpkg.DefaultFilename, fileContent)
gotEdits, err := f.Editor.Format(path.Join(stack.RelPath(), stackpkg.DefaultFilename))
assert.NoError(t, err)

want := []lsp.TextEdit{
{
NewText: "",
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Character: 0,
},
End: lsp.Position{
Line: 1,
Character: 3,
},
},
},
{
NewText: "stack {\n}",
},
}

if diff := cmp.Diff(gotEdits, want); diff != "" {
t.Fatalf(diff)
}
}

func TestDocumentOpenWithoutRootConfig(t *testing.T) {
t.Parallel()
f := lstest.SetupNoRootConfig(t)
Expand Down
16 changes: 16 additions & 0 deletions test/ls/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,21 @@ func (e *Editor) Change(path, content string) {
assert.NoError(t, err, "call %q", lsp.MethodTextDocumentDidChange)
}

// Format send a MethodTextDocumentFormatting event to the language server.
func (e *Editor) Format(path string) ([]lsp.TextEdit, error) {
t := e.t
t.Helper()
abspath := filepath.Join(e.sandbox.RootDir(), path)
var edits []lsp.TextEdit
_, err := e.call(lsp.MethodTextDocumentFormatting, lsp.DocumentFormattingParams{
TextDocument: lsp.TextDocumentIdentifier{
URI: uri.File(abspath),
},
}, &edits)

return edits, err
}

// Command invokes the provided command in the LSP server.
func (e *Editor) Command(cmd lsp.ExecuteCommandParams) (interface{}, error) {
t := e.t
Expand All @@ -150,6 +165,7 @@ func DefaultInitializeResult() lsp.InitializeResult {
"openClose": true,
"save": map[string]interface{}{},
},
DocumentFormattingProvider: true,
},
}
}
Expand Down

0 comments on commit 38ab7d9

Please sign in to comment.