Markdown parser fork with extended syntax for personal use.
1//! A tokenizer glues states from the state machine together.
2//!
3//! It facilitates everything needed to turn bytes into events with a state
4//! machine.
5//! It also enables the logic needed for parsing markdown, such as an
6//! [`attempt`][] to try and parse something, which can succeed or, when
7//! unsuccessful, revert the attempt.
8//!
9//! [`attempt`]: Tokenizer::attempt
10
11use crate::event::{Content, Event, Kind, Link, Name, Point, VOID_EVENTS};
12use crate::message;
13use crate::parser::ParseState;
14use crate::resolve::{call as call_resolve, Name as ResolveName};
15use crate::state::{call, State};
16use crate::subtokenize::Subresult;
17
18#[cfg(feature = "log")]
19use crate::util::char::format_byte_opt;
20
21use crate::util::{constant::TAB_SIZE, edit_map::EditMap};
22use alloc::{boxed::Box, string::String, vec, vec::Vec};
23
24/// Containers.
25///
26/// Containers are found when tokenizing
27/// [document content][crate::construct::document].
28/// They parse a portion at the start of one or more lines.
29/// The rest of those lines is a different content type (specifically, flow),
30/// which they “contain”.
31#[derive(Debug, Eq, PartialEq)]
32pub enum Container {
33 /// [Block quote][crate::construct::block_quote].
34 BlockQuote,
35 /// [List item][crate::construct::list_item].
36 ListItem,
37 /// [GFM: Footnote definition][crate::construct::gfm_footnote_definition].
38 GfmFootnoteDefinition,
39}
40
41/// Info used to tokenize a container.
42///
43/// Practically, these fields are only used for list items.
44#[derive(Debug)]
45pub struct ContainerState {
46 /// Kind.
47 pub kind: Container,
48 /// Whether the first line was blank.
49 pub blank_initial: bool,
50 /// Size.
51 pub size: usize,
52}
53
54/// How to handle a byte.
55#[derive(Debug, PartialEq)]
56enum ByteAction {
57 /// This is a normal byte.
58 ///
59 /// Includes replaced bytes.
60 Normal(u8),
61 /// This byte must be ignored.
62 Ignore,
63 /// This is a new byte.
64 Insert(u8),
65}
66
67/// Label start kind.
68#[derive(Debug, PartialEq, Eq)]
69pub enum LabelKind {
70 /// Label (image) start.
71 ///
72 /// ```markdown
73 /// > | a ![b] c
74 /// ^^
75 /// ```
76 ///
77 /// Construct: [Label start (image)][crate::construct::label_start_image].
78 Image,
79 /// Label (image) link.
80 ///
81 /// ```markdown
82 /// > | a [b] c
83 /// ^
84 /// ```
85 ///
86 /// Construct: [Label start (link)][crate::construct::label_start_link].
87 Link,
88 /// GFM: Label (footnote) link.
89 ///
90 /// ```markdown
91 /// > | a [^b] c
92 /// ^^
93 /// ```
94 ///
95 /// Construct: [GFM: Label start (footnote)][crate::construct::gfm_label_start_footnote].
96 GfmFootnote,
97 /// GFM: Label (footnote) link, not matching a footnote definition, so
98 /// handled as a label (link) start.
99 ///
100 /// ```markdown
101 /// > | a [^b](c) d
102 /// ^^
103 /// ```
104 ///
105 /// Construct: [Label end][crate::construct::label_end].
106 GfmUndefinedFootnote,
107}
108
109/// Label start, looking for an end.
110#[derive(Debug)]
111pub struct LabelStart {
112 /// Kind of start.
113 pub kind: LabelKind,
114 /// Indices of where the label starts and ends in `events`.
115 pub start: (usize, usize),
116 /// A boolean used internally to figure out if a (link) label start can’t
117 /// be used anymore (because it would contain another link).
118 /// That link start is still looking for a balanced closing bracket though,
119 /// so we can’t remove it just yet.
120 pub inactive: bool,
121}
122
123/// Valid label.
124#[derive(Debug)]
125pub struct Label {
126 pub kind: LabelKind,
127 /// Indices of label start.
128 pub start: (usize, usize),
129 /// Indices of label end.
130 pub end: (usize, usize),
131}
132
133/// Different kinds of attempts.
134#[derive(Debug, PartialEq)]
135enum AttemptKind {
136 /// Discard what was tokenized when unsuccessful.
137 Attempt,
138 /// Discard always.
139 Check,
140}
141
142/// How to handle [`State::Ok`][] or [`State::Nok`][].
143#[derive(Debug)]
144struct Attempt {
145 /// Where to go to when successful.
146 ok: State,
147 /// Where to go to when unsuccessful.
148 nok: State,
149 /// Kind of attempt.
150 kind: AttemptKind,
151 /// If needed, the progress to revert to.
152 ///
153 /// It is not needed to discard an [`AttemptKind::Attempt`] that has a
154 /// `nok` of [`State::Nok`][], because that means it is used in *another*
155 /// attempt, which will receive that `Nok`, and has to handle it.
156 progress: Option<Progress>,
157}
158
159/// The internal state of a tokenizer.
160///
161/// Not to be confused with states from the state machine, this instead is all
162/// the information on where we currently are and what’s going on.
163#[derive(Clone, Debug)]
164struct Progress {
165 /// Length of `events`.
166 ///
167 /// It’s not allowed to remove events, so reverting will just pop stuff off.
168 events_len: usize,
169 /// Length of the stack.
170 ///
171 /// It’s not allowed to decrease the stack in an attempt.
172 stack_len: usize,
173 /// Previous code.
174 previous: Option<u8>,
175 /// Current code.
176 current: Option<u8>,
177 /// Current place in the file.
178 point: Point,
179}
180
181/// A lot of shared fields used to tokenize things.
182#[allow(clippy::struct_excessive_bools)]
183#[derive(Debug)]
184pub struct TokenizeState<'a> {
185 // Couple complex fields used to tokenize the document.
186 /// Tokenizer, used to tokenize flow in document.
187 pub document_child: Option<Box<Tokenizer<'a>>>,
188 /// State, used to tokenize containers.
189 pub document_child_state: Option<State>,
190 /// Stack of currently active containers.
191 pub document_container_stack: Vec<ContainerState>,
192 /// How many active containers continued.
193 pub document_continued: usize,
194 /// Index of last `data`.
195 pub document_data_index: Option<usize>,
196 /// Container exits by line number.
197 pub document_exits: Vec<Option<Vec<Event>>>,
198 /// Whether the previous flow was a paragraph or a definition.
199 pub document_lazy_accepting_before: bool,
200 /// Whether this is the first paragraph (potentially after definitions) in
201 /// a list item.
202 /// Used for GFM task list items.
203 pub document_at_first_paragraph_of_list_item: bool,
204
205 // Couple of very frequent settings for parsing whitespace.
206 pub space_or_tab_eol_content: Option<Content>,
207 pub space_or_tab_eol_connect: bool,
208 pub space_or_tab_eol_ok: bool,
209 pub space_or_tab_connect: bool,
210 pub space_or_tab_content: Option<Content>,
211 pub space_or_tab_min: usize,
212 pub space_or_tab_max: usize,
213 pub space_or_tab_size: usize,
214 pub space_or_tab_token: Name,
215
216 pub wikilinks: Vec<Label>,
217 pub wikilink_starts: Vec<LabelStart>,
218
219 // Couple of media related fields.
220 /// List of usable label starts.
221 ///
222 /// Used when tokenizing [text content][crate::construct::text].
223 pub label_starts: Vec<LabelStart>,
224 /// List of unusable label starts.
225 ///
226 /// Used when tokenizing [text content][crate::construct::text].
227 pub label_starts_loose: Vec<LabelStart>,
228 /// Stack of images and links.
229 ///
230 /// Used when tokenizing [text content][crate::construct::text].
231 pub labels: Vec<Label>,
232
233 /// List of defined definition identifiers.
234 pub definitions: Vec<String>,
235 /// List of defined GFM footnote definition identifiers.
236 pub gfm_footnote_definitions: Vec<String>,
237
238 // Last error message provided at an EOF of an expression.
239 pub mdx_last_parse_error: Option<(String, String, String)>,
240
241 /// Whether to connect events.
242 pub connect: bool,
243 /// Marker.
244 pub marker: u8,
245 /// Secondary marker.
246 pub marker_b: u8,
247 /// Several markers.
248 pub markers: &'static [u8],
249 /// Whether something was seen.
250 pub seen: bool,
251 /// Size.
252 pub size: usize,
253 /// Secondary size.
254 pub size_b: usize,
255 /// Tertiary size.
256 pub size_c: usize,
257 /// Index.
258 pub start: usize,
259 /// Index.
260 pub end: usize,
261 /// Slot for an event name.
262 pub token_1: Name,
263 /// Slot for an event name.
264 pub token_2: Name,
265 /// Slot for an event name.
266 pub token_3: Name,
267 /// Slot for an event name.
268 pub token_4: Name,
269 /// Slot for an event name.
270 pub token_5: Name,
271 /// Slot for an event name.
272 pub token_6: Name,
273}
274
275/// A tokenizer itself.
276#[allow(clippy::struct_excessive_bools)]
277#[derive(Debug)]
278pub struct Tokenizer<'a> {
279 /// Jump between line endings.
280 column_start: Vec<(usize, usize)>,
281 // First line where this tokenizer starts.
282 first_line: usize,
283 /// Current point after the last line ending (excluding jump).
284 line_start: Point,
285 /// Track whether the current byte is already consumed (`true`) or expected
286 /// to be consumed (`false`).
287 ///
288 /// Tracked to make sure everything’s valid.
289 consumed: bool,
290 /// Stack of how to handle attempts.
291 attempts: Vec<Attempt>,
292 /// Current byte.
293 pub current: Option<u8>,
294 /// Previous byte.
295 pub previous: Option<u8>,
296 /// Current relative and absolute place in the file.
297 pub point: Point,
298 /// Semantic labels.
299 pub events: Vec<Event>,
300 /// Hierarchy of semantic labels.
301 ///
302 /// Tracked to make sure everything’s valid.
303 pub stack: Vec<Name>,
304 /// Edit map, to batch changes.
305 pub map: EditMap,
306 /// List of resolvers.
307 pub resolvers: Vec<ResolveName>,
308 /// Shared parsing state across tokenizers.
309 pub parse_state: &'a ParseState<'a>,
310 /// A lot of shared fields used to tokenize things.
311 pub tokenize_state: TokenizeState<'a>,
312 /// Whether we would be interrupting something.
313 ///
314 /// Used when tokenizing [flow content][crate::construct::flow].
315 pub interrupt: bool,
316 /// Whether containers cannot “pierce” into the current construct.
317 ///
318 /// Used when tokenizing [document content][crate::construct::document].
319 pub concrete: bool,
320 /// Whether this row is piercing into the current construct with more
321 /// containers.
322 ///
323 /// Used when tokenizing [document content][crate::construct::document].
324 pub pierce: bool,
325 /// Whether this line is lazy: there are less containers than before.
326 pub lazy: bool,
327}
328
329impl<'a> Tokenizer<'a> {
330 /// Create a new tokenizer.
331 pub fn new(point: Point, parse_state: &'a ParseState) -> Tokenizer<'a> {
332 Tokenizer {
333 previous: None,
334 current: None,
335 // To do: reserve size when feeding?
336 column_start: vec![],
337 first_line: point.line,
338 line_start: point.clone(),
339 consumed: true,
340 attempts: vec![],
341 point,
342 stack: vec![],
343 events: vec![],
344 parse_state,
345 tokenize_state: TokenizeState {
346 connect: false,
347 document_container_stack: vec![],
348 document_exits: vec![],
349 document_continued: 0,
350 document_lazy_accepting_before: false,
351 document_data_index: None,
352 document_child_state: None,
353 document_child: None,
354 document_at_first_paragraph_of_list_item: false,
355 definitions: vec![],
356 gfm_footnote_definitions: vec![],
357 mdx_last_parse_error: None,
358 end: 0,
359 label_starts: vec![],
360 label_starts_loose: vec![],
361 marker: 0,
362 marker_b: 0,
363 markers: &[],
364 labels: vec![],
365 seen: false,
366 size: 0,
367 size_b: 0,
368 size_c: 0,
369 space_or_tab_eol_content: None,
370 space_or_tab_eol_connect: false,
371 space_or_tab_eol_ok: false,
372 space_or_tab_connect: false,
373 space_or_tab_content: None,
374 space_or_tab_min: 0,
375 space_or_tab_max: 0,
376 space_or_tab_size: 0,
377 space_or_tab_token: Name::SpaceOrTab,
378 start: 0,
379 token_1: Name::Data,
380 token_2: Name::Data,
381 token_3: Name::Data,
382 token_4: Name::Data,
383 token_5: Name::Data,
384 token_6: Name::Data,
385 wikilinks: vec![],
386 wikilink_starts: vec![],
387 },
388 map: EditMap::new(),
389 interrupt: false,
390 pierce: false,
391 concrete: false,
392 lazy: false,
393 resolvers: vec![],
394 }
395 }
396
397 /// Register a resolver.
398 pub fn register_resolver(&mut self, name: ResolveName) {
399 if !self.resolvers.contains(&name) {
400 self.resolvers.push(name);
401 }
402 }
403
404 /// Register a resolver, before others.
405 pub fn register_resolver_before(&mut self, name: ResolveName) {
406 if !self.resolvers.contains(&name) {
407 self.resolvers.insert(0, name);
408 }
409 }
410
411 /// Define a jump between two places.
412 ///
413 /// This defines to which future index we move after a line ending.
414 pub fn define_skip(&mut self, mut point: Point) {
415 move_point_back(self, &mut point);
416
417 let info = (point.index, point.vs);
418
419 #[cfg(feature = "log")]
420 log::trace!("position: define skip: {:?} -> ({:?})", point.line, info);
421
422 let at = point.line - self.first_line;
423
424 if at >= self.column_start.len() {
425 self.column_start.push(info);
426 } else {
427 self.column_start[at] = info;
428 }
429
430 self.account_for_potential_skip();
431 }
432
433 /// Increment the current positional info if we’re right after a line
434 /// ending, which has a skip defined.
435 fn account_for_potential_skip(&mut self) {
436 let at = self.point.line - self.first_line;
437
438 if self.point.column == 1 && at != self.column_start.len() {
439 self.move_to(self.column_start[at]);
440 }
441 }
442
443 /// Prepare for a next byte to get consumed.
444 fn expect(&mut self, byte: Option<u8>) {
445 debug_assert!(self.consumed, "expected previous byte to be consumed");
446 self.consumed = false;
447 self.current = byte;
448 }
449
450 /// Consume the current byte.
451 /// Each state function is expected to call this to signal that this code is
452 /// used, or call a next function.
453 pub fn consume(&mut self) {
454 debug_assert!(!self.consumed, "expected code to *not* have been consumed: this might be because `State::Retry(x)` instead of `State::Next(x)` was returned");
455 self.move_one();
456
457 self.previous = self.current;
458 // While we’re not at eof, it is at least better to not have the
459 // same current code as `previous` *and* `current`.
460 self.current = None;
461 // Mark as consumed.
462 self.consumed = true;
463 }
464
465 /// Move to the next (virtual) byte.
466 fn move_one(&mut self) {
467 match byte_action(self.parse_state.bytes, &self.point) {
468 ByteAction::Ignore => {
469 self.point.index += 1;
470 }
471 ByteAction::Insert(byte) => {
472 self.previous = Some(byte);
473 self.point.column += 1;
474 self.point.vs += 1;
475 }
476 ByteAction::Normal(byte) => {
477 self.previous = Some(byte);
478 self.point.vs = 0;
479 self.point.index += 1;
480
481 if byte == b'\n' {
482 self.point.line += 1;
483 self.point.column = 1;
484
485 if self.point.line - self.first_line + 1 > self.column_start.len() {
486 self.column_start.push((self.point.index, self.point.vs));
487 }
488
489 self.line_start = self.point.clone();
490
491 self.account_for_potential_skip();
492
493 #[cfg(feature = "log")]
494 log::trace!("position: after eol: `{:?}`", self.point);
495 } else {
496 self.point.column += 1;
497 }
498 }
499 }
500 }
501
502 /// Move (virtual) bytes.
503 fn move_to(&mut self, to: (usize, usize)) {
504 let (to_index, to_vs) = to;
505 while self.point.index < to_index || self.point.index == to_index && self.point.vs < to_vs {
506 self.move_one();
507 }
508 }
509
510 /// Mark the start of a semantic label.
511 pub fn enter(&mut self, name: Name) {
512 enter_impl(self, name, None);
513 }
514
515 /// Enter with a link.
516 pub fn enter_link(&mut self, name: Name, link: Link) {
517 enter_impl(self, name, Some(link));
518 }
519
520 /// Mark the end of a semantic label.
521 pub fn exit(&mut self, name: Name) {
522 let current = self.stack.pop().expect("cannot close w/o open tokens");
523
524 debug_assert_eq!(current, name, "expected exit event to match current event");
525
526 let previous = self.events.last().expect("cannot close w/o open event");
527 let mut point = self.point.clone();
528
529 debug_assert!(
530 current != previous.name
531 || previous.point.index != point.index
532 || previous.point.vs != point.vs,
533 "expected non-empty event"
534 );
535
536 if VOID_EVENTS.iter().any(|d| d == &name) {
537 debug_assert!(
538 current == previous.name,
539 "expected event to be void, instead of including something"
540 );
541 }
542
543 // A bit weird, but if we exit right after a line ending, we *don’t* want to consider
544 // potential skips.
545 if matches!(self.previous, Some(b'\n')) {
546 point = self.line_start.clone();
547 } else {
548 move_point_back(self, &mut point);
549 }
550
551 #[cfg(feature = "log")]
552 log::debug!("exit: `{:?}`", name);
553
554 let event = Event {
555 kind: Kind::Exit,
556 name,
557 point,
558 link: None,
559 };
560 self.events.push(event);
561 }
562
563 /// Capture the tokenizer progress.
564 fn capture(&mut self) -> Progress {
565 Progress {
566 previous: self.previous,
567 current: self.current,
568 point: self.point.clone(),
569 events_len: self.events.len(),
570 stack_len: self.stack.len(),
571 }
572 }
573
574 /// Apply tokenizer progress.
575 fn free(&mut self, previous: Progress) {
576 self.previous = previous.previous;
577 self.current = previous.current;
578 self.point = previous.point;
579 debug_assert!(
580 self.events.len() >= previous.events_len,
581 "expected to restore less events than before"
582 );
583 self.events.truncate(previous.events_len);
584 debug_assert!(
585 self.stack.len() >= previous.stack_len,
586 "expected to restore less stack items than before"
587 );
588 self.stack.truncate(previous.stack_len);
589 }
590
591 /// Stack an attempt, moving to `ok` on [`State::Ok`][] and `nok` on
592 /// [`State::Nok`][], reverting in both cases.
593 pub fn check(&mut self, ok: State, nok: State) {
594 // Always capture (and restore) when checking.
595 // No need to capture (and restore) when `nok` is `State::Nok`, because the
596 // parent attempt will do it.
597 let progress = Some(self.capture());
598 let attempt = Attempt {
599 kind: AttemptKind::Check,
600 progress,
601 ok,
602 nok,
603 };
604 self.attempts.push(attempt);
605 }
606
607 /// Stack an attempt, moving to `ok` on [`State::Ok`][] and `nok` on
608 /// [`State::Nok`][], reverting in the latter case.
609 pub fn attempt(&mut self, ok: State, nok: State) {
610 // Always capture (and restore) when checking.
611 // No need to capture (and restore) when `nok` is `State::Nok`, because the
612 // parent attempt will do it.
613 let progress = if nok == State::Nok {
614 None
615 } else {
616 Some(self.capture())
617 };
618
619 let attempt = Attempt {
620 kind: AttemptKind::Attempt,
621 progress,
622 ok,
623 nok,
624 };
625 self.attempts.push(attempt);
626 }
627
628 /// Tokenize.
629 pub fn push(&mut self, from: (usize, usize), to: (usize, usize), state: State) -> State {
630 push_impl(self, from, to, state, false)
631 }
632
633 /// Flush.
634 pub fn flush(&mut self, state: State, resolve: bool) -> Result<Subresult, message::Message> {
635 let to = (self.point.index, self.point.vs);
636 let state = push_impl(self, to, to, state, true);
637
638 state.to_result()?;
639
640 let mut value = Subresult {
641 done: false,
642 gfm_footnote_definitions: self.tokenize_state.gfm_footnote_definitions.split_off(0),
643 definitions: self.tokenize_state.definitions.split_off(0),
644 };
645
646 if resolve {
647 let resolvers = self.resolvers.split_off(0);
648 let mut index = 0;
649 let defs = &mut value.definitions;
650 let fn_defs = &mut value.gfm_footnote_definitions;
651 while index < resolvers.len() {
652 if let Some(mut result) = call_resolve(self, resolvers[index])? {
653 fn_defs.append(&mut result.gfm_footnote_definitions);
654 defs.append(&mut result.definitions);
655 }
656 index += 1;
657 }
658
659 self.map.consume(&mut self.events);
660 }
661
662 Ok(value)
663 }
664}
665
666/// Move back past ignored bytes.
667fn move_point_back(tokenizer: &mut Tokenizer, point: &mut Point) {
668 while point.index > 0 {
669 point.index -= 1;
670 let action = byte_action(tokenizer.parse_state.bytes, point);
671 if !matches!(action, ByteAction::Ignore) {
672 point.index += 1;
673 break;
674 }
675 }
676}
677
678/// Enter.
679fn enter_impl(tokenizer: &mut Tokenizer, name: Name, link: Option<Link>) {
680 let mut point = tokenizer.point.clone();
681 move_point_back(tokenizer, &mut point);
682
683 #[cfg(feature = "log")]
684 log::debug!("enter: `{:?}`", name);
685
686 tokenizer.stack.push(name.clone());
687 tokenizer.events.push(Event {
688 kind: Kind::Enter,
689 name,
690 point,
691 link,
692 });
693}
694
695/// Run the tokenizer.
696fn push_impl(
697 tokenizer: &mut Tokenizer,
698 from: (usize, usize),
699 to: (usize, usize),
700 mut state: State,
701 flush: bool,
702) -> State {
703 debug_assert!(
704 from.0 > tokenizer.point.index
705 || (from.0 == tokenizer.point.index && from.1 >= tokenizer.point.vs),
706 "cannot move backwards"
707 );
708
709 tokenizer.move_to(from);
710
711 loop {
712 match state {
713 State::Error(_) => break,
714 State::Ok | State::Nok => {
715 if let Some(attempt) = tokenizer.attempts.pop() {
716 if attempt.kind == AttemptKind::Check || state == State::Nok {
717 if let Some(progress) = attempt.progress {
718 tokenizer.free(progress);
719 }
720 }
721
722 tokenizer.consumed = true;
723
724 let next = if state == State::Ok {
725 attempt.ok
726 } else {
727 attempt.nok
728 };
729
730 #[cfg(feature = "log")]
731 log::trace!("attempt: `{:?}` -> `{:?}`", state, next);
732
733 state = next;
734 } else {
735 break;
736 }
737 }
738 State::Next(name) => {
739 let action = if tokenizer.point.index < to.0
740 || (tokenizer.point.index == to.0 && tokenizer.point.vs < to.1)
741 {
742 Some(byte_action(tokenizer.parse_state.bytes, &tokenizer.point))
743 } else if flush {
744 None
745 } else {
746 break;
747 };
748
749 if let Some(ByteAction::Ignore) = action {
750 tokenizer.move_one();
751 } else {
752 let byte =
753 if let Some(ByteAction::Insert(byte) | ByteAction::Normal(byte)) = action {
754 Some(byte)
755 } else {
756 None
757 };
758
759 #[cfg(feature = "log")]
760 log::trace!("feed: {} to {:?}", format_byte_opt(byte), name);
761
762 tokenizer.expect(byte);
763 state = call(tokenizer, name);
764 }
765 }
766 State::Retry(name) => {
767 #[cfg(feature = "log")]
768 log::trace!("retry: `{:?}`", name);
769
770 state = call(tokenizer, name);
771 }
772 }
773 }
774
775 tokenizer.consumed = true;
776
777 if flush {
778 debug_assert!(matches!(state, State::Ok | State::Error(_)), "must be ok");
779 } else {
780 debug_assert!(
781 matches!(state, State::Next(_) | State::Error(_)),
782 "must have a next state"
783 );
784 }
785
786 state
787}
788
789/// Figure out how to handle a byte.
790fn byte_action(bytes: &[u8], point: &Point) -> ByteAction {
791 if point.index < bytes.len() {
792 let byte = bytes[point.index];
793
794 if byte == b'\r' {
795 // CRLF.
796 if point.index < bytes.len() - 1 && bytes[point.index + 1] == b'\n' {
797 ByteAction::Ignore
798 }
799 // CR.
800 else {
801 ByteAction::Normal(b'\n')
802 }
803 } else if byte == b'\t' {
804 let remainder = point.column % TAB_SIZE;
805 let vs = if remainder == 0 {
806 0
807 } else {
808 TAB_SIZE - remainder
809 };
810
811 // On the tab itself, first send it.
812 if point.vs == 0 {
813 if vs == 0 {
814 ByteAction::Normal(byte)
815 } else {
816 ByteAction::Insert(byte)
817 }
818 } else if vs == 0 {
819 ByteAction::Normal(b' ')
820 } else {
821 ByteAction::Insert(b' ')
822 }
823 } else {
824 ByteAction::Normal(byte)
825 }
826 } else {
827 unreachable!("out of bounds")
828 }
829}