Skip to content

Commit 65b901c

Browse files
committed
Moving more functions to pydantic
1 parent 66adf58 commit 65b901c

File tree

4 files changed

+119
-20
lines changed

4 files changed

+119
-20
lines changed

pylspclient/lsp_client.py

+31-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
1+
from typing import Optional
2+
13
from pydantic import ValidationError
24
from pylspclient import lsp_structs
3-
from pylspclient.lsp_pydantic_strcuts import TextDocumentItem, TextDocumentIdentifier, DocumentSymbol, SymbolInformation
5+
from pylspclient.lsp_endpoint import LspEndpoint
6+
from pylspclient.lsp_pydantic_strcuts import TextDocumentItem, TextDocumentIdentifier, DocumentSymbol, SymbolInformation, LocationLink, Location
7+
from pylspclient.lsp_pydantic_strcuts import Position
48

59
class LspClient(object):
6-
def __init__(self, lsp_endpoint):
10+
def __init__(self, lsp_endpoint: LspEndpoint):
711
"""
812
Constructs a new LspClient instance.
913
10-
:param lsp_endpoint: TODO
14+
:param lsp_endpoint:
1115
"""
1216
self.lsp_endpoint = lsp_endpoint
1317

@@ -85,7 +89,7 @@ def didOpen(self, textDocument: TextDocumentItem):
8589
8690
:param TextDocumentItem textDocument: The document that was opened.
8791
"""
88-
return self.lsp_endpoint.send_notification("textDocument/didOpen", textDocument=textDocument.dict())
92+
return self.lsp_endpoint.send_notification("textDocument/didOpen", textDocument=textDocument)
8993

9094

9195
def didChange(self, textDocument, contentChanges):
@@ -98,7 +102,7 @@ def didChange(self, textDocument, contentChanges):
98102
to the document. So if there are two content changes c1 and c2 for a document in state S then c1 move the document
99103
to S' and c2 to S''.
100104
"""
101-
return self.lsp_endpoint.send_notification("textDocument/didChange", textDocument=textDocument, contentChanges=contentChanges)
105+
self.lsp_endpoint.send_notification("textDocument/didChange", textDocument=textDocument, contentChanges=contentChanges)
102106

103107

