Skip to main content
The LSP API provides interfaces for implementing Language Server Protocol features in Android Code Studio, enabling code completion, navigation, diagnostics, and more.

Core Interfaces

ILanguageServer

The main interface for implementing language server functionality. Source: core/lsp-api/src/main/java/com/tom/rv2ide/lsp/api/ILanguageServer.kt:57
package com.tom.rv2ide.lsp.api

import com.tom.rv2ide.lsp.models.*
import com.tom.rv2ide.models.Range
import com.tom.rv2ide.projects.IWorkspace
import java.nio.file.Path

interface ILanguageServer {
  
  // Server identity
  val serverId: String?
  val client: ILanguageClient?
  
  // Lifecycle
  fun shutdown()
  fun connectClient(client: ILanguageClient?)
  fun applySettings(settings: IServerSettings?)
  fun setupWorkspace(workspace: IWorkspace)
  
  // Code completion
  fun complete(params: CompletionParams?): CompletionResult
  
  // Code navigation
  suspend fun findReferences(params: ReferenceParams): ReferenceResult
  suspend fun findDefinition(params: DefinitionParams): DefinitionResult
  suspend fun expandSelection(params: ExpandSelectionParams): Range
  
  // Documentation
  suspend fun signatureHelp(params: SignatureHelpParams): SignatureHelp
  suspend fun hover(params: DefinitionParams): MarkupContent
  
  // Diagnostics
  suspend fun analyze(file: Path): DiagnosticResult
  
  // Formatting
  fun formatCode(params: FormatCodeParams?): CodeFormatResult
  
  // Error handling
  fun handleFailure(failure: LSPFailure?): Boolean
}

ILanguageClient

Interface for receiving notifications from the language server. Source: core/lsp-api/src/main/java/com/tom/rv2ide/lsp/api/ILanguageClient.java
package com.tom.rv2ide.lsp.api;

import com.tom.rv2ide.lsp.models.DiagnosticResult;
import com.tom.rv2ide.lsp.models.ShowDocumentParams;
import java.nio.file.Path;

public interface ILanguageClient {
  
  // Diagnostics
  void publishDiagnostics(Path file, DiagnosticResult result);
  
  // Document operations
  void showDocument(ShowDocumentParams params);
  
  // Messages
  void logMessage(int type, String message);
  void showMessage(int type, String message);
}

Implementing a Language Server

Basic Server Implementation

Create a custom language server:
import com.tom.rv2ide.lsp.api.ILanguageServer
import com.tom.rv2ide.lsp.api.ILanguageClient
import com.tom.rv2ide.lsp.models.*
import com.tom.rv2ide.projects.IWorkspace
import java.nio.file.Path

class MyLanguageServer : ILanguageServer {
  
  override val serverId = "my-language-server"
  override var client: ILanguageClient? = null
  private var workspace: IWorkspace? = null
  
  override fun shutdown() {
    // Release resources
    workspace = null
    client = null
  }
  
  override fun connectClient(client: ILanguageClient?) {
    this.client = client
  }
  
  override fun applySettings(settings: IServerSettings?) {
    // Apply user preferences
  }
  
  override fun setupWorkspace(workspace: IWorkspace) {
    this.workspace = workspace
  }
  
  override fun complete(params: CompletionParams?): CompletionResult {
    // Implement code completion
    val items = mutableListOf<CompletionItem>()
    // ... populate completion items
    return CompletionResult(items)
  }
  
  override suspend fun findDefinition(params: DefinitionParams): DefinitionResult {
    // Implement go-to-definition
    // Return locations of definition
    return DefinitionResult.EMPTY
  }
  
  override suspend fun analyze(file: Path): DiagnosticResult {
    // Analyze file and return diagnostics
    val diagnostics = mutableListOf<DiagnosticItem>()
    // ... add errors, warnings, etc.
    return DiagnosticResult(diagnostics)
  }
  
  // Implement other methods...
}

Register Language Server

Register your server with the IDE:
import com.tom.rv2ide.lsp.api.ILanguageServerRegistry

val registry = ILanguageServerRegistry.getDefault()
val myServer = MyLanguageServer()

