Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: language-server formatting of files. #1707

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 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,69 @@ 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.DocumentFormattingParams
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")

var textedits []lsp.TextEdit
textedits = append(textedits,
// remove old content
lsp.TextEdit{
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])),
},
},
})

newlines := strings.Split(string(formatted), "\n")
for i, line := range newlines {
textedits = append(textedits, lsp.TextEdit{
NewText: line,
Range: lsp.Range{
Start: lsp.Position{
Line: uint32(i),
Character: 0,
},
End: lsp.Position{
Line: uint32(i),
Character: uint32(len(line) - 1),
},
},
})
}

return reply(ctx, textedits, 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
60 changes: 60 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,65 @@ 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 {",
Range: lsp.Range{
Start: lsp.Position{
Line: 0,
Character: 0,
},
End: lsp.Position{
Line: 0,
Character: 6,
},
},
},
{
NewText: "}",
Range: lsp.Range{
Start: lsp.Position{
Line: 1,
Character: 0,
},
End: lsp.Position{
Line: 1,
Character: 0,
},
},
},
}

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
Loading