104108
def documentSymbol(self, textDocument: TextDocumentIdentifier) -> list[DocumentSymbol] | list[SymbolInformation]:
@@ -115,15 +119,19 @@ def documentSymbol(self, textDocument: TextDocumentIdentifier) -> list[DocumentS
115119
return [SymbolInformation.parse_obj(sym) for sym in result_dict]
116120

117121

118-
def typeDefinition(self, textDocument, position):
122+
def typeDefinition(
123+
self,
124+
textDocument: TextDocumentIdentifier,
125+
position: Position
126+
) -> list[Location]:
119127
"""
120128
The goto type definition request is sent from the client to the server to resolve the type definition location of a symbol at a given text document position.
121129
122130
:param TextDocumentItem textDocument: The text document.
123131
:param Position position: The position inside the text document.
124132
"""
125133
result_dict = self.lsp_endpoint.call_method("textDocument/typeDefinition", textDocument=textDocument, position=position)
126-
return [lsp_structs.Location(**result) for result in result_dict]
134+
return [Location.parse_obj(result) for result in result_dict]
127135

128136

129137
def signatureHelp(self, textDocument, position):
@@ -153,7 +161,11 @@ def completion(self, textDocument, position, context):
153161
return [lsp_structs.CompletionItem(**result) for result in result_dict]
154162

155163

156-
def declaration(self, textDocument, position):
164+
def declaration(
165+
self,
166+
textDocument: TextDocumentIdentifier,
167+
position: Position
168+
) -> Location | list[Location] | list[LocationLink]:
157169
"""
158170
The go to declaration request is sent from the client to the server to resolve the declaration location of a
159171
symbol at a given text document position.
@@ -166,12 +178,18 @@ def declaration(self, textDocument, position):
166178
"""
167179
result_dict = self.lsp_endpoint.call_method("textDocument/declaration", textDocument=textDocument, position=position)
168180
if "uri" in result_dict:
169-
return lsp_structs.Location(**result_dict)
181+
return Location.parse_obj(result_dict)
170182

171-
return [lsp_structs.Location(**result) if "uri" in result else lsp_structs.LocationLink(**result) for result in result_dict]
183+
return [Location.parse_obj(result) if "uri" in result else LocationLink.parse_obj(result) for result in result_dict]
172184

173185

174-
def definition(self, textDocument, position):
186+
def definition(
187+
self,
188+
textDocument: TextDocumentIdentifier,
189+
position: Position,
190+
workDoneToken: Optional[str] = None,
191+
partialResultToken: Optional[str] = None
192+
) -> Location | list[Location] | list[LocationLink]:
175193
"""
176194
The go to definition request is sent from the client to the server to resolve the declaration location of a
177195
symbol at a given text document position.
@@ -184,6 +202,6 @@ def definition(self, textDocument, position):
184202
"""
185203
result_dict = self.lsp_endpoint.call_method("textDocument/definition", textDocument=textDocument, position=position)
186204
if "uri" in result_dict:
187-
return lsp_structs.Location(**result_dict)
205+
return Location.parse_obj(result_dict)
188206

189-
return [lsp_structs.Location(**result) if "uri" in result else lsp_structs.LocationLink(**result) for result in result_dict]
207+
return [Location.parse_obj(result) if "uri" in result else LocationLink.parse_obj(result) for result in result_dict]

pylspclient/lsp_pydantic_strcuts.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional, List
22
from enum import Enum, IntEnum
3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, HttpUrl
44

55

66
class LanguageIdentifier(str, Enum):
@@ -146,3 +146,30 @@ class SymbolInformation(BaseModel):
146146
deprecated: Optional[bool] = None
147147
location: Location
148148
containerName: Optional[str] = None
149+
150+
class TextDocumentPositionParams(BaseModel):
151+
"""
152+
A base class including the text document identifier and a position within that document.
153+
"""
154+
textDocument: TextDocumentIdentifier
155+
position: Position
156+
157+
class ReferenceContext(BaseModel):
158+
"""
159+
Additional information about the context of a reference request.
160+
"""
161+
includeDeclaration: bool # Whether to include the declaration of the symbol being referenced
162+
163+
class ReferenceParams(TextDocumentPositionParams):
164+
"""
165+
Parameters for a Reference Request in the Language Server Protocol.
166+
"""
167+
context: ReferenceContext
168+
workDoneToken: Optional[str] = None # Optional; used for progress reporting
169+
partialResultToken: Optional[str] = None # Optional; used for partial results
170+
171+
class LocationLink(BaseModel):
172+
originSelectionRange: Optional[Range]
173+
targetUri: HttpUrl
174+
targetRange: Range
175+
targetSelectionRange: Range

tests/test-workspace/lsp_client.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from pylspclient import lsp_structs
2+
from pylspclient.lsp_endpoint import LspEndpoint
23

34
class LspClient(object):
4-
def __init__(self, lsp_endpoint):
5+
def __init__(self, lsp_endpoint: LspEndpoint):
56
"""
67
Constructs a new LspClient instance.
78

tests/test_pylsp_integration.py

+58-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1-
import os.path
1+
from typing import Optional
2+
from os import path, listdir
23
import pytest
34
import subprocess
45
import threading
56

67
import pylspclient
7-
from pylspclient.lsp_pydantic_strcuts import TextDocumentIdentifier, TextDocumentItem, LanguageIdentifier
8+
from pylspclient.lsp_pydantic_strcuts import TextDocumentIdentifier, TextDocumentItem, LanguageIdentifier, Position, Range
89

910

1011
def to_uri(path: str) -> str:
12+
if path.startswith("uri://"):
13+
return path
1114
return f"uri://{path}"
1215

1316

17+
def from_uri(path: str) -> str:
18+
return path.replace("uri://", "").replace("uri:", "")
19+
20+
1421
class ReadPipe(threading.Thread):
1522
def __init__(self, pipe):
1623
threading.Thread.__init__(self)
@@ -43,7 +50,7 @@ def server_process() -> subprocess.Popen:
4350
}
4451
}
4552
}
46-
DEFAULT_ROOT = "./tests/test-workspace/"
53+
DEFAULT_ROOT = path.abspath("./tests/test-workspace/")
4754

4855

4956
@pytest.fixture
@@ -89,9 +96,9 @@ def test_initialize(json_rpc: pylspclient.JsonRpcEndpoint):
8996
lsp_client.exit()
9097

9198

92-
def test_type_definition(lsp_client: pylspclient.LspClient):
99+
def test_definition(lsp_client: pylspclient.LspClient):
93100
file_path = "lsp_client.py"
94-
relative_file_path = os.path.join(DEFAULT_ROOT, file_path)
101+
relative_file_path = path.join(DEFAULT_ROOT, file_path)
95102
uri = to_uri(relative_file_path)
96103
text = open(relative_file_path, "r").read()
97104
languageId = LanguageIdentifier.PYTHON
@@ -123,3 +130,49 @@ def test_type_definition(lsp_client: pylspclient.LspClient):
123130
'documentSymbol'
124131
]
125132
assert set(symbol.name for symbol in symbols) == set(expected_symbols)
133+
134+
135+
def add_dir(lsp_client: pylspclient.LspClient, root: str):
136+
for filename in listdir(root):
137+
if filename.endswith(".py"):
138+
add_file(lsp_client, path.join(root, filename))
139+
140+
141+
def add_file(lsp_client: pylspclient.LspClient, relative_file_path: str):
142+
uri = to_uri(relative_file_path)
143+
text = open(relative_file_path, "r").read()
144+
languageId = LanguageIdentifier.PYTHON
145+
version = 1
146+
# First need to open the file, and then iterate over the docuemnt's symbols
147+
lsp_client.didOpen(TextDocumentItem(uri=uri, languageId=languageId, version=version, text=text))
148+
149+
150+
def string_in_text_to_position(text: str, string: str) -> Optional[Position]:
151+
for i, line in enumerate(text.splitlines()):
152+
char = line.find(string)
153+
if char != -1:
154+
return Position(line=i, character=char)
155+
return None
156+
157+
158+
def range_in_text_to_string(text: str, range_: Range) -> Optional[str]:
159+
lines = text.splitlines()
160+
if range_.start.line == range_.end.line:
161+
# Same line
162+
return lines[range_.start.line][range_.start.character:range_.end.character]
163+
raise NotImplementedError
164+
165+
166+
def test_type_definition(lsp_client: pylspclient.LspClient):
167+
add_dir(lsp_client, DEFAULT_ROOT)
168+
file_path = "lsp_client.py"
169+
relative_file_path = path.join(DEFAULT_ROOT, file_path)
170+
uri = to_uri(relative_file_path)
171+
file_content = open(relative_file_path, "r").read()
172+
position = string_in_text_to_position(file_content, "send_notification")
173+
definitions = lsp_client.definition(TextDocumentIdentifier(uri=uri), position)
174+
assert len(definitions) == 1
175+
result_path = from_uri(definitions[0].uri)
176+
result_file_content = open(result_path, "r").read()
177+
result_definition = range_in_text_to_string(result_file_content, definitions[0].range)
178+
assert result_definition == "send_notification"

0 commit comments

Comments
 (0)