Hone logo
Hone
Problems

Building a Minimal Language Server in Python

This challenge is designed to help you understand the core concepts of the Language Server Protocol (LSP) by building a simplified language server in Python. A language server enhances code editing experiences by providing features like auto-completion, diagnostics, and go-to-definition for a specific programming language within compatible editors. This project will give you hands-on experience with inter-process communication and structured data exchange that powers modern development tools.

Problem Description

Your task is to implement a basic language server in Python that communicates with a client (an IDE or editor) using the Language Server Protocol. This server will focus on handling basic text document synchronization and providing simple diagnostics for a hypothetical custom language.

Key Requirements:

  1. Communication Protocol: Implement communication over standard input/output (stdio) as defined by the LSP. This involves sending and receiving JSON-RPC messages.
  2. Initialization: Handle the initialize request from the client. The server should respond with its capabilities.
  3. Text Document Synchronization:
    • Respond to textDocument/didOpen by storing the content of the opened document.
    • Respond to textDocument/didChange by updating the stored content.
    • Respond to textDocument/didClose by potentially removing the document from its internal state.
  4. Diagnostics: After a didChange event, analyze the document content for a specific, simple error pattern and send textDocument/publishDiagnostics notifications to the client.

Error Pattern for Diagnostics:

For this minimal server, we'll define a simple syntax rule: each line in the document must start with a specific keyword, say COMMAND:. If a line does not start with COMMAND:, it should be flagged as an error.

Expected Behavior:

  • When an editor connects, it will send an initialize request. Your server should respond with its capabilities, indicating it supports text document synchronization.
  • When the user opens a file, the textDocument/didOpen notification is sent. Your server should store the file content.
  • When the user types in the file, textDocument/didChange notifications are sent. Your server should update its stored content.
  • Immediately after processing a didChange notification, your server should scan the updated content. If any line fails to start with COMMAND:, it should send a textDocument/publishDiagnostics notification containing information about the erroneous lines (line number, column number, message).
  • When a file is closed, textDocument/didClose is sent. Your server can simply discard its stored content for that file.

Examples

Example 1: Initialization and Basic Diagnostics

  • Client (Editor) sends (excerpt):
    Content-Length: 123
    Content-Type: application/vscode-jsonrpc; charset=utf-8
    
    {
      "jsonrpc": "2.0",
      "id": "1",
      "method": "initialize",
      "params": {
        // ... client capabilities ...
      }
    }
    
  • Server sends (excerpt):
    Content-Length: 456
    Content-Type: application/vscode-jsonrpc; charset=utf-8
    
    {
      "jsonrpc": "2.0",
      "id": "1",
      "result": {
        "capabilities": {
          "textDocumentSync": 1 // Full sync
        }
      }
    }
    
  • Explanation: The client initiates the connection with an initialize request. The server confirms by returning its capabilities, stating it supports full text document synchronization.

Example 2: Opening a Document and Detecting an Error

  • Client sends:
    Content-Length: 300
    Content-Type: application/vscode-jsonrpc; charset=utf-8
    
    {
      "jsonrpc": "2.0",
      "method": "textDocument/didOpen",
      "params": {
        "textDocument": {
          "uri": "file:///path/to/your/file.mylang",
          "languageId": "mylang",
          "version": 1,
          "text": "COMMAND: task1\n\nThis is a comment line.\nCOMMAND: task2"
        }
      }
    }
    
  • Server sends:
    Content-Length: 500
    Content-Type: application/vscode-jsonrpc; charset=utf-8
    
    {
      "jsonrpc": "2.0",
      "method": "textDocument/publishDiagnostics",
      "params": {
        "uri": "file:///path/to/your/file.mylang",
        "diagnostics": [
          {
            "range": {
              "start": { "line": 1, "character": 0 },
              "end": { "line": 1, "character": 29 }
            },
            "message": "Line must start with 'COMMAND:'",
            "severity": 1 // Error
          }
        ]
      }
    }
    
  • Explanation: The client opens a file named file.mylang. The server receives the content, stores it, and then scans it. It detects that the second line (index 1) does not start with COMMAND: and sends a publishDiagnostics notification. The range specifies the problematic part of the line.

Example 3: Changing a Document and Correcting an Error

  • Client sends:
    Content-Length: 400
    Content-Type: application/vscode-jsonrpc; charset=utf-8
    
    {
      "jsonrpc": "2.0",
      "method": "textDocument/didChange",
      "params": {
        "textDocument": {
          "uri": "file:///path/to/your/file.mylang",
          "version": 2
        },
        "contentChanges": [
          {
            "range": {
              "start": { "line": 1, "character": 0 },
              "end": { "line": 1, "character": 29 }
            },
            "text": "COMMAND: another task"
          }
        ]
      }
    }
    
  • Server sends:
    Content-Length: 150
    Content-Type: application/vscode-jsonrpc; charset=utf-8
    
    {
      "jsonrpc": "2.0",
      "method": "textDocument/publishDiagnostics",
      "params": {
        "uri": "file:///path/to/your/file.mylang",
        "diagnostics": [] // No errors found
      }
    }
    
  • Explanation: The client modifies the second line to now start with COMMAND:. The server updates its content and re-scans. No errors are found, so it sends an empty diagnostics array, clearing any previous errors for that file.

Constraints

  • Communication: The server must use stdin and stdout for communication. Messages are framed with Content-Length and Content-Type headers.
  • JSON-RPC: All communication must adhere to the JSON-RPC 2.0 specification.
  • LSP Version: Implement the specified LSP methods and structures for version 3.16 (or a recent, stable version). Focus on initialize, textDocument/didOpen, textDocument/didChange, textDocument/didClose, and textDocument/publishDiagnostics.
  • Performance: While this is a minimal server, avoid extremely inefficient parsing or data manipulation that would make it unusable in a real editor. The diagnostic check should be reasonably fast.
  • Language ID: Assume the custom language is identified by "mylang".

Notes

  • JSON-RPC Framing: You'll need to implement logic to read the headers (Content-Length, Content-Type) and then parse the JSON payload. Likewise, when sending messages, you must format them correctly with these headers.
  • Message Handling: Design your server to process incoming messages asynchronously or in a loop. It should be able to handle requests and notifications.
  • State Management: You'll need to maintain a data structure to store the content of open documents (e.g., a dictionary mapping document URIs to their text content and version).
  • Error Severity: The LSP defines different severities for diagnostics. For this challenge, use severity: 1 for errors.
  • Line Endings: Be mindful of different line ending conventions (\n, \r\n). Your parsing should ideally handle this.
  • Resources: Refer to the official Language Server Protocol specification for detailed information on message formats, capabilities, and types: https://microsoft.github.io/language-server-protocol/spec/3.16/specification/
  • Testing: To test your server, you'll need a client. A simple Python script that mimics an LSP client, or an editor extension for VS Code (using the vscode-languageserver-node or similar tooling and then pointing it to your Python executable) can be used. You can also use tools like c2hs for testing LSP servers.
Loading editor...
python