Documentation
¶
Overview ¶
Package lsp implements a production-ready Language Server Protocol (LSP) server for GoSQLX.
The LSP server provides comprehensive SQL code intelligence features for IDEs and text editors, enabling real-time syntax validation, intelligent auto-completion, code formatting, and interactive documentation for SQL development.
Overview ¶
The GoSQLX LSP server transforms any LSP-compatible editor into a powerful SQL development environment. It leverages the GoSQLX SQL parser to provide accurate, real-time feedback on SQL syntax and offers intelligent code assistance through the Language Server Protocol.
Version: 1.0.0 (GoSQLX v1.6.0+)
Features ¶
The server implements the following LSP capabilities:
Diagnostics (textDocument/publishDiagnostics):
- Real-time SQL syntax validation
- Precise error location with line and column information
- Structured error codes from GoSQLX parser
- Immediate feedback as you type
Formatting (textDocument/formatting):
- Intelligent SQL code formatting
- Keyword capitalization
- Consistent indentation (configurable tab/space)
- Clause alignment for readability
Hover (textDocument/hover):
- Interactive documentation for 60+ SQL keywords
- Markdown-formatted help with syntax examples
- Context-sensitive keyword information
- Coverage: DML, DDL, JOINs, CTEs, Window Functions, Set Operations
Completion (textDocument/completion):
- Auto-complete for 100+ SQL keywords
- 22 pre-built code snippets for common patterns
- Trigger characters: space, dot, opening parenthesis
- Smart filtering based on current input
Document Symbol (textDocument/documentSymbol):
- Outline view of SQL statements
- Navigate between SELECT, INSERT, UPDATE, DELETE statements
- Hierarchical structure for complex queries
- Quick jump to specific statements
Signature Help (textDocument/signatureHelp):
- Parameter hints for 20+ SQL functions
- Active parameter highlighting
- Documentation for each parameter
- Coverage: Aggregates, Window Functions, String Functions, Type Conversions
Code Actions (textDocument/codeAction):
- Quick fixes for common syntax errors
- Automatic semicolon insertion
- Keyword case correction suggestions
- Context-aware refactoring hints
Architecture ¶
The LSP server consists of three main components:
Server (server.go):
- Main server loop and JSON-RPC 2.0 message handling
- Rate limiting (100 requests/second) to prevent abuse
- Message size limits (10MB per message, 5MB per document)
- Graceful error handling and recovery
- Thread-safe write operations
Handler (handler.go):
- Implementation of all LSP protocol methods
- Request routing and response generation
- Integration with GoSQLX parser for validation
- Error position extraction and diagnostic creation
DocumentManager (documents.go):
- Thread-safe document state management
- Support for incremental document synchronization
- Version tracking for stale diagnostic detection
- Efficient position-to-offset conversions
Protocol (protocol.go):
- Complete LSP protocol type definitions
- JSON-RPC 2.0 message structures
- Standard and LSP-specific error codes
- All LSP 3.17 data structures
Quick Start ¶
Starting the LSP server from command line:
./gosqlx lsp ./gosqlx lsp --log /tmp/gosqlx-lsp.log # With debug logging
Programmatic usage:
package main
import (
"log"
"os"
"github.com/ajitpratap0/GoSQLX/pkg/lsp"
)
func main() {
// Create logger that writes to file (not stdout!)
logFile, err := os.Create("/tmp/gosqlx-lsp.log")
if err != nil {
log.Fatal(err)
}
defer logFile.Close()
logger := log.New(logFile, "[GoSQLX LSP] ", log.LstdFlags)
// Create and run server
server := lsp.NewStdioServer(logger)
if err := server.Run(); err != nil {
logger.Fatalf("Server error: %v", err)
}
}
IDE Integration ¶
The LSP server integrates with popular editors and IDEs:
VSCode:
Add to your settings.json or create a VSCode extension:
{
"gosqlx-lsp": {
"command": "gosqlx",
"args": ["lsp"],
"filetypes": ["sql"],
"settings": {}
}
}
Or create .vscode/settings.json:
{
"sql.lsp.path": "gosqlx",
"sql.lsp.args": ["lsp"],
"sql.lsp.logLevel": "info"
}
Neovim (nvim-lspconfig):
Add to your init.lua:
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
if not configs.gosqlx then
configs.gosqlx = {
default_config = {
cmd = {'gosqlx', 'lsp'},
filetypes = {'sql'},
root_dir = lspconfig.util.root_pattern('.git', '.gosqlx.yml'),
settings = {},
},
}
end
lspconfig.gosqlx.setup{}
Or using vim.lsp.start directly:
vim.api.nvim_create_autocmd("FileType", {
pattern = "sql",
callback = function()
vim.lsp.start({
name = "gosqlx-lsp",
cmd = {"gosqlx", "lsp"},
root_dir = vim.fn.getcwd(),
})
end,
})
Emacs (lsp-mode):
Add to your init.el:
(require 'lsp-mode)
(add-to-list 'lsp-language-id-configuration '(sql-mode . "sql"))
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection '("gosqlx" "lsp"))
:activation-fn (lsp-activate-on "sql")
:major-modes '(sql-mode)
:server-id 'gosqlx-lsp))
(add-hook 'sql-mode-hook #'lsp)
Helix Editor:
Add to ~/.config/helix/languages.toml:
[[language]]
name = "sql"
language-server = { command = "gosqlx", args = ["lsp"] }
Sublime Text (LSP package):
Add to LSP.sublime-settings:
{
"clients": {
"gosqlx": {
"enabled": true,
"command": ["gosqlx", "lsp"],
"selector": "source.sql"
}
}
}
Configuration ¶
The LSP server can be configured via .gosqlx.yml in your project root:
# SQL dialect (postgresql, mysql, sqlite, sqlserver, oracle, generic)
dialect: postgresql
# Linting rules (see docs/LINTING_RULES.md)
linter:
enabled: true
rules:
L001: error # Keyword capitalization
L002: warn # Indentation style
L003: error # Trailing whitespace
# Formatting options
formatter:
indent_size: 2
indent_style: space
keyword_case: upper
max_line_length: 100
See docs/CONFIGURATION.md for complete configuration reference.
Keyword Documentation ¶
The LSP server provides hover documentation for these SQL keyword categories:
Core DML (Data Manipulation):
SELECT, INSERT, UPDATE, DELETE, MERGE FROM, WHERE, SET, VALUES
JOINs:
JOIN, INNER JOIN, LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN CROSS JOIN, NATURAL JOIN, LATERAL JOIN (PostgreSQL) ON, USING
Filtering and Grouping:
WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET DISTINCT, DISTINCT ON (PostgreSQL) FETCH FIRST (SQL standard)
CTEs (Common Table Expressions):
WITH, RECURSIVE Support for multiple CTEs and recursive queries
Set Operations:
UNION, UNION ALL, EXCEPT, INTERSECT Proper precedence and parenthesization
Window Functions (SQL-99):
ROW_NUMBER, RANK, DENSE_RANK, NTILE LAG, LEAD, FIRST_VALUE, LAST_VALUE OVER, PARTITION BY, ORDER BY ROWS BETWEEN, RANGE BETWEEN UNBOUNDED PRECEDING, CURRENT ROW, UNBOUNDED FOLLOWING
Aggregate Functions:
COUNT, SUM, AVG, MIN, MAX FILTER clause (SQL:2003) ORDER BY in aggregates (PostgreSQL)
Advanced Grouping (SQL-99):
ROLLUP, CUBE, GROUPING SETS Hierarchical and cross-tabulated aggregations
DDL (Data Definition):
CREATE TABLE, CREATE INDEX, CREATE VIEW, CREATE MATERIALIZED VIEW ALTER TABLE, DROP TABLE, DROP INDEX TRUNCATE TABLE
Constraints:
PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK NOT NULL, DEFAULT REFERENCES, CASCADE, RESTRICT
PostgreSQL Extensions:
JSON/JSONB operators (-> ->> #> #>> @> <@ ? ?| ?& #-) RETURNING clause FILTER clause Array operators
Operators and Expressions:
AND, OR, NOT IN, BETWEEN, LIKE, IS NULL, IS NOT NULL CASE WHEN THEN ELSE END NULLS FIRST, NULLS LAST
Functions:
String: SUBSTRING, TRIM, UPPER, LOWER, LENGTH, CONCAT Conversion: CAST, CONVERT, COALESCE, NULLIF Date/Time: NOW, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP
Code Snippets ¶
The completion system includes 22 code snippets for rapid development:
Query Patterns:
sel - Basic SELECT statement selall - SELECT * FROM table selcount - SELECT COUNT(*) with WHERE seljoin - SELECT with JOIN selleft - SELECT with LEFT JOIN selgroup - SELECT with GROUP BY and HAVING
DML Operations:
ins - INSERT INTO with VALUES inssel - INSERT INTO with SELECT upd - UPDATE with SET and WHERE del - DELETE FROM with WHERE
DDL Operations:
cretbl - CREATE TABLE with columns creidx - CREATE INDEX altertbl - ALTER TABLE ADD COLUMN droptbl - DROP TABLE IF EXISTS trunc - TRUNCATE TABLE
Advanced Features:
cte - Common Table Expression (WITH) cterec - Recursive CTE case - CASE expression casecol - CASE on column value window - Window function with PARTITION BY merge - MERGE statement with MATCHED clauses union - UNION query exists - EXISTS subquery subq - Subquery template
Each snippet uses placeholder variables (${1}, ${2}, etc.) for easy tab navigation.
Function Signatures ¶
Signature help is provided for these SQL function categories:
Aggregate Functions:
COUNT(expression) - Count rows matching criteria SUM(expression) - Sum numeric values AVG(expression) - Calculate average MIN(expression) - Find minimum value MAX(expression) - Find maximum value
Window Functions:
ROW_NUMBER() OVER (...) - Sequential row numbers RANK() OVER (...) - Ranks with gaps for ties DENSE_RANK() OVER (...) - Ranks without gaps NTILE(buckets) OVER (...) - Divide into N groups LAG(expr, offset, default) - Access previous row LEAD(expr, offset, default) - Access next row FIRST_VALUE(expr) OVER(...) - First value in window LAST_VALUE(expr) OVER(...) - Last value in window
String Functions:
SUBSTRING(string, start, length) - Extract substring TRIM([spec] chars FROM string) - Remove leading/trailing chars UPPER(string) - Convert to uppercase LOWER(string) - Convert to lowercase LENGTH(string) - String length CONCAT(str1, str2, ...) - Concatenate strings
Null Handling:
COALESCE(val1, val2, ...) - First non-null value NULLIF(expr1, expr2) - NULL if equal, else expr1
Type Conversion:
CAST(expression AS type) - Type conversion
Performance and Limits ¶
The LSP server includes built-in safeguards for stability:
Rate Limiting:
- 100 requests per second maximum (RateLimitRequests)
- 1-second rolling window (RateLimitWindow)
- Automatic recovery after window expires
- Client receives RequestCancelled (-32800) when exceeded
Message Size Limits:
- MaxContentLength: 10MB per JSON-RPC message
- MaxDocumentSize: 5MB per SQL document
- Oversized documents skip validation with warning
- Documents remain open but diagnostics disabled
Request Timeout:
- 30 seconds per request (RequestTimeout)
- Prevents hanging on malformed SQL
- Long-running parses automatically cancelled
Memory Management:
- GoSQLX object pooling for parser efficiency
- Document content copied to prevent races
- Automatic cleanup on document close
Performance Characteristics:
- Parsing: <1ms for typical queries, <10ms for complex CTEs
- Completion: <5ms for 100+ items with filtering
- Formatting: <10ms for documents up to 1000 lines
- Hover: <1ms for keyword lookup
- Validation: <50ms for complex multi-statement documents
Error Handling ¶
The server provides robust error handling throughout:
Position Extraction:
- Structured errors from GoSQLX with line/column info
- Regex fallback for unstructured error messages
- Multiple patterns: "line X, column Y", "[X:Y]", "position N"
- Conversion from absolute position to line/column
Error Codes:
- JSON-RPC standard codes (-32700 to -32603)
- LSP-specific codes (-32002, -32800 to -32803)
- GoSQLX error codes propagated to diagnostics
- Categorized by severity (Error, Warning, Info, Hint)
Diagnostic Features:
- Precise error ranges for IDE underlining
- Error code display in hover
- Related information for multi-location errors
- Automatic clearing on document close
Graceful Degradation:
- Parse errors don't crash server
- Malformed requests handled with error responses
- Unknown methods return MethodNotFound
- Oversized documents skip validation
Thread Safety ¶
All components are designed for safe concurrent operation:
Server Level:
- Write mutex for JSON-RPC output serialization
- Rate limiting mutex for request counting
- Atomic operations for rate limit counter
Document Manager:
- Read/write mutex for document map
- Read locks for Get/GetContent (concurrent reads)
- Write locks for Open/Update/Close (exclusive writes)
- Document copies returned to prevent races
Handler:
- Stateless request processing
- No shared mutable state
- Keywords instance is read-only after construction
- Safe for concurrent request handling
Logging and Debugging ¶
The server supports comprehensive logging for debugging:
Log Levels:
- Startup/Shutdown events
- Received requests with method names
- Sent responses with byte counts
- Parse errors with content snippets
- Rate limit violations
- Document lifecycle events
- Validation results (diagnostic counts)
Log Configuration:
- Logger must write to file or stderr (never stdout)
- Stdout is reserved for LSP protocol communication
- Use --log flag with gosqlx CLI for file logging
- Nil logger disables all logging (production use)
Example logging setup:
logFile, _ := os.Create("/tmp/gosqlx-lsp.log")
logger := log.New(logFile, "[LSP] ", log.LstdFlags|log.Lshortfile)
server := lsp.NewStdioServer(logger)
Protocol Compliance ¶
The implementation conforms to LSP 3.17 specification:
Lifecycle:
- initialize → initialize result with capabilities
- initialized notification
- shutdown request
- exit notification
Text Synchronization:
- Full and incremental sync modes
- Version tracking
- Open/Change/Close/Save notifications
Diagnostics:
- publishDiagnostics notification
- Version-tagged diagnostics
- Multiple diagnostics per document
- Automatic clearing on close
Code Intelligence:
- hover request/response
- completion request/response
- formatting request/response
- documentSymbol request/response
- signatureHelp request/response
- codeAction request/response
Error Handling:
- Standard JSON-RPC 2.0 error responses
- Error codes per specification
- Detailed error messages
- Error data field for additional context
Testing ¶
The LSP implementation includes comprehensive tests:
Unit Tests:
- Protocol message parsing
- Document state management
- Position/offset conversions
- Error extraction patterns
Integration Tests:
- Full request/response cycles
- Multi-document scenarios
- Concurrent request handling
- Rate limiting behavior
Benchmark Tests:
- Handler performance under load
- Document update performance
- Completion latency
- Parse and validation speed
See pkg/lsp/*_test.go for test suite details.
Related Documentation ¶
For more information about the LSP server and GoSQLX features:
- docs/LSP_GUIDE.md - Complete LSP server setup and IDE integration guide
- docs/LINTING_RULES.md - All linting rules (L001-L010) reference
- docs/CONFIGURATION.md - Configuration file (.gosqlx.yml) documentation
- docs/USAGE_GUIDE.md - Comprehensive GoSQLX usage guide
- docs/SQL_COMPATIBILITY.md - SQL dialect compatibility matrix
Standards and References ¶
Language Server Protocol:
https://microsoft.github.io/language-server-protocol/
JSON-RPC 2.0 Specification:
https://www.jsonrpc.org/specification
SQL Standards:
- SQL-92 (ISO/IEC 9075:1992)
- SQL-99 (ISO/IEC 9075:1999) - Window functions, CTEs
- SQL:2003 (ISO/IEC 9075:2003) - MERGE, XML
- SQL:2011 (ISO/IEC 9075:2011) - Temporal features
GoSQLX Project:
https://github.com/ajitpratap0/GoSQLX
Package lsp implements the Language Server Protocol (LSP) server for GoSQLX.
The LSP server provides comprehensive SQL code intelligence features for IDEs and text editors, including real-time diagnostics, formatting, completion, and navigation capabilities.
Protocol Implementation ¶
This file defines the LSP protocol types and structures according to the Language Server Protocol specification (version 3.17). It provides complete type definitions for:
- JSON-RPC 2.0 message structures (Request, Response, Notification)
- LSP lifecycle messages (Initialize, Initialized, Shutdown, Exit)
- Text document synchronization (didOpen, didChange, didClose, didSave)
- Code intelligence features (Completion, Hover, Formatting, etc.)
- Diagnostic publishing (Errors, Warnings, Information)
Error Codes ¶
The package defines standard JSON-RPC 2.0 error codes:
- ParseError (-32700): Invalid JSON received
- InvalidRequest (-32600): Invalid JSON-RPC request
- MethodNotFound (-32601): Method not supported
- InvalidParams (-32602): Invalid method parameters
- InternalError (-32603): Internal server error
And LSP-specific error codes:
- ServerNotInitialized (-32002): Server not yet initialized
- RequestCancelled (-32800): Request cancelled by client
- ContentModified (-32801): Content modified during operation
- RequestFailed (-32803): Request failed
Usage ¶
This package is typically not used directly. Instead, use the Server type from server.go to create and run an LSP server instance.
Index ¶
- Constants
- type ClientCapabilities
- type CodeAction
- type CodeActionContext
- type CodeActionKind
- type CodeActionOptions
- type CodeActionParams
- type Command
- type CompletionClientCapabilities
- type CompletionContext
- type CompletionItem
- type CompletionItemKind
- type CompletionList
- type CompletionOptions
- type CompletionParams
- type CompletionTriggerKind
- type Diagnostic
- type DiagnosticRelatedInformation
- type DiagnosticSeverity
- type DidChangeTextDocumentParams
- type DidCloseTextDocumentParams
- type DidOpenTextDocumentParams
- type DidSaveTextDocumentParams
- type Document
- type DocumentFormattingParams
- type DocumentManager
- func (dm *DocumentManager) Close(uri string)
- func (dm *DocumentManager) Get(uri string) (*Document, bool)
- func (dm *DocumentManager) GetContent(uri string) (string, bool)
- func (dm *DocumentManager) Open(uri, languageID string, version int, content string)
- func (dm *DocumentManager) Update(uri string, version int, changes []TextDocumentContentChangeEvent)
- type DocumentSymbol
- type DocumentSymbolParams
- type FormattingOptions
- type Handler
- type Hover
- type HoverClientCapabilities
- type InitializeParams
- type InitializeResult
- type InsertTextFormat
- type Location
- type MarkupContent
- type MarkupKind
- type MessageType
- type Notification
- type ParameterInformation
- type Position
- type PublishDiagnosticsClientCapabilities
- type PublishDiagnosticsParams
- type Range
- type Request
- type Response
- type ResponseError
- type SaveOptions
- type Server
- type ServerCapabilities
- type ServerInfo
- type ShowMessageParams
- type ShutdownResult
- type SignatureHelp
- type SignatureHelpOptions
- type SignatureInformation
- type SymbolInformation
- type SymbolKind
- type TextDocumentClientCapabilities
- type TextDocumentContentChangeEvent
- type TextDocumentEdit
- type TextDocumentIdentifier
- type TextDocumentItem
- type TextDocumentPositionParams
- type TextDocumentSyncClientCapabilities
- type TextDocumentSyncKind
- type TextDocumentSyncOptions
- type TextEdit
- type VersionedTextDocumentIdentifier
- type WorkspaceEdit
Constants ¶
const ( // JSON-RPC standard error codes ParseError = -32700 InvalidRequest = -32600 MethodNotFound = -32601 InvalidParams = -32602 InternalError = -32603 // LSP-specific error codes ServerNotInitialized = -32002 UnknownErrorCode = -32001 RequestCancelled = -32800 ContentModified = -32801 ServerCancelled = -32802 RequestFailed = -32803 )
Error codes
const ( // MaxContentLength limits the size of a single LSP message (10MB) MaxContentLength = 10 * 1024 * 1024 // MaxDocumentSize limits the size of SQL documents (5MB) MaxDocumentSize = 5 * 1024 * 1024 // RateLimitRequests is the max requests per rate limit window RateLimitRequests = 100 // RateLimitWindow is the time window for rate limiting RateLimitWindow = time.Second // RequestTimeout limits how long a request can take RequestTimeout = 30 * time.Second )
Server configuration constants
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ClientCapabilities ¶
type ClientCapabilities struct {
TextDocument TextDocumentClientCapabilities `json:"textDocument,omitempty"`
}
ClientCapabilities describes the client's capabilities
type CodeAction ¶
type CodeAction struct {
Title string `json:"title"`
Kind CodeActionKind `json:"kind,omitempty"`
Diagnostics []Diagnostic `json:"diagnostics,omitempty"`
IsPreferred bool `json:"isPreferred,omitempty"`
Edit *WorkspaceEdit `json:"edit,omitempty"`
Command *Command `json:"command,omitempty"`
}
CodeAction represents a code action
type CodeActionContext ¶
type CodeActionContext struct {
Diagnostics []Diagnostic `json:"diagnostics"`
Only []CodeActionKind `json:"only,omitempty"`
}
CodeActionContext contains additional diagnostic information
type CodeActionKind ¶
type CodeActionKind string
CodeActionKind represents the kind of code action
const ( CodeActionQuickFix CodeActionKind = "quickfix" CodeActionRefactor CodeActionKind = "refactor" CodeActionSource CodeActionKind = "source" CodeActionSourceOrganize CodeActionKind = "source.organizeImports" )
type CodeActionOptions ¶
type CodeActionOptions struct {
CodeActionKinds []CodeActionKind `json:"codeActionKinds,omitempty"`
}
CodeActionOptions describes code action options
type CodeActionParams ¶
type CodeActionParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
Range Range `json:"range"`
Context CodeActionContext `json:"context"`
}
CodeActionParams is the parameters for textDocument/codeAction
type Command ¶
type Command struct {
Title string `json:"title"`
Command string `json:"command"`
Arguments []interface{} `json:"arguments,omitempty"`
}
Command represents a command to be executed
type CompletionClientCapabilities ¶
type CompletionClientCapabilities struct {
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
CompletionItem *struct {
SnippetSupport bool `json:"snippetSupport,omitempty"`
} `json:"completionItem,omitempty"`
}
CompletionClientCapabilities describes completion capabilities
type CompletionContext ¶
type CompletionContext struct {
TriggerKind CompletionTriggerKind `json:"triggerKind"`
TriggerCharacter string `json:"triggerCharacter,omitempty"`
}
CompletionContext provides additional information about the context
type CompletionItem ¶
type CompletionItem struct {
Label string `json:"label"`
Kind CompletionItemKind `json:"kind,omitempty"`
Detail string `json:"detail,omitempty"`
Documentation interface{} `json:"documentation,omitempty"`
InsertText string `json:"insertText,omitempty"`
InsertTextFormat InsertTextFormat `json:"insertTextFormat,omitempty"`
}
CompletionItem represents a completion suggestion
type CompletionItemKind ¶
type CompletionItemKind int
CompletionItemKind defines the kind of completion item
const ( TextCompletion CompletionItemKind = 1 MethodCompletion CompletionItemKind = 2 FunctionCompletion CompletionItemKind = 3 KeywordCompletion CompletionItemKind = 14 SnippetCompletion CompletionItemKind = 15 )
type CompletionList ¶
type CompletionList struct {
IsIncomplete bool `json:"isIncomplete"`
Items []CompletionItem `json:"items"`
}
CompletionList represents a list of completion items
type CompletionOptions ¶
type CompletionOptions struct {
TriggerCharacters []string `json:"triggerCharacters,omitempty"`
ResolveProvider bool `json:"resolveProvider,omitempty"`
}
CompletionOptions describes completion options
type CompletionParams ¶
type CompletionParams struct {
TextDocumentPositionParams
Context *CompletionContext `json:"context,omitempty"`
}
CompletionParams describes completion request parameters
type CompletionTriggerKind ¶
type CompletionTriggerKind int
CompletionTriggerKind describes how completion was triggered
const ( // Invoked means completion was invoked explicitly Invoked CompletionTriggerKind = 1 // TriggerCharacter means completion was triggered by a character TriggerCharacter CompletionTriggerKind = 2 // TriggerForIncompleteCompletions means re-triggered for incomplete completions TriggerForIncompleteCompletions CompletionTriggerKind = 3 )
type Diagnostic ¶
type Diagnostic struct {
Range Range `json:"range"`
Severity DiagnosticSeverity `json:"severity,omitempty"`
Code interface{} `json:"code,omitempty"`
Source string `json:"source,omitempty"`
Message string `json:"message"`
RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"`
}
Diagnostic represents a diagnostic (error, warning, etc.)
type DiagnosticRelatedInformation ¶
type DiagnosticRelatedInformation struct {
Location Location `json:"location"`
Message string `json:"message"`
}
DiagnosticRelatedInformation provides additional context
type DiagnosticSeverity ¶
type DiagnosticSeverity int
DiagnosticSeverity represents the severity of a diagnostic
const ( // SeverityError reports an error SeverityError DiagnosticSeverity = 1 // SeverityWarning reports a warning SeverityWarning DiagnosticSeverity = 2 // SeverityInformation reports information SeverityInformation DiagnosticSeverity = 3 // SeverityHint reports a hint SeverityHint DiagnosticSeverity = 4 )
type DidChangeTextDocumentParams ¶
type DidChangeTextDocumentParams struct {
TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
ContentChanges []TextDocumentContentChangeEvent `json:"contentChanges"`
}
DidChangeTextDocumentParams is sent when a document changes
type DidCloseTextDocumentParams ¶
type DidCloseTextDocumentParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
}
DidCloseTextDocumentParams is sent when a document is closed
type DidOpenTextDocumentParams ¶
type DidOpenTextDocumentParams struct {
TextDocument TextDocumentItem `json:"textDocument"`
}
DidOpenTextDocumentParams is sent when a document is opened
type DidSaveTextDocumentParams ¶
type DidSaveTextDocumentParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
Text string `json:"text,omitempty"`
}
DidSaveTextDocumentParams is sent when a document is saved
type Document ¶
type Document struct {
URI string
LanguageID string
Version int
Content string
Lines []string // Cached line splits
}
Document represents an open SQL document with its current state.
Document stores all information needed to process LSP requests for a single SQL file. It maintains the current content, version, and metadata.
Fields:
- URI: Document identifier (file:// URI)
- LanguageID: Language identifier (typically "sql")
- Version: Monotonically increasing version number
- Content: Current full text content
- Lines: Cached line-split content for efficient position operations
The Lines field is automatically synchronized with Content to avoid repeated string splitting operations.
func (*Document) GetWordAtPosition ¶
GetWordAtPosition returns the word at the given position.
This method extracts the identifier or keyword at a specific cursor position, which is used for hover documentation and completion filtering.
The method uses rune-based indexing to properly handle UTF-8 encoded SQL identifiers that may contain international characters.
Word boundaries are defined as:
- Start: Beginning of line or non-word character
- End: End of line or non-word character
- Word characters: A-Z, a-z, 0-9, underscore
Parameters:
- pos: The cursor position (0-based line and character indices)
Returns:
- The word at the position, or empty string if:
- Position is out of bounds
- No word character at position
- Position is in whitespace
Example:
doc.Content = "SELECT name FROM users"
word := doc.GetWordAtPosition(Position{Line: 0, Character: 9})
// Returns: "name"
This method is safe for concurrent use as it operates on document fields without modifying state.
type DocumentFormattingParams ¶
type DocumentFormattingParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
Options FormattingOptions `json:"options"`
}
DocumentFormattingParams describes formatting request parameters
type DocumentManager ¶
type DocumentManager struct {
// contains filtered or unexported fields
}
DocumentManager manages open SQL documents in a thread-safe manner.
DocumentManager provides centralized document state management for the LSP server. It handles document lifecycle events (open, change, close) and maintains the current content and version for each document.
Thread Safety ¶
All operations are protected by a read/write mutex:
- Read operations (Get, GetContent): Use read lock for concurrent access
- Write operations (Open, Update, Close): Use write lock for exclusive access
This ensures safe concurrent access from multiple LSP request handlers.
Document Lifecycle ¶
Documents follow the LSP document lifecycle:
- Open: Document opened in editor (textDocument/didOpen)
- Update: Content changes as user edits (textDocument/didChange)
- Close: Document closed in editor (textDocument/didClose)
Synchronization Modes ¶
The manager supports both synchronization modes:
- Full sync: Entire document content sent on each change
- Incremental sync: Only changed portions sent (more efficient)
Document Versioning ¶
Each document has a version number that increments with changes. This enables the server to:
- Detect stale diagnostics
- Handle out-of-order updates
- Verify diagnostic freshness
Content Caching ¶
Documents cache their line-split content to optimize:
- Position-to-offset conversions
- Word extraction for hover and completion
- Incremental change application
func NewDocumentManager ¶
func NewDocumentManager() *DocumentManager
NewDocumentManager creates a new document manager.
This constructor initializes a DocumentManager with an empty document map. The returned manager is ready to handle document lifecycle events from LSP clients.
Returns:
- *DocumentManager: A new document manager instance
Thread Safety: The returned DocumentManager is fully thread-safe and ready for concurrent use by multiple LSP request handlers.
Usage:
dm := NewDocumentManager()
dm.Open("file:///query.sql", "sql", 1, "SELECT * FROM users")
Typically, this is called once when creating the LSP server, not for each document operation.
func (*DocumentManager) Close ¶
func (dm *DocumentManager) Close(uri string)
Close removes a document from the manager.
This method is called when the client sends a textDocument/didClose notification. It removes the document from the internal map and releases associated resources.
Parameters:
- uri: Document URI to close and remove
Thread Safety: This method uses a write lock to safely remove documents concurrently from multiple goroutines.
After closing a document, the server typically sends an empty diagnostics notification to clear any error markers in the editor:
dm.Close("file:///query.sql")
server.SendNotification("textDocument/publishDiagnostics", PublishDiagnosticsParams{
URI: "file:///query.sql",
Diagnostics: []Diagnostic{},
})
If the document doesn't exist, this method does nothing (safe to call redundantly).
Once closed, the document must be re-opened with Open() before it can be accessed again. Attempting to Update() or Get() a closed document will fail.
func (*DocumentManager) Get ¶
func (dm *DocumentManager) Get(uri string) (*Document, bool)
Get retrieves a copy of a document to avoid race conditions The returned document is a snapshot and modifications won't affect the original
func (*DocumentManager) GetContent ¶
func (dm *DocumentManager) GetContent(uri string) (string, bool)
GetContent retrieves a document's content
func (*DocumentManager) Open ¶
func (dm *DocumentManager) Open(uri, languageID string, version int, content string)
Open adds a document to the manager.
This method is called when the client sends a textDocument/didOpen notification. It stores the initial document state including URI, language, version, and content.
Parameters:
- uri: Document URI (e.g., "file:///path/to/query.sql")
- languageID: Language identifier (typically "sql")
- version: Initial version number (starts at 1, increments with changes)
- content: Full document text content
Thread Safety: This method uses a write lock to safely add documents concurrently from multiple goroutines.
The document's content is cached in both raw form (Content) and split into lines (Lines) for efficient position-to-offset conversions.
Example:
dm.Open("file:///query.sql", "sql", 1, "SELECT * FROM users WHERE active = true")
If a document with the same URI already exists, it will be replaced with the new content and version.
func (*DocumentManager) Update ¶
func (dm *DocumentManager) Update(uri string, version int, changes []TextDocumentContentChangeEvent)
Update updates a document's content.
This method is called when the client sends a textDocument/didChange notification. It applies content changes to an existing document and updates its version number.
Parameters:
- uri: Document URI to update
- version: New version number (should be greater than current version)
- changes: Array of content changes to apply
Thread Safety: This method uses a write lock to safely update documents concurrently from multiple goroutines.
The method supports two synchronization modes:
Full Sync (change.Range == nil):
- The entire document is replaced with change.Text
- Simple and robust, but sends more data over the network
Incremental Sync (change.Range != nil):
- Only the specified range is replaced with change.Text
- More efficient for large documents with small edits
- Requires proper position-to-offset conversion
Example - Full sync:
dm.Update("file:///query.sql", 2, []TextDocumentContentChangeEvent{
{Text: "SELECT id, name FROM users WHERE active = true"},
})
Example - Incremental sync:
dm.Update("file:///query.sql", 3, []TextDocumentContentChangeEvent{
{
Range: &Range{Start: Position{Line: 0, Character: 7}, End: Position{Line: 0, Character: 8}},
Text: "*",
},
})
If the document doesn't exist, this method does nothing. The document must first be opened with Open() before it can be updated.
After applying changes, the Lines cache is automatically rebuilt for efficient subsequent operations.
type DocumentSymbol ¶
type DocumentSymbol struct {
Name string `json:"name"`
Detail string `json:"detail,omitempty"`
Kind SymbolKind `json:"kind"`
Range Range `json:"range"`
SelectionRange Range `json:"selectionRange"`
Children []DocumentSymbol `json:"children,omitempty"`
}
DocumentSymbol represents a symbol in a document (hierarchical)
type DocumentSymbolParams ¶
type DocumentSymbolParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
}
DocumentSymbolParams is the parameters for textDocument/documentSymbol
type FormattingOptions ¶
type FormattingOptions struct {
TabSize int `json:"tabSize"`
InsertSpaces bool `json:"insertSpaces"`
TrimTrailingWhitespace bool `json:"trimTrailingWhitespace,omitempty"`
InsertFinalNewline bool `json:"insertFinalNewline,omitempty"`
TrimFinalNewlines bool `json:"trimFinalNewlines,omitempty"`
}
FormattingOptions describes formatting options
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler processes LSP requests and notifications.
Handler implements all LSP protocol handlers for the GoSQLX language server. It coordinates between the LSP protocol layer, document management, and the GoSQLX SQL parser to provide comprehensive code intelligence features.
Supported LSP Methods ¶
Lifecycle:
- initialize: Server initialization and capability negotiation
- initialized: Confirmation of successful initialization
- shutdown: Graceful shutdown preparation
- exit: Final shutdown notification
Text Synchronization:
- textDocument/didOpen: Document opened in editor
- textDocument/didChange: Document content modified (incremental sync supported)
- textDocument/didClose: Document closed in editor
- textDocument/didSave: Document saved to disk
Code Intelligence:
- textDocument/hover: Show keyword documentation (60+ SQL keywords)
- textDocument/completion: Auto-complete keywords and snippets (100+ items)
- textDocument/formatting: Format SQL with intelligent indentation
- textDocument/documentSymbol: Outline view of SQL statements
- textDocument/signatureHelp: Function parameter help (20+ functions)
- textDocument/codeAction: Quick fixes for common errors
Diagnostics:
- textDocument/publishDiagnostics: Real-time syntax error reporting
Keyword Documentation ¶
The handler provides hover documentation for SQL keywords including:
- Core DML: SELECT, INSERT, UPDATE, DELETE, MERGE
- DDL: CREATE, ALTER, DROP, TRUNCATE
- JOINs: INNER, LEFT, RIGHT, FULL OUTER, CROSS, NATURAL
- Clauses: WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
- CTEs: WITH, RECURSIVE
- Set Operations: UNION, EXCEPT, INTERSECT
- Window Functions: ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, etc.
- Aggregates: COUNT, SUM, AVG, MIN, MAX
- Advanced: ROLLUP, CUBE, GROUPING SETS, PARTITION BY
Completion Features ¶
Auto-completion includes:
- 100+ SQL keywords with context-appropriate filtering
- 22 code snippets for common SQL patterns
- Trigger characters: space, dot, opening parenthesis
- Prefix-based filtering for fast results
Snippet examples:
- "sel" → Complete SELECT statement template
- "cte" → Common Table Expression with RECURSIVE option
- "window" → Window function with PARTITION BY and ORDER BY
- "merge" → MERGE statement with MATCHED/NOT MATCHED clauses
Error Handling ¶
The handler provides sophisticated error reporting:
- Position extraction from GoSQLX structured errors
- Fallback regex patterns for unstructured error messages
- Error code propagation for diagnostic categorization
- Precise error ranges for IDE underlining
Document Size Limits ¶
Documents are subject to size limits for performance:
- MaxDocumentSize (5MB): Documents larger than this skip validation
- Warning message sent to client for oversized documents
- Documents still opened but diagnostics disabled
Thread Safety ¶
Handler operations are thread-safe through:
- DocumentManager's read/write locking
- Immutable keyword and snippet data structures
- No shared mutable state between requests
func NewHandler ¶
NewHandler creates a new LSP request handler.
This constructor initializes the handler with a reference to the server and sets up the SQL keywords database for hover documentation and completion.
The handler uses DialectGeneric for maximum SQL compatibility across PostgreSQL, MySQL, SQL Server, Oracle, and SQLite dialects.
Parameters:
- server: The LSP server instance that owns this handler
Returns a fully initialized Handler ready to process LSP requests.
func (*Handler) HandleNotification ¶
func (h *Handler) HandleNotification(method string, params json.RawMessage)
HandleNotification processes an LSP notification
func (*Handler) HandleRequest ¶
func (h *Handler) HandleRequest(method string, params json.RawMessage) (interface{}, error)
HandleRequest processes an LSP request and returns a result
type Hover ¶
type Hover struct {
Contents MarkupContent `json:"contents"`
Range *Range `json:"range,omitempty"`
}
Hover represents hover information
type HoverClientCapabilities ¶
type HoverClientCapabilities struct {
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
ContentFormat []string `json:"contentFormat,omitempty"`
}
HoverClientCapabilities describes hover capabilities
type InitializeParams ¶
type InitializeParams struct {
ProcessID int `json:"processId"`
RootURI string `json:"rootUri"`
RootPath string `json:"rootPath,omitempty"`
Capabilities ClientCapabilities `json:"capabilities"`
InitializationOptions interface{} `json:"initializationOptions,omitempty"`
}
InitializeParams contains initialization options
type InitializeResult ¶
type InitializeResult struct {
Capabilities ServerCapabilities `json:"capabilities"`
ServerInfo *ServerInfo `json:"serverInfo,omitempty"`
}
InitializeResult is the response to initialize
type InsertTextFormat ¶
type InsertTextFormat int
InsertTextFormat defines the format of the insert text
const ( PlainTextFormat InsertTextFormat = 1 SnippetFormat InsertTextFormat = 2 )
type MarkupContent ¶
type MarkupContent struct {
Kind MarkupKind `json:"kind"`
Value string `json:"value"`
}
MarkupContent represents markup content
type MarkupKind ¶
type MarkupKind string
MarkupKind describes the markup type
const ( // PlainText is plain text PlainText MarkupKind = "plaintext" // Markdown is markdown Markdown MarkupKind = "markdown" )
type MessageType ¶
type MessageType int
MessageType represents the type of message to show
const ( // MessageError is an error message MessageError MessageType = 1 // MessageWarning is a warning message MessageWarning MessageType = 2 // MessageInfo is an info message MessageInfo MessageType = 3 // MessageLog is a log message MessageLog MessageType = 4 )
type Notification ¶
type Notification struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
Notification represents a JSON-RPC 2.0 notification (request without ID).
A notification is a special type of request that does not expect a response. It has no ID field, and the server will not send a response. Notifications are used for events that the client sends to the server without needing acknowledgment, such as document change notifications.
Example notification:
{
"jsonrpc": "2.0",
"method": "textDocument/didChange",
"params": { "textDocument": { "uri": "file:///query.sql", "version": 2 }, "contentChanges": [...] }
}
type ParameterInformation ¶
type ParameterInformation struct {
Label interface{} `json:"label"` // string or [int, int]
Documentation interface{} `json:"documentation,omitempty"`
}
ParameterInformation represents a parameter of a function signature
type PublishDiagnosticsClientCapabilities ¶
type PublishDiagnosticsClientCapabilities struct {
RelatedInformation bool `json:"relatedInformation,omitempty"`
}
PublishDiagnosticsClientCapabilities describes diagnostics capabilities
type PublishDiagnosticsParams ¶
type PublishDiagnosticsParams struct {
URI string `json:"uri"`
Version int `json:"version,omitempty"`
Diagnostics []Diagnostic `json:"diagnostics"`
}
PublishDiagnosticsParams is sent to publish diagnostics
type Request ¶
type Request struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id,omitempty"`
Method string `json:"method"`
Params json.RawMessage `json:"params,omitempty"`
}
Request represents a JSON-RPC 2.0 request message.
A request is a message sent from the client to the server expecting a response. It contains a unique ID to correlate the request with its response, a method name identifying the operation to perform, and optional parameters for the method.
The JSONRPC field must always be "2.0" per the JSON-RPC 2.0 specification.
Example request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "textDocument/hover",
"params": { "textDocument": { "uri": "file:///query.sql" }, "position": { "line": 0, "character": 5 } }
}
type Response ¶
type Response struct {
JSONRPC string `json:"jsonrpc"`
ID interface{} `json:"id,omitempty"`
Result interface{} `json:"result,omitempty"`
Error *ResponseError `json:"error,omitempty"`
}
Response represents a JSON-RPC 2.0 response message.
A response is sent from the server back to the client in reply to a request. It contains the same ID as the request to correlate them. Either Result or Error will be set, but never both.
The JSONRPC field must always be "2.0" per the JSON-RPC 2.0 specification.
Example successful response:
{
"jsonrpc": "2.0",
"id": 1,
"result": { "contents": { "kind": "markdown", "value": "**SELECT** - Retrieves data..." } }
}
Example error response:
{
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32601, "message": "Method not found" }
}
type ResponseError ¶
type ResponseError struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
ResponseError represents a JSON-RPC 2.0 error.
This type carries error information when a request fails. The Code field contains a numeric error code (see error code constants), Message provides a human-readable description, and Data optionally contains additional context.
Standard error codes are defined as package constants (ParseError, InvalidRequest, etc.).
type SaveOptions ¶
type SaveOptions struct {
IncludeText bool `json:"includeText,omitempty"`
}
SaveOptions describes save options
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server represents the LSP server instance.
Server implements the Language Server Protocol for SQL code intelligence. It manages client-server communication over stdin/stdout using JSON-RPC 2.0, handles document lifecycle events, and coordinates all LSP protocol handlers.
Features ¶
The server provides the following capabilities:
- Real-time syntax validation with diagnostics (textDocument/publishDiagnostics)
- SQL code formatting with intelligent indentation (textDocument/formatting)
- Keyword hover documentation for 60+ SQL keywords (textDocument/hover)
- Auto-completion with 100+ keywords and 22 snippets (textDocument/completion)
- Document outline and symbol navigation (textDocument/documentSymbol)
- Function signature help for 20+ SQL functions (textDocument/signatureHelp)
- Quick fixes and code actions (textDocument/codeAction)
Architecture ¶
The server uses a multi-component architecture:
- Server: Main server loop and JSON-RPC message handling
- DocumentManager: Thread-safe document state management
- Handler: LSP protocol request and notification processing
Concurrency ¶
Server is designed for concurrent operation:
- Thread-safe document management with read/write locks
- Atomic rate limiting for request throttling
- Synchronized write operations to prevent message corruption
Rate Limiting ¶
Built-in rate limiting protects against request floods:
- Maximum 100 requests per second (configurable via RateLimitRequests)
- Automatic rate limit window reset
- Client receives RequestCancelled error when limit exceeded
Message Size Limits ¶
The server enforces size limits for stability:
- MaxContentLength: 10MB per LSP message
- MaxDocumentSize: 5MB per SQL document
Error Handling ¶
Robust error handling throughout the server:
- Malformed JSON-RPC messages handled gracefully
- Position information extracted from GoSQLX errors
- Structured errors with error codes for diagnostics
Example Usage ¶
logger := log.New(os.Stderr, "[LSP] ", log.LstdFlags)
server := lsp.NewStdioServer(logger)
if err := server.Run(); err != nil {
log.Fatal(err)
}
Or via the CLI:
./gosqlx lsp ./gosqlx lsp --log /tmp/lsp.log
IDE Integration ¶
The server can be integrated with various editors:
VSCode - Add to settings.json:
{
"gosqlx-lsp": {
"command": "gosqlx",
"args": ["lsp"],
"filetypes": ["sql"]
}
}
Neovim - Add to init.lua:
vim.api.nvim_create_autocmd("FileType", {
pattern = "sql",
callback = function()
vim.lsp.start({
name = "gosqlx-lsp",
cmd = {"gosqlx", "lsp"}
})
end
})
Emacs (lsp-mode) - Add to init.el:
(require 'lsp-mode)
(add-to-list 'lsp-language-id-configuration '(sql-mode . "sql"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection '("gosqlx" "lsp"))
:major-modes '(sql-mode)
:server-id 'gosqlx-lsp))
See docs/LSP_GUIDE.md for comprehensive integration documentation.
func NewServer ¶
NewServer creates a new LSP server with custom input/output streams.
This constructor allows you to specify custom reader and writer for the JSON-RPC 2.0 communication. The server will read LSP messages from reader and write responses to writer.
Parameters:
- reader: Input stream for receiving LSP messages (typically os.Stdin)
- writer: Output stream for sending LSP responses (typically os.Stdout)
- logger: Logger for server diagnostics (use io.Discard for silent operation)
The logger parameter can be nil, in which case logging will be disabled. For production deployments, it's recommended to provide a logger that writes to a file rather than stderr to avoid interfering with LSP communication.
Example:
logFile, _ := os.Create("/tmp/gosqlx-lsp.log")
logger := log.New(logFile, "[GoSQLX LSP] ", log.LstdFlags)
server := lsp.NewServer(os.Stdin, os.Stdout, logger)
defer logFile.Close()
Returns a fully initialized Server ready to call Run().
func NewStdioServer ¶
NewStdioServer creates a new LSP server using stdin/stdout.
This is the standard constructor for LSP servers that communicate over standard input/output streams, which is the typical mode for editor integration.
The server reads LSP protocol messages from os.Stdin and writes responses to os.Stdout. This is the recommended way to create an LSP server for use with editors like VSCode, Neovim, and Emacs.
Parameters:
- logger: Logger for server diagnostics. Should write to a file or os.Stderr, never to os.Stdout (which is reserved for LSP communication)
Example:
logFile, _ := os.Create("/tmp/gosqlx-lsp.log")
logger := log.New(logFile, "", log.LstdFlags)
server := lsp.NewStdioServer(logger)
if err := server.Run(); err != nil {
logger.Fatal(err)
}
This is equivalent to:
NewServer(os.Stdin, os.Stdout, logger)
func (*Server) Documents ¶
func (s *Server) Documents() *DocumentManager
Documents returns the server's document manager.
The DocumentManager provides access to all currently open SQL documents and their state. This method is primarily used internally by request handlers to access document content when processing LSP requests.
Returns:
- *DocumentManager: The server's document manager instance
Thread Safety: The returned DocumentManager is thread-safe and can be used concurrently from multiple request handlers.
Usage:
doc, ok := server.Documents().Get("file:///query.sql")
if ok {
content := doc.Content
// Process document content
}
func (*Server) Logger ¶
Logger returns the server's logger instance.
The logger is used for debugging and diagnostic output. It should write to a file or os.Stderr, never to os.Stdout (which is reserved for LSP protocol communication).
Returns:
- *log.Logger: The server's logger, or a logger that discards output if the server was created with a nil logger
Thread Safety: The standard log.Logger is thread-safe and can be used concurrently from multiple goroutines.
Example:
server.Logger().Printf("Processing request: %s", method)
func (*Server) MaxDocumentSizeBytes ¶
MaxDocumentSizeBytes returns the maximum allowed document size
func (*Server) Run ¶
Run starts the server's main loop and processes LSP messages.
This method blocks until the server receives an exit notification or encounters an unrecoverable error. It continuously reads LSP messages from the input stream, processes them, and sends responses.
The main loop:
- Reads a complete LSP message (headers + content)
- Validates message size against MaxContentLength
- Applies rate limiting (RateLimitRequests per RateLimitWindow)
- Parses JSON-RPC 2.0 structure
- Dispatches to appropriate handler
- Sends response or error back to client
Shutdown Sequence:
The server follows the LSP shutdown protocol:
- Client sends "shutdown" request → Server responds with empty result
- Client sends "exit" notification → Server stops message loop
- Run() returns nil for clean shutdown
Error Handling:
The server handles various error conditions gracefully:
- EOF on stdin: Assumes client disconnected, returns nil
- Parse errors: Sends ParseError response, continues
- Rate limit exceeded: Sends RequestCancelled error
- Malformed JSON: Attempts to extract ID for error response
- Unknown methods: Sends MethodNotFound error
Returns:
- nil on clean shutdown (exit notification received)
- nil on EOF (client disconnected)
- error only for unexpected fatal conditions
Example:
server := lsp.NewStdioServer(logger)
if err := server.Run(); err != nil {
log.Fatalf("LSP server error: %v", err)
}
func (*Server) SendNotification ¶
SendNotification sends a notification to the client.
This method sends a JSON-RPC 2.0 notification (a request without an ID) to the client. Notifications are one-way messages that do not expect a response.
The server uses this method to push information to the client asynchronously, such as diagnostic results (textDocument/publishDiagnostics) or progress updates.
Parameters:
- method: The LSP method name (e.g., "textDocument/publishDiagnostics")
- params: The parameters object to send (will be JSON-marshaled)
Thread Safety: This method is thread-safe and can be called concurrently from multiple goroutines. Write operations are protected by a mutex.
Common notification methods:
- "textDocument/publishDiagnostics": Send syntax errors to client
- "window/showMessage": Display message to user
- "window/logMessage": Log message in client
Example:
s.SendNotification("textDocument/publishDiagnostics", PublishDiagnosticsParams{
URI: "file:///query.sql",
Diagnostics: diagnostics,
})
If params is nil, an empty notification without params will be sent. If marshaling params fails, the error is logged but no notification is sent.
func (*Server) SetShutdown ¶
func (s *Server) SetShutdown()
SetShutdown marks the server for shutdown.
This method is called when the server receives an "exit" notification from the client. It sets an internal flag that causes the main message loop in Run() to terminate cleanly.
Thread Safety: This method is safe to call concurrently, though it's typically only called from the exit notification handler.
The shutdown sequence:
- Client sends "shutdown" request → Server responds with empty result
- Client sends "exit" notification → Server calls SetShutdown()
- Run() method checks shutdown flag and returns nil
This method does not immediately stop the server; it only marks it for shutdown. The actual termination occurs when the Run() loop checks the flag.
type ServerCapabilities ¶
type ServerCapabilities struct {
TextDocumentSync *TextDocumentSyncOptions `json:"textDocumentSyncOptions,omitempty"`
CompletionProvider *CompletionOptions `json:"completionProvider,omitempty"`
HoverProvider bool `json:"hoverProvider,omitempty"`
DocumentFormattingProvider bool `json:"documentFormattingProvider,omitempty"`
DocumentSymbolProvider bool `json:"documentSymbolProvider,omitempty"`
SignatureHelpProvider *SignatureHelpOptions `json:"signatureHelpProvider,omitempty"`
CodeActionProvider interface{} `json:"codeActionProvider,omitempty"` // bool or CodeActionOptions
}
ServerCapabilities describes what the server can do
type ServerInfo ¶
ServerInfo provides information about the server
type ShowMessageParams ¶
type ShowMessageParams struct {
Type MessageType `json:"type"`
Message string `json:"message"`
}
ShowMessageParams is used to show a message to the user
type ShutdownResult ¶
type ShutdownResult struct{}
ShutdownResult is the result of a shutdown request
type SignatureHelp ¶
type SignatureHelp struct {
Signatures []SignatureInformation `json:"signatures"`
ActiveSignature int `json:"activeSignature,omitempty"`
ActiveParameter int `json:"activeParameter,omitempty"`
}
SignatureHelp represents signature help information
type SignatureHelpOptions ¶
type SignatureHelpOptions struct {
TriggerCharacters []string `json:"triggerCharacters,omitempty"`
RetriggerCharacters []string `json:"retriggerCharacters,omitempty"`
}
SignatureHelpOptions describes signature help options
type SignatureInformation ¶
type SignatureInformation struct {
Label string `json:"label"`
Documentation interface{} `json:"documentation,omitempty"`
Parameters []ParameterInformation `json:"parameters,omitempty"`
}
SignatureInformation represents a function signature
type SymbolInformation ¶
type SymbolInformation struct {
Name string `json:"name"`
Kind SymbolKind `json:"kind"`
Location Location `json:"location"`
ContainerName string `json:"containerName,omitempty"`
}
SymbolInformation represents a symbol (flat list)
type SymbolKind ¶
type SymbolKind int
SymbolKind represents the kind of symbol
const ( SymbolFile SymbolKind = 1 SymbolModule SymbolKind = 2 SymbolNamespace SymbolKind = 3 SymbolPackage SymbolKind = 4 SymbolClass SymbolKind = 5 SymbolMethod SymbolKind = 6 SymbolProperty SymbolKind = 7 SymbolField SymbolKind = 8 SymbolConstructor SymbolKind = 9 SymbolEnum SymbolKind = 10 SymbolInterface SymbolKind = 11 SymbolFunction SymbolKind = 12 SymbolVariable SymbolKind = 13 SymbolConstant SymbolKind = 14 SymbolString SymbolKind = 15 SymbolNumber SymbolKind = 16 SymbolBoolean SymbolKind = 17 SymbolArray SymbolKind = 18 SymbolObject SymbolKind = 19 SymbolKey SymbolKind = 20 SymbolNull SymbolKind = 21 SymbolEnumMember SymbolKind = 22 SymbolStruct SymbolKind = 23 SymbolEvent SymbolKind = 24 SymbolOperator SymbolKind = 25 SymbolTypeParam SymbolKind = 26 )
type TextDocumentClientCapabilities ¶
type TextDocumentClientCapabilities struct {
Synchronization *TextDocumentSyncClientCapabilities `json:"synchronization,omitempty"`
Completion *CompletionClientCapabilities `json:"completion,omitempty"`
Hover *HoverClientCapabilities `json:"hover,omitempty"`
PublishDiagnostics *PublishDiagnosticsClientCapabilities `json:"publishDiagnostics,omitempty"`
}
TextDocumentClientCapabilities describes text document capabilities
type TextDocumentContentChangeEvent ¶
type TextDocumentContentChangeEvent struct {
Range *Range `json:"range,omitempty"`
RangeLength int `json:"rangeLength,omitempty"`
Text string `json:"text"`
}
TextDocumentContentChangeEvent describes a content change
type TextDocumentEdit ¶
type TextDocumentEdit struct {
TextDocument VersionedTextDocumentIdentifier `json:"textDocument"`
Edits []TextEdit `json:"edits"`
}
TextDocumentEdit represents an edit to a single document
type TextDocumentIdentifier ¶
type TextDocumentIdentifier struct {
URI string `json:"uri"`
}
TextDocumentIdentifier identifies a document
type TextDocumentItem ¶
type TextDocumentItem struct {
URI string `json:"uri"`
LanguageID string `json:"languageId"`
Version int `json:"version"`
Text string `json:"text"`
}
TextDocumentItem represents a document
type TextDocumentPositionParams ¶
type TextDocumentPositionParams struct {
TextDocument TextDocumentIdentifier `json:"textDocument"`
Position Position `json:"position"`
}
TextDocumentPositionParams identifies a position in a document
type TextDocumentSyncClientCapabilities ¶
type TextDocumentSyncClientCapabilities struct {
DynamicRegistration bool `json:"dynamicRegistration,omitempty"`
WillSave bool `json:"willSave,omitempty"`
WillSaveWaitUntil bool `json:"willSaveWaitUntil,omitempty"`
DidSave bool `json:"didSave,omitempty"`
}
TextDocumentSyncClientCapabilities describes sync capabilities
type TextDocumentSyncKind ¶
type TextDocumentSyncKind int
TextDocumentSyncKind defines how the client syncs document changes
const ( // SyncNone means documents should not be synced at all SyncNone TextDocumentSyncKind = 0 // SyncFull means documents are synced by sending the full content SyncFull TextDocumentSyncKind = 1 // SyncIncremental means documents are synced by sending incremental updates SyncIncremental TextDocumentSyncKind = 2 )
type TextDocumentSyncOptions ¶
type TextDocumentSyncOptions struct {
OpenClose bool `json:"openClose,omitempty"`
Change TextDocumentSyncKind `json:"change,omitempty"`
Save *SaveOptions `json:"save,omitempty"`
}
TextDocumentSyncOptions describes how documents are synced
type VersionedTextDocumentIdentifier ¶
type VersionedTextDocumentIdentifier struct {
TextDocumentIdentifier
Version int `json:"version"`
}
VersionedTextDocumentIdentifier identifies a specific version of a document
type WorkspaceEdit ¶
type WorkspaceEdit struct {
Changes map[string][]TextEdit `json:"changes,omitempty"`
DocumentChanges []TextDocumentEdit `json:"documentChanges,omitempty"`
}
WorkspaceEdit represents changes to be applied to a workspace