diff --git a/internal/analysis/diagnostic_kind.go b/internal/analysis/diagnostic_kind.go index 3d0c0724..a93a26aa 100644 --- a/internal/analysis/diagnostic_kind.go +++ b/internal/analysis/diagnostic_kind.go @@ -17,8 +17,8 @@ const ( _ Severity = iota ErrorSeverity WarningSeverity - Information - Hint + InformationSeverity + HintSeverity ) func SeverityToAnsiString(s Severity) string { @@ -27,9 +27,9 @@ func SeverityToAnsiString(s Severity) string { return ansi.ColorRed("Error") case WarningSeverity: return ansi.ColorYellow("Warning") - case Information: + case InformationSeverity: return "Info" - case Hint: + case HintSeverity: return "Hint" default: return utils.NonExhaustiveMatchPanic[string](s) @@ -42,9 +42,9 @@ func SeverityToString(s Severity) string { return "Error" case WarningSeverity: return "Warning" - case Information: + case InformationSeverity: return "Info" - case Hint: + case HintSeverity: return "Hint" default: return utils.NonExhaustiveMatchPanic[string](s) diff --git a/internal/analysis/inlay_hints.go b/internal/analysis/inlay_hints.go new file mode 100644 index 00000000..29c859c4 --- /dev/null +++ b/internal/analysis/inlay_hints.go @@ -0,0 +1,44 @@ +package analysis + +import ( + "fmt" + "slices" + + "github.com/formancehq/numscript/internal/parser" +) + +type InlayHint struct { + Position parser.Position + Label string +} + +func GetInlayHints( + checkResult CheckResult, +) []InlayHint { + if checkResult.Program.Vars == nil { + return nil + } + + typePrinter := NewTypePrinter() + + var hints []InlayHint + for _, decl := range checkResult.Program.Vars.Declarations { + shouldShowAsset := slices.Contains( + []string{TypeMonetary, TypeAsset}, + decl.Type.Name, + ) + + if !shouldShowAsset { + continue + } + + t := checkResult.GetVarDeclType(decl) + + hints = append(hints, InlayHint{ + Position: decl.Type.End, + Label: fmt.Sprintf("<%s>", typePrinter.Print(t)), + }) + } + + return hints +} diff --git a/internal/analysis/union_find.go b/internal/analysis/union_find.go index 4fd7086f..6505cfd1 100644 --- a/internal/analysis/union_find.go +++ b/internal/analysis/union_find.go @@ -82,3 +82,42 @@ func TypeToString(r Type) string { return utils.NonExhaustiveMatchPanic[string](r) } + +type TypePrinter struct { + nextId uint16 + store map[*TVar]uint16 +} + +func NewTypePrinter() TypePrinter { + return TypePrinter{ + nextId: 0, + store: make(map[*TVar]uint16), + } +} + +func (p *TypePrinter) getVarId(v *TVar) uint16 { + prev, ok := p.store[v] + if ok { + return prev + } + + id := p.nextId + p.nextId += 1 + p.store[v] = id + return id +} + +func (p *TypePrinter) Print(r Type) string { + r = r.Resolve() + switch r := r.(type) { + case *TVar: + return fmt.Sprintf("asset_%d", p.getVarId(r)) + + case *TAsset: + return string(*r) + + } + + return utils.NonExhaustiveMatchPanic[string](r) + +} diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index 1f4ff58a..f4ec9a13 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -8,6 +8,7 @@ import ( "github.com/formancehq/numscript/internal/analysis" "github.com/formancehq/numscript/internal/jsonrpc2" + "github.com/formancehq/numscript/internal/lsp/lsp_types_extra" "github.com/formancehq/numscript/internal/parser" "github.com/formancehq/numscript/internal/utils" "go.lsp.dev/protocol" @@ -157,6 +158,25 @@ func (state *State) handleGetSymbols(params protocol.DocumentSymbolParams) []pro return lspDocumentSymbols } +func (state *State) handleGetInlayHints(params lsp_types_extra.InlayHintParams) []lsp_types_extra.InlayHint { + doc, ok := state.documents.Get(params.TextDocument.URI) + if !ok { + return nil + } + + k := lsp_types_extra.InlayHintKindType + hints := analysis.GetInlayHints(doc.CheckResult) + var res []lsp_types_extra.InlayHint + for _, hint := range hints { + res = append(res, lsp_types_extra.InlayHint{ + Position: ParserToLspPosition(hint.Position), + Label: hint.Label, + Kind: &k, + }) + } + return res +} + func (state *State) handleCodeAction(params protocol.CodeActionParams) []protocol.CodeAction { doc, ok := state.documents.Get(params.TextDocument.URI) if !ok { @@ -225,16 +245,19 @@ func toLspDiagnostic(d analysis.Diagnostic) protocol.Diagnostic { } } -var initializeResult protocol.InitializeResult = protocol.InitializeResult{ - Capabilities: protocol.ServerCapabilities{ - TextDocumentSync: protocol.TextDocumentSyncOptions{ - OpenClose: true, - Change: protocol.TextDocumentSyncKindFull, +var initializeResult lsp_types_extra.InitializeResult = lsp_types_extra.InitializeResult{ + Capabilities: lsp_types_extra.ServerCapabilities{ + ServerCapabilities: protocol.ServerCapabilities{ + TextDocumentSync: protocol.TextDocumentSyncOptions{ + OpenClose: true, + Change: protocol.TextDocumentSyncKindFull, + }, + HoverProvider: true, + DefinitionProvider: true, + DocumentSymbolProvider: true, + CodeActionProvider: true, }, - HoverProvider: true, - DefinitionProvider: true, - DocumentSymbolProvider: true, - CodeActionProvider: true, + InlayHintProvider: true, }, ServerInfo: &protocol.ServerInfo{ Name: "numscript-ls", @@ -266,7 +289,9 @@ func NewConn(objStream jsonrpc2.MessageStream) *jsonrpc2.Conn { text := p.ContentChanges[len(p.ContentChanges)-1].Text state.updateDocument(conn, p.TextDocument.URI, text) }), - + jsonrpc2.NewRequestHandler("textDocument/inlayHint", jsonrpc2.AsyncHandling, func(p lsp_types_extra.InlayHintParams, conn *jsonrpc2.Conn) any { + return state.handleGetInlayHints(p) + }), jsonrpc2.NewRequestHandler("textDocument/hover", jsonrpc2.AsyncHandling, func(p protocol.HoverParams, conn *jsonrpc2.Conn) any { return state.handleHover(p) }), diff --git a/internal/lsp/lsp_types_extra/types.go b/internal/lsp/lsp_types_extra/types.go new file mode 100644 index 00000000..570bee83 --- /dev/null +++ b/internal/lsp/lsp_types_extra/types.go @@ -0,0 +1,45 @@ +package lsp_types_extra + +import "go.lsp.dev/protocol" + +type InlayHintParams struct { + TextDocument protocol.TextDocumentIdentifier `json:"textDocument"` + Range protocol.Range `json:"range"` +} + +type InlayHintKind string + +const ( + InlayHintKindType InlayHintKind = "type" + InlayHintKindParam InlayHintKind = "parameter" +) + +type InlayHint struct { + Position protocol.Position `json:"position"` + Label string `json:"label"` + Kind *InlayHintKind `json:"kind,omitempty"` + Tooltip *string `json:"tooltip,omitempty"` + TextEdits []protocol.TextEdit `json:"textEdits,omitempty"` +} + +type InlayHintOptions struct { + ResolveProvider bool `json:"resolveProvider,omitempty"` +} + +type InlayHintRegistrationOptions struct { + protocol.TextDocumentRegistrationOptions + InlayHintOptions +} + +type InlayHintParamsResult []InlayHint + +// -- Initialize +type ServerCapabilities struct { + protocol.ServerCapabilities + InlayHintProvider bool `json:"inlayHintProvider,omitempty"` +} + +type InitializeResult struct { + Capabilities ServerCapabilities `json:"capabilities"` + ServerInfo *protocol.ServerInfo `json:"serverInfo,omitempty"` +}