Remove custom LSP manager plugins and add new language server clients

- Delete old lsp_manager plugin (custom websocket-based LSP client implementation)
- Delete java_lsp_client plugin
- Delete python_lsp_client plugin
- Remove unused LSP DTO files in src/libs/dto/code/lsp/
- Add new language_server_clients plugin directory
- Improve event_factory with register_events method
- Add PYTHONDONTWRITEBYTECODE to user config
- Update events init.py docstring
This commit is contained in:
2026-03-15 20:45:58 -05:00
parent d1643091c3
commit 080ba41cf1
68 changed files with 595 additions and 108 deletions

View File

@@ -0,0 +1,214 @@
{
"info": "https://download.eclipse.org/jdtls/",
"info-init-options": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line",
"info-import-build": "https://www.javahotchocolate.com/tutorials/build-path.html",
"info-external-class-paths": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3291",
"link": "https://download.eclipse.org/jdtls/milestones/?d",
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
"alt-command": "lsp-ws-proxy -- jdtls",
"alt-command2": "java-language-server",
"socket": "ws://127.0.0.1:9999/java",
"socket-two": "ws://127.0.0.1:9999/?name=jdtls",
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
"initialization-options": {
"bundles": [
"intellicode-core.jar"
],
"workspaceFolders": [
"file://{workspace.folder}"
],
"extendedClientCapabilities": {
"classFileContentsSupport": true,
"executeClientCommandSupport": false
},
"settings": {
"java": {
"autobuild": {
"enabled": true
},
"jdt": {
"ls": {
"javac": {
"enabled": true
},
"java": {
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
},
"lombokSupport": {
"enabled": true
},
"protobufSupport":{
"enabled": true
},
"androidSupport": {
"enabled": true
}
}
},
"configuration": {
"updateBuildConfiguration": "automatic",
"maven": {
"userSettings": "{user.home}/.config/jdtls/settings.xml",
"globalSettings": "{user.home}/.config/jdtls/settings.xml"
},
"runtimes": [
{
"name": "JavaSE-17",
"path": "/usr/lib/jvm/java-17-openjdk",
"javadoc": "https://docs.oracle.com/en/java/javase/17/docs/api/",
"default": false
},
{
"name": "JavaSE-22",
"path": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2",
"javadoc": "https://docs.oracle.com/en/java/javase/22/docs/api/",
"default": true
}
]
},
"classPath": [
"{user.home}/.config/jdtls/m2/repository/**/*-sources.jar",
"lib/**/*-sources.jar"
],
"docPath": [
"{user.home}/.config/jdtls/m2/repository/**/*-javadoc.jar",
"lib/**/*-javadoc.jar"
],
"project": {
"encoding": "ignore",
"outputPath": "bin",
"referencedLibraries": [
"{user.home}/.config/jdtls/m2/repository/**/*.jar",
"lib/**/*.jar"
],
"importOnFirstTimeStartup": "automatic",
"importHint": true,
"resourceFilters": [
"node_modules",
"\\.git"
],
"sourcePaths": [
"src",
"{user.home}/.config/jdtls/m2/repository/**/*.jar"
]
},
"sources": {
"organizeImports": {
"starThreshold": 99,
"staticStarThreshold": 99
}
},
"imports": {
"gradle": {
"wrapper": {
"checksums": []
}
}
},
"import": {
"maven": {
"enabled": true,
"offline": {
"enabled": false
},
"disableTestClasspathFlag": false
},
"gradle": {
"enabled": false,
"wrapper": {
"enabled": true
},
"version": "",
"home": "abs(static/gradle-7.3.3)",
"java": {
"home": "abs(static/launch_jres/17.0.6-linux-x86_64)"
},
"offline": {
"enabled": false
},
"arguments": [],
"jvmArguments": [],
"user": {
"home": ""
},
"annotationProcessing": {
"enabled": true
}
},
"exclusions": [
"**/node_modules/**",
"**/.metadata/**",
"**/archetype-resources/**",
"**/META-INF/maven/**"
],
"generatesMetadataFilesAtProjectRoot": false
},
"maven": {
"downloadSources": true,
"updateSnapshots": true
},
"silentNotification": true,
"contentProvider": {
"preferred": "fernflower"
},
"signatureHelp": {
"enabled": true,
"description": {
"enabled": true
}
},
"completion": {
"enabled": true,
"engine": "ecj",
"matchCase": "firstletter",
"maxResults": 25,
"guessMethodArguments": true,
"lazyResolveTextEdit": {
"enabled": true
},
"postfix": {
"enabled": true
},
"favoriteStaticMembers": [
"org.junit.Assert.*",
"org.junit.Assume.*",
"org.junit.jupiter.api.Assertions.*",
"org.junit.jupiter.api.Assumptions.*",
"org.junit.jupiter.api.DynamicContainer.*",
"org.junit.jupiter.api.DynamicTest.*"
],
"importOrder": [
"#",
"java",
"javax",
"org",
"com"
]
},
"references": {
"includeAccessors": true,
"includeDecompiledSources": true
},
"codeGeneration": {
"toString": {
"template": "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
},
"insertionLocation": "afterCursor",
"useBlocks": true
},
"implementationsCodeLens": {
"enabled": true
},
"referencesCodeLens": {
"enabled": true
},
"progressReports": {
"enabled": false
},
"saveActions": {
"organizeImports": true
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "Java LSP Client",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -0,0 +1,37 @@
# Python imports
from os import path
# Lib imports
import gi
from gi.repository import GLib
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .response_handler import JavaHandler
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
dirPth = path.dirname( path.realpath(__file__) )
with open(f"{dirPth}/config/lsp-server-config.json", "r") as f:
config = f.read()
event = Event_Factory.create_event("register_lsp_client",
lang_id = "java",
lang_config = config,
handler = JavaHandler
)
self.emit_to("lsp_manager", event)
def run(self):
...

View File

@@ -0,0 +1 @@
from .java import JavaHandler

View File

@@ -9,7 +9,7 @@ from gi.repository import GtkSource
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from .default import DefaultHandler
from lsp_manager.response_handlers.default import DefaultHandler

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -8,8 +8,9 @@ import gi
from gi.repository import GLib
# Application imports
from libs.dto.code.lsp.lsp_messages import get_message_str
from libs.dto.code.lsp.lsp_message_structs import LSPResponseTypes, ClientRequest, ClientNotification
from ..dto.code.lsp.lsp_messages import get_message_str
from ..dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification
from .lsp_client_websocket import LSPClientWebsocket

View File

@@ -3,8 +3,9 @@
# Lib imports
# Application imports
from ..dto.code.lsp.lsp_message_structs import ClientRequest, ClientNotification
from .lsp_client_events import LSPClientEvents
from libs.dto.code.lsp.lsp_message_structs import ClientRequest, ClientNotification

View File

@@ -4,15 +4,15 @@ import os
# Lib imports
# Application imports
from libs.dto.code.lsp.lsp_messages import get_message_obj
from libs.dto.code.lsp.lsp_messages import didopen_notification
from libs.dto.code.lsp.lsp_messages import didsave_notification
from libs.dto.code.lsp.lsp_messages import didclose_notification
from libs.dto.code.lsp.lsp_messages import didchange_notification
from libs.dto.code.lsp.lsp_messages import completion_request
from libs.dto.code.lsp.lsp_messages import definition_request
from libs.dto.code.lsp.lsp_messages import references_request
from libs.dto.code.lsp.lsp_messages import symbols_request
from ..dto.code.lsp.lsp_messages import get_message_obj
from ..dto.code.lsp.lsp_messages import didopen_notification
from ..dto.code.lsp.lsp_messages import didsave_notification
from ..dto.code.lsp.lsp_messages import didclose_notification
from ..dto.code.lsp.lsp_messages import didchange_notification
from ..dto.code.lsp.lsp_messages import completion_request
from ..dto.code.lsp.lsp_messages import definition_request
from ..dto.code.lsp.lsp_messages import references_request
from ..dto.code.lsp.lsp_messages import symbols_request

View File

@@ -5,8 +5,8 @@ from gi.repository import GLib
# Application imports
# from libs import websockets
from libs.dto.code.lsp.lsp_messages import get_message_str, get_message_obj
from libs.dto.code.lsp.lsp_message_structs import \
from ..dto.code.lsp.lsp_messages import get_message_str, get_message_obj
from ..dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification, \
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification

View File

@@ -0,0 +1,8 @@
"""
Libs Code DTO(s) Events Package
"""
from .lsp_event import LspEvent
from .register_lsp_client_event import RegisterLspClientEvent
from .unregister_lsp_client_event import UnregisterLspClientEvent

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from libs.dto.code.events import CodeEvent
@dataclass
class LspEvent(CodeEvent):
...

View File

@@ -0,0 +1,17 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from ....response_handlers.base_handler import BaseHandler
from .lsp_event import LspEvent
@dataclass
class RegisterLspClientEvent(LspEvent):
lang_id: str = ""
lang_config: str = "{}"
handler: BaseHandler = None

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from .lsp_event import LspEvent
@dataclass
class UnregisterLspClientEvent(LspEvent):
lang_id: str = ""

View File

@@ -0,0 +1,95 @@
# Python imports
import json
import enum
from dataclasses import dataclass
# Lib imports
# Application imports
from .lsp_structs import TextDocumentItem
class MessageEncoder(json.JSONEncoder):
"""
Encodes an object in JSON
"""
def default(self, o): # pylint: disable=E0202
return o.__dict__
@dataclass
class ClientRequest(object):
def __init__(self, id: int, method: str, params: dict):
"""
Constructs a new Client Request instance.
:param int id: Message id to track instance.
:param str method: The type of lsp request being made.
:param dict params: The arguments of the given method.
"""
self.jsonrpc = "2.0"
self.id = id
self.method = method
self.params = params
@dataclass
class ClientNotification(object):
def __init__(self, method: str, params: dict):
"""
Constructs a new Client Notification instance.
:param str method: The type of lsp notification being made.
:param dict params: The arguments of the given method.
"""
self.jsonrpc = "2.0"
self.method = method
self.params = params
@dataclass
class LSPResponseRequest(object):
"""
Constructs a new LSP Response Request instance.
:param id result: The id of the given message.
:param dict result: The arguments of the given method.
"""
jsonrpc: str
id: int
result: dict
@dataclass
class LSPResponseNotification(object):
"""
Constructs a new LSP Response Notification instance.
:param str method: The type of lsp notification being made.
:params dict result: The arguments of the given method.
"""
jsonrpc: str
method: str
params: dict
@dataclass
class LSPIDResponseNotification(object):
"""
Constructs a new LSP Response Notification instance.
:param str method: The type of lsp notification being made.
:params dict result: The arguments of the given method.
"""
jsonrpc: str
id: int
method: str
params: dict
class MessageTypes(ClientRequest, ClientNotification, LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification):
...
class ClientMessageTypes(ClientRequest, ClientNotification):
...
class LSPResponseTypes(LSPResponseRequest, LSPResponseNotification):
...

View File

@@ -0,0 +1,193 @@
# Python imports
import json
# Lib imports
# Application imports
from .lsp_message_structs import MessageEncoder
LEN_HEADER = "Content-Length: "
TYPE_HEADER = "Content-Type: "
def get_message_str(data: dict) -> str:
return json.dumps(data, separators = (',', ':'), indent = 4, cls = MessageEncoder)
def get_message_obj(data: str):
return json.loads(data)
# Request type formatting
# https://github.com/microsoft/multilspy/blob/main/src/multilspy/language_server.py#L417
content_part = {
"method": "textDocument/definition",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
},
"position": {
"line": 5,
"character": 12,
"offset": 0
}
}
}
didopen_notification = {
"method": "textDocument/didOpen",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
}
}
}
didsave_notification = {
"method": "textDocument/didSave",
"params": {
"textDocument": {
"uri": "file://"
},
"text": ""
}
}
didclose_notification = {
"method": "textDocument/didClose",
"params": {
"textDocument": {
"uri": "file://"
}
}
}
didchange_notification = {
"method": "textDocument/didChange",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
},
"contentChanges": [
{
"text": ""
}
]
}
}
didchange_notification_range = {
"method": "textDocument/didChange",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
},
"contentChanges": [
{
"range": {
"start": {
"line": 1,
"character": 1,
},
"end": {
"line": 1,
"character": 1,
},
"rangeLength": 0
}
}
]
}
}
# CompletionTriggerKind = 1 | 2 | 3;
# export const Invoked: 1 = 1;
# export const TriggerCharacter: 2 = 2;
# export const TriggerForIncompleteCompletions: 3 = 3;
completion_request = {
"method": "textDocument/completion",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
},
"position": {
"line": 5,
"character": 12,
"offset": 0
},
"contet": {
"triggerKind": 3,
"triggerCharacter": ""
}
}
}
definition_request = {
"method": "textDocument/definition",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
},
"position": {
"line": 5,
"character": 12,
"offset": 0
}
}
}
references_request = {
"method": "textDocument/references",
"params": {
"context": {
"includeDeclaration": False
},
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
},
"position": {
"line": 30,
"character": 13,
"offset": 0
}
}
}
symbols_request = {
"method": "textDocument/documentSymbol",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
}
}
}