// Register for specific file types
registry.register(myServer, listOf(".kt", ".java"))

Code Completion

Completion Parameters

Handle completion requests:
import com.tom.rv2ide.lsp.models.*
import com.tom.rv2ide.models.Position

override fun complete(params: CompletionParams?): CompletionResult {
  if (params == null) return CompletionResult.EMPTY
  
  val file = params.file
  val position = params.position  // Cursor position
  val prefix = params.prefix      // Text before cursor
  
  val items = mutableListOf<CompletionItem>()
  
  // Add keyword completions
  items.add(CompletionItem().apply {
    label = "class"
    detail = "Keyword"
    kind = CompletionItemKind.Keyword
    insertText = "class "
  })
  
  // Add method completions
  items.add(CompletionItem().apply {
    label = "toString()"
    detail = "String"
    kind = CompletionItemKind.Method
    insertText = "toString()"
  })
  
  return CompletionResult(items)
}
params.file
Path
The file being edited
params.position
Position
The cursor position where completion was triggered
params.prefix
String
The text before the cursor on the current line

Completion Items

Create rich completion items:
val item = CompletionItem().apply {
  label = "myMethod"                    // Display text
  detail = "void myMethod(String arg)"  // Type/signature
  documentation = "Does something"      // Description
  kind = CompletionItemKind.Method
  insertText = "myMethod($1)$0"        // Snippet with placeholders
  sortText = "0000"                     // Sort priority
  filterText = "myMethod"               // Text to match against
}

Code Navigation

Go to Definition

Implement definition lookup:
import com.tom.rv2ide.lsp.models.*
import com.tom.rv2ide.models.*

override suspend fun findDefinition(params: DefinitionParams): DefinitionResult {
  val file = params.file
  val position = params.position
  
  // Find the symbol at position
  val symbol = findSymbolAt(file, position)
  
  if (symbol != null) {
    // Return definition location
    val location = Location().apply {
      this.file = symbol.definitionFile
      this.range = Range(
        Position(symbol.line, symbol.column),
        Position(symbol.line, symbol.column + symbol.name.length)
      )
    }
    return DefinitionResult(listOf(location))
  }
  
  return DefinitionResult.EMPTY
}

Find References

Find all references to a symbol:
override suspend fun findReferences(params: ReferenceParams): ReferenceResult {
  val file = params.file
  val position = params.position
  val includeDeclaration = params.includeDeclaration
  
  val locations = mutableListOf<Location>()
  
  // Search for references across the workspace
  workspace?.getSubProjects()?.forEach { project ->
    // Search in project files
    val refs = searchReferences(project, file, position)
    locations.addAll(refs)
  }
  
  return ReferenceResult(locations)
}

Diagnostics

Analyze Files

Perform analysis and report issues:
import com.tom.rv2ide.lsp.models.*
import java.nio.file.Path

override suspend fun analyze(file: Path): DiagnosticResult {
  val diagnostics = mutableListOf<DiagnosticItem>()
  
  // Parse and analyze file
  val errors = analyzeFile(file)
  
  errors.forEach { error ->
    diagnostics.add(DiagnosticItem().apply {
      range = error.range
      severity = DiagnosticSeverity.ERROR
      message = error.message
      source = "my-language-server"
    })
  }
  
  return DiagnosticResult(diagnostics)
}

Publish Diagnostics

Send diagnostics to the client:
fun analyzeAndPublish(file: Path) {
  val result = analyze(file)
  client?.publishDiagnostics(file, result)
}
DiagnosticResult
object
diagnostics
List<DiagnosticItem>
List of diagnostic items (errors, warnings, info)

Diagnostic Severity

enum class DiagnosticSeverity {
  ERROR,    // Red error
  WARNING,  // Yellow warning
  INFO,     // Blue information
  HINT      // Suggestion
}

Signature Help

Parameter Hints

Provide parameter information:
import com.tom.rv2ide.lsp.models.*

