Coverage for src/debputy/lsp/quickfixes.py: 42%
63 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
1from typing import (
2 Literal,
3 TypedDict,
4 Callable,
5 Iterable,
6 Union,
7 TypeVar,
8 Mapping,
9 Dict,
10 Optional,
11 List,
12 cast,
13)
15from lsprotocol.types import (
16 CodeAction,
17 Command,
18 CodeActionParams,
19 Diagnostic,
20 CodeActionDisabledType,
21 TextEdit,
22 WorkspaceEdit,
23 TextDocumentEdit,
24 OptionalVersionedTextDocumentIdentifier,
25 Range,
26 Position,
27 CodeActionKind,
28)
30from debputy.util import _warn
32try:
33 from debian._deb822_repro.locatable import Position as TEPosition, Range as TERange
35 from pygls.server import LanguageServer
36 from pygls.workspace import TextDocument
37except ImportError:
38 pass
41CodeActionName = Literal["correct-text", "remove-line"]
44class CorrectTextCodeAction(TypedDict):
45 code_action: Literal["correct-text"]
46 correct_value: str
49class RemoveLineCodeAction(TypedDict):
50 code_action: Literal["remove-line"]
53def propose_correct_text_quick_fix(correct_value: str) -> CorrectTextCodeAction:
54 return {
55 "code_action": "correct-text",
56 "correct_value": correct_value,
57 }
60def propose_remove_line_quick_fix() -> RemoveLineCodeAction:
61 return {
62 "code_action": "remove-line",
63 }
66CODE_ACTION_HANDLERS: Dict[
67 CodeActionName,
68 Callable[
69 [Mapping[str, str], CodeActionParams, Diagnostic],
70 Iterable[Union[CodeAction, Command]],
71 ],
72] = {}
73M = TypeVar("M", bound=Mapping[str, str])
74Handler = Callable[
75 [M, CodeActionParams, Diagnostic],
76 Iterable[Union[CodeAction, Command]],
77]
80def _code_handler_for(action_name: CodeActionName) -> Callable[[Handler], Handler]:
81 def _wrapper(func: Handler) -> Handler:
82 assert action_name not in CODE_ACTION_HANDLERS
83 CODE_ACTION_HANDLERS[action_name] = func
84 return func
86 return _wrapper
89@_code_handler_for("correct-text")
90def _correct_value_code_action(
91 code_action_data: CorrectTextCodeAction,
92 code_action_params: CodeActionParams,
93 diagnostic: Diagnostic,
94) -> Iterable[Union[CodeAction, Command]]:
95 corrected_value = code_action_data["correct_value"]
96 edits = [
97 TextEdit(
98 diagnostic.range,
99 corrected_value,
100 ),
101 ]
102 yield CodeAction(
103 title=f'Replace with "{corrected_value}"',
104 kind=CodeActionKind.QuickFix,
105 diagnostics=[diagnostic],
106 edit=WorkspaceEdit(
107 changes={code_action_params.text_document.uri: edits},
108 document_changes=[
109 TextDocumentEdit(
110 text_document=OptionalVersionedTextDocumentIdentifier(
111 uri=code_action_params.text_document.uri,
112 ),
113 edits=edits,
114 )
115 ],
116 ),
117 )
120def range_compatible_with_remove_line_fix(range_: Range) -> bool:
121 start = range_.start
122 end = range_.end
123 if start.line != end.line and (start.line + 1 != end.line or end.character > 0):
124 return False
125 return True
128@_code_handler_for("remove-line")
129def _correct_value_code_action(
130 _code_action_data: RemoveLineCodeAction,
131 code_action_params: CodeActionParams,
132 diagnostic: Diagnostic,
133) -> Iterable[Union[CodeAction, Command]]:
134 start = code_action_params.range.start
135 if range_compatible_with_remove_line_fix(code_action_params.range):
136 _warn(
137 "Bug: the quick was used for a diagnostic that spanned multiple lines and would corrupt the file."
138 )
139 return
141 edits = [
142 TextEdit(
143 Range(
144 start=Position(
145 line=start.line,
146 character=0,
147 ),
148 end=Position(
149 line=start.line + 1,
150 character=0,
151 ),
152 ),
153 "",
154 ),
155 ]
156 yield CodeAction(
157 title="Remove the line",
158 kind=CodeActionKind.QuickFix,
159 diagnostics=[diagnostic],
160 edit=WorkspaceEdit(
161 changes={code_action_params.text_document.uri: edits},
162 document_changes=[
163 TextDocumentEdit(
164 text_document=OptionalVersionedTextDocumentIdentifier(
165 uri=code_action_params.text_document.uri,
166 ),
167 edits=edits,
168 )
169 ],
170 ),
171 )
174def provide_standard_quickfixes_from_diagnostics(
175 code_action_params: CodeActionParams,
176) -> Optional[List[Union[Command, CodeAction]]]:
177 actions = []
178 for diagnostic in code_action_params.context.diagnostics:
179 data = diagnostic.data
180 if not isinstance(data, list):
181 data = [data]
182 for action_suggestion in data:
183 if (
184 action_suggestion
185 and isinstance(action_suggestion, Mapping)
186 and "code_action" in action_suggestion
187 ):
188 action_name: CodeActionName = action_suggestion["code_action"]
189 handler = CODE_ACTION_HANDLERS.get(action_name)
190 if handler is not None:
191 actions.extend(
192 handler(
193 cast("Mapping[str, str]", action_suggestion),
194 code_action_params,
195 diagnostic,
196 )
197 )
198 else:
199 _warn(f"No codeAction handler for {action_name} !?")
200 if not actions:
201 return None
202 return actions