Skip to main content

Mountain/Environment/Utility/
TextEdit.rs

1#![allow(unused_variables, dead_code, unused_imports)]
2
3//! Pure text-editing utilities shared across workspace and document providers.
4//!
5//! These are side-effect-free helper functions that compute line offsets and
6//! translate `(line, character)` positions to byte offsets, matching VS Code's
7//! UTF-16 code unit counting convention for `Range`/`Position` values.
8
9/// Pre-compute the byte offset of the start of every line in `Source`.
10/// The returned vec always has at least one entry (`[0]`).
11pub(crate) fn ComputeLineOffsets(Source:&str) -> Vec<usize> {
12	let mut Offsets = Vec::with_capacity(Source.len() / 40 + 1);
13
14	Offsets.push(0);
15
16	for (Index, Byte) in Source.bytes().enumerate() {
17		if Byte == b'\n' {
18			Offsets.push(Index + 1);
19		}
20	}
21
22	Offsets
23}
24
25/// Resolve `(line, character)` to an absolute byte offset in `Source`.
26/// `character` is counted in **UTF-16 code units** to match VS Code's
27/// `Range`/`Position` semantics. Falls back to EOF when line/character
28/// exceeds the source length.
29pub(crate) fn LinePosToOffset(LineOffsets:&[usize], Source:&str, Line:usize, Character:usize) -> usize {
30	if Line >= LineOffsets.len() {
31		return Source.len();
32	}
33
34	let LineStart = LineOffsets[Line];
35
36	let LineEnd = if Line + 1 < LineOffsets.len() {
37		LineOffsets[Line + 1].saturating_sub(1)
38	} else {
39		Source.len()
40	};
41
42	let LineText = &Source[LineStart..LineEnd.min(Source.len())];
43
44	let mut Utf16Count:usize = 0;
45
46	for (ByteOffset, Char) in LineText.char_indices() {
47		if Utf16Count >= Character {
48			return LineStart + ByteOffset;
49		}
50
51		Utf16Count += Char.len_utf16();
52	}
53
54	LineStart + LineText.len()
55}
56
57/// Minimal percent-decoder for `file://` URI paths. Self-contained to avoid
58/// an extra crate dependency; handles `%XX` sequences only.
59pub(crate) fn percent_decode(Input:&str) -> String {
60	let mut Out = String::with_capacity(Input.len());
61
62	let mut Bytes = Input.as_bytes().iter().peekable();
63
64	while let Some(&Byte) = Bytes.next() {
65		if Byte == b'%' {
66			let H = Bytes.next().copied();
67
68			let L = Bytes.next().copied();
69
70			if let (Some(H), Some(L)) = (H, L) {
71				if let (Some(Hi), Some(Lo)) = (hex_digit(H), hex_digit(L)) {
72					Out.push((Hi * 16 + Lo) as char);
73
74					continue;
75				}
76
77				Out.push('%');
78
79				Out.push(H as char);
80
81				Out.push(L as char);
82
83				continue;
84			}
85
86			Out.push('%');
87		} else {
88			Out.push(Byte as char);
89		}
90	}
91
92	Out
93}
94
95fn hex_digit(Byte:u8) -> Option<u8> {
96	match Byte {
97		b'0'..=b'9' => Some(Byte - b'0'),
98
99		b'a'..=b'f' => Some(Byte - b'a' + 10),
100
101		b'A'..=b'F' => Some(Byte - b'A' + 10),
102
103		_ => None,
104	}
105}