View File

@@ -0,0 +1,571 @@
# Python imports
import enum
# Lib imports
# Application imports
def to_type(o, new_type):
'''
Helper funciton that receives an object or a dict and convert it to a new
given type.
:param object|dict o: The object to convert
:param Type new_type: The type to convert to.
'''
return o if new_type == type(o) else new_type(**o)
class Position(object):
def __init__(self, line, character):
"""
Constructs a new Position instance.
:param int line: Line position in a document (zero-based).
:param int character: Character offset on a line in a document
(zero-based).
"""
self.line = line
self.character = character
class Range(object):
def __init__(self, start, end):
"""
Constructs a new Range instance.
:param Position start: The range's start position.
:param Position end: The range's end position.
"""
self.start = to_type(start, Position)
self.end = to_type(end, Position)
class Location(object):
"""
Represents a location inside a resource, such as a line inside a text file.
"""
def __init__(self, uri, range):
"""
Constructs a new Location instance.
:param str uri: Resource file.
:param Range range: The range inside the file
"""
self.uri = uri
self.range = to_type(range, Range)
class LocationLink(object):
"""
Represents a link between a source and a target location.
"""
def __init__(self, originSelectionRange, targetUri, targetRange, targetSelectionRange):
"""
Constructs a new LocationLink instance.
:param Range originSelectionRange: Span of the origin of this link.
Used as the underlined span for mouse interaction. Defaults to the word range at the mouse position.
:param str targetUri: The target resource identifier of this link.
:param Range targetRange: The full target range of this link. If the target for example is a symbol then target
range is the range enclosing this symbol not including leading/trailing whitespace but everything else
like comments. This information is typically used to highlight the range in the editor.
:param Range targetSelectionRange: The range that should be selected and revealed when this link is being followed,
e.g the name of a function. Must be contained by the the `targetRange`. See also `DocumentSymbol#range`
"""
self.originSelectionRange = to_type(originSelectionRange, Range)
self.targetUri = targetUri
self.targetRange = to_type(targetRange, Range)
self.targetSelectionRange = to_type(targetSelectionRange, Range)
class Diagnostic(object):
def __init__(self, range, severity, code, source, message, relatedInformation):
"""
Constructs a new Diagnostic instance.
:param Range range: The range at which the message applies.Resource file.
:param int severity: The diagnostic's severity. Can be omitted. If omitted it is up to the
client to interpret diagnostics as error, warning, info or hint.
:param str code: The diagnostic's code, which might appear in the user interface.
:param str source: A human-readable string describing the source of this
diagnostic, e.g. 'typescript' or 'super lint'.
:param str message: The diagnostic's message.
:param list relatedInformation: An array of related diagnostic information, e.g. when symbol-names within
a scope collide all definitions can be marked via this property.
"""
self.range = range
self.severity = severity
self.code = code
self.source = source
self.message = message
self.relatedInformation = relatedInformation
class DiagnosticSeverity(object):
Error = 1
Warning = 2 # TODO: warning is known in python
Information = 3
Hint = 4
class DiagnosticRelatedInformation(object):
def __init__(self, location, message):
"""
Constructs a new Diagnostic instance.
:param Location location: The location of this related diagnostic information.
:param str message: The message of this related diagnostic information.
"""
self.location = location
self.message = message
class Command(object):
def __init__(self, title, command, arguments):
"""
Constructs a new Diagnostic instance.
:param str title: Title of the command, like `save`.
:param str command: The identifier of the actual command handler.
:param list argusments: Arguments that the command handler should be invoked with.
"""
self.title = title
self.command = command
self.arguments = arguments
class TextDocumentItem(object):
"""
An item to transfer a text document from the client to the server.
"""
def __init__(self, uri, languageId, version, text):
"""
Constructs a new Diagnostic instance.
:param DocumentUri uri: uri file path.
:param str languageId: The identifier of the actual command handler.
:param int version: Arguments that the command handler should be invoked with.
:param str text: Arguments that the command handler should be invoked with.
"""
self.uri = uri
self.languageId = languageId
self.version = version
self.text = text
class TextDocumentIdentifier(object):
"""
Text documents are identified using a URI. On the protocol level, URIs are passed as strings.
"""
def __init__(self, uri):
"""
Constructs a new TextDocumentIdentifier instance.
:param DocumentUri uri: The text document's URI.
"""
self.uri = uri
class VersionedTextDocumentIdentifier(TextDocumentIdentifier):
"""
An identifier to denote a specific version of a text document.
"""
def __init__(self, uri, version):
"""
Constructs a new TextDocumentIdentifier instance.
:param DocumentUri uri: The text document's URI.
:param int version: The version number of this document. If a versioned
text document identifier is sent from the server to the client and
the file is not open in the editor (the server has not received an
open notification before) the server can send `null` to indicate
that the version is known and the content on disk is the truth (as
speced with document content ownership).
The version number of a document will increase after each change, including
undo/redo. The number doesn't need to be consecutive.
"""
super(VersionedTextDocumentIdentifier, self).__init__(uri)
self.version = version
class TextDocumentContentChangeEvent(object):
"""
An event describing a change to a text document. If range and rangeLength are omitted
the new text is considered to be the full content of the document.
"""
def __init__(self, range, rangeLength, text):
"""
Constructs a new TextDocumentContentChangeEvent instance.
:param Range range: The range of the document that changed.
:param int rangeLength: The length of the range that got replaced.
:param str text: The new text of the range/document.
"""
self.range = range
self.rangeLength = rangeLength
self.text = text
class TextDocumentPositionParams(object):
"""
A parameter literal used in requests to pass a text document and a position inside that document.
"""
def __init__(self, textDocument, position):
"""
Constructs a new TextDocumentPositionParams instance.
:param TextDocumentIdentifier textDocument: The text document.
:param Position position: The position inside the text document.
"""
self.textDocument = textDocument
self.position = position
class LANGUAGE_IDENTIFIER(object):
BAT = "bat"
BIBTEX = "bibtex"
CLOJURE = "clojure"
COFFESCRIPT = "coffeescript"
C = "c"
CPP = "cpp"
CSHARP = "csharp"
CSS = "css"
DIFF = "diff"
DOCKERFILE = "dockerfile"
FSHARP = "fsharp"
GIT_COMMIT = "git-commit"
GIT_REBASE = "git-rebase"
GO = "go"
GROOVY = "groovy"
HANDLEBARS = "handlebars"
HTML = "html"
INI = "ini"
JAVA = "java"
JAVASCRIPT = "javascript"
JSON = "json"
LATEX = "latex"
LESS = "less"
LUA = "lua"
MAKEFILE = "makefile"
MARKDOWN = "markdown"
OBJECTIVE_C = "objective-c"
OBJECTIVE_CPP = "objective-cpp"
Perl = "perl"
PHP = "php"
POWERSHELL = "powershell"
PUG = "jade"
PYTHON = "python"
R = "r"
RAZOR = "razor"
RUBY = "ruby"
RUST = "rust"
SASS = "sass"
SCSS = "scss"
ShaderLab = "shaderlab"
SHELL_SCRIPT = "shellscript"
SQL = "sql"
SWIFT = "swift"
TYPE_SCRIPT = "typescript"
TEX = "tex"
VB = "vb"
XML = "xml"
XSL = "xsl"
YAML = "yaml"
class SymbolKind(enum.Enum):
File = 1
Module = 2
Namespace = 3
Package = 4
Class = 5
Method = 6
Property = 7
Field = 8
Constructor = 9
Enum = 10
Interface = 11
Function = 12
Variable = 13
Constant = 14
String = 15
Number = 16
Boolean = 17
Array = 18
Object = 19
Key = 20
Null = 21
EnumMember = 22
Struct = 23
Event = 24
Operator = 25
TypeParameter = 26
class SymbolInformation(object):
"""
Represents information about programming constructs like variables, classes, interfaces etc.
"""
def __init__(self, name, kind, location, containerName = None, deprecated = False):
"""
Constructs a new SymbolInformation instance.
:param str name: The name of this symbol.
:param int kind: The kind of this symbol.
:param bool Location: The location of this symbol. The location's range is used by a tool
to reveal the location in the editor. If the symbol is selected in the
tool the range's start information is used to position the cursor. So
the range usually spans more then the actual symbol's name and does
normally include things like visibility modifiers.
The range doesn't have to denote a node range in the sense of a abstract
syntax tree. It can therefore not be used to re-construct a hierarchy of
the symbols.
:param str containerName: The name of the symbol containing this symbol. This information is for
user interface purposes (e.g. to render a qualifier in the user interface
if necessary). It can't be used to re-infer a hierarchy for the document
symbols.
:param bool deprecated: Indicates if this symbol is deprecated.
"""
self.name = name
self.kind = SymbolKind(kind)
self.deprecated = deprecated
self.location = to_type(location, Location)
self.containerName = containerName
class ParameterInformation(object):
"""
Represents a parameter of a callable-signature. A parameter can
have a label and a doc-comment.
"""
def __init__(self, label, documentation = ""):
"""
Constructs a new ParameterInformation instance.
:param str label: The label of this parameter. Will be shown in the UI.
:param str documentation: The human-readable doc-comment of this parameter. Will be shown in the UI but can be omitted.
"""
self.label = label
self.documentation = documentation
class SignatureInformation(object):
"""
Represents the signature of something callable. A signature
can have a label, like a function-name, a doc-comment, and
a set of parameters.
"""
def __init__(self, label, documentation = "", parameters = []):
"""
Constructs a new SignatureInformation instance.
:param str label: The label of this signature. Will be shown in the UI.
:param str documentation: The human-readable doc-comment of this signature. Will be shown in the UI but can be omitted.
:param ParameterInformation[] parameters: The parameters of this signature.
"""
self.label = label
self.documentation = documentation
self.parameters = [to_type(parameter, ParameterInformation) for parameter in parameters]
class SignatureHelp(object):
"""
Signature help represents the signature of something
callable. There can be multiple signature but only one
active and only one active parameter.
"""
def __init__(self, signatures, activeSignature = 0, activeParameter = 0):
"""
Constructs a new SignatureHelp instance.
:param SignatureInformation[] signatures: One or more signatures.
:param int activeSignature:
:param int activeParameter:
"""
self.signatures = [to_type(signature, SignatureInformation) for signature in signatures]
self.activeSignature = activeSignature
self.activeParameter = activeParameter
class CompletionTriggerKind(object):
Invoked = 1
TriggerCharacter = 2
TriggerForIncompleteCompletions = 3
class CompletionContext(object):
"""
Contains additional information about the context in which a completion request is triggered.
"""
def __init__(self, triggerKind, triggerCharacter = None):
"""
Constructs a new CompletionContext instance.
:param CompletionTriggerKind triggerKind: How the completion was triggered.
:param str triggerCharacter: The trigger character (a single character) that has trigger code complete.
Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
"""
self.triggerKind = triggerKind
if triggerCharacter:
self.triggerCharacter = triggerCharacter
class TextEdit(object):
"""
A textual edit applicable to a text document.
"""
def __init__(self, range, newText):
"""
:param Range range: The range of the text document to be manipulated. To insert
text into a document create a range where start === end.
:param str newText: The string to be inserted. For delete operations use an empty string.
"""
self.range = range
self.newText = newText
class InsertTextFormat(object):
PlainText = 1
Snippet = 2
class CompletionItem(object):
"""
"""
def __init__(self, label, \
kind = None, \
detail = None, \
documentation = None, \
deprecated = None, \
preselect = None, \
sortText = None, \
filterText = None, \
insertText = None, \
insertTextFormat = None, \
textEdit = None, \
additionalTextEdits = None, \
commitCharacters = None, \
command = None, \
data = None, \
score = 0.0
):
"""
:param str label: The label of this completion item. By default also the text that is inserted when selecting
this completion.
:param int kind: The kind of this completion item. Based of the kind an icon is chosen by the editor.
:param str detail: A human-readable string with additional information about this item, like type or symbol information.
:param tr ocumentation: A human-readable string that represents a doc-comment.
:param bool deprecated: Indicates if this item is deprecated.
:param bool preselect: Select this item when showing. Note: that only one completion item can be selected and that the
tool / client decides which item that is. The rule is that the first item of those that match best is selected.
:param str sortText: A string that should be used when comparing this item with other items. When `falsy` the label is used.
:param str filterText: A string that should be used when filtering a set of completion items. When `falsy` the label is used.
:param str insertText: A string that should be inserted into a document when selecting this completion. When `falsy` the label is used.
The `insertText` is subject to interpretation by the client side. Some tools might not take the string literally. For example
VS Code when code complete is requested in this example `con<cursor position>` and a completion item with an `insertText` of `console` is provided it
will only insert `sole`. Therefore it is recommended to use `textEdit` instead since it avoids additional client side interpretation.
@deprecated Use textEdit instead.
:param InsertTextFormat insertTextFormat: The format of the insert text. The format applies to both the `insertText` property
and the `newText` property of a provided `textEdit`.
:param TextEdit textEdit: An edit which is applied to a document when selecting this completion. When an edit is provided the value of `insertText` is ignored.
Note:* The range of the edit must be a single line range and it must contain the position at which completion
has been requested.
:param TextEdit additionalTextEdits: An optional array of additional text edits that are applied when selecting this completion.
Edits must not overlap (including the same insert position) with the main edit nor with themselves.
Additional text edits should be used to change text unrelated to the current cursor position
(for example adding an import statement at the top of the file if the completion item will
insert an unqualified type).
:param str commitCharacters: An optional set of characters that when pressed while this completion is active will accept it first and
then type that character. *Note* that all commit characters should have `length=1` and that superfluous
characters will be ignored.
:param Command command: An optional command that is executed *after* inserting this completion. Note: that
additional modifications to the current document should be described with the additionalTextEdits-property.
:param data: An data entry field that is preserved on a completion item between a completion and a completion resolve request.
:param float score: Score of the code completion item.
"""
self.label = label
self.kind = kind
self.detail = detail
self.documentation = documentation
self.deprecated = deprecated
self.preselect = preselect
self.sortText = sortText
self.filterText = filterText
self.insertText = insertText
self.insertTextFormat = insertTextFormat
self.textEdit = textEdit
self.additionalTextEdits = additionalTextEdits
self.commitCharacters = commitCharacters
self.command = command
self.data = data
self.score = score
class CompletionItemKind(enum.Enum):
Text = 1
Method = 2
Function = 3
Constructor = 4
Field = 5
Variable = 6
Class = 7
Interface = 8
Module = 9
Property = 10
Unit = 11
Value = 12
Enum = 13
Keyword = 14
Snippet = 15
Color = 16
File = 17
Reference = 18
Folder = 19
EnumMember = 20
Constant = 21
Struct = 22
Event = 23
Operator = 24
TypeParameter = 25
class CompletionList(object):
"""
Represents a collection of [completion items](#CompletionItem) to be preselect in the editor.
"""
def __init__(self, isIncomplete, items):
"""
Constructs a new CompletionContext instance.
:param bool isIncomplete: This list it not complete. Further typing should result in recomputing this list.
:param CompletionItem items: The completion items.
"""
self.isIncomplete = isIncomplete
self.items = [to_type(i, CompletionItem) for i in items]
class ErrorCodes(enum.Enum):
# Defined by JSON RPC
ParseError = -32700
InvalidRequest = -32600
MethodNotFound = -32601
InvalidParams = -32602
InternalError = -32603
serverErrorStart = -32099
serverErrorEnd = -32000
ServerNotInitialized = -32002
UnknownErrorCode = -32001
# Defined by the protocol.
RequestCancelled = -32800
ContentModified = -32801
class ResponseError(Exception):
def __init__(self, code, message, data = None):
self.code = code
self.message = message
if data:
self.data = data

