Coverage for src/debputy/lsp/text_util.py: 67%

79 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2025-01-27 13:59 +0000

1from typing import List, Optional, Sequence, Union, Iterable, TYPE_CHECKING 

2 

3from debputy.commands.debputy_cmd.output import MAN_URL_REWRITE 

4from debputy.lsprotocol.types import ( 

5 TextEdit, 

6 Position, 

7 Range, 

8 WillSaveTextDocumentParams, 

9 DocumentFormattingParams, 

10) 

11 

12from debputy.linting.lint_util import LinterPositionCodec 

13 

14try: 

15 from debputy.lsp.vendoring._deb822_repro.locatable import ( 

16 Position as TEPosition, 

17 Range as TERange, 

18 ) 

19 from debputy.lsp.debputy_ls import DebputyLanguageServer 

20except ImportError: 

21 pass 

22 

23try: 

24 from pygls.server import LanguageServer 

25 from pygls.workspace import TextDocument, PositionCodec 

26except ImportError: 

27 pass 

28 

29if TYPE_CHECKING: 

30 LintCapablePositionCodec = Union[LinterPositionCodec, PositionCodec] 

31else: 

32 LintCapablePositionCodec = LinterPositionCodec 

33 

34 

35def markdown_urlify(uri: str) -> str: 

36 if uri.startswith("man:"): 36 ↛ 44line 36 didn't jump to line 44 because the condition on line 36 was always true

37 m = MAN_URL_REWRITE.match(uri) 

38 if m: 38 ↛ 42line 38 didn't jump to line 42 because the condition on line 38 was always true

39 page, section = m.groups() 

40 link_url = f"https://manpages.debian.org/{page}.{section}" 

41 return f"[{uri}]({link_url})" 

42 return uri 

43 

44 return f"<{uri}>" 

45 

46 

47def normalize_dctrl_field_name(f: str) -> str: 

48 if not f or not f.startswith(("x", "X")): 

49 return f 

50 i = 0 

51 for i in range(1, len(f)): 51 ↛ 57line 51 didn't jump to line 57 because the loop on line 51 didn't complete

52 if f[i] == "-": 

53 i += 1 

54 break 

55 if f[i] not in ("b", "B", "s", "S", "c", "C"): 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true

56 return f 

57 assert i > 0 

58 return f[i:] 

59 

60 

61def on_save_trim_end_of_line_whitespace( 

62 ls: "LanguageServer", 

63 params: Union[WillSaveTextDocumentParams, DocumentFormattingParams], 

64) -> Optional[Sequence[TextEdit]]: 

65 doc = ls.workspace.get_text_document(params.text_document.uri) 

66 return trim_end_of_line_whitespace(doc.position_codec, doc.lines) 

67 

68 

69def trim_end_of_line_whitespace( 

70 position_codec: "LintCapablePositionCodec", 

71 lines: List[str], 

72 *, 

73 line_range: Optional[Iterable[int]] = None, 

74 line_relative_line_no: int = 0, 

75) -> Optional[Sequence[TextEdit]]: 

76 edits = [] 

77 if line_range is None: 

78 line_range = range(0, len(lines)) 

79 for line_no in line_range: 

80 orig_line = lines[line_no] 

81 orig_len = len(orig_line) 

82 if orig_line.endswith("\n"): 

83 orig_len -= 1 

84 stripped_len = len(orig_line.rstrip()) 

85 if stripped_len == orig_len: 

86 continue 

87 

88 stripped_len_client_off = position_codec.client_num_units( 

89 orig_line[:stripped_len] 

90 ) 

91 orig_len_client_off = position_codec.client_num_units(orig_line[:orig_len]) 

92 edit_range = position_codec.range_to_client_units( 

93 lines, 

94 Range( 

95 Position( 

96 line_no + line_relative_line_no, 

97 stripped_len_client_off, 

98 ), 

99 Position( 

100 line_no + line_relative_line_no, 

101 orig_len_client_off, 

102 ), 

103 ), 

104 ) 

105 edits.append( 

106 TextEdit( 

107 edit_range, 

108 "", 

109 ) 

110 ) 

111 

112 return edits 

113 

114 

115class SemanticTokensState: 

116 __slots__ = ("ls", "doc", "lines", "tokens", "_previous_line", "_previous_col") 

117 

118 def __init__( 

119 self, 

120 ls: "DebputyLanguageServer", 

121 doc: "TextDocument", 

122 lines: List[str], 

123 tokens: List[int], 

124 ) -> None: 

125 self.ls = ls 

126 self.doc = doc 

127 self.lines = lines 

128 self.tokens = tokens 

129 self._previous_line = 0 

130 self._previous_col = 0 

131 

132 def emit_token( 

133 self, 

134 start_pos: Position, 

135 len_client_units: int, 

136 token_code: int, 

137 *, 

138 token_modifiers: int = 0, 

139 ) -> None: 

140 line_delta = start_pos.line - self._previous_line 

141 self._previous_line = start_pos.line 

142 previous_col = self._previous_col 

143 

144 if line_delta: 

145 previous_col = 0 

146 

147 column_delta = start_pos.character - previous_col 

148 self._previous_col = start_pos.character 

149 

150 tokens = self.tokens 

151 tokens.append(line_delta) # Line delta 

152 tokens.append(column_delta) # Token column delta 

153 tokens.append(len_client_units) # Token length 

154 tokens.append(token_code) 

155 tokens.append(token_modifiers)