Coverage for src/debputy/lsp/text_util.py: 85%
80 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-10-12 15:06 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-10-12 15:06 +0000
1from typing import List, Optional, Union, TYPE_CHECKING
2from collections.abc import Sequence, Iterable
4from debputy.commands.debputy_cmd.output import MAN_URL_REWRITE
5from debputy.lsprotocol.types import (
6 TextEdit,
7 Position,
8 Range,
9 WillSaveTextDocumentParams,
10 DocumentFormattingParams,
11)
13from debputy.linting.lint_util import LinterPositionCodec
15try:
16 from debputy.lsp.vendoring._deb822_repro.locatable import (
17 Position as TEPosition,
18 Range as TERange,
19 )
20 from debputy.lsp.debputy_ls import DebputyLanguageServer
21except ImportError:
22 pass
24try:
25 from pygls.server import LanguageServer
26 from pygls.workspace import TextDocument, PositionCodec
27except ImportError:
28 pass
30if TYPE_CHECKING:
31 LintCapablePositionCodec = Union[LinterPositionCodec, PositionCodec]
32else:
33 LintCapablePositionCodec = LinterPositionCodec
36def markdown_urlify(uri: str) -> str:
37 if uri.startswith("man:"): 37 ↛ 45line 37 didn't jump to line 45 because the condition on line 37 was always true
38 m = MAN_URL_REWRITE.match(uri)
39 if m: 39 ↛ 43line 39 didn't jump to line 43 because the condition on line 39 was always true
40 page, section = m.groups()
41 link_url = f"https://manpages.debian.org/{page}.{section}"
42 return f"[{uri}]({link_url})"
43 return uri
45 return f"<{uri}>"
48def normalize_dctrl_field_name(f: str) -> str:
49 if not f or not f.startswith(("x", "X")):
50 return f
51 i = 0
52 for i in range(1, len(f)): 52 ↛ 58line 52 didn't jump to line 58 because the loop on line 52 didn't complete
53 if f[i] == "-":
54 i += 1
55 break
56 if f[i] not in ("b", "B", "s", "S", "c", "C"): 56 ↛ 57line 56 didn't jump to line 57 because the condition on line 56 was never true
57 return f
58 assert i > 0
59 return f[i:]
62def on_save_trim_end_of_line_whitespace(
63 ls: "LanguageServer",
64 params: WillSaveTextDocumentParams | DocumentFormattingParams,
65) -> Sequence[TextEdit] | None:
66 doc = ls.workspace.get_text_document(params.text_document.uri)
67 return trim_end_of_line_whitespace(doc.position_codec, doc.lines)
70def trim_end_of_line_whitespace(
71 position_codec: "LintCapablePositionCodec",
72 lines: list[str],
73 *,
74 line_range: Iterable[int] | None = None,
75 line_relative_line_no: int = 0,
76) -> Sequence[TextEdit] | None:
77 edits = []
78 if line_range is None:
79 line_range = range(0, len(lines))
80 for line_no in line_range:
81 orig_line = lines[line_no]
82 orig_len = len(orig_line)
83 if orig_line.endswith("\n"): 83 ↛ 85line 83 didn't jump to line 85 because the condition on line 83 was always true
84 orig_len -= 1
85 stripped_len = len(orig_line.rstrip())
86 if stripped_len == orig_len: 86 ↛ 89line 86 didn't jump to line 89 because the condition on line 86 was always true
87 continue
89 stripped_len_client_off = position_codec.client_num_units(
90 orig_line[:stripped_len]
91 )
92 orig_len_client_off = position_codec.client_num_units(orig_line[:orig_len])
93 edit_range = position_codec.range_to_client_units(
94 lines,
95 Range(
96 Position(
97 line_no + line_relative_line_no,
98 stripped_len_client_off,
99 ),
100 Position(
101 line_no + line_relative_line_no,
102 orig_len_client_off,
103 ),
104 ),
105 )
106 edits.append(
107 TextEdit(
108 edit_range,
109 "",
110 )
111 )
113 return edits
116class SemanticTokensState:
117 __slots__ = ("ls", "doc", "lines", "tokens", "_previous_line", "_previous_col")
119 def __init__(
120 self,
121 ls: "DebputyLanguageServer",
122 doc: "TextDocument",
123 lines: list[str],
124 tokens: list[int],
125 ) -> None:
126 self.ls = ls
127 self.doc = doc
128 self.lines = lines
129 self.tokens = tokens
130 self._previous_line = 0
131 self._previous_col = 0
133 def emit_token(
134 self,
135 start_pos: Position,
136 len_client_units: int,
137 token_code: int,
138 *,
139 token_modifiers: int = 0,
140 ) -> None:
141 line_delta = start_pos.line - self._previous_line
142 self._previous_line = start_pos.line
143 previous_col = self._previous_col
145 if line_delta:
146 previous_col = 0
148 column_delta = start_pos.character - previous_col
149 self._previous_col = start_pos.character
151 tokens = self.tokens
152 tokens.append(line_delta) # Line delta
153 tokens.append(column_delta) # Token column delta
154 tokens.append(len_client_units) # Token length
155 tokens.append(token_code)
156 tokens.append(token_modifiers)