View File

@@ -3,7 +3,13 @@
# Lib imports
# Application imports
from libs.dto.code.lsp.lsp_message_structs import LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Code_Event_Types
from .dto.code.events import \
RegisterLspClientEvent, UnregisterLspClientEvent
from .dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
from .provider import Provider
from .provider_response_cache import ProviderResponseCache
@@ -13,7 +19,7 @@ from .response_handlers.response_registry import ResponseRegistry
class LSPManager:
class LSPManager(ControllerBase):
def __init__(self):
super(LSPManager, self).__init__()
@@ -37,6 +43,20 @@ class LSPManager:
self.response_cache.set_lsp_client(self.lsp_manager_client)
self.provider.response_cache = self.response_cache
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, RegisterLspClientEvent):
self.response_registry.register_handler(event.lang_id, event.handler)
self.lsp_manager_ui.add_client_listing(event.lang_id, event.lang_config)
elif isinstance(event, UnregisterLspClientEvent):
self.response_registry.unregister_handler(event.lang_id)
# if isinstance(event, Code_Event_Types.RegisterLspClientEvent):
# self.response_registry.register_handler(event.lang_id, event.handler)
# self.lsp_manager_ui.add_client_listing(event.lang_id, event.lang_config)
# elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent):
# self.response_registry.unregister_handler(event.lang_id)
def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool:
init_opts = ui.get_init_opts(lang_id)
result = self.create_client(lang_id, workspace_uri, init_opts)

