diff --git a/internal/lsp/initialize.go b/internal/lsp/initialize.go index 5ece3d5..325cb0c 100644 --- a/internal/lsp/initialize.go +++ b/internal/lsp/initialize.go @@ -25,10 +25,11 @@ func NewInitializeResponse(id int) InitializeResponse { }, Result: protocol.InitializeResult{ Capabilities: protocol.ServerCapabilities{ - TextDocumentSync: 1, - HoverProvider: true, - DefinitionProvider: true, - CompletionProvider: &protocol.CompletionOptions{}, + TextDocumentSync: 1, + HoverProvider: true, + DefinitionProvider: true, + CompletionProvider: &protocol.CompletionOptions{}, + DocumentFormattingProvider: true, }, ServerInfo: &protocol.ServerInfo{ Name: name, diff --git a/internal/lsp/textdocument_format.go b/internal/lsp/textdocument_format.go new file mode 100644 index 0000000..9064374 --- /dev/null +++ b/internal/lsp/textdocument_format.go @@ -0,0 +1,13 @@ +package lsp + +import "go.lsp.dev/protocol" + +type FormatRequest struct { + Request + Params protocol.DocumentFormattingParams `json:"params"` +} + +type FormatResponse struct { + Response + Result []protocol.TextEdit `json:"result"` +} diff --git a/internal/tg/state.go b/internal/tg/state.go index d733496..2aec94c 100644 --- a/internal/tg/state.go +++ b/internal/tg/state.go @@ -95,22 +95,22 @@ func (s *State) Hover(l logger.Logger, id int, docURI protocol.DocumentURI, posi ) if word == "" { - return buildEmptyHoverResponse(id) + return newEmptyHoverResponse(id) } //nolint:gocritic switch context { case hover.HoverContextLocal: if store.Cfg == nil { - return buildEmptyHoverResponse(id) + return newEmptyHoverResponse(id) } if _, ok := store.Cfg.Locals[word]; !ok { - return buildEmptyHoverResponse(id) + return newEmptyHoverResponse(id) } if store.CfgAsCty.IsNull() { - return buildEmptyHoverResponse(id) + return newEmptyHoverResponse(id) } locals := store.CfgAsCty.GetAttr("locals") @@ -134,10 +134,10 @@ func (s *State) Hover(l logger.Logger, id int, docURI protocol.DocumentURI, posi } } - return buildEmptyHoverResponse(id) + return newEmptyHoverResponse(id) } -func buildEmptyHoverResponse(id int) lsp.HoverResponse { +func newEmptyHoverResponse(id int) lsp.HoverResponse { return lsp.HoverResponse{ Response: lsp.Response{ RPC: lsp.RPCVersion, @@ -168,7 +168,7 @@ func (s *State) Definition(l logger.Logger, id int, docURI protocol.DocumentURI, ) if target == "" { - return buildEmptyDefinitionResponse(id, docURI, position) + return newEmptyDefinitionResponse(id, docURI, position) } //nolint:gocritic @@ -180,7 +180,7 @@ func (s *State) Definition(l logger.Logger, id int, docURI protocol.DocumentURI, ) if store.Cfg == nil { - return buildEmptyDefinitionResponse(id, docURI, position) + return newEmptyDefinitionResponse(id, docURI, position) } l.Debug( @@ -225,10 +225,10 @@ func (s *State) Definition(l logger.Logger, id int, docURI protocol.DocumentURI, } } - return buildEmptyDefinitionResponse(id, docURI, position) + return newEmptyDefinitionResponse(id, docURI, position) } -func buildEmptyDefinitionResponse(id int, docURI protocol.DocumentURI, position protocol.Position) lsp.DefinitionResponse { +func newEmptyDefinitionResponse(id int, docURI protocol.DocumentURI, position protocol.Position) lsp.DefinitionResponse { return lsp.DefinitionResponse{ Response: lsp.Response{ RPC: lsp.RPCVersion, @@ -257,3 +257,56 @@ func (s *State) TextDocumentCompletion(l logger.Logger, id int, docURI protocol. return response } + +func (s *State) TextDocumentFormatting(l logger.Logger, id int, docURI protocol.DocumentURI) lsp.FormatResponse { + store := s.Configs[docURI.Filename()] + + l.Debug( + "Formatting requested", + "uri", docURI, + ) + + if store.Cfg == nil { + return newEmptyFormatResponse(id) + } + + formatted := hclwrite.Format([]byte(store.Document)) + + return lsp.FormatResponse{ + Response: lsp.Response{ + RPC: lsp.RPCVersion, + ID: &id, + }, + Result: []protocol.TextEdit{ + { + Range: protocol.Range{ + Start: protocol.Position{ + Line: 0, + Character: 0, + }, + End: getEndOfDocument(store.Document), + }, + NewText: string(formatted), + }, + }, + } +} + +func newEmptyFormatResponse(id int) lsp.FormatResponse { + return lsp.FormatResponse{ + Response: lsp.Response{ + RPC: lsp.RPCVersion, + ID: &id, + }, + Result: []protocol.TextEdit{}, + } +} + +func getEndOfDocument(doc string) protocol.Position { + lines := strings.Split(doc, "\n") + + return protocol.Position{ + Line: uint32(len(lines) - 1), + Character: uint32(len(lines[len(lines)-1])), + } +} diff --git a/main.go b/main.go index d6b33b4..43bf731 100644 --- a/main.go +++ b/main.go @@ -203,6 +203,25 @@ func handleMessage(l logger.Logger, writer io.Writer, state tg.State, method str "Response", response, ) + writeResponse(l, writer, response) + + case protocol.MethodTextDocumentFormatting: + var request lsp.FormatRequest + if err := json.Unmarshal(contents, &request); err != nil { + l.Error( + "Failed to parse format request", + "error", + err, + ) + } + + l.Debug( + "Formatting", + "URI", request.Params.TextDocument.URI, + ) + + response := state.TextDocumentFormatting(l, request.ID, request.Params.TextDocument.URI) + writeResponse(l, writer, response) } }