コンテンツにスキップ

LSP ソースコード解析

モジュール概要

LSP(Language Server Protocol)モジュールは、OpenCodeにIDEレベルのコード理解機能を提供します。標準化されたLSPプロトコルを通じて、OpenCodeは正確な診断情報(コンパイルエラー、型エラー、lint警告)、定義へのジャンプ、参照の検索、ホバー情報、シンボル階層を取得できます。これにより、AIアシスタントは正確なコードセマンティクスに基づいて意思決定できます。

このモジュールはpackages/opencode/src/lsp/にあり、合計5ファイルで約2900行のコードで構成されています。コアエントリーポイントのLSP名前空間はEffect Serviceアーキテクチャを採用しており、Instance.state()を通じてグローバルシングルトン状態を管理しています。このモジュールには30以上のLanguage Server(TypeScript、Gopls、Rust Analyzer、Pyright、Clangd、Lua LSなど)の組み込み定義が含まれており、それぞれ独自のプロジェクトルートディレクトリ検出ロジックとspawn実装を持っています。

コア設計の選択:

  • Effect ServiceパターンLSP名前空間はServiceMap.Service経由でグローバルサービスとして登録され、Instance.state()がライフサイクルを管理します
  • インテリジェントなクライアント再利用getClients(file)(root, serverID)で重複排除し、プロジェクトごとに1つのサーバーインスタンスのみを起動します
  • デバウンスされた診断収集waitForDiagnostics()は150msのデバウンス+3秒のタイムアウトを使用して、エージェントのブロックを避けます
  • 自動ダウンロードと分離:Language ServerバイナリはGlobal.Path.binに自動ダウンロードされ、Flag.OPENCODE_DISABLE_LSP_DOWNLOADで制御されます

主要ファイル

