Minor simplification of sourcekit_lsp.py, added sleeping at startup

This commit is contained in:
MischaPanch authored and Michael Panchenko committed 2025-08-07 22:06:35 +02:00
1 parent 5236ffe2b6
commit 6d28c7d722
2 files changed
+96 -313

No files matched your search

+94 -305
View File
@@ -3,17 +3,16 @@ import os
import pathlib import pathlib
import subprocess import subprocess
import threading import threading
import time
from overrides import override from overrides import override
from solidlsp.ls import SolidLanguageServer from solidlsp.ls import SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig from solidlsp.ls_config import LanguageServerConfig
from solidlsp.ls_logger import LanguageServerLogger from solidlsp.ls_logger import LanguageServerLogger
from solidlsp.lsp_protocol_handler import lsp_types
from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams from solidlsp.lsp_protocol_handler.lsp_types import InitializeParams
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings from solidlsp.settings import SolidLSPSettings
from solidlsp.lsp_protocol_handler import lsp_types as LSPTypes
class SourceKitLSP(SolidLanguageServer): class SourceKitLSP(SolidLanguageServer):
@@ -31,53 +30,25 @@ class SourceKitLSP(SolidLanguageServer):
return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"] return super().is_ignored_dirname(dirname) or dirname in [".build", ".swiftpm", "node_modules", "dist", "build"]
@staticmethod @staticmethod
def _get_swift_version(): def _get_sourcekit_lsp_version() -> str:
"""Get the installed Swift version or None if not found.""" """Get the installed sourcekit-lsp version or raise error if sourcekit was not found."""
try:
result = subprocess.run(["swift", "--version"], capture_output=True, text=True, check=False)
if result.returncode == 0:
return result.stdout.strip()
except FileNotFoundError:
return None
return None
@staticmethod
def _get_sourcekit_lsp_version():
"""Get the installed sourcekit-lsp version or None if not found."""
try: try:
result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False) result = subprocess.run(["sourcekit-lsp", "-h"], capture_output=True, text=True, check=False)
if result.returncode == 0: if result.returncode == 0:
return result.stdout.strip() return result.stdout.strip()
except FileNotFoundError: else:
return None raise Exception(f"`sourcekit-lsp -h` resulted in: {result}")
return None except Exception as e:
@staticmethod
def _setup_runtime_dependency():
"""
Check if required Swift runtime dependencies are available.
Raises RuntimeError with helpful message if dependencies are missing.
"""
swift_version = SourceKitLSP._get_swift_version()
if not swift_version:
raise RuntimeError( raise RuntimeError(
"Swift is not installed. Please install Swift from https://swift.org/download/ and make sure it is added to your PATH." "Could not find sourcekit-lsp, please install it as described in https://github.com/apple/sourcekit-lsp#installation"
) "And make sure it is available on your PATH."
) from e
sourcekit_lsp_version = SourceKitLSP._get_sourcekit_lsp_version()
if not sourcekit_lsp_version:
raise RuntimeError(
"Found a Swift version but sourcekit-lsp is not installed.\n"
"Please install sourcekit-lsp as described in https://github.com/apple/sourcekit-lsp#installation\n\n"
"After installation, make sure it is added to your PATH."
)
return True
def __init__( def __init__(
self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings self, config: LanguageServerConfig, logger: LanguageServerLogger, repository_root_path: str, solidlsp_settings: SolidLSPSettings
): ):
self._setup_runtime_dependency() sourcekit_version = self._get_sourcekit_lsp_version()
logger.log(f"Starting sourcekit lsp with version: {sourcekit_version}", logging.INFO)
super().__init__( super().__init__(
config, config,
@@ -100,36 +71,21 @@ class SourceKitLSP(SolidLanguageServer):
initialize_params = { initialize_params = {
"capabilities": { "capabilities": {
"general": { "general": {
"markdown": { "markdown": {"parser": "marked", "version": "1.1.0"},
"parser": "marked", "positionEncodings": ["utf-16"],
"version": "1.1.0" "regularExpressions": {"engine": "ECMAScript", "version": "ES2020"},
},
"positionEncodings": [
"utf-16"
],
"regularExpressions": {
"engine": "ECMAScript",
"version": "ES2020"
},
"staleRequestSupport": { "staleRequestSupport": {
"cancel": True, "cancel": True,
"retryOnContentModified": [ "retryOnContentModified": [
"textDocument/semanticTokens/full", "textDocument/semanticTokens/full",
"textDocument/semanticTokens/range", "textDocument/semanticTokens/range",
"textDocument/semanticTokens/full/delta" "textDocument/semanticTokens/full/delta",
] ],
}
},
"notebookDocument": {
"synchronization": {
"dynamicRegistration": True,
"executionSummarySupport": True
}
},
"textDocument": {
"callHierarchy": {
"dynamicRegistration": True
}, },
},
"notebookDocument": {"synchronization": {"dynamicRegistration": True, "executionSummarySupport": True}},
"textDocument": {
"callHierarchy": {"dynamicRegistration": True},
"codeAction": { "codeAction": {
"codeActionLiteralSupport": { "codeActionLiteralSupport": {
"codeActionKind": { "codeActionKind": {
@@ -141,7 +97,7 @@ class SourceKitLSP(SolidLanguageServer):
"refactor.inline", "refactor.inline",
"refactor.rewrite", "refactor.rewrite",
"source", "source",
"source.organizeImports" "source.organizeImports",
] ]
} }
}, },
@@ -150,193 +106,85 @@ class SourceKitLSP(SolidLanguageServer):
"dynamicRegistration": True, "dynamicRegistration": True,
"honorsChangeAnnotations": True, "honorsChangeAnnotations": True,
"isPreferredSupport": True, "isPreferredSupport": True,
"resolveSupport": { "resolveSupport": {"properties": ["edit"]},
"properties": [
"edit"
]
}
},
"codeLens": {
"dynamicRegistration": True
},
"colorProvider": {
"dynamicRegistration": True
}, },
"codeLens": {"dynamicRegistration": True},
"colorProvider": {"dynamicRegistration": True},
"completion": { "completion": {
"completionItem": { "completionItem": {
"commitCharactersSupport": True, "commitCharactersSupport": True,
"deprecatedSupport": True, "deprecatedSupport": True,
"documentationFormat": [ "documentationFormat": ["markdown", "plaintext"],
"markdown",
"plaintext"
],
"insertReplaceSupport": True, "insertReplaceSupport": True,
"insertTextModeSupport": { "insertTextModeSupport": {"valueSet": [1, 2]},
"valueSet": [
1,
2
]
},
"labelDetailsSupport": True, "labelDetailsSupport": True,
"preselectSupport": True, "preselectSupport": True,
"resolveSupport": { "resolveSupport": {"properties": ["documentation", "detail", "additionalTextEdits"]},
"properties": [
"documentation",
"detail",
"additionalTextEdits"
]
},
"snippetSupport": True, "snippetSupport": True,
"tagSupport": { "tagSupport": {"valueSet": [1]},
"valueSet": [
1
]
}
}, },
"completionItemKind": { "completionItemKind": {
"valueSet": [ "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25
]
},
"completionList": {
"itemDefaults": [
"commitCharacters",
"editRange",
"insertTextFormat",
"insertTextMode",
"data"
]
}, },
"completionList": {"itemDefaults": ["commitCharacters", "editRange", "insertTextFormat", "insertTextMode", "data"]},
"contextSupport": True, "contextSupport": True,
"dynamicRegistration": True, "dynamicRegistration": True,
"insertTextMode": 2 "insertTextMode": 2,
},
"declaration": {
"dynamicRegistration": True,
"linkSupport": True
},
"definition": {
"dynamicRegistration": True,
"linkSupport": True
},
"diagnostic": {
"dynamicRegistration": True,
"relatedDocumentSupport": False
},
"documentHighlight": {
"dynamicRegistration": True
},
"documentLink": {
"dynamicRegistration": True,
"tooltipSupport": True
}, },
"declaration": {"dynamicRegistration": True, "linkSupport": True},
"definition": {"dynamicRegistration": True, "linkSupport": True},
"diagnostic": {"dynamicRegistration": True, "relatedDocumentSupport": False},
"documentHighlight": {"dynamicRegistration": True},
"documentLink": {"dynamicRegistration": True, "tooltipSupport": True},
"documentSymbol": { "documentSymbol": {
"dynamicRegistration": True, "dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True, "hierarchicalDocumentSymbolSupport": True,
"labelSupport": True, "labelSupport": True,
"symbolKind": { "symbolKind": {
"valueSet": [ "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
]
}, },
"tagSupport": { "tagSupport": {"valueSet": [1]},
"valueSet": [
1
]
}
}, },
"foldingRange": { "foldingRange": {
"dynamicRegistration": True, "dynamicRegistration": True,
"foldingRange": { "foldingRange": {"collapsedText": False},
"collapsedText": False "foldingRangeKind": {"valueSet": ["comment", "imports", "region"]},
},
"foldingRangeKind": {
"valueSet": [
"comment",
"imports",
"region"
]
},
"lineFoldingOnly": True, "lineFoldingOnly": True,
"rangeLimit": 5000 "rangeLimit": 5000,
},
"formatting": {
"dynamicRegistration": True
},
"hover": {
"contentFormat": [
"markdown",
"plaintext"
],
"dynamicRegistration": True
},
"implementation": {
"dynamicRegistration": True,
"linkSupport": True
}, },
"formatting": {"dynamicRegistration": True},
"hover": {"contentFormat": ["markdown", "plaintext"], "dynamicRegistration": True},
"implementation": {"dynamicRegistration": True, "linkSupport": True},
"inlayHint": { "inlayHint": {
"dynamicRegistration": True, "dynamicRegistration": True,
"resolveSupport": { "resolveSupport": {"properties": ["tooltip", "textEdits", "label.tooltip", "label.location", "label.command"]},
"properties": [
"tooltip",
"textEdits",
"label.tooltip",
"label.location",
"label.command"
]
}
},
"inlineValue": {
"dynamicRegistration": True
},
"linkedEditingRange": {
"dynamicRegistration": True
},
"onTypeFormatting": {
"dynamicRegistration": True
}, },
"inlineValue": {"dynamicRegistration": True},
"linkedEditingRange": {"dynamicRegistration": True},
"onTypeFormatting": {"dynamicRegistration": True},
"publishDiagnostics": { "publishDiagnostics": {
"codeDescriptionSupport": True, "codeDescriptionSupport": True,
"dataSupport": True, "dataSupport": True,
"relatedInformation": True, "relatedInformation": True,
"tagSupport": { "tagSupport": {"valueSet": [1, 2]},
"valueSet": [ "versionSupport": False,
1,
2
]
},
"versionSupport": False
},
"rangeFormatting": {
"dynamicRegistration": True,
"rangesSupport": True
},
"references": {
"dynamicRegistration": True
}, },
"rangeFormatting": {"dynamicRegistration": True, "rangesSupport": True},
"references": {"dynamicRegistration": True},
"rename": { "rename": {
"dynamicRegistration": True, "dynamicRegistration": True,
"honorsChangeAnnotations": True, "honorsChangeAnnotations": True,
"prepareSupport": True, "prepareSupport": True,
"prepareSupportDefaultBehavior": 1 "prepareSupportDefaultBehavior": 1,
},
"selectionRange": {
"dynamicRegistration": True
}, },
"selectionRange": {"dynamicRegistration": True},
"semanticTokens": { "semanticTokens": {
"augmentsSyntaxTokens": True, "augmentsSyntaxTokens": True,
"dynamicRegistration": True, "dynamicRegistration": True,
"formats": [ "formats": ["relative"],
"relative"
],
"multilineTokenSupport": False, "multilineTokenSupport": False,
"overlappingTokenSupport": False, "overlappingTokenSupport": False,
"requests": { "requests": {"full": {"delta": True}, "range": True},
"full": {
"delta": True
},
"range": True
},
"serverCancelSupport": True, "serverCancelSupport": True,
"tokenModifiers": [ "tokenModifiers": [
"declaration", "declaration",
@@ -348,7 +196,7 @@ class SourceKitLSP(SolidLanguageServer):
"async", "async",
"modification", "modification",
"documentation", "documentation",
"defaultLibrary" "defaultLibrary",
], ],
"tokenTypes": [ "tokenTypes": [
"namespace", "namespace",
@@ -373,67 +221,35 @@ class SourceKitLSP(SolidLanguageServer):
"number", "number",
"regexp", "regexp",
"operator", "operator",
"decorator" "decorator",
] ],
}, },
"signatureHelp": { "signatureHelp": {
"contextSupport": True, "contextSupport": True,
"dynamicRegistration": True, "dynamicRegistration": True,
"signatureInformation": { "signatureInformation": {
"activeParameterSupport": True, "activeParameterSupport": True,
"documentationFormat": [ "documentationFormat": ["markdown", "plaintext"],
"markdown", "parameterInformation": {"labelOffsetSupport": True},
"plaintext" },
],
"parameterInformation": {
"labelOffsetSupport": True
}
}
}, },
"synchronization": { "synchronization": {"didSave": True, "dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True},
"didSave": True, "typeDefinition": {"dynamicRegistration": True, "linkSupport": True},
"dynamicRegistration": True, "typeHierarchy": {"dynamicRegistration": True},
"willSave": True,
"willSaveWaitUntil": True
},
"typeDefinition": {
"dynamicRegistration": True,
"linkSupport": True
},
"typeHierarchy": {
"dynamicRegistration": True
}
}, },
"window": { "window": {
"showDocument": { "showDocument": {"support": True},
"support": True "showMessage": {"messageActionItem": {"additionalPropertiesSupport": True}},
}, "workDoneProgress": True,
"showMessage": {
"messageActionItem": {
"additionalPropertiesSupport": True
}
},
"workDoneProgress": True
}, },
"workspace": { "workspace": {
"applyEdit": True, "applyEdit": True,
"codeLens": { "codeLens": {"refreshSupport": True},
"refreshSupport": True
},
"configuration": True, "configuration": True,
"diagnostics": { "diagnostics": {"refreshSupport": True},
"refreshSupport": True "didChangeConfiguration": {"dynamicRegistration": True},
}, "didChangeWatchedFiles": {"dynamicRegistration": True, "relativePatternSupport": True},
"didChangeConfiguration": { "executeCommand": {"dynamicRegistration": True},
"dynamicRegistration": True
},
"didChangeWatchedFiles": {
"dynamicRegistration": True,
"relativePatternSupport": True
},
"executeCommand": {
"dynamicRegistration": True
},
"fileOperations": { "fileOperations": {
"didCreate": True, "didCreate": True,
"didDelete": True, "didDelete": True,
@@ -441,70 +257,38 @@ class SourceKitLSP(SolidLanguageServer):
"dynamicRegistration": True, "dynamicRegistration": True,
"willCreate": True, "willCreate": True,
"willDelete": True, "willDelete": True,
"willRename": True "willRename": True,
},
"foldingRange": {
"refreshSupport": True
},
"inlayHint": {
"refreshSupport": True
},
"inlineValue": {
"refreshSupport": True
},
"semanticTokens": {
"refreshSupport": False
}, },
"foldingRange": {"refreshSupport": True},
"inlayHint": {"refreshSupport": True},
"inlineValue": {"refreshSupport": True},
"semanticTokens": {"refreshSupport": False},
"symbol": { "symbol": {
"dynamicRegistration": True, "dynamicRegistration": True,
"resolveSupport": { "resolveSupport": {"properties": ["location.range"]},
"properties": [
"location.range"
]
},
"symbolKind": { "symbolKind": {
"valueSet": [ "valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
]
}, },
"tagSupport": { "tagSupport": {"valueSet": [1]},
"valueSet": [
1
]
}
}, },
"workspaceEdit": { "workspaceEdit": {
"changeAnnotationSupport": { "changeAnnotationSupport": {"groupsOnLabel": True},
"groupsOnLabel": True
},
"documentChanges": True, "documentChanges": True,
"failureHandling": "textOnlyTransactional", "failureHandling": "textOnlyTransactional",
"normalizesLineEndings": True, "normalizesLineEndings": True,
"resourceOperations": [ "resourceOperations": ["create", "rename", "delete"],
"create",
"rename",
"delete"
]
}, },
"workspaceFolders": True "workspaceFolders": True,
} },
},
"clientInfo": {
"name": "Visual Studio Code",
"version": "1.102.2"
}, },
"clientInfo": {"name": "Visual Studio Code", "version": "1.102.2"},
"initializationOptions": { "initializationOptions": {
"backgroundIndexing": True, "backgroundIndexing": True,
"backgroundPreparationMode": "enabled", "backgroundPreparationMode": "enabled",
"textDocument/codeLens": { "textDocument/codeLens": {"supportedCommands": {"swift.debug": "swift.debug", "swift.run": "swift.run"}},
"supportedCommands": {
"swift.debug": "swift.debug",
"swift.run": "swift.run"
}
},
"window/didChangeActiveDocument": True, "window/didChangeActiveDocument": True,
"workspace/getReferenceDocument": True, "workspace/getReferenceDocument": True,
"workspace/peekDocuments": True "workspace/peekDocuments": True,
}, },
"locale": "en", "locale": "en",
"processId": os.getpid(), "processId": os.getpid(),
@@ -556,6 +340,11 @@ class SourceKitLSP(SolidLanguageServer):
self.server.notify.initialized({}) self.server.notify.initialized({})
self.completions_available.set() self.completions_available.set()
# sourcekit-lsp server is typically ready immediately after initialization
self.server_ready.set() self.server_ready.set()
self.server_ready.wait() self.server_ready.wait()
# SourceKit LSP needs a short initialization period after startup
# before it can provide accurate reference information. This sleep
# prevents race conditions where references might not be available yet.
# Unfortunately, sourcekit doesn't send a signal when it's really ready
time.sleep(5)
+2 -8
View File
@@ -6,8 +6,6 @@ like request_references using the Swift test repository.
""" """
import os import os
import sys
import time
import platform import platform
import pytest import pytest
@@ -17,15 +15,16 @@ from serena.text_utils import LineType
from solidlsp import SolidLanguageServer from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language from solidlsp.ls_config import Language
# Skip Swift tests on Windows due to complex GitHub Actions configuration # Skip Swift tests on Windows due to complex GitHub Actions configuration
WINDOWS_SKIP = platform.system() == "Windows" WINDOWS_SKIP = platform.system() == "Windows"
WINDOWS_SKIP_REASON = "GitHub Actions configuration for Swift on Windows is complex, skipping for now." WINDOWS_SKIP_REASON = "GitHub Actions configuration for Swift on Windows is complex, skipping for now."
pytestmark = [pytest.mark.swift, pytest.mark.skipif(WINDOWS_SKIP, reason=WINDOWS_SKIP_REASON)] pytestmark = [pytest.mark.swift, pytest.mark.skipif(WINDOWS_SKIP, reason=WINDOWS_SKIP_REASON)]
class TestSwiftLanguageServerBasics: class TestSwiftLanguageServerBasics:
"""Test basic functionality of the Swift language server.""" """Test basic functionality of the Swift language server."""
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True) @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_goto_definition_calculator_class(self, language_server: SolidLanguageServer) -> None: def test_goto_definition_calculator_class(self, language_server: SolidLanguageServer) -> None:
"""Test goto_definition on Calculator class usage.""" """Test goto_definition on Calculator class usage."""
@@ -102,10 +101,6 @@ class TestSwiftLanguageServerBasics:
@pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True) @pytest.mark.parametrize("language_server", [Language.SWIFT], indirect=True)
def test_request_references_calculator_class(self, language_server: SolidLanguageServer) -> None: def test_request_references_calculator_class(self, language_server: SolidLanguageServer) -> None:
# SourceKit LSP needs a short initialization period after startup
# before it can provide accurate reference information. This sleep
# prevents race conditions where references might not be available yet.
time.sleep(5)
"""Test request_references on the Calculator class.""" """Test request_references on the Calculator class."""
# Get references to the Calculator class in main.swift # Get references to the Calculator class in main.swift
file_path = os.path.join("src", "main.swift") file_path = os.path.join("src", "main.swift")
@@ -170,7 +165,6 @@ class TestSwiftLanguageServerBasics:
assert len(line_12_refs) > 0, "Utils should be referenced at line 12" assert len(line_12_refs) > 0, "Utils should be referenced at line 12"
class TestSwiftProjectBasics: class TestSwiftProjectBasics:
@pytest.mark.parametrize("project", [Language.SWIFT], indirect=True) @pytest.mark.parametrize("project", [Language.SWIFT], indirect=True)
def test_retrieve_content_around_line(self, project: Project) -> None: def test_retrieve_content_around_line(self, project: Project) -> None: