codex-lens: ship default LSP config and fix entrypoint

This commit is contained in:
catlog22
2026-03-05 14:27:16 +08:00
parent 1fb49c0e39
commit 6341ed43e1
5 changed files with 221 additions and 16 deletions

View File

@@ -14,6 +14,7 @@ authors = [
]
dependencies = [
"typer~=0.9.0",
"click>=8.0.0,<9",
"rich~=13.0.0",
"pydantic~=2.0.0",
"tree-sitter~=0.20.0",
@@ -31,7 +32,7 @@ dependencies = [
# Semantic search using fastembed (ONNX-based, lightweight ~200MB)
semantic = [
"numpy~=1.26.0",
"fastembed~=0.2.0",
"fastembed~=0.2.1",
"hnswlib~=0.8.0",
]
@@ -39,7 +40,7 @@ semantic = [
# Install with: pip install codexlens[semantic-gpu]
semantic-gpu = [
"numpy~=1.26.0",
"fastembed~=0.2.0",
"fastembed~=0.2.1",
"hnswlib~=0.8.0",
"onnxruntime-gpu~=1.15.0", # CUDA support
]
@@ -48,7 +49,7 @@ semantic-gpu = [
# Install with: pip install codexlens[semantic-directml]
semantic-directml = [
"numpy~=1.26.0",
"fastembed~=0.2.0",
"fastembed~=0.2.1",
"hnswlib~=0.8.0",
"onnxruntime-directml~=1.15.0", # DirectML support
]
@@ -105,10 +106,13 @@ lsp = [
]
[project.scripts]
codexlens-lsp = "codexlens.lsp:main"
codexlens-lsp = "codexlens.lsp.server:main"
[project.urls]
Homepage = "https://github.com/openai/codex-lens"
[tool.setuptools]
package-dir = { "" = "src" }
[tool.setuptools.package-data]
"codexlens.lsp" = ["lsp-servers.json"]

View File

@@ -0,0 +1,88 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"version": "1.0.0",
"description": "Default language server configuration for codex-lens standalone LSP client",
"servers": [
{
"languageId": "python",
"displayName": "Pyright",
"extensions": ["py", "pyi"],
"command": ["pyright-langserver", "--stdio"],
"enabled": true,
"initializationOptions": {
"pythonPath": "",
"pythonPlatform": "",
"pythonVersion": "3.13"
},
"settings": {
"python.analysis": {
"typeCheckingMode": "standard",
"diagnosticMode": "workspace",
"exclude": ["**/node_modules", "**/__pycache__", "build", "dist"],
"include": ["src/**", "tests/**"],
"stubPath": "typings"
}
}
},
{
"languageId": "typescript",
"displayName": "TypeScript Language Server",
"extensions": ["ts", "tsx"],
"command": ["typescript-language-server", "--stdio"],
"enabled": true,
"initializationOptions": {},
"settings": {}
},
{
"languageId": "javascript",
"displayName": "TypeScript Language Server (for JS)",
"extensions": ["js", "jsx", "mjs", "cjs"],
"command": ["typescript-language-server", "--stdio"],
"enabled": true,
"initializationOptions": {},
"settings": {}
},
{
"languageId": "go",
"displayName": "Gopls",
"extensions": ["go"],
"command": ["gopls", "serve"],
"enabled": true,
"initializationOptions": {},
"settings": {}
},
{
"languageId": "rust",
"displayName": "Rust Analyzer",
"extensions": ["rs"],
"command": ["rust-analyzer"],
"enabled": false,
"initializationOptions": {},
"settings": {}
},
{
"languageId": "c",
"displayName": "Clangd",
"extensions": ["c", "h"],
"command": ["clangd"],
"enabled": false,
"initializationOptions": {},
"settings": {}
},
{
"languageId": "cpp",
"displayName": "Clangd",
"extensions": ["cpp", "hpp", "cc", "cxx"],
"command": ["clangd"],
"enabled": false,
"initializationOptions": {},
"settings": {}
}
],
"defaults": {
"rootDir": ".",
"timeout": 30000,
"restartInterval": 5000,
"maxRestarts": 3
}
}

View File

@@ -14,6 +14,7 @@ Features:
from __future__ import annotations
import asyncio
import importlib.resources as resources
import json
import logging
import os
@@ -117,7 +118,6 @@ class StandaloneLspManager:
1. Explicit config_file parameter
2. {workspace_root}/lsp-servers.json
3. {workspace_root}/.codexlens/lsp-servers.json
4. Package default (codexlens/lsp-servers.json)
"""
search_paths = []
@@ -127,7 +127,6 @@ class StandaloneLspManager:
search_paths.extend([
self.workspace_root / self.DEFAULT_CONFIG_FILE,
self.workspace_root / ".codexlens" / self.DEFAULT_CONFIG_FILE,
Path(__file__).parent.parent.parent.parent / self.DEFAULT_CONFIG_FILE, # package root
])
for path in search_paths:
@@ -135,21 +134,73 @@ class StandaloneLspManager:
return path
return None
def _load_builtin_config(self) -> Optional[dict[str, Any]]:
"""Load the built-in default lsp-servers.json shipped with the package."""
try:
text = (
resources.files("codexlens.lsp")
.joinpath(self.DEFAULT_CONFIG_FILE)
.read_text(encoding="utf-8")
)
except Exception as exc:
logger.error(
"Failed to load built-in %s template from package: %s",
self.DEFAULT_CONFIG_FILE,
exc,
)
return None
try:
return json.loads(text)
except Exception as exc:
logger.error(
"Built-in %s template shipped with the package is invalid JSON: %s",
self.DEFAULT_CONFIG_FILE,
exc,
)
return None
def _load_config(self) -> None:
"""Load language server configuration from JSON file."""
self._configs.clear()
self._extension_map.clear()
config_path = self._find_config_file()
if not config_path:
logger.warning(f"No {self.DEFAULT_CONFIG_FILE} found, using empty config")
return
try:
with open(config_path, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
logger.error(f"Failed to load config from {config_path}: {e}")
return
data = self._load_builtin_config()
if data is None:
logger.warning(
"No %s found and built-in defaults could not be loaded; using empty config",
self.DEFAULT_CONFIG_FILE,
)
return
root_config_path = self.workspace_root / self.DEFAULT_CONFIG_FILE
codexlens_config_path = (
self.workspace_root / ".codexlens" / self.DEFAULT_CONFIG_FILE
)
logger.info(
"No %s found at %s or %s; using built-in defaults shipped with codex-lens. "
"To customize, copy the template to one of those locations and restart. "
"Language servers are spawned on-demand when first needed. "
"Ensure the language server commands in the config are installed and on PATH.",
self.DEFAULT_CONFIG_FILE,
root_config_path,
codexlens_config_path,
)
config_source = "built-in defaults"
else:
try:
with open(config_path, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
logger.error(f"Failed to load config from {config_path}: {e}")
return
config_source = str(config_path)
# Parse defaults
defaults = data.get("defaults", {})
@@ -186,7 +237,11 @@ class StandaloneLspManager:
for ext in config.extensions:
self._extension_map[ext.lower()] = language_id
logger.info(f"Loaded {len(self._configs)} language server configs from {config_path}")
logger.info(
"Loaded %d language server configs from %s",
len(self._configs),
config_source,
)
def get_language_id(self, file_path: str) -> Optional[str]:
"""Get language ID for a file based on extension.

View File

@@ -0,0 +1,27 @@
"""Packaging metadata tests for codex-lens (LSP/semantic extras)."""
from __future__ import annotations
from pathlib import Path
def _read_pyproject() -> str:
repo_root = Path(__file__).resolve().parents[2]
return (repo_root / "pyproject.toml").read_text(encoding="utf-8")
def test_lsp_script_entrypoint_points_to_server_main() -> None:
pyproject = _read_pyproject()
assert 'codexlens-lsp = "codexlens.lsp.server:main"' in pyproject
def test_semantic_extras_do_not_pin_yanked_fastembed_020() -> None:
pyproject = _read_pyproject()
assert "fastembed~=0.2.0" not in pyproject
assert "fastembed~=0.2.1" in pyproject
def test_click_dependency_is_explicitly_guarded() -> None:
pyproject = _read_pyproject()
assert "click>=8.0.0,<9" in pyproject

View File

@@ -0,0 +1,31 @@
"""Tests for StandaloneLspManager default config behavior."""
from __future__ import annotations
import asyncio
import logging
from pathlib import Path
import pytest
from codexlens.lsp.standalone_manager import StandaloneLspManager
def test_loads_builtin_defaults_when_no_config_found(
tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
manager = StandaloneLspManager(workspace_root=str(tmp_path))
with caplog.at_level(logging.INFO):
asyncio.run(manager.start())
assert manager._configs # type: ignore[attr-defined]
assert manager.get_language_id(str(tmp_path / "example.py")) == "python"
expected_root = str(tmp_path / "lsp-servers.json")
expected_codexlens = str(tmp_path / ".codexlens" / "lsp-servers.json")
assert "using built-in defaults" in caplog.text.lower()
assert expected_root in caplog.text
assert expected_codexlens in caplog.text