ファイルパス行数責任範囲
src/lsp/index.ts約559モジュールメインエントリ:Effect Service定義、InstanceState状態管理、getClientsインテリジェントマッチング、すべての公開API
src/lsp/client.ts約253LSPクライアント実装:vscode-jsonrpc接続管理、診断収集、ファイルバージョン管理、接続シャットダウン
src/lsp/server.ts約196830以上のLanguage Server定義:LSPServer.Infoインターフェース、NearestRoot高階関数、spawn実装と自動ダウンロード
src/lsp/launch.ts約22Spawnヘルパー関数:Process.spawnをラップし、サーバー起動コード簡略化
src/lsp/language.ts約120LANGUAGE_EXTENSIONSマッピングテーブル:ファイル拡張子(例:.tsx)→ LSP languageId(例:typescriptreact

型システム

RangeとDiagnostic — 位置と診断

// Text range (line number + character offset)
export const Range = z.object({
  start: z.object({ line: z.number(), character: z.number() }),
  end: z.object({ line: z.number(), character: z.number() }),
})

// Symbol information (for documentSymbol / workspaceSymbol)
export const Symbol = z.object({
  name: z.string(),
  kind: z.number(),       // LSP SymbolKind enum value
  location: z.object({
    uri: z.string(),
    range: Range,
  }),
})

// Server connection status
export const Status = z.object({
  id: z.string(),         // Unique server identifier
  name: z.string(),       // Human-readable name
  root: z.string(),       // Project root directory
  status: z.union([
    z.literal("connected"),
    z.literal("error"),
  ]),
})

InstanceState — グローバル状態

// LSP module global state structure
{
  clients: LSPClient.Info[],                 // All active client connections
  servers: Record<string, LSPServer.Info>,   // Registered server definitions (key = serverID)
  broken: Set<string>,                       // Known failed server IDs, to avoid retrying
  spawning: Map<string, Promise<LSPClient.Info | undefined>>,  // Currently launching clients (deduplication)
}

主要フィールドの説明:

  • clients:すべてのアクティブなLSPクライアント接続。各クライアントは(root, serverID)の組み合わせにバインドされています
  • servers:組み込み定義とユーザー設定からマージされたサーバーレジストリ。keyはサーバーIDです(例:"typescript""gopls"
  • broken:起動に失敗したサーバーIDのセット。このセットに追加されると、以後のgetClients()呼び出しは再試行せずにスキップします
  • spawning:インフライトのクライアント起動Promise。同じ(root, serverID)への同時リクエストは同じPromiseを共有し、重複起動を防ぎます

LSPServer.Info — サーバー定義

// Definition interface for each Language Server in server.ts
interface Info {
  id: string              // Unique identifier, e.g. "typescript", "gopls", "rust-analyzer"
  extensions: string[]    // Associated file extensions, e.g. [".ts", ".tsx"]
  root: RootFunction      // Function to determine project root directory
  spawn(root: string): Promise<Handle | undefined>  // Launch server process
}

LSPClient.Info — クライアントインスタンス

// Core client structure in client.ts
{
  serverID: string                           // Corresponding server ID
  root: string                               // Project root directory
  connection: MessageConnection              // vscode-jsonrpc message connection
  diagnostics: Map<string, Diagnostic[]>     // File path → diagnostic list
  files: Record<string, number>              // File path → version number (incrementing counter)
  process: ChildProcess                      // Server child process
}

コアフロー

getClients — インテリジェントファイル-クライアントマッチング

getClients(file)はLSPモジュールで最も重要な関数であり、指定されたファイルに対する適切なLSPクライアントを見つけ出すか作成する責任を持ちます。完全なロジックは8ステップで構成されています:

// index.ts — simplified core logic
async function getClients(file: string): Promise<LSPClient.Info[]> {
  const state = await instanceState()
  const results: LSPClient.Info[] = []

  // Step 1: Check if the file is within the Instance directory
  // Files outside the project directory are not processed
  if (!file.startsWith(instance.directory)) return results

  // Step 2: Extract file extension
  const ext = path.extname(file)  // e.g. ".ts", ".go"

  // Step 3: Iterate all registered servers, check extension match
  for (const [serverID, server] of Object.entries(state.servers)) {
    if (!server.extensions.includes(ext)) continue

    // Step 4: Call server.root(file) to determine project root directory
    const root = server.root(file)
    if (!root) continue  // File does not belong to any project

    // Step 5: Check broken set, skip known failed servers
    if (state.broken.has(serverID)) continue

    // Step 6: Look for existing client (root + serverID match)
    const existing = state.clients.find(
      c => c.root === root && c.serverID === serverID
    )
    if (existing) {
      results.push(existing)
      continue
    }

    // Step 7: Check spawning Map for concurrent requests
    const spawnKey = `${root}:${serverID}`
    const spawning = state.spawning.get(spawnKey)
    if (spawning) {
      const client = await spawning
      if (client) results.push(client)
      continue
    }

    // Step 8: Create new client and store in cache
    const promise = LSPClient.create({ serverID, server: server.handle, root })
      .catch(() => {
        state.broken.add(serverID)  // Launch failed → add to broken set
        return undefined
      })
    state.spawning.set(spawnKey, promise)
    const client = await promise
    state.spawning.delete(spawnKey)
    if (client) {
      state.clients.push(client)
      results.push(client)
    }
  }

  return results
}

このロジックは完全なクライアント再利用と重複排除を実装しています:

  • 同じプロジェクトの同じタイプのLanguage Serverが2つ起動されることはありません
  • 同時のgetClients呼び出しは同じspawn Promiseを共有します
  • 失敗したサーバーはbrokenセットに記録され、再試行されません

LSPClient.create — JSON-RPC接続確立

LSPClient.create()はクライアント作成のコア関数であり、Language Serverとの通信チャネルを確立します:

// client.ts — core flow (simplified)
async function create(input: {
  serverID: string
  server: LSPServer.Handle
  root: string
}): Promise<Info> {
  // 1. Launch server child process
  const handle = await input.server.spawn(input.root)
  if (!handle) throw new Error("spawn failed")

  // 2. Establish JSON-RPC message connection
  const connection = createMessageConnection(
    new StreamMessageReader(handle.process.stdout),
    new StreamMessageWriter(handle.process.stdin),
  )

  // 3. Start message listener
  connection.listen()

  // 4. Send initialize request (45-second timeout)
  const initResult = await withTimeout(
    connection.sendRequest("initialize", {
      rootUri: pathToUri(input.root),
      capabilities: {
        textDocument: {
          synchronization: { didOpen: true, didChange: true, didClose: true },
          publishDiagnostics: { relatedInformation: true },
        },
        workspace: { configuration: true },
      },
    }),
    45_000,
  )

  // 5. Send initialized notification
  connection.sendNotification("initialized", {})

  // 6. Create client instance
  return {
    serverID: input.serverID,
    root: input.root,
    connection,
    process: handle.process,
    diagnostics: new Map(),
    files: {},
  }
}

接続が確立された後、クライアントはtextDocument/publishDiagnostics通知をリッスンし、診断マップを自動的に更新します。

診断収集フロー

診断収集はLSPモジュールの最も重要な出力であり、エージェントがコードを修正する能力に直接影響します。完全なフロー:

1. Agent edits file
   └─ LSP.touchFile(filePath, content, true)

2. getClients(file) matches appropriate clients
   └─ by extension → server → root → reuse/create client

3. Client sends file change notification
   ├─ First open? → textDocument/didOpen
   └─ Already open? → textDocument/didChange (with version number)

4. Language Server analyzes code and pushes diagnostics
   └─ textDocument/publishDiagnostics notification

5. Client updates diagnostics Map
   └─ TypeScript server special: first diagnostic does not publish event (!exists check)

6. Broadcast to upper layers via Bus
   └─ Bus.publish(Event.Diagnostics, { ... })

waitForDiagnosticsデバウンスメカニズム

// client.ts — diagnostic waiting (simplified)
async function waitForDiagnostics(
  client: LSPClient.Info,
  uri: string,
): Promise<Diagnostic[]> {
  const DIAGNOSTICS_DEBOUNCE_MS = 150   // Debounce interval
  const DIAGNOSTICS_TIMEOUT_MS = 3000   // Total timeout

  return new Promise((resolve) => {
    let timer: ReturnType<typeof setTimeout>

    // Subscribe to diagnostic events
    const unsub = Bus.subscribe(Event.Diagnostics, (event) => {
      // Only care about the current file
      if (event.uri !== uri) return

      // Reset timer after receiving diagnostics (debounce)
      clearTimeout(timer)
      timer = setTimeout(() => {
        unsub()
        resolve(client.diagnostics.get(uri) ?? [])
      }, DIAGNOSTICS_DEBOUNCE_MS)
    })

    // Total timeout protection: force return after 3 seconds
    setTimeout(() => {
      unsub()
      clearTimeout(timer)
      resolve(client.diagnostics.get(uri) ?? [])
    }, DIAGNOSTICS_TIMEOUT_MS)
  })
}

150msのデバウンスの理由:Language Serverは通常、構文診断を最初に送信し、その後意味診断を送信します。デバウンスにより、エージェントは中間状態ではなく完全な診断結果を受け取れます。3秒の合計タイムアウトにより、エージェントが無限に待機するのを防ぎます。

サーバー定義とNearestRoot

server.tsはこのモジュールで最も大きなファイル(約1968行)であり、30以上のLanguage Serverを定義しています。各サーバー定義の中核は2つの関数で構成されています:root()spawn()

NearestRoot高階関数

// server.ts — project root directory detection (simplified)
function NearestRoot(
  files: string[],      // Target file name list, e.g. ["package.json", "tsconfig.json"]
  exclude?: string[],   // Exclusion patterns, e.g. ["deno.json"]
): (file: string) => string | undefined {
  return (file: string) => {
    // Search upward from the file's directory until Instance.directory
    let dir = path.dirname(file)
    while (dir.startsWith(instance.directory)) {
      // Check if target files exist
      for (const name of files) {
        const target = path.join(dir, name)
        if (fs.existsSync(target)) {
          // Check exclusion patterns
          if (exclude?.some(ex => fs.existsSync(path.join(dir, ex)))) {
            continue  // Matched exclusion rule, skip this directory
          }
          return dir
        }
      }
      dir = path.dirname(dir)
    }
    return undefined
  }
}

この高階関数はモノレポシナリオをサポートしています:異なるサブディレクトリ内のファイルは異なるプロジェクトルートにマッチでき、したがって独立したLanguage Serverインスタンスを起動します。除外パターンは(Denoプロジェクトで誤ったTSサーバーが起動するのを避けるために、TypeScriptサーバーがdeno.jsonを除外するなどの)特殊ケースを処理します。

組み込みサーバー概要(30以上のタイプ):

カテゴリサーバールート検出戦略
フロントエンドTypeScript、Deno、Vue、Astro、SvelteNearestRoot(["package.json", "tsconfig.json"], ["deno.json"])
システム言語Gopls、Rust Analyzer、Clangd、SourceKit (Swift)NearestRoot(["go.mod"]) / Cargo.toml / compile_commands.json / Package.swift
スクリプト言語Pyright、JDTLS (Java)、KotlinLS、Rubocop (Ruby)、JuliaLSNearestRoot(["pyproject.toml", "setup.py"]) / pom.xml / build.gradleなど
関数型言語LuaLS、Zls (Zig)、Gleam、Ocaml、HLS (Haskell)NearestRoot([".luarc.json"]) / build.zig / gleam.toml / dune / hie.yaml
インフラストラクチャTerraformLS、DockerfileLS、YamlLS、NixdNearestRoot(["*.tf"]) / Dockerfile / *.yml / flake.nix
データ/クエリPrisma、TexLab (LaTeX)、SQLSNearestRoot(["*.prisma"]) / *.tex / *.sql
その他Biome、ESLint、Dart、Clojure、PHPIntelephense、CSharp、FSharpそれぞれの設定ファイル検出
タイプセットTinymist (Typst)NearestRoot(["*.typ"])

自動ダウンロードロジック

一部のLanguage Server(例:Biome、Deno、Ty)は自動ダウンロードをサポートしています。フローは以下の通りです:

// server.ts — auto-download (simplified)
async function spawnWithDownload(input: {
  command: string
  args: string[]
  url: string    // Download URL template
}): Promise<Handle | undefined> {
  // Check flag
  if (Flag.OPENCODE_DISABLE_LSP_DOWNLOAD) {
    // Only try local lookup, no download
    return Process.spawn(input.command, input.args)
  }

  // Try local launch
  const local = await Process.spawn(input.command, input.args)
  if (local) return local

  // Not found locally → auto-download to Global.Path.bin
  const binPath = path.join(Global.Path.bin, input.command)
  if (!fs.existsSync(binPath)) {
    await download(input.url, binPath)
  }

  return Process.spawn(binPath, input.args)
}

コードナビゲーションAPI

LSPモジュールは完全なコードナビゲーション機能を提供します。すべてのAPIはrun()ヘルパー関数を介して一致するクライアントにルーティングします:

// index.ts — run helper function (simplified)
async function run<T>(
  file: string,
  fn: (client: LSPClient.Info, uri: string) => Promise<T>,
): Promise<T[]> {
  const clients = await getClients(file)
  const uri = pathToUri(file)
  return Promise.all(clients.map(c => fn(c, uri)))
}
APILSPメソッド説明
definition(file, position)textDocument/definition参照から定義へジャンプ、Location[]を返します
references(file, position)textDocument/referencesシンボルのすべての参照位置を検索
hover(file, position)textDocument/hoverシンボルの型情報とドキュメントを取得
workspaceSymbol(query)workspace/symbolグローバルシンボル検索、kind 5/6/11/12などをフィルター、最大10件の結果
documentSymbol(uri)textDocument/documentSymbolドキュメントシンボルリスト、DocumentSymbolまたはSymbolを返します
incomingCalls(file, position)2ステップリクエストprepareCallHierarchycallHierarchy/incomingCalls
outgoingCalls(file, position)2ステップリクエストprepareCallHierarchycallHierarchy/outgoingCalls

workspaceSymbolフィルタリングロジック:

// index.ts — workspaceSymbol (simplified)
async function workspaceSymbol(query: string): Promise<Symbol[]> {
  const allClients = await allClients()
  const results: Symbol[] = []

  for (const client of allClients) {
    const symbols = await client.connection.sendRequest(
      "workspace/symbol",
      { query },
    )
    for (const sym of symbols) {
      // Filter to key types: Class(5), Function(6/12), Method(6), Enum(10)
      if ([5, 6, 11, 12].includes(sym.kind)) {
        results.push(sym)
      }
    }
  }

  // Return at most 10 results to avoid context bloat
  return results.slice(0, 10)
}

呼び出しチェーンの例

チェーン1:エージェントがファイルを編集 → 診断収集 → エージェントが修正

1. Agent executes EditTool to modify file
   └─ SessionProcessor processes tool-result event

2. Agent calls LSP.touchFile(filePath, newContent, true)
   ├─ getClients(file)
   │  ├─ Extract extension: ".ts"
   │  ├─ Match server: Typescript (extensions includes ".ts")
   │  ├─ root(file) → NearestRoot(["package.json"]) → "/project/root"
   │  ├─ Find existing client: root="/project/root", serverID="typescript" → found
   │  └─ Return [existingClient]
   └─ client.notify.didChange({ uri, version: 3, content: newContent })

3. Language Server analyzes the modified file
   └─ Pushes textDocument/publishDiagnostics notification

4. Client updates diagnostics Map
   └─ Bus.publish(Event.Diagnostics, { uri, diagnostics: [...] })

5. Agent calls LSP.waitForDiagnostics(file)
   ├─ Subscribe to Diagnostics event
   ├─ Received notification → start 150ms debounce timer
   ├─ No new diagnostics within 150ms → resolve
   └─ Return [{ message: "Type 'string' is not assignable to 'number'", range: {...} }]

6. Agent decides whether to continue fixing based on diagnostics
   └─ In the next iteration of SessionPrompt.loop(), the Agent sees the diagnostic results and decides on a fix strategy

チェーン2:最初のファイルオープン → サーバー起動 → 診断準備完了

1. Agent reads "main.go" for the first time
   └─ LSP.touchFile("main.go", content, true)

2. getClients("main.go")
   ├─ Extension: ".go"
   ├─ Match server: Gopls (extensions includes ".go")
   ├─ root("main.go") → NearestRoot(["go.mod"]) → "/project/root"
   ├─ Find existing client: no match (first open)
   ├─ Check spawning Map: none (first time)
   └─ Create new client:
      ├─ LSPClient.create({ serverID: "gopls", root: "/project/root" })
      │  ├─ server.spawn(root) → Process.spawn("gopls", ["serve"])
      │  ├─ createMessageConnection(stdout, stdin)
      │  ├─ connection.sendRequest("initialize", { rootUri, capabilities })
      │  ├─ connection.sendNotification("initialized", {})
      │  └─ Return new client
      ├─ state.clients.push(newClient)
      └─ Return [newClient]

3. New client sends textDocument/didOpen
   └─ connection.sendNotification("textDocument/didOpen", { textDocument: { uri, languageId: "go", version: 1, text: content } })

4. Gopls analyzes and pushes diagnostics
   └─ publishDiagnostics → diagnostics Map updated → Bus broadcast

5. waitForDiagnostics("main.go")
   ├─ 150ms debounce wait
   └─ Return diagnostic results

チェーン3:モノレポ内の複数プロジェクトが共存

Project structure:
/project
├── frontend/
│   ├── package.json
│   └── app.tsx       ← TypeScript file
├── backend/
│   ├── go.mod
│   └── main.go       ← Go file
└── shared/
    └── utils.ts      ← TypeScript file

1. Agent opens "frontend/app.tsx"
   ├─ getClients → Typescript server
   ├─ root("frontend/app.tsx") → NearestRoot → "/project/frontend"
   └─ Launch TS Client A (root="/project/frontend")

2. Agent opens "shared/utils.ts"
   ├─ getClients → Typescript server
   ├─ root("shared/utils.ts") → NearestRoot → "/project" (searches upward to project root's package.json)
   └─ Launch TS Client B (root="/project") — different root, different instance

3. Agent opens "backend/main.go"
   ├─ getClients → Gopls server
   ├─ root("backend/main.go") → NearestRoot → "/project/backend"
   └─ Launch Go Client C (root="/project/backend")

Result: 3 independent LSP clients coexisting without interference

チェーン4:サーバー起動失敗 → Brokenマーク → 以後のスキップ

1. Agent opens "broken.rs"
   └─ getClients → Rust Analyzer server

2. LSPClient.create() → server.spawn() fails
   ├─ catch block captures error
   ├─ state.broken.add("rust-analyzer")
   └─ Return undefined

3. Agent opens "other.rs" again
   └─ getClients("other.rs")
      ├─ Match server: Rust Analyzer
      ├─ Check broken set: "rust-analyzer" ∈ broken
      └─ Skip, no retry → return empty list

4. Agent sees no diagnostic results, continues working normally

設計上のトレードオフ

決定事項根拠
プレーンモジュールではなくEffect ServiceLSPの状態はInstanceライフサイクルに従う必要があります(プロジェクトが開かれたときに初期化され、閉じられたときにクリーンアップ)。Instance.state() + Effect.addFinalizerにより、プロジェクトが閉じられたときにクライアント接続と子プロセスが適切にクリーンアップされます
再試行ではなくBrokenセットLanguage Serverの起動失敗は通常、バイナリ欠落やポート競合などの環境問題が原因です。繰り返し再試行しても時間とリソースを浪費するだけで、直接マークしてスキップする方が効率的です
spawning Map重複排除同時のgetClients呼び出し(例:エージェントが複数の.tsファイルを同時に編集)は同じspawn Promiseを共有する必要があります。そうでなければ複数の同一Language Serverプロセスが起動されます
150msデバウンス診断Language Serverは通常、構文診断(ASTベース)を最初にプッシュし、その後意味診断(型チェックベース)をプッシュします。デバウンスにより、エージェントは完全な診断セットを確実に見ます
3秒の合計タイムアウトエージェントはLSP応答の遅延により無限に待機することはできません。3秒は「診断は通常十分に完了している」と「エージェントは長く待機できない」のバランスポイントです
NearestRoot高階関数異なるサーバーは異なる設定ファイルを検出する必要があります;高階関数はこのパターンを抽象化します。除外パターン(例:TypeScriptがdeno.jsonを除外)はLanguage Server間の競合を処理します
30以上の組み込みサーバーメインストリームの開発言語の箱出しサポートはOpenCodeのコアアピールポイントです。ユーザーはConfig.lsp経由でカスタムサーバーを追加したり、組み込みサーバーを無効にしたりもできます
エラーではなく自動ダウンロード欠落しているLanguage Serverバイナリ(例:Biome、Deno)を自動的にダウンロードする方が、エラーで終了するより優れています。Flag.OPENCODE_DISABLE_LSP_DOWNLOADにより、制限された環境でこの動作を無効にできます
workspaceSymbolを10件の結果にフィルターエージェントのコンテキストウィンドウは限られています;多すぎるシンボルは有用な情報を薄めます。Class/Function/Method/Enumなどの重要なタイプにフィルターし、10件の結果に制限することで、情報の密度と完全性のバランスを取ります

状態管理とライフサイクル

InstanceState初期化

LSP状態はInstance.state()を介して遅延初期化されるシングルトンとして作成されます:

// index.ts — state initialization (simplified)
const instanceState = Instance.state(async () => {
  const cfg = await Config.get()

  // 1. Collect built-in server definitions
  const servers: Record<string, LSPServer.Info> = {}
  for (const server of builtInServers) {
    servers[server.id] = server
  }

  // 2. Merge user-defined server configuration
  if (cfg.lsp) {
    for (const [id, custom] of Object.entries(cfg.lsp)) {
      if (custom.disabled) {
        delete servers[id]  // Disable built-in server
        continue
      }
      // Override built-in server's command, extensions, env, initialization, etc.
      if (servers[id]) {
        servers[id] = mergeServerConfig(servers[id], custom)
      } else {
        servers[id] = createCustomServer(id, custom)
      }
    }
  }

  // 3. Global disable check
  if (cfg.lsp === false) {
    return { clients: [], servers: {}, broken: new Set(), spawning: new Map() }
  }

  return { clients: [], servers, broken: new Set(), spawning: new Map() }
})

Effect.addFinalizerクリーンアップ

// index.ts — lifecycle cleanup (simplified)
Effect.addFinalizer(async () => {
  const state = await instanceState()
  for (const client of state.clients) {
    await LSPClient.shutdown(client)
  }
  state.clients = []
  state.broken.clear()
  state.spawning.clear()
})

Instance(プロジェクト)が閉じられると、すべてのLSPクライアント接続が切断され、子プロセスが終了されます。

LSPClient.shutdown — 接続シャットダウン

// client.ts — shutdown flow (simplified)
async function shutdown(client: LSPClient.Info): Promise<void> {
  try {
    // 1. Send shutdown request (graceful shutdown)
    await withTimeout(
      client.connection.sendRequest("shutdown"),
      5000,
    )
    // 2. Send exit notification
    client.connection.sendNotification("exit")
  } catch {
    // Timeout or error → force kill process
  } finally {
    // 3. Close connection
    client.connection.dispose()
    // 4. Kill child process
    client.process.kill()
  }
}

他のモジュールとの関係

  • Agent:Agentはファイル編集後にLSP.touchFile()を呼び出し、LSP.diagnostics()を使用して最新の診断を取得して、コード修正が必要かどうかを判断します。hover()definition()はAgentがシンボルセマンティクスを理解するのに役立ちます
  • Session / SessionPromptSessionPromptはユーザーメッセージを構築する際にLSP.documentSymbol()LSP.workspaceSymbol()を呼び出して、LLMにコード構造の概要を提供します
  • Bus:LSPはBus.publish(Event.Diagnostics)BusEvent.define("lsp.client.diagnostics")経由で診断状態の変化を通知します。上位層モジュール(Agent、UI)はこれらのイベントを購読します
  • ConfigConfig.lspは有効化されたサーバー、カスタムコマンド、環境変数を定義します;cfg.lsp === falseでLSPモジュール全体をグローバルに無効にできます
  • Instance:LSP状態はInstance.state()を介して管理され、プロジェクトインスタンスライフサイクルに従います。Instance.directoryNearestRootの検索境界です
  • Global/FlagGlobal.Path.binは自動ダウンロードされたLanguage Serverバイナリを保存します;Flag.OPENCODE_DISABLE_LSP_DOWNLOADは自動ダウンロードを許可するかを制御します
  • Processlaunch.tsspawn()関数はProcess.spawn()をラップし、子プロセス起動のためのエラー処理と環境変数注入を統一します