View File

@@ -1,5 +1,4 @@
# Python imports
from os import path
import json
# Lib imports
@@ -25,12 +24,8 @@ class LSPManagerUI(Gtk.Dialog):
def __init__(self):
super(LSPManagerUI, self).__init__()
self._SCRIPT_PTH: str = path.dirname( path.realpath(__file__) )
self._USER_HOME: str = path.expanduser('~')
self._LSP_SERVERS_CONFIG: str = ""
self.servers_config: dict = {}
self.client_configs: dict[str, str] = {}
self.parent = None
self.source_view = None
self._setup_styling()
@@ -69,9 +64,10 @@ class LSPManagerUI(Gtk.Dialog):
self.path_entry.set_can_focus(False)
self.path_entry.set_placeholder_text("Workspace Folder...")
self.path_entry.connect("changed", self._path_changed, bttn_box)
self.path_bttn.set_halign(Gtk.Align.FILL)
self.path_bttn.connect("file-set", self._file_set)
self.path_bttn.set_halign(Gtk.Align.FILL)
self.combo_box.connect("changed", self._on_combo_changed)
self.hide_bttn.connect("clicked", lambda widget: self.hide())
self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn)
self.close_client_bttn.connect("clicked", self._close_client, self.create_client_bttn)
@@ -115,16 +111,56 @@ class LSPManagerUI(Gtk.Dialog):
def _path_changed(self, widget, buttons_widget):
if not widget.get_text():
self.path_bttn.unselect_all()
self.path_bttn.emit("file-set")
buttons_widget.hide()
return
self.set_source_view_text( self.path_entry.get_text() )
buttons_widget.show()
def _file_set(self, widget):
self.path_entry.set_text(
widget.get_filename()
fname = widget.get_filename()
fname = "" if not fname else fname
self.path_entry.set_text(fname)
lang_id = self.combo_box.get_active_text()
if not lang_id or lang_id not in self.client_configs: return
self.set_source_view_text(
"{workspace.folder}" if not fname else fname
)
self.load_lsp_servers_config_placeholders()
def _create_client(self, widget, sibling):
if not self.source_view: return
buffer = self.source_view.get_buffer()
lang_id = self.combo_box.get_active_text()
if not lang_id: return
workspace_dir = self.path_entry.get_text()
self.emit('create-client', lang_id, workspace_dir)
def _close_client(self, widget, sibling):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
self.emit('close-client', lang_id)
def _on_combo_changed(self, combo: Gtk.ComboBoxText):
lang_id = combo.get_active_text()
self.set_source_view_text( self.path_entry.get_text() )
def set_source_view_text(self, workspace_dir: str):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
json_str = self.client_configs[lang_id].replace("{workspace.folder}", workspace_dir)
buffer = self.source_view.get_buffer()
buffer.set_text(json_str, -1)
def map_parent_resize_event(self, parent):
parent.connect("size-allocate", lambda w, r: self._map_resize(self, parent))
@@ -147,68 +183,20 @@ class LSPManagerUI(Gtk.Dialog):
scrolled_win.show_all()
def load_lsp_servers_config(self):
try:
with open(f"{self._SCRIPT_PTH}/configs/lsp-servers-config.json") as file:
self._LSP_SERVERS_CONFIG = file.read()
except FileNotFoundError:
logger.error(f"Config file not found: {self._SCRIPT_PTH}/configs/lsp-servers-config.json")
def load_lsp_servers_config_placeholders(self):
if not self._LSP_SERVERS_CONFIG: return
if not self.source_view: return
data = self._LSP_SERVERS_CONFIG \
.replace("{user.home}", self._USER_HOME) \
.replace("{workspace.folder}", self.path_entry.get_text())
self.servers_config = json.loads(data)
buffer = self.source_view.get_buffer()
start_itr, \
end_itr = buffer.get_bounds()
buffer.delete(start_itr, end_itr)
buffer.insert(start_itr, data, -1)
self.set_language_combo_box( list(self.servers_config.keys()) )
def set_language_combo_box(self, lang_ids: list[str]):
self.combo_box.remove_all()
for lang_id in lang_ids:
self.combo_box.append_text(lang_id)
def add_client_listing(self, lang_id: str, lang_config: str):
self.combo_box.append_text(lang_id)
self.client_configs[lang_id] = lang_config
def get_init_opts(self, lang_id: str) -> dict:
buffer = self.source_view.get_buffer()
if not lang_id or lang_id not in self.client_configs: return {}
try:
self.servers_config = json.loads(
buffer.get_text( *buffer.get_bounds() )
)
lang_config = json.loads(self.client_configs[lang_id])
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON: {e}")
logger.error(f"Invalid JSON for {lang_id}: {e}")
return {}
if not lang_id or not lang_id in self.servers_config: return {}
return self.servers_config[lang_id].get("initialization-options", {})
def _create_client(self, widget, sibling):
if not self.source_view: return
buffer = self.source_view.get_buffer()
lang_id = self.combo_box.get_active_text()
if not lang_id: return
workspace_dir = self.path_entry.get_text()
self.emit('create-client', lang_id, workspace_dir)
def _close_client(self, widget, sibling):
lang_id = self.combo_box.get_active_text()
if not lang_id: return
self.emit('close-client', lang_id)
return lang_config.get("initialization-options", {})
def toggle_client_buttons(self, show_close: bool):
self.create_client_bttn.set_visible(not show_close)

View File

@@ -3,5 +3,6 @@
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"pre_launch": true,
"requests": {}
}

