Markdown parser fork with extended syntax for personal use.
at main 824 lines 26 kB view raw
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 // Couple of media related fields. 217 /// List of usable label starts. 218 /// 219 /// Used when tokenizing [text content][crate::construct::text]. 220 pub label_starts: Vec<LabelStart>, 221 /// List of unusable label starts. 222 /// 223 /// Used when tokenizing [text content][crate::construct::text]. 224 pub label_starts_loose: Vec<LabelStart>, 225 /// Stack of images and links. 226 /// 227 /// Used when tokenizing [text content][crate::construct::text]. 228 pub labels: Vec<Label>, 229 230 /// List of defined definition identifiers. 231 pub definitions: Vec<String>, 232 /// List of defined GFM footnote definition identifiers. 233 pub gfm_footnote_definitions: Vec<String>, 234 235 // Last error message provided at an EOF of an expression. 236 pub mdx_last_parse_error: Option<(String, String, String)>, 237 238 /// Whether to connect events. 239 pub connect: bool, 240 /// Marker. 241 pub marker: u8, 242 /// Secondary marker. 243 pub marker_b: u8, 244 /// Several markers. 245 pub markers: &'static [u8], 246 /// Whether something was seen. 247 pub seen: bool, 248 /// Size. 249 pub size: usize, 250 /// Secondary size. 251 pub size_b: usize, 252 /// Tertiary size. 253 pub size_c: usize, 254 /// Index. 255 pub start: usize, 256 /// Index. 257 pub end: usize, 258 /// Slot for an event name. 259 pub token_1: Name, 260 /// Slot for an event name. 261 pub token_2: Name, 262 /// Slot for an event name. 263 pub token_3: Name, 264 /// Slot for an event name. 265 pub token_4: Name, 266 /// Slot for an event name. 267 pub token_5: Name, 268 /// Slot for an event name. 269 pub token_6: Name, 270} 271 272/// A tokenizer itself. 273#[allow(clippy::struct_excessive_bools)] 274#[derive(Debug)] 275pub struct Tokenizer<'a> { 276 /// Jump between line endings. 277 column_start: Vec<(usize, usize)>, 278 // First line where this tokenizer starts. 279 first_line: usize, 280 /// Current point after the last line ending (excluding jump). 281 line_start: Point, 282 /// Track whether the current byte is already consumed (`true`) or expected 283 /// to be consumed (`false`). 284 /// 285 /// Tracked to make sure everything’s valid. 286 consumed: bool, 287 /// Stack of how to handle attempts. 288 attempts: Vec<Attempt>, 289 /// Current byte. 290 pub current: Option<u8>, 291 /// Previous byte. 292 pub previous: Option<u8>, 293 /// Current relative and absolute place in the file. 294 pub point: Point, 295 /// Semantic labels. 296 pub events: Vec<Event>, 297 /// Hierarchy of semantic labels. 298 /// 299 /// Tracked to make sure everything’s valid. 300 pub stack: Vec<Name>, 301 /// Edit map, to batch changes. 302 pub map: EditMap, 303 /// List of resolvers. 304 pub resolvers: Vec<ResolveName>, 305 /// Shared parsing state across tokenizers. 306 pub parse_state: &'a ParseState<'a>, 307 /// A lot of shared fields used to tokenize things. 308 pub tokenize_state: TokenizeState<'a>, 309 /// Whether we would be interrupting something. 310 /// 311 /// Used when tokenizing [flow content][crate::construct::flow]. 312 pub interrupt: bool, 313 /// Whether containers cannot “pierce” into the current construct. 314 /// 315 /// Used when tokenizing [document content][crate::construct::document]. 316 pub concrete: bool, 317 /// Whether this row is piercing into the current construct with more 318 /// containers. 319 /// 320 /// Used when tokenizing [document content][crate::construct::document]. 321 pub pierce: bool, 322 /// Whether this line is lazy: there are less containers than before. 323 pub lazy: bool, 324} 325 326impl<'a> Tokenizer<'a> { 327 /// Create a new tokenizer. 328 pub fn new(point: Point, parse_state: &'a ParseState) -> Tokenizer<'a> { 329 Tokenizer { 330 previous: None, 331 current: None, 332 // To do: reserve size when feeding? 333 column_start: vec![], 334 first_line: point.line, 335 line_start: point.clone(), 336 consumed: true, 337 attempts: vec![], 338 point, 339 stack: vec![], 340 events: vec![], 341 parse_state, 342 tokenize_state: TokenizeState { 343 connect: false, 344 document_container_stack: vec![], 345 document_exits: vec![], 346 document_continued: 0, 347 document_lazy_accepting_before: false, 348 document_data_index: None, 349 document_child_state: None, 350 document_child: None, 351 document_at_first_paragraph_of_list_item: false, 352 definitions: vec![], 353 gfm_footnote_definitions: vec![], 354 mdx_last_parse_error: None, 355 end: 0, 356 label_starts: vec![], 357 label_starts_loose: vec![], 358 marker: 0, 359 marker_b: 0, 360 markers: &[], 361 labels: vec![], 362 seen: false, 363 size: 0, 364 size_b: 0, 365 size_c: 0, 366 space_or_tab_eol_content: None, 367 space_or_tab_eol_connect: false, 368 space_or_tab_eol_ok: false, 369 space_or_tab_connect: false, 370 space_or_tab_content: None, 371 space_or_tab_min: 0, 372 space_or_tab_max: 0, 373 space_or_tab_size: 0, 374 space_or_tab_token: Name::SpaceOrTab, 375 start: 0, 376 token_1: Name::Data, 377 token_2: Name::Data, 378 token_3: Name::Data, 379 token_4: Name::Data, 380 token_5: Name::Data, 381 token_6: Name::Data, 382 }, 383 map: EditMap::new(), 384 interrupt: false, 385 pierce: false, 386 concrete: false, 387 lazy: false, 388 resolvers: vec![], 389 } 390 } 391 392 /// Register a resolver. 393 pub fn register_resolver(&mut self, name: ResolveName) { 394 if !self.resolvers.contains(&name) { 395 self.resolvers.push(name); 396 } 397 } 398 399 /// Register a resolver, before others. 400 pub fn register_resolver_before(&mut self, name: ResolveName) { 401 if !self.resolvers.contains(&name) { 402 self.resolvers.insert(0, name); 403 } 404 } 405 406 /// Define a jump between two places. 407 /// 408 /// This defines to which future index we move after a line ending. 409 pub fn define_skip(&mut self, mut point: Point) { 410 move_point_back(self, &mut point); 411 412 let info = (point.index, point.vs); 413 414 #[cfg(feature = "log")] 415 log::trace!("position: define skip: {:?} -> ({:?})", point.line, info); 416 417 let at = point.line - self.first_line; 418 419 if at >= self.column_start.len() { 420 self.column_start.push(info); 421 } else { 422 self.column_start[at] = info; 423 } 424 425 self.account_for_potential_skip(); 426 } 427 428 /// Increment the current positional info if we’re right after a line 429 /// ending, which has a skip defined. 430 fn account_for_potential_skip(&mut self) { 431 let at = self.point.line - self.first_line; 432 433 if self.point.column == 1 && at != self.column_start.len() { 434 self.move_to(self.column_start[at]); 435 } 436 } 437 438 /// Prepare for a next byte to get consumed. 439 fn expect(&mut self, byte: Option<u8>) { 440 debug_assert!(self.consumed, "expected previous byte to be consumed"); 441 self.consumed = false; 442 self.current = byte; 443 } 444 445 /// Consume the current byte. 446 /// Each state function is expected to call this to signal that this code is 447 /// used, or call a next function. 448 pub fn consume(&mut self) { 449 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"); 450 self.move_one(); 451 452 self.previous = self.current; 453 // While we’re not at eof, it is at least better to not have the 454 // same current code as `previous` *and* `current`. 455 self.current = None; 456 // Mark as consumed. 457 self.consumed = true; 458 } 459 460 /// Move to the next (virtual) byte. 461 fn move_one(&mut self) { 462 match byte_action(self.parse_state.bytes, &self.point) { 463 ByteAction::Ignore => { 464 self.point.index += 1; 465 } 466 ByteAction::Insert(byte) => { 467 self.previous = Some(byte); 468 self.point.column += 1; 469 self.point.vs += 1; 470 } 471 ByteAction::Normal(byte) => { 472 self.previous = Some(byte); 473 self.point.vs = 0; 474 self.point.index += 1; 475 476 if byte == b'\n' { 477 self.point.line += 1; 478 self.point.column = 1; 479 480 if self.point.line - self.first_line + 1 > self.column_start.len() { 481 self.column_start.push((self.point.index, self.point.vs)); 482 } 483 484 self.line_start = self.point.clone(); 485 486 self.account_for_potential_skip(); 487 488 #[cfg(feature = "log")] 489 log::trace!("position: after eol: `{:?}`", self.point); 490 } else { 491 self.point.column += 1; 492 } 493 } 494 } 495 } 496 497 /// Move (virtual) bytes. 498 fn move_to(&mut self, to: (usize, usize)) { 499 let (to_index, to_vs) = to; 500 while self.point.index < to_index || self.point.index == to_index && self.point.vs < to_vs { 501 self.move_one(); 502 } 503 } 504 505 /// Mark the start of a semantic label. 506 pub fn enter(&mut self, name: Name) { 507 enter_impl(self, name, None); 508 } 509 510 /// Enter with a link. 511 pub fn enter_link(&mut self, name: Name, link: Link) { 512 enter_impl(self, name, Some(link)); 513 } 514 515 /// Mark the end of a semantic label. 516 pub fn exit(&mut self, name: Name) { 517 let current = self.stack.pop().expect("cannot close w/o open tokens"); 518 519 debug_assert_eq!(current, name, "expected exit event to match current event"); 520 521 let previous = self.events.last().expect("cannot close w/o open event"); 522 let mut point = self.point.clone(); 523 524 debug_assert!( 525 current != previous.name 526 || previous.point.index != point.index 527 || previous.point.vs != point.vs, 528 "expected non-empty event" 529 ); 530 531 if VOID_EVENTS.iter().any(|d| d == &name) { 532 debug_assert!( 533 current == previous.name, 534 "expected event to be void, instead of including something" 535 ); 536 } 537 538 // A bit weird, but if we exit right after a line ending, we *don’t* want to consider 539 // potential skips. 540 if matches!(self.previous, Some(b'\n')) { 541 point = self.line_start.clone(); 542 } else { 543 move_point_back(self, &mut point); 544 } 545 546 #[cfg(feature = "log")] 547 log::debug!("exit: `{:?}`", name); 548 549 let event = Event { 550 kind: Kind::Exit, 551 name, 552 point, 553 link: None, 554 }; 555 self.events.push(event); 556 } 557 558 /// Capture the tokenizer progress. 559 fn capture(&mut self) -> Progress { 560 Progress { 561 previous: self.previous, 562 current: self.current, 563 point: self.point.clone(), 564 events_len: self.events.len(), 565 stack_len: self.stack.len(), 566 } 567 } 568 569 /// Apply tokenizer progress. 570 fn free(&mut self, previous: Progress) { 571 self.previous = previous.previous; 572 self.current = previous.current; 573 self.point = previous.point; 574 debug_assert!( 575 self.events.len() >= previous.events_len, 576 "expected to restore less events than before" 577 ); 578 self.events.truncate(previous.events_len); 579 debug_assert!( 580 self.stack.len() >= previous.stack_len, 581 "expected to restore less stack items than before" 582 ); 583 self.stack.truncate(previous.stack_len); 584 } 585 586 /// Stack an attempt, moving to `ok` on [`State::Ok`][] and `nok` on 587 /// [`State::Nok`][], reverting in both cases. 588 pub fn check(&mut self, ok: State, nok: State) { 589 // Always capture (and restore) when checking. 590 // No need to capture (and restore) when `nok` is `State::Nok`, because the 591 // parent attempt will do it. 592 let progress = Some(self.capture()); 593 let attempt = Attempt { 594 kind: AttemptKind::Check, 595 progress, 596 ok, 597 nok, 598 }; 599 self.attempts.push(attempt); 600 } 601 602 /// Stack an attempt, moving to `ok` on [`State::Ok`][] and `nok` on 603 /// [`State::Nok`][], reverting in the latter case. 604 pub fn attempt(&mut self, ok: State, nok: State) { 605 // Always capture (and restore) when checking. 606 // No need to capture (and restore) when `nok` is `State::Nok`, because the 607 // parent attempt will do it. 608 let progress = if nok == State::Nok { 609 None 610 } else { 611 Some(self.capture()) 612 }; 613 614 let attempt = Attempt { 615 kind: AttemptKind::Attempt, 616 progress, 617 ok, 618 nok, 619 }; 620 self.attempts.push(attempt); 621 } 622 623 /// Tokenize. 624 pub fn push(&mut self, from: (usize, usize), to: (usize, usize), state: State) -> State { 625 push_impl(self, from, to, state, false) 626 } 627 628 /// Flush. 629 pub fn flush(&mut self, state: State, resolve: bool) -> Result<Subresult, message::Message> { 630 let to = (self.point.index, self.point.vs); 631 let state = push_impl(self, to, to, state, true); 632 633 state.to_result()?; 634 635 let mut value = Subresult { 636 done: false, 637 gfm_footnote_definitions: self.tokenize_state.gfm_footnote_definitions.split_off(0), 638 definitions: self.tokenize_state.definitions.split_off(0), 639 }; 640 641 if resolve { 642 let resolvers = self.resolvers.split_off(0); 643 let mut index = 0; 644 let defs = &mut value.definitions; 645 let fn_defs = &mut value.gfm_footnote_definitions; 646 while index < resolvers.len() { 647 if let Some(mut result) = call_resolve(self, resolvers[index])? { 648 fn_defs.append(&mut result.gfm_footnote_definitions); 649 defs.append(&mut result.definitions); 650 } 651 index += 1; 652 } 653 654 self.map.consume(&mut self.events); 655 } 656 657 Ok(value) 658 } 659} 660 661/// Move back past ignored bytes. 662fn move_point_back(tokenizer: &mut Tokenizer, point: &mut Point) { 663 while point.index > 0 { 664 point.index -= 1; 665 let action = byte_action(tokenizer.parse_state.bytes, point); 666 if !matches!(action, ByteAction::Ignore) { 667 point.index += 1; 668 break; 669 } 670 } 671} 672 673/// Enter. 674fn enter_impl(tokenizer: &mut Tokenizer, name: Name, link: Option<Link>) { 675 let mut point = tokenizer.point.clone(); 676 move_point_back(tokenizer, &mut point); 677 678 #[cfg(feature = "log")] 679 log::debug!("enter: `{:?}`", name); 680 681 tokenizer.stack.push(name.clone()); 682 tokenizer.events.push(Event { 683 kind: Kind::Enter, 684 name, 685 point, 686 link, 687 }); 688} 689 690/// Run the tokenizer. 691fn push_impl( 692 tokenizer: &mut Tokenizer, 693 from: (usize, usize), 694 to: (usize, usize), 695 mut state: State, 696 flush: bool, 697) -> State { 698 debug_assert!( 699 from.0 > tokenizer.point.index 700 || (from.0 == tokenizer.point.index && from.1 >= tokenizer.point.vs), 701 "cannot move backwards" 702 ); 703 704 tokenizer.move_to(from); 705 706 loop { 707 match state { 708 State::Error(_) => break, 709 State::Ok | State::Nok => { 710 if let Some(attempt) = tokenizer.attempts.pop() { 711 if attempt.kind == AttemptKind::Check || state == State::Nok { 712 if let Some(progress) = attempt.progress { 713 tokenizer.free(progress); 714 } 715 } 716 717 tokenizer.consumed = true; 718 719 let next = if state == State::Ok { 720 attempt.ok 721 } else { 722 attempt.nok 723 }; 724 725 #[cfg(feature = "log")] 726 log::trace!("attempt: `{:?}` -> `{:?}`", state, next); 727 728 state = next; 729 } else { 730 break; 731 } 732 } 733 State::Next(name) => { 734 let action = if tokenizer.point.index < to.0 735 || (tokenizer.point.index == to.0 && tokenizer.point.vs < to.1) 736 { 737 Some(byte_action(tokenizer.parse_state.bytes, &tokenizer.point)) 738 } else if flush { 739 None 740 } else { 741 break; 742 }; 743 744 if let Some(ByteAction::Ignore) = action { 745 tokenizer.move_one(); 746 } else { 747 let byte = 748 if let Some(ByteAction::Insert(byte) | ByteAction::Normal(byte)) = action { 749 Some(byte) 750 } else { 751 None 752 }; 753 754 #[cfg(feature = "log")] 755 log::trace!("feed: {} to {:?}", format_byte_opt(byte), name); 756 757 tokenizer.expect(byte); 758 state = call(tokenizer, name); 759 } 760 } 761 State::Retry(name) => { 762 #[cfg(feature = "log")] 763 log::trace!("retry: `{:?}`", name); 764 765 state = call(tokenizer, name); 766 } 767 } 768 } 769 770 tokenizer.consumed = true; 771 772 if flush { 773 debug_assert!(matches!(state, State::Ok | State::Error(_)), "must be ok"); 774 } else { 775 debug_assert!( 776 matches!(state, State::Next(_) | State::Error(_)), 777 "must have a next state" 778 ); 779 } 780 781 state 782} 783 784/// Figure out how to handle a byte. 785fn byte_action(bytes: &[u8], point: &Point) -> ByteAction { 786 if point.index < bytes.len() { 787 let byte = bytes[point.index]; 788 789 if byte == b'\r' { 790 // CRLF. 791 if point.index < bytes.len() - 1 && bytes[point.index + 1] == b'\n' { 792 ByteAction::Ignore 793 } 794 // CR. 795 else { 796 ByteAction::Normal(b'\n') 797 } 798 } else if byte == b'\t' { 799 let remainder = point.column % TAB_SIZE; 800 let vs = if remainder == 0 { 801 0 802 } else { 803 TAB_SIZE - remainder 804 }; 805 806 // On the tab itself, first send it. 807 if point.vs == 0 { 808 if vs == 0 { 809 ByteAction::Normal(byte) 810 } else { 811 ByteAction::Insert(byte) 812 } 813 } else if vs == 0 { 814 ByteAction::Normal(b' ') 815 } else { 816 ByteAction::Insert(b' ') 817 } 818 } else { 819 ByteAction::Normal(byte) 820 } 821 } else { 822 unreachable!("out of bounds") 823 } 824}