1use std::ops::Range;
13
14use md5::Md5;
15use serde::{Deserialize, Serialize};
16use sha1::Sha1;
17use sha2::{Digest, Sha256};
18
19use crate::action::{Action, ActionError};
20use crate::buffer::{Indent, LineEnding};
21use crate::changeset::{Bias, ChangeSet, Edit};
22use crate::history::EditKind;
23use crate::position::{ByteOffset, byte_to_visual_col};
24use crate::resolve::Resolved;
25use crate::selection::{Selection, SelectionSet};
26use crate::text::Document;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub enum Codec {
31 Base64,
35 Url,
39 Hex,
42}
43
44impl Codec {
45 pub fn name(self) -> &'static str {
46 match self {
47 Codec::Base64 => "Base64",
48 Codec::Url => "URL encoding",
49 Codec::Hex => "hex",
50 }
51 }
52
53 pub fn encode(self, text: &str) -> String {
55 match self {
56 Codec::Base64 => base64_encode(text.as_bytes()),
57 Codec::Url => url_encode(text.as_bytes()),
58 Codec::Hex => hex_encode(text.as_bytes()),
59 }
60 }
61
62 pub fn decode(self, text: &str) -> Result<String, String> {
64 let bytes = match self {
65 Codec::Base64 => base64_decode(text)?,
66 Codec::Url => url_decode(text)?,
67 Codec::Hex => hex_decode(text)?,
68 };
69 String::from_utf8(bytes).map_err(|_| "the decoded bytes are not UTF-8 text".to_owned())
70 }
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct JsonError {
79 pub offset: usize,
81 pub message: String,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct InvalidJson {
87 pub at: ByteOffset,
88 pub line: usize,
90 pub column: usize,
92 pub message: String,
93}
94
95const JSON_SPACE: [char; 4] = [' ', '\t', '\n', '\r'];
97
98pub const MAX_FORMAT_DEPTH: usize = 1000;
100
101pub fn format_json(text: &str, indent: Indent) -> Result<String, JsonError> {
105 let unit = indent_unit(indent);
106 run_json(
107 text,
108 Style::Pretty {
109 unit: &unit,
110 base: "",
111 },
112 &mut [],
113 )
114}
115
116pub fn minify_json(text: &str) -> Result<String, JsonError> {
118 run_json(text, Style::Minify, &mut [])
119}
120
121pub fn validate_json(text: &str) -> Result<(), JsonError> {
123 run_json(text, Style::Check, &mut []).map(drop)
124}
125
126fn indent_unit(indent: Indent) -> String {
127 if indent.use_spaces {
128 " ".repeat(indent.width.max(1))
129 } else {
130 "\t".to_owned()
131 }
132}
133
134#[derive(Clone, Copy)]
136enum Style<'a> {
137 Check,
139 Minify,
141 Pretty { unit: &'a str, base: &'a str },
144}
145
146#[derive(Clone, Copy, PartialEq, Eq)]
147enum Expect {
148 Value,
150 FirstElement,
152 FirstKey,
154 Key,
156 Colon,
157 CommaOrClose,
159 End,
161}
162
163struct Writer<'a, 'p> {
165 out: String,
166 style: Style<'a>,
167 positions: &'p mut [usize],
169 next: usize,
170 after_last: usize,
172 break_due: bool,
174}
175
176impl Writer<'_, '_> {
177 fn token(&mut self, token: &str, start: usize) {
181 if matches!(self.style, Style::Check) {
182 return;
183 }
184 while let Some(p) = self.positions.get_mut(self.next).filter(|p| **p < start) {
185 *p = self.after_last;
186 self.next += 1;
187 }
188 let out_start = self.out.len();
189 self.out.push_str(token);
190 let end = start + token.len();
191 while let Some(p) = self.positions.get_mut(self.next).filter(|p| **p < end) {
192 *p = out_start + (*p - start);
193 self.next += 1;
194 }
195 self.after_last = self.out.len();
196 }
197
198 fn line_break(&mut self, depth: usize) {
200 if let Style::Pretty { unit, base } = self.style {
201 self.out.push('\n');
202 self.out.push_str(base);
203 for _ in 0..depth {
204 self.out.push_str(unit);
205 }
206 }
207 }
208
209 fn element(&mut self, depth: usize) {
211 if std::mem::take(&mut self.break_due) {
212 self.line_break(depth);
213 }
214 }
215
216 fn space(&mut self) {
217 if let Style::Pretty { .. } = self.style {
218 self.out.push(' ');
219 }
220 }
221
222 fn finish(self) -> String {
224 if !matches!(self.style, Style::Check) {
225 for p in &mut self.positions[self.next..] {
226 *p = self.out.len();
227 }
228 }
229 self.out
230 }
231}
232
233fn found(text: &str, i: usize) -> String {
235 text.get(i..)
236 .and_then(|s| s.chars().next())
237 .map_or_else(|| "the end".to_owned(), shown)
238}
239
240fn shown(c: char) -> String {
242 if c.is_control() {
243 format!("U+{:04X}", c as u32)
244 } else {
245 format!("'{c}'")
246 }
247}
248
249fn json_error(offset: usize, message: impl Into<String>) -> JsonError {
250 JsonError {
251 offset,
252 message: message.into(),
253 }
254}
255
256fn run_json(text: &str, style: Style, positions: &mut [usize]) -> Result<String, JsonError> {
260 let b = text.as_bytes();
261 let capacity = match style {
262 Style::Check => 0,
263 Style::Minify => text.len(),
264 Style::Pretty { .. } => text.len() + text.len() / 2,
265 };
266 let mut w = Writer {
267 out: String::with_capacity(capacity),
268 style,
269 positions,
270 next: 0,
271 after_last: 0,
272 break_due: false,
273 };
274 let mut stack: Vec<bool> = Vec::new();
276 let mut expect = Expect::Value;
277 let skip = |mut i: usize| {
278 while i < b.len() && matches!(b[i], b' ' | b'\t' | b'\n' | b'\r') {
279 i += 1;
280 }
281 i
282 };
283 let after_value = |stack: &[bool]| {
284 if stack.is_empty() {
285 Expect::End
286 } else {
287 Expect::CommaOrClose
288 }
289 };
290 let mut i = skip(0);
291 while i < b.len() {
292 let c = b[i];
293 match expect {
294 Expect::End => {
295 return Err(json_error(
296 i,
297 format!("unexpected {} after the end of the JSON", found(text, i)),
298 ));
299 }
300 Expect::Colon => {
301 if c != b':' {
302 return Err(json_error(
303 i,
304 format!("expected ':' after the key but found {}", found(text, i)),
305 ));
306 }
307 w.token(":", i);
308 w.space();
309 expect = Expect::Value;
310 i += 1;
311 }
312 Expect::CommaOrClose => {
313 let object = stack.last() == Some(&true);
314 match c {
315 b',' => {
316 w.token(",", i);
317 w.break_due = true;
318 expect = if object { Expect::Key } else { Expect::Value };
319 }
320 b'}' if object => {
321 stack.pop();
322 w.line_break(stack.len());
323 w.token("}", i);
324 expect = after_value(&stack);
325 }
326 b']' if !object => {
327 stack.pop();
328 w.line_break(stack.len());
329 w.token("]", i);
330 expect = after_value(&stack);
331 }
332 _ => {
333 return Err(json_error(
334 i,
335 format!(
336 "expected ',' or '{}' but found {}",
337 if object { '}' } else { ']' },
338 found(text, i)
339 ),
340 ));
341 }
342 }
343 i += 1;
344 }
345 Expect::FirstKey if c == b'}' => {
346 stack.pop();
347 w.break_due = false;
348 w.token("}", i);
349 expect = after_value(&stack);
350 i += 1;
351 }
352 Expect::FirstElement if c == b']' => {
353 stack.pop();
354 w.break_due = false;
355 w.token("]", i);
356 expect = after_value(&stack);
357 i += 1;
358 }
359 Expect::FirstKey | Expect::Key => {
360 if c == b'"' {
361 let end = scan_string(text, i)?;
362 w.element(stack.len());
363 w.token(&text[i..end], i);
364 expect = Expect::Colon;
365 i = end;
366 } else {
367 let wanted = if expect == Expect::FirstKey {
368 "a key in quotes or '}'"
369 } else {
370 "a key in quotes"
371 };
372 return Err(json_error(
373 i,
374 format!("expected {wanted} but found {}", found(text, i)),
375 ));
376 }
377 }
378 Expect::Value | Expect::FirstElement => {
379 let end = match c {
380 b'{' | b'[' => i + 1,
381 b'"' => scan_string(text, i)?,
382 b'-' | b'0'..=b'9' => scan_number(text, i)?,
383 _ => ["true", "false", "null"]
384 .into_iter()
385 .find(|lit| text[i..].starts_with(lit))
386 .map(|lit| i + lit.len())
387 .ok_or_else(|| {
388 json_error(i, format!("expected a value but found {}", found(text, i)))
389 })?,
390 };
391 w.element(stack.len());
392 w.token(&text[i..end], i);
393 if matches!(c, b'{' | b'[')
397 && matches!(w.style, Style::Pretty { .. })
398 && stack.len() >= MAX_FORMAT_DEPTH
399 {
400 return Err(json_error(
401 i,
402 format!(
403 "nested more than {MAX_FORMAT_DEPTH} levels deep, too deep to format"
404 ),
405 ));
406 }
407 match c {
408 b'{' => {
409 stack.push(true);
410 w.break_due = true;
411 expect = Expect::FirstKey;
412 }
413 b'[' => {
414 stack.push(false);
415 w.break_due = true;
416 expect = Expect::FirstElement;
417 }
418 _ => expect = after_value(&stack),
419 }
420 i = end;
421 }
422 }
423 i = skip(i);
424 }
425 if expect != Expect::End {
426 let message = match expect {
427 Expect::Value | Expect::FirstElement if stack.is_empty() => {
428 "expected a value but found the end".to_owned()
429 }
430 _ => format!(
431 "the JSON ends before its {} is closed",
432 if stack.last() == Some(&true) {
433 "object"
434 } else {
435 "array"
436 }
437 ),
438 };
439 return Err(json_error(b.len(), message));
440 }
441 Ok(w.finish())
442}
443
444fn scan_string(text: &str, start: usize) -> Result<usize, JsonError> {
446 let b = text.as_bytes();
447 let mut j = start + 1;
448 while j < b.len() {
449 match b[j] {
450 b'"' => return Ok(j + 1),
451 b'\\' => match b.get(j + 1) {
452 Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => j += 2,
453 Some(b'u') => {
454 if b.len() >= j + 6 && b[j + 2..j + 6].iter().all(u8::is_ascii_hexdigit) {
455 j += 6;
456 } else {
457 return Err(json_error(j, "\\u must be followed by four hex digits"));
458 }
459 }
460 _ => return Err(json_error(j, "invalid escape sequence in a string")),
461 },
462 b'\n' | b'\r' => {
463 return Err(json_error(
464 j,
465 "a string cannot contain a line break (write \\n)",
466 ));
467 }
468 c if c < 0x20 => {
469 return Err(json_error(
470 j,
471 format!("a string cannot contain the control character U+{c:04X}"),
472 ));
473 }
474 _ => j += 1,
475 }
476 }
477 Err(json_error(start, "this string has no closing quote"))
478}
479
480fn scan_number(text: &str, start: usize) -> Result<usize, JsonError> {
482 let b = text.as_bytes();
483 let digits = |mut j: usize| {
484 while j < b.len() && b[j].is_ascii_digit() {
485 j += 1;
486 }
487 j
488 };
489 let mut j = start;
490 if b[j] == b'-' {
491 j += 1;
492 }
493 match b.get(j) {
494 Some(b'0') => {
495 j += 1;
496 if b.get(j).is_some_and(u8::is_ascii_digit) {
497 return Err(json_error(j, "a number cannot have a leading zero"));
498 }
499 }
500 Some(c) if c.is_ascii_digit() => j = digits(j),
501 _ => return Err(json_error(j, "expected digits after '-'")),
502 }
503 if b.get(j) == Some(&b'.') {
504 let k = digits(j + 1);
505 if k == j + 1 {
506 return Err(json_error(k, "expected digits after the decimal point"));
507 }
508 j = k;
509 }
510 if matches!(b.get(j), Some(b'e' | b'E')) {
511 j += 1;
512 if matches!(b.get(j), Some(b'+' | b'-')) {
513 j += 1;
514 }
515 let k = digits(j);
516 if k == j {
517 return Err(json_error(k, "expected digits in the exponent"));
518 }
519 j = k;
520 }
521 Ok(j)
522}
523
524const BASE64: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
528
529pub fn base64_encode(bytes: &[u8]) -> String {
531 let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
532 for chunk in bytes.chunks(3) {
533 let at = |i: usize| chunk.get(i).copied().unwrap_or(0) as u32;
534 let n = (at(0) << 16) | (at(1) << 8) | at(2);
535 let sextet = |shift: u32| BASE64[(n >> shift) as usize & 63] as char;
536 out.push(sextet(18));
537 out.push(sextet(12));
538 out.push(if chunk.len() > 1 { sextet(6) } else { '=' });
539 out.push(if chunk.len() > 2 { sextet(0) } else { '=' });
540 }
541 out
542}
543
544pub fn base64_decode(text: &str) -> Result<Vec<u8>, String> {
547 let mut out = Vec::with_capacity(text.len() / 4 * 3 + 2);
548 let (mut acc, mut n, mut padding) = (0u32, 0usize, 0usize);
549 for c in text.chars() {
550 let v = match c {
551 'A'..='Z' => c as u32 - 'A' as u32,
552 'a'..='z' => c as u32 - 'a' as u32 + 26,
553 '0'..='9' => c as u32 - '0' as u32 + 52,
554 '+' | '-' => 62,
555 '/' | '_' => 63,
556 '=' => {
557 padding += 1;
558 continue;
559 }
560 ' ' | '\t' | '\n' | '\r' => continue,
561 _ => return Err(format!("{} is not a Base64 character", shown(c))),
562 };
563 if padding > 0 {
564 return Err("there is text after the '=' padding".to_owned());
565 }
566 acc = (acc << 6) | v;
567 n += 1;
568 if n == 4 {
569 out.extend([(acc >> 16) as u8, (acc >> 8) as u8, acc as u8]);
570 (acc, n) = (0, 0);
571 }
572 }
573 match n {
574 0 if padding == 0 => {}
575 2 if padding <= 2 => out.push((acc >> 4) as u8),
576 3 if padding <= 1 => out.extend([(acc >> 10) as u8, (acc >> 2) as u8]),
577 1 => return Err("the text is cut short: one character too many or too few".to_owned()),
578 _ => return Err("the '=' padding does not fit the text".to_owned()),
579 }
580 Ok(out)
581}
582
583pub fn url_encode(bytes: &[u8]) -> String {
585 const HEX: &[u8; 16] = b"0123456789ABCDEF";
586 let mut out = String::with_capacity(bytes.len());
587 for &b in bytes {
588 if b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~') {
589 out.push(b as char);
590 } else {
591 out.push('%');
592 out.push(HEX[(b >> 4) as usize] as char);
593 out.push(HEX[(b & 15) as usize] as char);
594 }
595 }
596 out
597}
598
599pub fn url_decode(text: &str) -> Result<Vec<u8>, String> {
601 let b = text.as_bytes();
602 let mut out = Vec::with_capacity(b.len());
603 let mut i = 0;
604 while i < b.len() {
605 if b[i] != b'%' {
606 out.push(b[i]);
607 i += 1;
608 continue;
609 }
610 let hex = |j: usize| b.get(j).and_then(|&x| (x as char).to_digit(16));
611 match (hex(i + 1), hex(i + 2)) {
612 (Some(hi), Some(lo)) => {
613 out.push((hi * 16 + lo) as u8);
614 i += 3;
615 }
616 _ => {
617 let bad: String = text[i..].chars().take(3).collect();
618 return Err(format!(
619 "\"{bad}\" is not an escape: '%' must be followed by two hex digits"
620 ));
621 }
622 }
623 }
624 Ok(out)
625}
626
627pub fn hex_encode(bytes: &[u8]) -> String {
629 const HEX: &[u8; 16] = b"0123456789ABCDEF";
630 let mut out = String::with_capacity(bytes.len() * 2);
631 for &b in bytes {
632 out.push(HEX[(b >> 4) as usize] as char);
633 out.push(HEX[(b & 15) as usize] as char);
634 }
635 out
636}
637
638pub fn hex_decode(text: &str) -> Result<Vec<u8>, String> {
640 let mut out = Vec::with_capacity(text.len() / 2);
641 let mut high: Option<u8> = None;
642 for c in text.chars().filter(|c| !c.is_whitespace()) {
643 let Some(digit) = c.to_digit(16) else {
644 return Err(format!("'{c}' is not a hex digit"));
645 };
646 let digit = digit as u8;
647 match high.take() {
648 Some(h) => out.push(h << 4 | digit),
649 None => high = Some(digit),
650 }
651 }
652 if high.is_some() {
653 return Err("the hex digits do not pair up: one is left over".to_owned());
654 }
655 Ok(out)
656}
657
658#[derive(Clone, Debug, PartialEq, Eq)]
663pub struct Hashes {
664 pub bytes: usize,
666 pub line_breaks: usize,
668 pub md5: String,
669 pub sha1: String,
670 pub sha256: String,
671}
672
673pub fn hashes(text: &str, ending: LineEnding) -> Hashes {
675 let newline = ending_str(ending).as_bytes();
676 let (mut md5, mut sha1, mut sha256) = (Md5::new(), Sha1::new(), Sha256::new());
677 let (mut bytes, mut line_breaks) = (0, 0);
678 let mut feed = |data: &[u8]| {
679 md5.update(data);
680 sha1.update(data);
681 sha256.update(data);
682 bytes += data.len();
683 };
684 for (i, line) in text.split('\n').enumerate() {
685 if i > 0 {
686 feed(newline);
687 line_breaks += 1;
688 }
689 feed(line.as_bytes());
690 }
691 let hex = |digest: &[u8]| digest.iter().map(|b| format!("{b:02x}")).collect();
692 Hashes {
693 bytes,
694 line_breaks,
695 md5: hex(&md5.finalize()),
696 sha1: hex(&sha1.finalize()),
697 sha256: hex(&sha256.finalize()),
698 }
699}
700
701pub fn hash_selections(doc: &Document, sels: &SelectionSet, ending: LineEnding) -> (Hashes, bool) {
705 let whole = nothing_selected(sels);
706 let text = if whole {
707 doc.to_string()
708 } else {
709 sels.ranges()
710 .iter()
711 .filter(|s| !s.is_empty())
712 .map(|s| doc.slice(s.range()))
713 .collect::<Vec<_>>()
714 .join("\n")
715 };
716 (hashes(&text, ending), whole)
717}
718
719fn ending_str(ending: LineEnding) -> &'static str {
720 match ending {
721 LineEnding::Lf => "\n",
722 LineEnding::CrLf => "\r\n",
723 LineEnding::Cr => "\r",
724 }
725}
726
727fn nothing_selected(sels: &SelectionSet) -> bool {
731 sels.ranges().iter().all(Selection::is_empty)
732}
733
734fn trimmed(doc: &Document, r: Range<ByteOffset>) -> (Range<ByteOffset>, String) {
736 let text = doc.slice(r.clone());
737 let start = r.start.0 + (text.len() - text.trim_start_matches(JSON_SPACE).len());
738 let body = text.trim_matches(JSON_SPACE).to_owned();
739 (ByteOffset(start)..ByteOffset(start + body.len()), body)
740}
741
742fn targets(doc: &Document, sels: &SelectionSet, trim: bool) -> Vec<(Range<ByteOffset>, String)> {
746 if nothing_selected(sels) {
747 return vec![trimmed(doc, ByteOffset(0)..doc.len())];
748 }
749 sels.ranges()
750 .iter()
751 .filter(|s| !s.is_empty())
752 .map(|s| {
753 if trim {
754 trimmed(doc, s.range())
755 } else {
756 (s.range(), doc.slice(s.range()))
757 }
758 })
759 .collect()
760}
761
762fn line_indent(doc: &Document, pos: ByteOffset) -> String {
764 doc.line_text(doc.line_of(pos))
765 .chars()
766 .take_while(|c| *c == ' ' || *c == '\t')
767 .collect()
768}
769
770fn invalid_json(doc: &Document, start: ByteOffset, e: JsonError, tab_width: usize) -> ActionError {
771 let at = ByteOffset(start.0 + e.offset);
772 ActionError::InvalidJson(Box::new(InvalidJson {
773 at,
774 line: doc.line_of(at) + 1,
775 column: byte_to_visual_col(doc, at, tab_width).0 + 1,
776 message: e.message,
777 }))
778}
779
780pub fn validate_json_in(
783 doc: &Document,
784 sels: &SelectionSet,
785 tab_width: usize,
786) -> Result<(), ActionError> {
787 for (r, text) in targets(doc, sels, true) {
788 run_json(&text, Style::Check, &mut [])
789 .map_err(|e| invalid_json(doc, r.start, e, tab_width))?;
790 }
791 Ok(())
792}
793
794pub(crate) fn resolve(
797 action: &Action,
798 doc: &Document,
799 sels: &SelectionSet,
800 indent: Indent,
801 ending: LineEnding,
802) -> Result<Resolved, ActionError> {
803 match action {
804 Action::FormatJson { indent: chosen } => {
805 let unit = indent_unit(chosen.unwrap_or(indent));
806 json(doc, sels, Some(&unit), indent.width)
807 }
808 Action::MinifyJson => json(doc, sels, None, indent.width),
809 Action::Encode(codec) => recode(doc, sels, |text| {
810 let text = match ending {
811 LineEnding::Lf => text.to_owned(),
812 other => text.replace('\n', ending_str(other)),
813 };
814 Ok(codec.encode(&text))
815 }),
816 Action::Decode(codec) => {
817 let what = if nothing_selected(sels) {
818 "the document"
819 } else {
820 "the selection"
821 };
822 recode(doc, sels, |text| {
823 let decoded = codec.decode(text).map_err(|e| {
824 ActionError::Decode(format!("{what} is not valid {}: {e}", codec.name()))
825 })?;
826 Ok(if decoded.contains('\r') {
828 decoded.replace("\r\n", "\n").replace('\r', "\n")
829 } else {
830 decoded
831 })
832 })
833 }
834 _ => Ok(Resolved::Selections(sels.clone())),
835 }
836}
837
838fn json(
841 doc: &Document,
842 sels: &SelectionSet,
843 unit: Option<&str>,
844 tab_width: usize,
845) -> Result<Resolved, ActionError> {
846 let whole = nothing_selected(sels);
847 let mut edits = Vec::new();
848 let mut moved: Vec<(usize, ByteOffset)> = Vec::new();
850 for (r, text) in targets(doc, sels, true) {
851 let base = unit.map(|_| line_indent(doc, r.start)).unwrap_or_default();
852 let style = match unit {
853 Some(unit) => Style::Pretty { unit, base: &base },
854 None => Style::Minify,
855 };
856 let mut inside: Vec<(usize, usize)> = if whole {
857 sels.ranges()
858 .iter()
859 .enumerate()
860 .filter(|(_, s)| r.start <= s.head && s.head <= r.end)
861 .map(|(i, s)| (s.head.0 - r.start.0, i))
862 .collect()
863 } else {
864 Vec::new()
865 };
866 inside.sort_unstable();
867 let mut positions: Vec<usize> = inside.iter().map(|(p, _)| *p).collect();
868 let out = run_json(&text, style, &mut positions)
869 .map_err(|e| invalid_json(doc, r.start, e, tab_width))?;
870 moved.extend(
873 inside
874 .iter()
875 .zip(&positions)
876 .map(|((_, i), p)| (*i, ByteOffset(r.start.0 + p))),
877 );
878 if out != text {
879 edits.push(Edit::new(r.start, r.end, out));
880 }
881 }
882 let changes = ChangeSet::new(doc.len(), edits);
883 if changes.is_empty() {
884 return Ok(Resolved::Selections(sels.clone()));
885 }
886 let after = if whole {
887 let ranges = sels
888 .ranges()
889 .iter()
890 .enumerate()
891 .map(|(i, s)| {
892 let pos = moved
893 .iter()
894 .find(|(j, _)| *j == i)
895 .map_or_else(|| changes.map_position(s.head, Bias::Before), |m| m.1);
896 Selection::caret(pos)
897 })
898 .collect();
899 SelectionSet::new(ranges, sels.primary_index())
900 } else {
901 cover_new_text(sels, &changes)
902 };
903 Ok(Resolved::Edit {
904 changes,
905 after,
906 kind: EditKind::Other,
907 })
908}
909
910fn recode(
912 doc: &Document,
913 sels: &SelectionSet,
914 f: impl Fn(&str) -> Result<String, ActionError>,
915) -> Result<Resolved, ActionError> {
916 let mut edits = Vec::new();
917 for (r, text) in targets(doc, sels, false) {
918 let new = f(&text)?;
919 if new != text {
920 edits.push(Edit::new(r.start, r.end, new));
921 }
922 }
923 let changes = ChangeSet::new(doc.len(), edits);
924 if changes.is_empty() {
925 return Ok(Resolved::Selections(sels.clone()));
926 }
927 Ok(Resolved::Edit {
928 after: cover_new_text(sels, &changes),
929 changes,
930 kind: EditKind::Other,
931 })
932}
933
934fn cover_new_text(sels: &SelectionSet, changes: &ChangeSet) -> SelectionSet {
936 sels.map(|s| {
937 if s.is_empty() {
938 return Selection::caret(changes.map_position(s.head, Bias::Before));
939 }
940 let start = changes.map_position(s.start(), Bias::Before);
941 let end = changes.map_position(s.end(), Bias::After);
942 if s.head < s.anchor {
943 Selection::new(end, start)
944 } else {
945 Selection::new(start, end)
946 }
947 })
948}
949
950#[cfg(test)]
951mod tests {
952 use super::*;
953
954 const SPACES4: Indent = Indent {
955 use_spaces: true,
956 width: 4,
957 };
958
959 #[test]
960 fn formats_nested_json_keeping_order_numbers_and_strings() {
961 let input = r#"{"z":1,"a":[true,null,{"c":"d"}],"e":{},"f":[],"n":-0.0,"big":1E+10,"s":"caf\u00e9 \"q\" é 漢\n"}"#;
962 let expected = r#"{
963 "z": 1,
964 "a": [
965 true,
966 null,
967 {
968 "c": "d"
969 }
970 ],
971 "e": {},
972 "f": [],
973 "n": -0.0,
974 "big": 1E+10,
975 "s": "caf\u00e9 \"q\" é 漢\n"
976}"#;
977 assert_eq!(format_json(input, SPACES4).unwrap(), expected);
978 assert_eq!(minify_json(expected).unwrap(), input);
979 let tabs = format_json("[1,[2]]", Indent::default()).unwrap();
980 assert_eq!(tabs, "[\n\t1,\n\t[\n\t\t2\n\t]\n]");
981 }
982
983 #[test]
984 fn scalars_and_empty_containers_are_json_too() {
985 for s in [
986 "0", "-1.5e-3", "\"\"", "true", "false", "null", "{}", "[]", " [ ] ",
987 ] {
988 assert!(validate_json(s).is_ok(), "{s}");
989 }
990 assert_eq!(format_json(" {} ", SPACES4).unwrap(), "{}");
991 assert_eq!(minify_json("[ 1 , { } ]").unwrap(), "[1,{}]");
992 }
993
994 #[test]
995 fn invalid_json_says_where_and_why() {
996 let cases: &[(&str, usize, &str)] = &[
997 ("", 0, "expected a value but found the end"),
998 ("{\"a\":1,}", 7, "expected a key in quotes but found '}'"),
999 ("[1,]", 3, "expected a value but found ']'"),
1000 ("[1 2]", 3, "expected ',' or ']' but found '2'"),
1001 ("{\"a\" 1}", 5, "expected ':' after the key but found '1'"),
1002 ("{1:2}", 1, "expected a key in quotes or '}' but found '1'"),
1003 ("{} x", 3, "unexpected 'x' after the end of the JSON"),
1004 ("[1", 2, "the JSON ends before its array is closed"),
1005 ("{\"a\":[", 6, "the JSON ends before its array is closed"),
1006 ("\"abc", 0, "this string has no closing quote"),
1007 (
1008 "\"a\nb\"",
1009 2,
1010 "a string cannot contain a line break (write \\n)",
1011 ),
1012 ("\"\\x\"", 1, "invalid escape sequence in a string"),
1013 ("\"\\u12\"", 1, "\\u must be followed by four hex digits"),
1014 ("01", 1, "a number cannot have a leading zero"),
1015 ("-", 1, "expected digits after '-'"),
1016 ("1.", 2, "expected digits after the decimal point"),
1017 ("1e+", 3, "expected digits in the exponent"),
1018 ("tru", 0, "expected a value but found 't'"),
1019 ("[é]", 1, "expected a value but found 'é'"),
1020 ("// x\n{}", 0, "expected a value but found '/'"),
1021 ];
1022 for (input, offset, message) in cases {
1023 let e = validate_json(input).unwrap_err();
1024 assert_eq!(
1025 (e.offset, e.message.as_str()),
1026 (*offset, *message),
1027 "{input:?}"
1028 );
1029 }
1030 }
1031
1032 #[test]
1033 fn deep_nesting_does_not_overflow_the_stack() {
1034 let deep = format!("{}{}", "[".repeat(100_000), "]".repeat(100_000));
1035 assert!(validate_json(&deep).is_ok());
1036 assert_eq!(minify_json(&deep).unwrap(), deep);
1037 let indent = Indent {
1039 use_spaces: true,
1040 width: 4,
1041 };
1042 let err = format_json(&deep, indent).unwrap_err();
1043 assert_eq!(err.offset, MAX_FORMAT_DEPTH);
1044 assert!(
1045 err.message.contains("too deep to format"),
1046 "{}",
1047 err.message
1048 );
1049 let ok = format!(
1051 "{}{}",
1052 "[".repeat(MAX_FORMAT_DEPTH),
1053 "]".repeat(MAX_FORMAT_DEPTH)
1054 );
1055 assert!(format_json(&ok, indent).is_ok());
1056 }
1057
1058 #[test]
1059 fn positions_follow_their_tokens() {
1060 let text = r#"{"a": 1, "b": "xyz"}"#;
1061 let mut positions = vec![7, 8, 10, 17, text.len()];
1063 let unit = " ";
1064 let out = run_json(text, Style::Pretty { unit, base: "" }, &mut positions).unwrap();
1065 assert_eq!(out, "{\n \"a\": 1,\n \"b\": \"xyz\"\n}");
1066 let at = |p: usize| &out[p..];
1067 assert!(at(positions[0]).starts_with(",\n"), "after the 1");
1068 assert!(at(positions[1]).starts_with("\n \"b\""), "after the comma");
1069 assert!(at(positions[2]).starts_with("\"b\""));
1070 assert!(at(positions[3]).starts_with("yz\""));
1071 assert_eq!(positions[4], out.len());
1072 }
1073
1074 #[test]
1075 fn base64_round_trips_every_padding_length() {
1076 let cases = [
1077 ("", ""),
1078 ("f", "Zg=="),
1079 ("fo", "Zm8="),
1080 ("foo", "Zm9v"),
1081 ("foob", "Zm9vYg=="),
1082 ("fooba", "Zm9vYmE="),
1083 ("foobar", "Zm9vYmFy"),
1084 ("é漢👍", "w6nmvKLwn5GN"),
1085 ];
1086 for (plain, encoded) in cases {
1087 assert_eq!(base64_encode(plain.as_bytes()), encoded);
1088 assert_eq!(Codec::Base64.decode(encoded).unwrap(), plain);
1089 }
1090 assert_eq!(base64_decode("Zm9vYg").unwrap(), b"foob");
1092 assert_eq!(base64_decode("Zm9v\nYmFy\n").unwrap(), b"foobar");
1093 assert_eq!(
1094 base64_decode("-_-_").unwrap(),
1095 base64_decode("+/+/").unwrap()
1096 );
1097 }
1098
1099 #[test]
1100 fn hex_round_trips_and_takes_blanks() {
1101 assert_eq!(Codec::Hex.encode("Hi é"), "486920C3A9");
1102 assert_eq!(Codec::Hex.decode("486920C3A9").unwrap(), "Hi é");
1103 assert_eq!(Codec::Hex.decode("48 69\n20 c3 a9").unwrap(), "Hi é");
1104 assert_eq!(Codec::Hex.encode(""), "");
1105 assert_eq!(Codec::Hex.decode("").unwrap(), "");
1106 assert_eq!(
1107 Codec::Hex.decode("4G").unwrap_err(),
1108 "'G' is not a hex digit"
1109 );
1110 assert!(Codec::Hex.decode("486").is_err(), "a digit left over");
1111 assert!(Codec::Hex.decode("FF").is_err());
1113 }
1114
1115 #[test]
1116 fn bad_base64_is_refused() {
1117 assert_eq!(
1118 base64_decode("Zm9v!").unwrap_err(),
1119 "'!' is not a Base64 character"
1120 );
1121 assert!(base64_decode("Zm9vY").is_err(), "one character too many");
1122 assert!(base64_decode("Zg==Zg==").is_err(), "text after padding");
1123 assert!(
1124 base64_decode("Zm9v=").is_err(),
1125 "padding without a partial group"
1126 );
1127 assert_eq!(
1129 Codec::Base64.decode("/w==").unwrap_err(),
1130 "the decoded bytes are not UTF-8 text"
1131 );
1132 }
1133
1134 #[test]
1135 fn url_encoding_keeps_only_unreserved_characters() {
1136 let plain = "a b&c=d/é?~._-+%";
1137 let encoded = "a%20b%26c%3Dd%2F%C3%A9%3F~._-%2B%25";
1138 assert_eq!(url_encode(plain.as_bytes()), encoded);
1139 assert_eq!(Codec::Url.decode(encoded).unwrap(), plain);
1140 assert_eq!(Codec::Url.decode("a+b%2b%2B").unwrap(), "a+b++");
1141 assert!(url_decode("100%").is_err());
1142 assert!(url_decode("%G0").is_err());
1143 assert_eq!(
1144 Codec::Url.decode("%FF").unwrap_err(),
1145 "the decoded bytes are not UTF-8 text"
1146 );
1147 }
1148
1149 #[test]
1150 fn hashes_use_the_line_ending() {
1151 let h = hashes("abc", LineEnding::CrLf);
1152 assert_eq!(h.md5, "900150983cd24fb0d6963f7d28e17f72");
1153 assert_eq!(h.sha1, "a9993e364706816aba3e25717850c26c9cd0d89d");
1154 assert_eq!(
1155 h.sha256,
1156 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
1157 );
1158 assert_eq!((h.bytes, h.line_breaks), (3, 0));
1159 let empty = hashes("", LineEnding::Lf);
1160 assert_eq!(
1161 empty.sha256,
1162 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
1163 );
1164 let crlf = hashes("a\nb", LineEnding::CrLf);
1166 assert_eq!((crlf.bytes, crlf.line_breaks), (4, 1));
1167 assert_eq!(crlf, hashes("a\r\nb", LineEnding::Lf));
1168 }
1169}