View File

@@ -11,6 +11,7 @@ from libs.dto.states import SourceViewStates
from plugins.plugin_types import PluginCode
from .dto.code import events as lsp_events
from .lsp_manager import LSPManager
@@ -28,6 +29,10 @@ class Plugin(PluginCode):
...
def load(self):
Event_Factory.register_events( lsp_events.__dict__.items() )
self.register_controller("lsp_manager", lsp_manager)
window = self.request_ui_element("main-window")
lsp_manager.lsp_manager_ui.map_parent_resize_event(window)
@@ -55,11 +60,11 @@ class Plugin(PluginCode):
self.emit_to("source_views", event)
source_view = event.response
lsp_manager.lsp_manager_ui.load_lsp_servers_config()
lsp_manager.lsp_manager_ui.set_source_view(source_view)
lsp_manager.lsp_manager_ui.load_lsp_servers_config_placeholders()
lsp_manager.response_registry.set_event_hub(self.emit, self.emit_to, lsp_manager.provider)
lsp_manager.response_registry.set_event_hub(
self.emit, self.emit_to, lsp_manager.provider
)
def run(self):
...

View File

@@ -0,0 +1,3 @@
from .base_handler import BaseHandler
from .default import DefaultHandler
from .response_registry import ResponseRegistry

View File

