summaryrefslogtreecommitdiffstats
path: root/src/debputy/lsp/text_util.py
blob: d66cb28f05e2f919c66a025ad37813619ab2b9fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from typing import List, Optional, Sequence, Union, Iterable

from lsprotocol.types import (
    TextEdit,
    Position,
    Range,
    WillSaveTextDocumentParams,
)

from debputy.linting.lint_util import LinterPositionCodec

try:
    from debian._deb822_repro.locatable import Position as TEPosition, Range as TERange
except ImportError:
    pass

try:
    from pygls.workspace import LanguageServer, TextDocument, PositionCodec

    LintCapablePositionCodec = Union[LinterPositionCodec, PositionCodec]
except ImportError:
    LintCapablePositionCodec = LinterPositionCodec


try:
    from Levenshtein import distance
except ImportError:

    def detect_possible_typo(
        provided_value: str,
        known_values: Iterable[str],
    ) -> Sequence[str]:
        return tuple()

else:

    def detect_possible_typo(
        provided_value: str,
        known_values: Iterable[str],
    ) -> Sequence[str]:
        k_len = len(provided_value)
        candidates = []
        for known_value in known_values:
            if abs(k_len - len(known_value)) > 2:
                continue
            d = distance(provided_value, known_value)
            if d > 2:
                continue
            candidates.append(known_value)
        return candidates


def normalize_dctrl_field_name(f: str) -> str:
    if not f or not f.startswith(("x", "X")):
        return f
    i = 0
    for i in range(1, len(f)):
        if f[i] == "-":
            i += 1
            break
        if f[i] not in ("b", "B", "s", "S", "c", "C"):
            return f
    assert i > 0
    return f[i:]


def on_save_trim_end_of_line_whitespace(
    ls: "LanguageServer",
    params: WillSaveTextDocumentParams,
) -> Optional[Sequence[TextEdit]]:
    doc = ls.workspace.get_text_document(params.text_document.uri)
    return trim_end_of_line_whitespace(doc, doc.lines)


def trim_end_of_line_whitespace(
    doc: "TextDocument",
    lines: List[str],
) -> Optional[Sequence[TextEdit]]:
    edits = []
    for line_no, orig_line in enumerate(lines):
        orig_len = len(orig_line)
        if orig_line.endswith("\n"):
            orig_len -= 1
        stripped_len = len(orig_line.rstrip())
        if stripped_len == orig_len:
            continue

        edit_range = doc.position_codec.range_to_client_units(
            lines,
            Range(
                Position(
                    line_no,
                    stripped_len,
                ),
                Position(
                    line_no,
                    orig_len,
                ),
            ),
        )
        edits.append(
            TextEdit(
                edit_range,
                "",
            )
        )

    return edits


def te_position_to_lsp(te_position: "TEPosition") -> Position:
    return Position(
        te_position.line_position,
        te_position.cursor_position,
    )


def te_range_to_lsp(te_range: "TERange") -> Range:
    return Range(
        te_position_to_lsp(te_range.start_pos),
        te_position_to_lsp(te_range.end_pos),
    )