-
Notifications
You must be signed in to change notification settings - Fork 82
feat: add perl language support #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
britterm-cisco
wants to merge
1
commit into
microsoft:main
Choose a base branch
from
britterm-cisco:perl-language-server-updates
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
243 changes: 243 additions & 0 deletions
243
src/multilspy/language_servers/perl_language_server/perl_language_server.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
""" | ||
Provides Perl specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Perl. | ||
""" | ||
|
||
import json | ||
import logging | ||
import os | ||
import pathlib | ||
from contextlib import asynccontextmanager | ||
from typing import AsyncIterator | ||
|
||
from multilspy.multilspy_logger import MultilspyLogger | ||
from multilspy.language_server import LanguageServer | ||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo | ||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams | ||
from multilspy.multilspy_config import MultilspyConfig | ||
from multilspy.multilspy_exceptions import MultilspyException | ||
|
||
|
||
class PerlLanguageServer(LanguageServer): | ||
""" | ||
Provides Perl specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Perl. | ||
""" | ||
|
||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str): | ||
""" | ||
Creates a PerlLanguageServer instance. This class is not meant to be instantiated directly. | ||
Use LanguageServer.create() instead. | ||
""" | ||
try: | ||
import subprocess | ||
cmd = None | ||
|
||
# Perl::LanguageServer | ||
if not cmd: | ||
perl_cmd = "perl -e 'eval \"use Coro; use AnyEvent; use AnyEvent::AIO; use Perl::LanguageServer;\"; print $@ ? 0 : 1'" | ||
result = subprocess.run(perl_cmd, shell=True, capture_output=True, text=True) | ||
if result.stdout.strip() == "1": | ||
logger.log("Using Perl::LanguageServer", logging.INFO) | ||
cmd = "perl -MPerl::LanguageServer -e 'Perl::LanguageServer::run(0, 1, 1, 1, 1, 1)'" | ||
|
||
if not cmd: | ||
logger.log("No Perl Language Server found. Please install:", logging.ERROR) | ||
logger.log("Perl::LanguageServer: cpanm Coro AnyEvent AnyEvent::AIO Perl::LanguageServer", logging.ERROR) | ||
raise MultilspyException("No Perl Language Server installed") | ||
|
||
except Exception as e: | ||
logger.log(f"Error checking Perl Language Servers: {str(e)}", logging.ERROR) | ||
raise MultilspyException(f"Error checking Perl Language Servers: {str(e)}") | ||
|
||
super().__init__( | ||
config, | ||
logger, | ||
repository_root_path, | ||
ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), | ||
"perl", | ||
) | ||
|
||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams: | ||
""" | ||
Returns the initialize params for the Perl Language Server. | ||
""" | ||
# Create a basic initialize params structure | ||
# This can be expanded with more specific settings if needed | ||
params = { | ||
"processId": os.getpid(), | ||
"rootPath": repository_absolute_path, | ||
"rootUri": pathlib.Path(repository_absolute_path).as_uri(), | ||
"capabilities": { | ||
"workspace": { | ||
"applyEdit": True, | ||
"workspaceEdit": { | ||
"documentChanges": True | ||
}, | ||
"didChangeConfiguration": { | ||
"dynamicRegistration": True | ||
}, | ||
"didChangeWatchedFiles": { | ||
"dynamicRegistration": True | ||
}, | ||
"symbol": { | ||
"dynamicRegistration": True | ||
}, | ||
"executeCommand": { | ||
"dynamicRegistration": True | ||
} | ||
}, | ||
"textDocument": { | ||
"synchronization": { | ||
"dynamicRegistration": True, | ||
"willSave": True, | ||
"willSaveWaitUntil": True, | ||
"didSave": True | ||
}, | ||
"completion": { | ||
"dynamicRegistration": True, | ||
"completionItem": { | ||
"snippetSupport": True, | ||
"commitCharactersSupport": True, | ||
"documentationFormat": ["markdown", "plaintext"], | ||
"deprecatedSupport": True, | ||
"preselectSupport": True | ||
}, | ||
"contextSupport": True | ||
}, | ||
"hover": { | ||
"dynamicRegistration": True, | ||
"contentFormat": ["markdown", "plaintext"] | ||
}, | ||
"signatureHelp": { | ||
"dynamicRegistration": True, | ||
"signatureInformation": { | ||
"documentationFormat": ["markdown", "plaintext"], | ||
"parameterInformation": { | ||
"labelOffsetSupport": True | ||
} | ||
} | ||
}, | ||
"definition": { | ||
"dynamicRegistration": True | ||
}, | ||
"references": { | ||
"dynamicRegistration": True | ||
}, | ||
"documentHighlight": { | ||
"dynamicRegistration": True | ||
}, | ||
"documentSymbol": { | ||
"dynamicRegistration": True, | ||
"symbolKind": { | ||
"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 | ||
] | ||
}, | ||
"hierarchicalDocumentSymbolSupport": True | ||
}, | ||
"codeAction": { | ||
"dynamicRegistration": True, | ||
"codeActionLiteralSupport": { | ||
"codeActionKind": { | ||
"valueSet": [ | ||
"", "quickfix", "refactor", "refactor.extract", "refactor.inline", "refactor.rewrite", | ||
"source", "source.organizeImports" | ||
] | ||
} | ||
} | ||
}, | ||
"codeLens": { | ||
"dynamicRegistration": True | ||
}, | ||
"formatting": { | ||
"dynamicRegistration": True | ||
}, | ||
"rangeFormatting": { | ||
"dynamicRegistration": True | ||
}, | ||
"onTypeFormatting": { | ||
"dynamicRegistration": True | ||
}, | ||
"rename": { | ||
"dynamicRegistration": True | ||
}, | ||
"publishDiagnostics": { | ||
"relatedInformation": True, | ||
"tagSupport": { | ||
"valueSet": [1, 2] | ||
} | ||
} | ||
} | ||
}, | ||
"workspaceFolders": [ | ||
{ | ||
"uri": pathlib.Path(repository_absolute_path).as_uri(), | ||
"name": os.path.basename(repository_absolute_path) | ||
} | ||
] | ||
} | ||
|
||
return params | ||
|
||
@asynccontextmanager | ||
async def start_server(self) -> AsyncIterator["PerlLanguageServer"]: | ||
""" | ||
Starts the Perl Language Server, waits for the server to be ready and yields the LanguageServer instance. | ||
|
||
Usage: | ||
``` | ||
async with lsp.start_server(): | ||
# LanguageServer has been initialized and ready to serve requests | ||
await lsp.request_definition(...) | ||
await lsp.request_references(...) | ||
# Shutdown the LanguageServer on exit from scope | ||
# LanguageServer has been shutdown | ||
``` | ||
""" | ||
|
||
async def execute_client_command_handler(params): | ||
return [] | ||
|
||
async def do_nothing(params): | ||
return | ||
|
||
async def check_experimental_status(params): | ||
if params.get("quiescent", False) == True: | ||
self.completions_available.set() | ||
|
||
async def window_log_message(msg): | ||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO) | ||
|
||
self.server.on_request("client/registerCapability", do_nothing) | ||
self.server.on_notification("language/status", do_nothing) | ||
self.server.on_notification("window/logMessage", window_log_message) | ||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler) | ||
self.server.on_notification("$/progress", do_nothing) | ||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing) | ||
self.server.on_notification("language/actionableNotification", do_nothing) | ||
self.server.on_notification("experimental/serverStatus", check_experimental_status) | ||
|
||
async with super().start_server(): | ||
self.logger.log("Starting Perl Language Server process", logging.INFO) | ||
await self.server.start() | ||
initialize_params = self._get_initialize_params(self.repository_root_path) | ||
|
||
self.logger.log( | ||
"Sending initialize request from LSP client to LSP server and awaiting response", | ||
logging.INFO, | ||
) | ||
init_response = await self.server.send.initialize(initialize_params) | ||
|
||
# Check for expected capabilities | ||
# These may need to be adjusted based on the actual capabilities of the Perl Language Server | ||
if "textDocumentSync" in init_response["capabilities"]: | ||
self.logger.log(f"textDocumentSync: {init_response['capabilities']['textDocumentSync']}", logging.INFO) | ||
|
||
if "completionProvider" in init_response["capabilities"]: | ||
self.logger.log(f"completionProvider: {init_response['capabilities']['completionProvider']}", logging.INFO) | ||
|
||
self.server.notify.initialized({}) | ||
|
||
yield self | ||
|
||
await self.server.shutdown() | ||
await self.server.stop() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
""" | ||
This file contains tests for running the Perl Language Server: Perl::LanguageServer | ||
""" | ||
|
||
import pytest | ||
from multilspy import LanguageServer | ||
from multilspy.multilspy_config import Language | ||
from tests.test_utils import create_test_context | ||
from pathlib import PurePath | ||
|
||
pytest_plugins = ("pytest_asyncio",) | ||
|
||
@pytest.mark.asyncio | ||
async def test_multilspy_perl_dancer2(): | ||
""" | ||
Test the working of multilspy with perl repository - Dancer2 | ||
""" | ||
code_language = Language.PERL | ||
params = { | ||
"code_language": code_language, | ||
"repo_url": "https://github.com/PerlDancer/Dancer2/", | ||
"repo_commit": "9f0f5e0b9b0a9c8e8e8e8e8e8e8e8e8e8e8e8e8e" # Replace with an actual commit hash | ||
} | ||
with create_test_context(params) as context: | ||
lsp = LanguageServer.create(context.config, context.logger, context.source_directory) | ||
|
||
# All the communication with the language server must be performed inside the context manager | ||
# The server process is started when the context manager is entered and is terminated when the context manager is exited. | ||
# The context manager is an asynchronous context manager, so it must be used with async with. | ||
async with lsp.start_server(): | ||
# Test request_definition | ||
result = await lsp.request_definition(str(PurePath("lib/Dancer2.pm")), 10, 4) | ||
|
||
assert isinstance(result, list) | ||
# The exact assertions will depend on the actual response from the Perl Language Server | ||
# These are placeholder assertions that should be updated with actual expected values | ||
assert len(result) >= 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please add actual expected values instead of placeholders? |
||
|
||
# Test request_references | ||
result = await lsp.request_references(str(PurePath("lib/Dancer2.pm")), 10, 4) | ||
|
||
assert isinstance(result, list) | ||
# The exact assertions will depend on the actual response from the Perl Language Server | ||
# These are placeholder assertions that should be updated with actual expected values | ||
assert len(result) >= 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
""" | ||
This file contains tests for running the Perl Language Server: Perl::LanguageServer in synchronous mode | ||
""" | ||
|
||
import pytest | ||
from multilspy import SyncLanguageServer | ||
from multilspy.multilspy_config import Language | ||
from tests.test_utils import create_test_context | ||
from pathlib import PurePath | ||
|
||
def test_sync_multilspy_perl_dancer2(): | ||
""" | ||
Test the working of multilspy with perl repository - Dancer2 in synchronous mode | ||
""" | ||
code_language = Language.PERL | ||
params = { | ||
"code_language": code_language, | ||
"repo_url": "https://github.com/PerlDancer/Dancer2/", | ||
"repo_commit": "9f0f5e0b9b0a9c8e8e8e8e8e8e8e8e8e8e8e8e8e" # Replace with an actual commit hash | ||
} | ||
with create_test_context(params) as context: | ||
lsp = SyncLanguageServer.create(context.config, context.logger, context.source_directory) | ||
|
||
# All the communication with the language server must be performed inside the context manager | ||
# The server process is started when the context manager is entered and is terminated when the context manager is exited. | ||
with lsp.start_server(): | ||
# Test request_definition | ||
result = lsp.request_definition(str(PurePath("lib/Dancer2.pm")), 10, 4) | ||
|
||
assert isinstance(result, list) | ||
# The exact assertions will depend on the actual response from the Perl Language Server | ||
# These are placeholder assertions that should be updated with actual expected values | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||
assert len(result) >= 0 | ||
|
||
# Test request_references | ||
result = lsp.request_references(str(PurePath("lib/Dancer2.pm")), 10, 4) | ||
|
||
assert isinstance(result, list) | ||
# The exact assertions will depend on the actual response from the Perl Language Server | ||
# These are placeholder assertions that should be updated with actual expected values | ||
assert len(result) >= 0 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you write this configuration to a separate json file in the same directory, and read from there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See other language server implementations for example