New editor.
This commit is contained in:
		
							parent
							
								
									e62da0aef1
								
							
						
					
					
						commit
						89d639e44e
					
				@ -7,32 +7,92 @@ import { NjetComponent} from "/njet.js"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const style = document.createElement('style');
 | 
					    const style = document.createElement('style');
 | 
				
			||||||
    style.textContent = `
 | 
					    style.textContent = `
 | 
				
			||||||
 | 
					      :host {
 | 
				
			||||||
 | 
					        display: block;
 | 
				
			||||||
 | 
					        position: relative;
 | 
				
			||||||
 | 
					        height: 100%;
 | 
				
			||||||
 | 
					        font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
      #editor {
 | 
					      #editor {
 | 
				
			||||||
        padding: 1rem;
 | 
					        padding: 1rem;
 | 
				
			||||||
        outline: none;
 | 
					        outline: none;
 | 
				
			||||||
        white-space: pre-wrap;
 | 
					        white-space: pre-wrap;
 | 
				
			||||||
        line-height: 1.5;
 | 
					        line-height: 1.5;
 | 
				
			||||||
            height: 100%;
 | 
					        height: calc(100% - 30px);
 | 
				
			||||||
        overflow-y: auto;
 | 
					        overflow-y: auto;
 | 
				
			||||||
        background: #1e1e1e;
 | 
					        background: #1e1e1e;
 | 
				
			||||||
        color: #d4d4d4;
 | 
					        color: #d4d4d4;
 | 
				
			||||||
 | 
					        font-size: 14px;
 | 
				
			||||||
 | 
					        caret-color: #fff;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
          #command-line {
 | 
					      
 | 
				
			||||||
 | 
					      #editor.insert-mode {
 | 
				
			||||||
 | 
					        caret-color: #4ec9b0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      #editor.visual-mode {
 | 
				
			||||||
 | 
					        caret-color: #c586c0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      #editor::selection {
 | 
				
			||||||
 | 
					        background: #264f78;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      #status-bar {
 | 
				
			||||||
        position: absolute;
 | 
					        position: absolute;
 | 
				
			||||||
        bottom: 0;
 | 
					        bottom: 0;
 | 
				
			||||||
        left: 0;
 | 
					        left: 0;
 | 
				
			||||||
 | 
					        right: 0;
 | 
				
			||||||
 | 
					        height: 30px;
 | 
				
			||||||
 | 
					        background: #007acc;
 | 
				
			||||||
 | 
					        color: #fff;
 | 
				
			||||||
 | 
					        display: flex;
 | 
				
			||||||
 | 
					        align-items: center;
 | 
				
			||||||
 | 
					        padding: 0 1rem;
 | 
				
			||||||
 | 
					        font-size: 12px;
 | 
				
			||||||
 | 
					        font-weight: 500;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      #mode-indicator {
 | 
				
			||||||
 | 
					        text-transform: uppercase;
 | 
				
			||||||
 | 
					        margin-right: 20px;
 | 
				
			||||||
 | 
					        font-weight: bold;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      #command-line {
 | 
				
			||||||
 | 
					        position: absolute;
 | 
				
			||||||
 | 
					        bottom: 30px;
 | 
				
			||||||
 | 
					        left: 0;
 | 
				
			||||||
        width: 100%;
 | 
					        width: 100%;
 | 
				
			||||||
            padding: 0.2rem 1rem;
 | 
					        padding: 0.3rem 1rem;
 | 
				
			||||||
            background: #333;
 | 
					        background: #2d2d2d;
 | 
				
			||||||
            color: #0f0;
 | 
					        color: #d4d4d4;
 | 
				
			||||||
        display: none;
 | 
					        display: none;
 | 
				
			||||||
            font-family: monospace;
 | 
					        font-family: inherit;
 | 
				
			||||||
 | 
					        border-top: 1px solid #3e3e3e;
 | 
				
			||||||
 | 
					        font-size: 14px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      #command-input {
 | 
				
			||||||
 | 
					        background: transparent;
 | 
				
			||||||
 | 
					        border: none;
 | 
				
			||||||
 | 
					        color: inherit;
 | 
				
			||||||
 | 
					        outline: none;
 | 
				
			||||||
 | 
					        font-family: inherit;
 | 
				
			||||||
 | 
					        font-size: inherit;
 | 
				
			||||||
 | 
					        width: calc(100% - 20px);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      .visual-selection {
 | 
				
			||||||
 | 
					        background: #264f78 !important;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.editor = document.createElement('div');
 | 
					    this.editor = document.createElement('div');
 | 
				
			||||||
    this.editor.id = 'editor';
 | 
					    this.editor.id = 'editor';
 | 
				
			||||||
    this.editor.contentEditable = true;
 | 
					    this.editor.contentEditable = true;
 | 
				
			||||||
 | 
					    this.editor.spellcheck = false;
 | 
				
			||||||
    this.editor.innerText = `Welcome to VimEditor Component
 | 
					    this.editor.innerText = `Welcome to VimEditor Component
 | 
				
			||||||
Line 2 here
 | 
					Line 2 here
 | 
				
			||||||
Another line
 | 
					Another line
 | 
				
			||||||
@ -40,22 +100,90 @@ Try i, Esc, v, :, yy, dd, 0, $, gg, G, and p`;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    this.cmdLine = document.createElement('div');
 | 
					    this.cmdLine = document.createElement('div');
 | 
				
			||||||
    this.cmdLine.id = 'command-line';
 | 
					    this.cmdLine.id = 'command-line';
 | 
				
			||||||
        this.shadowRoot.append(style, this.editor, this.cmdLine);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        this.mode = 'normal'; // normal | insert | visual | command
 | 
					    const cmdPrompt = document.createElement('span');
 | 
				
			||||||
 | 
					    cmdPrompt.textContent = ':';
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.cmdInput = document.createElement('input');
 | 
				
			||||||
 | 
					    this.cmdInput.id = 'command-input';
 | 
				
			||||||
 | 
					    this.cmdInput.type = 'text';
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.cmdLine.append(cmdPrompt, this.cmdInput);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.statusBar = document.createElement('div');
 | 
				
			||||||
 | 
					    this.statusBar.id = 'status-bar';
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.modeIndicator = document.createElement('span');
 | 
				
			||||||
 | 
					    this.modeIndicator.id = 'mode-indicator';
 | 
				
			||||||
 | 
					    this.modeIndicator.textContent = 'NORMAL';
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.statusBar.appendChild(this.modeIndicator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.shadowRoot.append(style, this.editor, this.cmdLine, this.statusBar);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.mode = 'normal';
 | 
				
			||||||
    this.keyBuffer = '';
 | 
					    this.keyBuffer = '';
 | 
				
			||||||
    this.lastDeletedLine = '';
 | 
					    this.lastDeletedLine = '';
 | 
				
			||||||
    this.yankedLine = '';
 | 
					    this.yankedLine = '';
 | 
				
			||||||
 | 
					    this.visualStartOffset = null;
 | 
				
			||||||
 | 
					    this.visualEndOffset = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.editor.addEventListener('keydown', this.handleKeydown.bind(this));
 | 
					    // Bind event handlers
 | 
				
			||||||
 | 
					    this.handleKeydown = this.handleKeydown.bind(this);
 | 
				
			||||||
 | 
					    this.handleCmdKeydown = this.handleCmdKeydown.bind(this);
 | 
				
			||||||
 | 
					    this.updateVisualSelection = this.updateVisualSelection.bind(this);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    this.editor.addEventListener('keydown', this.handleKeydown);
 | 
				
			||||||
 | 
					    this.cmdInput.addEventListener('keydown', this.handleCmdKeydown);
 | 
				
			||||||
 | 
					    this.editor.addEventListener('beforeinput', this.handleBeforeInput.bind(this));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  connectedCallback() {
 | 
					  connectedCallback() {
 | 
				
			||||||
    this.editor.focus();
 | 
					    this.editor.focus();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setMode(mode) {
 | 
				
			||||||
 | 
					    this.mode = mode;
 | 
				
			||||||
 | 
					    this.modeIndicator.textContent = mode.toUpperCase();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Update editor classes
 | 
				
			||||||
 | 
					    this.editor.classList.remove('insert-mode', 'visual-mode', 'normal-mode');
 | 
				
			||||||
 | 
					    this.editor.classList.add(`${mode}-mode`);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (mode === 'visual') {
 | 
				
			||||||
 | 
					      this.visualStartOffset = this.getCaretOffset();
 | 
				
			||||||
 | 
					      this.editor.addEventListener('selectionchange', this.updateVisualSelection);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.clearVisualSelection();
 | 
				
			||||||
 | 
					      this.editor.removeEventListener('selectionchange', this.updateVisualSelection);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (mode === 'command') {
 | 
				
			||||||
 | 
					      this.cmdLine.style.display = 'block';
 | 
				
			||||||
 | 
					      this.cmdInput.value = '';
 | 
				
			||||||
 | 
					      this.cmdInput.focus();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.cmdLine.style.display = 'none';
 | 
				
			||||||
 | 
					      if (mode !== 'insert') {
 | 
				
			||||||
 | 
					        // Keep focus on editor for all non-insert modes
 | 
				
			||||||
 | 
					        this.editor.focus();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateVisualSelection() {
 | 
				
			||||||
 | 
					    if (this.mode !== 'visual') return;
 | 
				
			||||||
 | 
					    this.visualEndOffset = this.getCaretOffset();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  clearVisualSelection() {
 | 
				
			||||||
 | 
					    const sel = this.shadowRoot.getSelection();
 | 
				
			||||||
 | 
					    if (sel) sel.removeAllRanges();
 | 
				
			||||||
 | 
					    this.visualStartOffset = null;
 | 
				
			||||||
 | 
					    this.visualEndOffset = null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getCaretOffset() {
 | 
					  getCaretOffset() {
 | 
				
			||||||
        let caretOffset = 0;
 | 
					 | 
				
			||||||
    const sel = this.shadowRoot.getSelection();
 | 
					    const sel = this.shadowRoot.getSelection();
 | 
				
			||||||
    if (!sel || sel.rangeCount === 0) return 0;
 | 
					    if (!sel || sel.rangeCount === 0) return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,160 +191,300 @@ Try i, Esc, v, :, yy, dd, 0, $, gg, G, and p`;
 | 
				
			|||||||
    const preCaretRange = range.cloneRange();
 | 
					    const preCaretRange = range.cloneRange();
 | 
				
			||||||
    preCaretRange.selectNodeContents(this.editor);
 | 
					    preCaretRange.selectNodeContents(this.editor);
 | 
				
			||||||
    preCaretRange.setEnd(range.endContainer, range.endOffset);
 | 
					    preCaretRange.setEnd(range.endContainer, range.endOffset);
 | 
				
			||||||
        caretOffset = preCaretRange.toString().length;
 | 
					    return preCaretRange.toString().length;
 | 
				
			||||||
        return caretOffset;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setCaretOffset(offset) {
 | 
					  setCaretOffset(offset) {
 | 
				
			||||||
 | 
					    const textContent = this.editor.innerText;
 | 
				
			||||||
 | 
					    offset = Math.max(0, Math.min(offset, textContent.length));
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    const range = document.createRange();
 | 
					    const range = document.createRange();
 | 
				
			||||||
    const sel = this.shadowRoot.getSelection();
 | 
					    const sel = this.shadowRoot.getSelection();
 | 
				
			||||||
        const walker = document.createTreeWalker(this.editor, NodeFilter.SHOW_TEXT, null, false);
 | 
					    const walker = document.createTreeWalker(
 | 
				
			||||||
 | 
					      this.editor, 
 | 
				
			||||||
 | 
					      NodeFilter.SHOW_TEXT, 
 | 
				
			||||||
 | 
					      null, 
 | 
				
			||||||
 | 
					      false
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let currentOffset = 0;
 | 
					    let currentOffset = 0;
 | 
				
			||||||
    let node;
 | 
					    let node;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    while ((node = walker.nextNode())) {
 | 
					    while ((node = walker.nextNode())) {
 | 
				
			||||||
          if (currentOffset + node.length >= offset) {
 | 
					      const nodeLength = node.textContent.length;
 | 
				
			||||||
 | 
					      if (currentOffset + nodeLength >= offset) {
 | 
				
			||||||
        range.setStart(node, offset - currentOffset);
 | 
					        range.setStart(node, offset - currentOffset);
 | 
				
			||||||
        range.collapse(true);
 | 
					        range.collapse(true);
 | 
				
			||||||
        sel.removeAllRanges();
 | 
					        sel.removeAllRanges();
 | 
				
			||||||
        sel.addRange(range);
 | 
					        sel.addRange(range);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
          currentOffset += node.length;
 | 
					      currentOffset += nodeLength;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // If we couldn't find the position, set to end
 | 
				
			||||||
 | 
					    if (this.editor.lastChild) {
 | 
				
			||||||
 | 
					      range.selectNodeContents(this.editor.lastChild);
 | 
				
			||||||
 | 
					      range.collapse(false);
 | 
				
			||||||
 | 
					      sel.removeAllRanges();
 | 
				
			||||||
 | 
					      sel.addRange(range);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleBeforeInput(e) {
 | 
				
			||||||
 | 
					    if (this.mode !== 'insert') {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleCmdKeydown(e) {
 | 
				
			||||||
 | 
					    if (e.key === 'Enter') {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					      this.executeCommand(this.cmdInput.value);
 | 
				
			||||||
 | 
					      this.setMode('normal');
 | 
				
			||||||
 | 
					    } else if (e.key === 'Escape') {
 | 
				
			||||||
 | 
					      e.preventDefault();
 | 
				
			||||||
 | 
					      this.setMode('normal');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  executeCommand(cmd) {
 | 
				
			||||||
 | 
					    const trimmedCmd = cmd.trim();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Handle basic vim commands
 | 
				
			||||||
 | 
					    if (trimmedCmd === 'w' || trimmedCmd === 'write') {
 | 
				
			||||||
 | 
					      console.log('Save command (not implemented)');
 | 
				
			||||||
 | 
					    } else if (trimmedCmd === 'q' || trimmedCmd === 'quit') {
 | 
				
			||||||
 | 
					      console.log('Quit command (not implemented)');
 | 
				
			||||||
 | 
					    } else if (trimmedCmd === 'wq' || trimmedCmd === 'x') {
 | 
				
			||||||
 | 
					      console.log('Save and quit command (not implemented)');
 | 
				
			||||||
 | 
					    } else if (/^\d+$/.test(trimmedCmd)) {
 | 
				
			||||||
 | 
					      // Go to line number
 | 
				
			||||||
 | 
					      const lineNum = parseInt(trimmedCmd, 10) - 1;
 | 
				
			||||||
 | 
					      this.goToLine(lineNum);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  goToLine(lineNum) {
 | 
				
			||||||
 | 
					    const lines = this.editor.innerText.split('\n');
 | 
				
			||||||
 | 
					    if (lineNum < 0 || lineNum >= lines.length) return;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let offset = 0;
 | 
				
			||||||
 | 
					    for (let i = 0; i < lineNum; i++) {
 | 
				
			||||||
 | 
					      offset += lines[i].length + 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.setCaretOffset(offset);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getCurrentLineInfo() {
 | 
				
			||||||
 | 
					    const text = this.editor.innerText;
 | 
				
			||||||
 | 
					    const caretPos = this.getCaretOffset();
 | 
				
			||||||
 | 
					    const lines = text.split('\n');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    let charCount = 0;
 | 
				
			||||||
 | 
					    for (let i = 0; i < lines.length; i++) {
 | 
				
			||||||
 | 
					      if (caretPos <= charCount + lines[i].length) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          lineIndex: i,
 | 
				
			||||||
 | 
					          lines: lines,
 | 
				
			||||||
 | 
					          lineStartOffset: charCount,
 | 
				
			||||||
 | 
					          positionInLine: caretPos - charCount
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      charCount += lines[i].length + 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      lineIndex: lines.length - 1,
 | 
				
			||||||
 | 
					      lines: lines,
 | 
				
			||||||
 | 
					      lineStartOffset: charCount - lines[lines.length - 1].length - 1,
 | 
				
			||||||
 | 
					      positionInLine: 0
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  handleKeydown(e) {
 | 
					  handleKeydown(e) {
 | 
				
			||||||
        const key = e.key;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (this.mode === 'insert') {
 | 
					    if (this.mode === 'insert') {
 | 
				
			||||||
          if (key === 'Escape') {
 | 
					      if (e.key === 'Escape') {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
            this.mode = 'normal';
 | 
					        this.setMode('normal');
 | 
				
			||||||
            this.editor.blur();
 | 
					        // Move cursor one position left (vim behavior)
 | 
				
			||||||
            this.editor.focus();
 | 
					        const offset = this.getCaretOffset();
 | 
				
			||||||
 | 
					        if (offset > 0) {
 | 
				
			||||||
 | 
					          this.setCaretOffset(offset - 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.mode === 'command') {
 | 
					    if (this.mode === 'command') {
 | 
				
			||||||
          if (key === 'Enter' || key === 'Escape') {
 | 
					      return; // Command mode input is handled by cmdInput
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.cmdLine.style.display = 'none';
 | 
					 | 
				
			||||||
            this.mode = 'normal';
 | 
					 | 
				
			||||||
            this.keyBuffer = '';
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.mode === 'visual') {
 | 
					    if (this.mode === 'visual') {
 | 
				
			||||||
          if (key === 'Escape') {
 | 
					      if (e.key === 'Escape') {
 | 
				
			||||||
        e.preventDefault();
 | 
					        e.preventDefault();
 | 
				
			||||||
            this.mode = 'normal';
 | 
					        this.setMode('normal');
 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
        // Handle normal mode
 | 
					      // Allow movement in visual mode
 | 
				
			||||||
        this.keyBuffer += key;
 | 
					      if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
 | 
				
			||||||
 | 
					        return; // Let default behavior handle selection
 | 
				
			||||||
        const text = this.editor.innerText;
 | 
					 | 
				
			||||||
        const caretPos = this.getCaretOffset();
 | 
					 | 
				
			||||||
        const lines = text.split('\n');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let charCount = 0, lineIdx = 0;
 | 
					 | 
				
			||||||
        for (let i = 0; i < lines.length; i++) {
 | 
					 | 
				
			||||||
          if (caretPos <= charCount + lines[i].length) {
 | 
					 | 
				
			||||||
            lineIdx = i;
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          charCount += lines[i].length + 1;
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
        const offsetToLine = idx =>
 | 
					      if (e.key === 'y') {
 | 
				
			||||||
          text.split('\n').slice(0, idx).reduce((acc, l) => acc + l.length + 1, 0);
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        // Yank selected text
 | 
				
			||||||
 | 
					        const sel = this.shadowRoot.getSelection();
 | 
				
			||||||
 | 
					        if (sel && sel.rangeCount > 0) {
 | 
				
			||||||
 | 
					          this.yankedLine = sel.toString();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.setMode('normal');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      
 | 
					      
 | 
				
			||||||
 | 
					      if (e.key === 'd' || e.key === 'x') {
 | 
				
			||||||
 | 
					        e.preventDefault();
 | 
				
			||||||
 | 
					        // Delete selected text
 | 
				
			||||||
 | 
					        const sel = this.shadowRoot.getSelection();
 | 
				
			||||||
 | 
					        if (sel && sel.rangeCount > 0) {
 | 
				
			||||||
 | 
					          this.lastDeletedLine = sel.toString();
 | 
				
			||||||
 | 
					          document.execCommand('delete');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        this.setMode('normal');
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Normal mode handling
 | 
				
			||||||
 | 
					    e.preventDefault();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Special keys that should be handled immediately
 | 
				
			||||||
 | 
					    if (e.key === 'Escape') {
 | 
				
			||||||
 | 
					      this.keyBuffer = '';
 | 
				
			||||||
 | 
					      this.setMode('normal');
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Build key buffer for commands
 | 
				
			||||||
 | 
					    this.keyBuffer += e.key;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    const lineInfo = this.getCurrentLineInfo();
 | 
				
			||||||
 | 
					    const { lineIndex, lines, lineStartOffset, positionInLine } = lineInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Process commands
 | 
				
			||||||
    switch (this.keyBuffer) {
 | 
					    switch (this.keyBuffer) {
 | 
				
			||||||
      case 'i':
 | 
					      case 'i':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.mode = 'insert';
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setMode('insert');
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case 'a':
 | 
				
			||||||
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setCaretOffset(this.getCaretOffset() + 1);
 | 
				
			||||||
 | 
					        this.setMode('insert');
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case 'v':
 | 
					      case 'v':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.mode = 'visual';
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setMode('visual');
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case ':':
 | 
					      case ':':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.mode = 'command';
 | 
					 | 
				
			||||||
            this.cmdLine.style.display = 'block';
 | 
					 | 
				
			||||||
            this.cmdLine.textContent = ':';
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setMode('command');
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case 'yy':
 | 
					      case 'yy':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.yankedLine = lines[lineIdx];
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.yankedLine = lines[lineIndex];
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case 'dd':
 | 
					      case 'dd':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.lastDeletedLine = lines[lineIdx];
 | 
					 | 
				
			||||||
            lines.splice(lineIdx, 1);
 | 
					 | 
				
			||||||
            this.editor.innerText = lines.join('\n');
 | 
					 | 
				
			||||||
            this.setCaretOffset(offsetToLine(lineIdx));
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.lastDeletedLine = lines[lineIndex];
 | 
				
			||||||
 | 
					        lines.splice(lineIndex, 1);
 | 
				
			||||||
 | 
					        if (lines.length === 0) lines.push('');
 | 
				
			||||||
 | 
					        this.editor.innerText = lines.join('\n');
 | 
				
			||||||
 | 
					        this.setCaretOffset(lineStartOffset);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case 'p':
 | 
					      case 'p':
 | 
				
			||||||
            e.preventDefault();
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
        const lineToPaste = this.yankedLine || this.lastDeletedLine;
 | 
					        const lineToPaste = this.yankedLine || this.lastDeletedLine;
 | 
				
			||||||
        if (lineToPaste) {
 | 
					        if (lineToPaste) {
 | 
				
			||||||
              lines.splice(lineIdx + 1, 0, lineToPaste);
 | 
					          lines.splice(lineIndex + 1, 0, lineToPaste);
 | 
				
			||||||
          this.editor.innerText = lines.join('\n');
 | 
					          this.editor.innerText = lines.join('\n');
 | 
				
			||||||
              this.setCaretOffset(offsetToLine(lineIdx + 1));
 | 
					          this.setCaretOffset(lineStartOffset + lines[lineIndex].length + 1);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            this.keyBuffer = '';
 | 
					 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case '0':
 | 
					      case '0':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.setCaretOffset(offsetToLine(lineIdx));
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setCaretOffset(lineStartOffset);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case '$':
 | 
					      case '$':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.setCaretOffset(offsetToLine(lineIdx) + lines[lineIdx].length);
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setCaretOffset(lineStartOffset + lines[lineIndex].length);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case 'gg':
 | 
					      case 'gg':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.setCaretOffset(0);
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setCaretOffset(0);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      case 'G':
 | 
					      case 'G':
 | 
				
			||||||
            e.preventDefault();
 | 
					 | 
				
			||||||
            this.setCaretOffset(text.length);
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setCaretOffset(this.editor.innerText.length);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          case 'Escape':
 | 
					      case 'h':
 | 
				
			||||||
            e.preventDefault();
 | 
					      case 'ArrowLeft':
 | 
				
			||||||
            this.mode = 'normal';
 | 
					 | 
				
			||||||
        this.keyBuffer = '';
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
            this.cmdLine.style.display = 'none';
 | 
					        const currentOffset = this.getCaretOffset();
 | 
				
			||||||
 | 
					        if (currentOffset > 0) {
 | 
				
			||||||
 | 
					          this.setCaretOffset(currentOffset - 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case 'l':
 | 
				
			||||||
 | 
					      case 'ArrowRight':
 | 
				
			||||||
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        this.setCaretOffset(this.getCaretOffset() + 1);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case 'j':
 | 
				
			||||||
 | 
					      case 'ArrowDown':
 | 
				
			||||||
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        if (lineIndex < lines.length - 1) {
 | 
				
			||||||
 | 
					          const nextLineStart = lineStartOffset + lines[lineIndex].length + 1;
 | 
				
			||||||
 | 
					          const nextLineLength = lines[lineIndex + 1].length;
 | 
				
			||||||
 | 
					          const newPosition = Math.min(positionInLine, nextLineLength);
 | 
				
			||||||
 | 
					          this.setCaretOffset(nextLineStart + newPosition);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      case 'k':
 | 
				
			||||||
 | 
					      case 'ArrowUp':
 | 
				
			||||||
 | 
					        this.keyBuffer = '';
 | 
				
			||||||
 | 
					        if (lineIndex > 0) {
 | 
				
			||||||
 | 
					          let prevLineStart = 0;
 | 
				
			||||||
 | 
					          for (let i = 0; i < lineIndex - 1; i++) {
 | 
				
			||||||
 | 
					            prevLineStart += lines[i].length + 1;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          const prevLineLength = lines[lineIndex - 1].length;
 | 
				
			||||||
 | 
					          const newPosition = Math.min(positionInLine, prevLineLength);
 | 
				
			||||||
 | 
					          this.setCaretOffset(prevLineStart + newPosition);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      default:
 | 
					      default:
 | 
				
			||||||
            // allow up to 2 chars for combos
 | 
					        // Clear buffer if it gets too long or contains invalid sequences
 | 
				
			||||||
            if (this.keyBuffer.length > 2) this.keyBuffer = '';
 | 
					        if (this.keyBuffer.length > 2 || 
 | 
				
			||||||
 | 
					            (this.keyBuffer.length === 2 && !['dd', 'yy', 'gg'].includes(this.keyBuffer))) {
 | 
				
			||||||
 | 
					          this.keyBuffer = '';
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user