summaryrefslogtreecommitdiffstats
path: root/src/debputy/lsp/text_util.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/debputy/lsp/text_util.py')
-rw-r--r--src/debputy/lsp/text_util.py122
1 files changed, 122 insertions, 0 deletions
diff --git a/src/debputy/lsp/text_util.py b/src/debputy/lsp/text_util.py
new file mode 100644
index 0000000..d66cb28
--- /dev/null
+++ b/src/debputy/lsp/text_util.py
@@ -0,0 +1,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),
+ )