overtype 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/overtype.js CHANGED
@@ -164,7 +164,8 @@ class OverType {
164
164
  showActiveLineRaw: false,
165
165
  showStats: false,
166
166
  toolbar: false,
167
- statsFormatter: null
167
+ statsFormatter: null,
168
+ smartLists: true // Enable smart list continuation
168
169
  };
169
170
 
170
171
  // Remove theme and colors from options - these are now global
@@ -583,6 +584,14 @@ class OverType {
583
584
  return;
584
585
  }
585
586
 
587
+ // Handle Enter key for smart list continuation
588
+ if (event.key === 'Enter' && !event.shiftKey && !event.metaKey && !event.ctrlKey && this.options.smartLists) {
589
+ if (this.handleSmartListContinuation()) {
590
+ event.preventDefault();
591
+ return;
592
+ }
593
+ }
594
+
586
595
  // Let shortcuts manager handle other keys
587
596
  const handled = this.shortcuts.handleKeydown(event);
588
597
 
@@ -592,6 +601,141 @@ class OverType {
592
601
  }
593
602
  }
594
603
 
604
+ /**
605
+ * Handle smart list continuation
606
+ * @returns {boolean} Whether the event was handled
607
+ */
608
+ handleSmartListContinuation() {
609
+ const textarea = this.textarea;
610
+ const cursorPos = textarea.selectionStart;
611
+ const context = MarkdownParser.getListContext(textarea.value, cursorPos);
612
+
613
+ if (!context || !context.inList) return false;
614
+
615
+ // Handle empty list item (exit list)
616
+ if (context.content.trim() === '' && cursorPos >= context.markerEndPos) {
617
+ this.deleteListMarker(context);
618
+ return true;
619
+ }
620
+
621
+ // Handle text splitting if cursor is in middle of content
622
+ if (cursorPos > context.markerEndPos && cursorPos < context.lineEnd) {
623
+ this.splitListItem(context, cursorPos);
624
+ } else {
625
+ // Just add new item after current line
626
+ this.insertNewListItem(context);
627
+ }
628
+
629
+ // Handle numbered list renumbering
630
+ if (context.listType === 'numbered') {
631
+ this.scheduleNumberedListUpdate();
632
+ }
633
+
634
+ return true;
635
+ }
636
+
637
+ /**
638
+ * Delete list marker and exit list
639
+ * @private
640
+ */
641
+ deleteListMarker(context) {
642
+ // Select from line start to marker end
643
+ this.textarea.setSelectionRange(context.lineStart, context.markerEndPos);
644
+ document.execCommand('delete');
645
+
646
+ // Trigger input event
647
+ this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
648
+ }
649
+
650
+ /**
651
+ * Insert new list item
652
+ * @private
653
+ */
654
+ insertNewListItem(context) {
655
+ const newItem = MarkdownParser.createNewListItem(context);
656
+ document.execCommand('insertText', false, '\n' + newItem);
657
+
658
+ // Trigger input event
659
+ this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
660
+ }
661
+
662
+ /**
663
+ * Split list item at cursor position
664
+ * @private
665
+ */
666
+ splitListItem(context, cursorPos) {
667
+ // Get text after cursor
668
+ const textAfterCursor = context.content.substring(cursorPos - context.markerEndPos);
669
+
670
+ // Delete text after cursor
671
+ this.textarea.setSelectionRange(cursorPos, context.lineEnd);
672
+ document.execCommand('delete');
673
+
674
+ // Insert new list item with remaining text
675
+ const newItem = MarkdownParser.createNewListItem(context);
676
+ document.execCommand('insertText', false, '\n' + newItem + textAfterCursor);
677
+
678
+ // Position cursor after new list marker
679
+ const newCursorPos = this.textarea.selectionStart - textAfterCursor.length;
680
+ this.textarea.setSelectionRange(newCursorPos, newCursorPos);
681
+
682
+ // Trigger input event
683
+ this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
684
+ }
685
+
686
+ /**
687
+ * Schedule numbered list renumbering
688
+ * @private
689
+ */
690
+ scheduleNumberedListUpdate() {
691
+ // Clear any pending update
692
+ if (this.numberUpdateTimeout) {
693
+ clearTimeout(this.numberUpdateTimeout);
694
+ }
695
+
696
+ // Schedule update after current input cycle
697
+ this.numberUpdateTimeout = setTimeout(() => {
698
+ this.updateNumberedLists();
699
+ }, 10);
700
+ }
701
+
702
+ /**
703
+ * Update/renumber all numbered lists
704
+ * @private
705
+ */
706
+ updateNumberedLists() {
707
+ const value = this.textarea.value;
708
+ const cursorPos = this.textarea.selectionStart;
709
+
710
+ const newValue = MarkdownParser.renumberLists(value);
711
+
712
+ if (newValue !== value) {
713
+ // Calculate cursor offset
714
+ let offset = 0;
715
+ const oldLines = value.split('\n');
716
+ const newLines = newValue.split('\n');
717
+ let charCount = 0;
718
+
719
+ for (let i = 0; i < oldLines.length && charCount < cursorPos; i++) {
720
+ if (oldLines[i] !== newLines[i]) {
721
+ const diff = newLines[i].length - oldLines[i].length;
722
+ if (charCount + oldLines[i].length < cursorPos) {
723
+ offset += diff;
724
+ }
725
+ }
726
+ charCount += oldLines[i].length + 1; // +1 for newline
727
+ }
728
+
729
+ // Update textarea
730
+ this.textarea.value = newValue;
731
+ const newCursorPos = cursorPos + offset;
732
+ this.textarea.setSelectionRange(newCursorPos, newCursorPos);
733
+
734
+ // Trigger update
735
+ this.textarea.dispatchEvent(new Event('input', { bubbles: true }));
736
+ }
737
+ }
738
+
595
739
  /**
596
740
  * Handle scroll events
597
741
  * @private
package/src/parser.js CHANGED
@@ -517,4 +517,166 @@ export class MarkdownParser {
517
517
 
518
518
  return processed;
519
519
  }
520
+
521
+ /**
522
+ * List pattern definitions
523
+ */
524
+ static LIST_PATTERNS = {
525
+ bullet: /^(\s*)([-*+])\s+(.*)$/,
526
+ numbered: /^(\s*)(\d+)\.\s+(.*)$/,
527
+ checkbox: /^(\s*)-\s+\[([ x])\]\s+(.*)$/
528
+ };
529
+
530
+ /**
531
+ * Get list context at cursor position
532
+ * @param {string} text - Full text content
533
+ * @param {number} cursorPosition - Current cursor position
534
+ * @returns {Object} List context information
535
+ */
536
+ static getListContext(text, cursorPosition) {
537
+ // Find the line containing the cursor
538
+ const lines = text.split('\n');
539
+ let currentPos = 0;
540
+ let lineIndex = 0;
541
+ let lineStart = 0;
542
+
543
+ for (let i = 0; i < lines.length; i++) {
544
+ const lineLength = lines[i].length;
545
+ if (currentPos + lineLength >= cursorPosition) {
546
+ lineIndex = i;
547
+ lineStart = currentPos;
548
+ break;
549
+ }
550
+ currentPos += lineLength + 1; // +1 for newline
551
+ }
552
+
553
+ const currentLine = lines[lineIndex];
554
+ const lineEnd = lineStart + currentLine.length;
555
+
556
+ // Check for checkbox first (most specific)
557
+ const checkboxMatch = currentLine.match(this.LIST_PATTERNS.checkbox);
558
+ if (checkboxMatch) {
559
+ return {
560
+ inList: true,
561
+ listType: 'checkbox',
562
+ indent: checkboxMatch[1],
563
+ marker: '-',
564
+ checked: checkboxMatch[2] === 'x',
565
+ content: checkboxMatch[3],
566
+ lineStart,
567
+ lineEnd,
568
+ markerEndPos: lineStart + checkboxMatch[1].length + checkboxMatch[2].length + 5 // indent + "- [ ] "
569
+ };
570
+ }
571
+
572
+ // Check for bullet list
573
+ const bulletMatch = currentLine.match(this.LIST_PATTERNS.bullet);
574
+ if (bulletMatch) {
575
+ return {
576
+ inList: true,
577
+ listType: 'bullet',
578
+ indent: bulletMatch[1],
579
+ marker: bulletMatch[2],
580
+ content: bulletMatch[3],
581
+ lineStart,
582
+ lineEnd,
583
+ markerEndPos: lineStart + bulletMatch[1].length + bulletMatch[2].length + 1 // indent + marker + space
584
+ };
585
+ }
586
+
587
+ // Check for numbered list
588
+ const numberedMatch = currentLine.match(this.LIST_PATTERNS.numbered);
589
+ if (numberedMatch) {
590
+ return {
591
+ inList: true,
592
+ listType: 'numbered',
593
+ indent: numberedMatch[1],
594
+ marker: parseInt(numberedMatch[2]),
595
+ content: numberedMatch[3],
596
+ lineStart,
597
+ lineEnd,
598
+ markerEndPos: lineStart + numberedMatch[1].length + numberedMatch[2].length + 2 // indent + number + ". "
599
+ };
600
+ }
601
+
602
+ // Not in a list
603
+ return {
604
+ inList: false,
605
+ listType: null,
606
+ indent: '',
607
+ marker: null,
608
+ content: currentLine,
609
+ lineStart,
610
+ lineEnd,
611
+ markerEndPos: lineStart
612
+ };
613
+ }
614
+
615
+ /**
616
+ * Create a new list item based on context
617
+ * @param {Object} context - List context from getListContext
618
+ * @returns {string} New list item text
619
+ */
620
+ static createNewListItem(context) {
621
+ switch (context.listType) {
622
+ case 'bullet':
623
+ return `${context.indent}${context.marker} `;
624
+ case 'numbered':
625
+ return `${context.indent}${context.marker + 1}. `;
626
+ case 'checkbox':
627
+ return `${context.indent}- [ ] `;
628
+ default:
629
+ return '';
630
+ }
631
+ }
632
+
633
+ /**
634
+ * Renumber all numbered lists in text
635
+ * @param {string} text - Text containing numbered lists
636
+ * @returns {string} Text with renumbered lists
637
+ */
638
+ static renumberLists(text) {
639
+ const lines = text.split('\n');
640
+ const numbersByIndent = new Map();
641
+ let inList = false;
642
+
643
+ const result = lines.map(line => {
644
+ const match = line.match(this.LIST_PATTERNS.numbered);
645
+
646
+ if (match) {
647
+ const indent = match[1];
648
+ const indentLevel = indent.length;
649
+ const content = match[3];
650
+
651
+ // If we weren't in a list or indent changed, reset lower levels
652
+ if (!inList) {
653
+ numbersByIndent.clear();
654
+ }
655
+
656
+ // Get the next number for this indent level
657
+ const currentNumber = (numbersByIndent.get(indentLevel) || 0) + 1;
658
+ numbersByIndent.set(indentLevel, currentNumber);
659
+
660
+ // Clear deeper indent levels
661
+ for (const [level] of numbersByIndent) {
662
+ if (level > indentLevel) {
663
+ numbersByIndent.delete(level);
664
+ }
665
+ }
666
+
667
+ inList = true;
668
+ return `${indent}${currentNumber}. ${content}`;
669
+ } else {
670
+ // Not a numbered list item
671
+ if (line.trim() === '' || !line.match(/^\s/)) {
672
+ // Empty line or non-indented line breaks the list
673
+ inList = false;
674
+ numbersByIndent.clear();
675
+ }
676
+ return line;
677
+ }
678
+ });
679
+
680
+ return result.join('\n');
681
+ }
520
682
  }