override suspend fun signatureHelp(params: SignatureHelpParams): SignatureHelp {
  val file = params.file
  val position = params.position
  
  // Find function call at position
  val call = findFunctionCall(file, position)
  
  if (call != null) {
    val signature = SignatureInformation().apply {
      label = "myFunction(arg1: String, arg2: Int)"
      documentation = "Performs an operation"
      parameters = listOf(
        ParameterInformation("arg1", "The first argument"),
        ParameterInformation("arg2", "The second argument")
      )
    }
    
    return SignatureHelp().apply {
      signatures = listOf(signature)
      activeSignature = 0
      activeParameter = call.currentParameter
    }
  }
  
  return SignatureHelp.EMPTY
}

Code Formatting

Format Code

Implement code formatting:
import com.tom.rv2ide.lsp.models.*

override fun formatCode(params: FormatCodeParams?): CodeFormatResult {
  if (params == null) return CodeFormatResult(false, emptyList())
  
  val content = params.content
  val edits = mutableListOf<TextEdit>()
  
  // Format the code
  val formatted = formatSource(content)
  
  // Create text edit for entire document
  edits.add(TextEdit().apply {
    range = Range(
      Position(0, 0),
      Position(content.lines().size, 0)
    )
    newText = formatted
  })
  
  return CodeFormatResult(true, edits)
}

Hover Documentation

Show Documentation

Provide hover information:
override suspend fun hover(params: DefinitionParams): MarkupContent {
  val file = params.file
  val position = params.position
  
  val symbol = findSymbolAt(file, position)
  
  if (symbol != null) {
    return MarkupContent().apply {
      kind = MarkupKind.MARKDOWN
      value = """
        ### ${symbol.name}
        
        **Type:** ${symbol.type}
        
        ${symbol.documentation}
        
        ```java
        ${symbol.signature}
""".trimIndent() } } return MarkupContent() }

## Complete Example

Here's a complete language server implementation:

```kotlin
import com.tom.rv2ide.lsp.api.*
import com.tom.rv2ide.lsp.models.*
import com.tom.rv2ide.models.*
import com.tom.rv2ide.projects.IWorkspace
import java.nio.file.Path

class KotlinLanguageServer : ILanguageServer {
  
  override val serverId = "kotlin-language-server"
  override var client: ILanguageClient? = null
  private var workspace: IWorkspace? = null
  private val analyzer = KotlinAnalyzer()
  
  override fun shutdown() {
    analyzer.shutdown()
    workspace = null
    client = null
  }
  
  override fun connectClient(client: ILanguageClient?) {
    this.client = client
    client?.logMessage(1, "Kotlin language server connected")
  }
  
  override fun setupWorkspace(workspace: IWorkspace) {
    this.workspace = workspace
    analyzer.indexWorkspace(workspace)
  }
  
  override fun applySettings(settings: IServerSettings?) {
    // Apply settings
  }
  
  override fun complete(params: CompletionParams?): CompletionResult {
    if (params == null) return CompletionResult.EMPTY
    
    return analyzer.complete(
      params.file,
      params.position,
      params.prefix
    )
  }
  
  override suspend fun findDefinition(params: DefinitionParams): DefinitionResult {
    return analyzer.findDefinition(params.file, params.position)
  }
  
  override suspend fun findReferences(params: ReferenceParams): ReferenceResult {
    return analyzer.findReferences(
      params.file,
      params.position,
      params.includeDeclaration
    )
  }
  
  override suspend fun signatureHelp(params: SignatureHelpParams): SignatureHelp {
    return analyzer.getSignatureHelp(params.file, params.position)
  }
  
  override suspend fun analyze(file: Path): DiagnosticResult {
    val result = analyzer.analyze(file)
    client?.publishDiagnostics(file, result)
    return result
  }
  
  override suspend fun expandSelection(params: ExpandSelectionParams): Range {
    return analyzer.expandSelection(params.file, params.range)
  }
  
  override fun formatCode(params: FormatCodeParams?): CodeFormatResult {
    if (params == null) return CodeFormatResult(false, emptyList())
    return analyzer.format(params.content)
  }
  
  override fun handleFailure(failure: LSPFailure?): Boolean {
    if (failure != null) {
      client?.logMessage(3, "LSP Error: ${failure.message}")
      return true
    }
    return false
  }
}

Editor API

Integrate LSP with editors

Project API

Access workspace information

Build docs developers (and LLMs) love