Skip to content
Merged
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
51 changes: 49 additions & 2 deletions language-server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ func NewServer(version string, lintRequest *utils.LintFileRequest) *ServerState
serverCapabilities := handler.CreateServerCapabilities()
serverCapabilities.TextDocumentSync = protocol.TextDocumentSyncKindIncremental
serverCapabilities.CompletionProvider = &protocol.CompletionOptions{}
serverCapabilities.CodeActionProvider = &protocol.CodeActionOptions{
CodeActionKinds: []protocol.CodeActionKind{protocol.CodeActionKindQuickFix},
}
serverCapabilities.ExecuteCommandProvider = &protocol.ExecuteCommandOptions{
Commands: []string{"vacuum.openUrl"},
}

return protocol.InitializeResult{
Capabilities: serverCapabilities,
Expand Down Expand Up @@ -99,6 +105,36 @@ func NewServer(version string, lintRequest *utils.LintFileRequest) *ServerState
handler.TextDocumentCompletion = func(context *glsp.Context, params *protocol.CompletionParams) (any, error) {
return nil, nil
}

handler.TextDocumentCodeAction = func(context *glsp.Context, params *protocol.CodeActionParams) (any, error) {
var actions []protocol.CodeAction

for _, diagnostic := range params.Context.Diagnostics {
if diagnostic.CodeDescription != nil && diagnostic.CodeDescription.HRef != "" {
quickFixKind := protocol.CodeActionKindQuickFix
actions = append(actions, protocol.CodeAction{
Title: "View documentation",
Kind: &quickFixKind,
Command: &protocol.Command{
Title: "Open documentation",
Command: "vacuum.openUrl",
Arguments: []interface{}{diagnostic.CodeDescription.HRef},
},
})
}
}

return actions, nil
}

handler.WorkspaceExecuteCommand = func(context *glsp.Context, params *protocol.ExecuteCommandParams) (any, error) {
if params.Command == "vacuum.openUrl" && len(params.Arguments) > 0 {
if url, ok := params.Arguments[0].(string); ok {
utils.OpenURL(url)
}
}
return nil, nil
}
return state
}

Expand Down Expand Up @@ -166,7 +202,9 @@ func ConvertResultIntoDiagnostic(vacuumResult *model.RuleFunctionResult) protoco
severity := GetDiagnosticSeverityFromRule(vacuumResult.Rule)

diagnosticErrorHref := fmt.Sprintf("%s/rules/unknown", model.WebsiteUrl)
if vacuumResult.Rule.RuleCategory != nil {
if vacuumResult.Rule.DocumentationURL != "" {
diagnosticErrorHref = vacuumResult.Rule.DocumentationURL
} else if vacuumResult.Rule.RuleCategory != nil {
diagnosticErrorHref = fmt.Sprintf("%s/rules/%s/%s", model.WebsiteUrl,
strings.ToLower(vacuumResult.Rule.RuleCategory.Id),
strings.ReplaceAll(strings.ToLower(vacuumResult.Rule.Id), "$", ""))
Expand All @@ -185,6 +223,15 @@ func ConvertResultIntoDiagnostic(vacuumResult *model.RuleFunctionResult) protoco
endChar = vacuumResult.EndNode.Column - 1
}

// Build comprehensive message with rule details
message := vacuumResult.Message
if vacuumResult.Rule.Description != "" {
message += "\n\nDescription: " + vacuumResult.Rule.Description
}
if vacuumResult.Rule.HowToFix != "" {
message += "\n\nHow to fix: " + vacuumResult.Rule.HowToFix + "\n\nRule ID: " + vacuumResult.Rule.Id + "\n"
}

return protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{Line: protocol.UInteger(startLine),
Expand All @@ -196,7 +243,7 @@ func ConvertResultIntoDiagnostic(vacuumResult *model.RuleFunctionResult) protoco
Source: &serverName,
Code: &protocol.IntegerOrString{Value: vacuumResult.Rule.Id},
CodeDescription: &protocol.CodeDescription{HRef: diagnosticErrorHref},
Message: vacuumResult.Message,
Message: message,
}
}

Expand Down
18 changes: 2 additions & 16 deletions tui/lint_details_table_render.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ package tui

import (
"fmt"
"os/exec"
"runtime"
"strings"

tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/glamour"
"github.com/daveshanley/vacuum/color"
"github.com/daveshanley/vacuum/utils"
"github.com/muesli/termenv"
)

Expand Down Expand Up @@ -330,20 +329,7 @@ func (m *ViolationResultTableModel) fetchDocumentation(ruleID string) tea.Cmd {
// If it's not the default quobix.com pattern, open in browser
if !strings.Contains(customURL, "quobix.com/vacuum/rules/") {
return func() tea.Msg {
// Open URL in default browser
var cmd *exec.Cmd
switch runtime.GOOS {
case "linux":
cmd = exec.Command("xdg-open", customURL)
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", customURL)
case "darwin":
cmd = exec.Command("open", customURL)
default:
return docsErrorMsg{ruleID: ruleID, err: "Unsupported platform for opening browser", is404: false}
}

if err := cmd.Start(); err != nil {
if err := utils.OpenURL(customURL); err != nil {
return docsErrorMsg{ruleID: ruleID, err: fmt.Sprintf("Failed to open browser: %s", err.Error()), is404: false}
}

Expand Down
20 changes: 20 additions & 0 deletions utils/open_url.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package utils

import (
"os/exec"
"runtime"
)

// OpenURL opens the given URL in the system's default browser
func OpenURL(url string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
case "darwin":
cmd = exec.Command("open", url)
default:
cmd = exec.Command("xdg-open", url)
}
return cmd.Start()
}