@@ -8,7 +8,7 @@ from gi.repository import GLib
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from .base import BaseHandler
from .base_handler import BaseHandler

View File

@@ -3,10 +3,8 @@
# Lib imports
# Application imports
from .base import BaseHandler
from .base_handler import BaseHandler
from .default import DefaultHandler
from .python import PythonHandler
from .java import JavaHandler
@@ -15,12 +13,11 @@ class ResponseRegistry:
self._instances: dict = {}
self._lang_handlers: dict = {
"default": DefaultHandler,
"python": PythonHandler,
"java": JavaHandler,
"default": DefaultHandler
}
def set_event_hub(self, emit, emit_to, provider=None):
def set_event_hub(self, emit, emit_to, provider = None):
self.emit = emit
self.emit_to = emit_to
self._provider = provider
@@ -36,6 +33,9 @@ class ResponseRegistry:
def register_handler(self, lang_id: str, handler_cls: type[BaseHandler]):
self._lang_handlers[lang_id] = handler_cls
def unregister_handler(self, lang_id: str, handler_cls: type[BaseHandler]):
del self._lang_handlers[lang_id]
def get_handler(self, lang_id: str = "", method: str = ""):
handler_cls = self._lang_handlers.get(
lang_id, self._lang_handlers.get("default", DefaultHandler)

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,100 @@
{
"info": "https://github.com/python-lsp/python-lsp-server",
"command": "lsp-ws-proxy -- pylsp",
"alt-command": "pylsp",
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
"alt-command3": "pylsp --ws --port 4114",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
"initialization-options": {
"pylsp": {
"rope": {
"ropeFolder": "{user.home}/.config/newton/lsps/ropeproject"
},
"plugins": {
"ruff": {
"enabled": true,
"extendSelect": ["I"],
"lineLength": 80
},
"pycodestyle": {
"enabled": false
},
"pyflakes": {
"enabled": false
},
"pylint": {
"enabled": true
},
"mccabe": {
"enabled": false
},
"pylsp_rope": {
"rename": false
},
"rope_rename": {
"enabled": false
},
"rope_autoimport": {
"enabled": true
},
"rope_completion": {
"enabled": false,
"eager": false
},
"jedi_rename": {
"enabled": true
},
"jedi_completion": {
"enabled": true,
"include_class_objects": true,
"include_function_objects": true,
"fuzzy": false
},
"jedi": {
"root_dir": "file://{workspace.folder}",
"extra_paths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
]
}
}
}
}
},
"python - jedi-language-server": {
"hidden": true,
"info": "https://pypi.org/project/jedi-language-server/",
"command": "jedi-language-server",
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
"initialization-options": {
"jediSettings": {
"autoImportModules": [],
"caseInsensitiveCompletion": true,
"debug": false
},
"completion": {
"disableSnippets": false,
"resolveEagerly": false,
"ignorePatterns": []
},
"markupKindPreferred": "markdown",
"workspace": {
"extraPaths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
],
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
"symbols": {
"ignoreFolders": [
".nox",
".tox",
".venv",
"__pycache__",
"venv"
],
"maxSymbols": 20
}
}
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "Python LSP Client",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -0,0 +1,37 @@
# Python imports
from os import path
# Lib imports
import gi
from gi.repository import GLib
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .response_handler import PythonHandler
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
dirPth = path.dirname( path.realpath(__file__) )
with open(f"{dirPth}/config/lsp-server-config.json", "r") as f:
config = f.read()
event = Event_Factory.create_event("register_lsp_client",
lang_id = "python",
lang_config = config,
handler = PythonHandler
)
self.emit_to("lsp_manager", event)
def run(self):
...

View File

@@ -0,0 +1 @@
from .python import PythonHandler

View File

@@ -3,7 +3,7 @@
# Lib imports
# Application imports
from .default import DefaultHandler
from lsp_manager.response_handlers.default import DefaultHandler

View File

@@ -1,5 +0,0 @@
from .base import BaseHandler
from .default import DefaultHandler
from .python import PythonHandler
from .java import JavaHandler
from .response_registry import ResponseRegistry