Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Environment/Utility/
TextEdit.rs

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