New editor.
This commit is contained in:
parent
e62da0aef1
commit
89d639e44e
@ -1,226 +1,494 @@
|
|||||||
import { NjetComponent} from "/njet.js"
|
import { NjetComponent } from "/njet.js"
|
||||||
|
|
||||||
class NjetEditor extends NjetComponent {
|
class NjetEditor extends NjetComponent {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.attachShadow({ mode: 'open' });
|
this.attachShadow({ mode: 'open' });
|
||||||
|
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
#editor {
|
:host {
|
||||||
padding: 1rem;
|
display: block;
|
||||||
outline: none;
|
position: relative;
|
||||||
white-space: pre-wrap;
|
height: 100%;
|
||||||
line-height: 1.5;
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
height: 100%;
|
}
|
||||||
overflow-y: auto;
|
|
||||||
background: #1e1e1e;
|
|
||||||
color: #d4d4d4;
|
|
||||||
}
|
|
||||||
#command-line {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.2rem 1rem;
|
|
||||||
background: #333;
|
|
||||||
color: #0f0;
|
|
||||||
display: none;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
this.editor = document.createElement('div');
|
#editor {
|
||||||
this.editor.id = 'editor';
|
padding: 1rem;
|
||||||
this.editor.contentEditable = true;
|
outline: none;
|
||||||
this.editor.innerText = `Welcome to VimEditor Component
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.5;
|
||||||
|
height: calc(100% - 30px);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
font-size: 14px;
|
||||||
|
caret-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor.insert-mode {
|
||||||
|
caret-color: #4ec9b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor.visual-mode {
|
||||||
|
caret-color: #c586c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor::selection {
|
||||||
|
background: #264f78;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 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%;
|
||||||
|
padding: 0.3rem 1rem;
|
||||||
|
background: #2d2d2d;
|
||||||
|
color: #d4d4d4;
|
||||||
|
display: none;
|
||||||
|
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.id = 'editor';
|
||||||
|
this.editor.contentEditable = true;
|
||||||
|
this.editor.spellcheck = false;
|
||||||
|
this.editor.innerText = `Welcome to VimEditor Component
|
||||||
Line 2 here
|
Line 2 here
|
||||||
Another line
|
Another line
|
||||||
Try i, Esc, v, :, yy, dd, 0, $, gg, G, and p`;
|
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');
|
||||||
this.keyBuffer = '';
|
cmdPrompt.textContent = ':';
|
||||||
this.lastDeletedLine = '';
|
|
||||||
this.yankedLine = '';
|
|
||||||
|
|
||||||
this.editor.addEventListener('keydown', this.handleKeydown.bind(this));
|
this.cmdInput = document.createElement('input');
|
||||||
}
|
this.cmdInput.id = 'command-input';
|
||||||
|
this.cmdInput.type = 'text';
|
||||||
|
|
||||||
connectedCallback() {
|
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.lastDeletedLine = '';
|
||||||
|
this.yankedLine = '';
|
||||||
|
this.visualStartOffset = null;
|
||||||
|
this.visualEndOffset = null;
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
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();
|
this.editor.focus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getCaretOffset() {
|
updateVisualSelection() {
|
||||||
let caretOffset = 0;
|
if (this.mode !== 'visual') return;
|
||||||
const sel = this.shadowRoot.getSelection();
|
this.visualEndOffset = this.getCaretOffset();
|
||||||
if (!sel || sel.rangeCount === 0) return 0;
|
}
|
||||||
|
|
||||||
const range = sel.getRangeAt(0);
|
clearVisualSelection() {
|
||||||
const preCaretRange = range.cloneRange();
|
const sel = this.shadowRoot.getSelection();
|
||||||
preCaretRange.selectNodeContents(this.editor);
|
if (sel) sel.removeAllRanges();
|
||||||
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
this.visualStartOffset = null;
|
||||||
caretOffset = preCaretRange.toString().length;
|
this.visualEndOffset = null;
|
||||||
return caretOffset;
|
}
|
||||||
|
|
||||||
|
getCaretOffset() {
|
||||||
|
const sel = this.shadowRoot.getSelection();
|
||||||
|
if (!sel || sel.rangeCount === 0) return 0;
|
||||||
|
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
const preCaretRange = range.cloneRange();
|
||||||
|
preCaretRange.selectNodeContents(this.editor);
|
||||||
|
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
||||||
|
return preCaretRange.toString().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCaretOffset(offset) {
|
||||||
|
const textContent = this.editor.innerText;
|
||||||
|
offset = Math.max(0, Math.min(offset, textContent.length));
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
const sel = this.shadowRoot.getSelection();
|
||||||
|
const walker = document.createTreeWalker(
|
||||||
|
this.editor,
|
||||||
|
NodeFilter.SHOW_TEXT,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
let currentOffset = 0;
|
||||||
|
let node;
|
||||||
|
|
||||||
|
while ((node = walker.nextNode())) {
|
||||||
|
const nodeLength = node.textContent.length;
|
||||||
|
if (currentOffset + nodeLength >= offset) {
|
||||||
|
range.setStart(node, offset - currentOffset);
|
||||||
|
range.collapse(true);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
currentOffset += nodeLength;
|
||||||
|
}
|
||||||
|
|
||||||
setCaretOffset(offset) {
|
// If we couldn't find the position, set to end
|
||||||
const range = document.createRange();
|
if (this.editor.lastChild) {
|
||||||
const sel = this.shadowRoot.getSelection();
|
range.selectNodeContents(this.editor.lastChild);
|
||||||
const walker = document.createTreeWalker(this.editor, NodeFilter.SHOW_TEXT, null, false);
|
range.collapse(false);
|
||||||
|
sel.removeAllRanges();
|
||||||
|
sel.addRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let currentOffset = 0;
|
handleBeforeInput(e) {
|
||||||
let node;
|
if (this.mode !== 'insert') {
|
||||||
while ((node = walker.nextNode())) {
|
e.preventDefault();
|
||||||
if (currentOffset + node.length >= offset) {
|
}
|
||||||
range.setStart(node, offset - currentOffset);
|
}
|
||||||
range.collapse(true);
|
|
||||||
sel.removeAllRanges();
|
handleCmdKeydown(e) {
|
||||||
sel.addRange(range);
|
if (e.key === 'Enter') {
|
||||||
return;
|
e.preventDefault();
|
||||||
}
|
this.executeCommand(this.cmdInput.value);
|
||||||
currentOffset += node.length;
|
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) {
|
||||||
|
if (this.mode === 'insert') {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.setMode('normal');
|
||||||
|
// Move cursor one position left (vim behavior)
|
||||||
|
const offset = this.getCaretOffset();
|
||||||
|
if (offset > 0) {
|
||||||
|
this.setCaretOffset(offset - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
handleKeydown(e) {
|
if (this.mode === 'command') {
|
||||||
const key = e.key;
|
return; // Command mode input is handled by cmdInput
|
||||||
|
}
|
||||||
|
|
||||||
if (this.mode === 'insert') {
|
if (this.mode === 'visual') {
|
||||||
if (key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.mode = 'normal';
|
this.setMode('normal');
|
||||||
this.editor.blur();
|
return;
|
||||||
this.editor.focus();
|
}
|
||||||
}
|
|
||||||
return;
|
// Allow movement in visual mode
|
||||||
|
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) {
|
||||||
|
return; // Let default behavior handle selection
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.key === 'y') {
|
||||||
|
e.preventDefault();
|
||||||
|
// Yank selected text
|
||||||
|
const sel = this.shadowRoot.getSelection();
|
||||||
|
if (sel && sel.rangeCount > 0) {
|
||||||
|
this.yankedLine = sel.toString();
|
||||||
}
|
}
|
||||||
|
this.setMode('normal');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.mode === 'command') {
|
if (e.key === 'd' || e.key === 'x') {
|
||||||
if (key === 'Enter' || key === 'Escape') {
|
e.preventDefault();
|
||||||
e.preventDefault();
|
// Delete selected text
|
||||||
this.cmdLine.style.display = 'none';
|
const sel = this.shadowRoot.getSelection();
|
||||||
this.mode = 'normal';
|
if (sel && sel.rangeCount > 0) {
|
||||||
this.keyBuffer = '';
|
this.lastDeletedLine = sel.toString();
|
||||||
}
|
document.execCommand('delete');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mode === 'visual') {
|
|
||||||
if (key === 'Escape') {
|
|
||||||
e.preventDefault();
|
|
||||||
this.mode = 'normal';
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle normal mode
|
|
||||||
this.keyBuffer += key;
|
|
||||||
|
|
||||||
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 =>
|
|
||||||
text.split('\n').slice(0, idx).reduce((acc, l) => acc + l.length + 1, 0);
|
|
||||||
|
|
||||||
switch (this.keyBuffer) {
|
|
||||||
case 'i':
|
|
||||||
e.preventDefault();
|
|
||||||
this.mode = 'insert';
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'v':
|
|
||||||
e.preventDefault();
|
|
||||||
this.mode = 'visual';
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ':':
|
|
||||||
e.preventDefault();
|
|
||||||
this.mode = 'command';
|
|
||||||
this.cmdLine.style.display = 'block';
|
|
||||||
this.cmdLine.textContent = ':';
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'yy':
|
|
||||||
e.preventDefault();
|
|
||||||
this.yankedLine = lines[lineIdx];
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'dd':
|
|
||||||
e.preventDefault();
|
|
||||||
this.lastDeletedLine = lines[lineIdx];
|
|
||||||
lines.splice(lineIdx, 1);
|
|
||||||
this.editor.innerText = lines.join('\n');
|
|
||||||
this.setCaretOffset(offsetToLine(lineIdx));
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'p':
|
|
||||||
e.preventDefault();
|
|
||||||
const lineToPaste = this.yankedLine || this.lastDeletedLine;
|
|
||||||
if (lineToPaste) {
|
|
||||||
lines.splice(lineIdx + 1, 0, lineToPaste);
|
|
||||||
this.editor.innerText = lines.join('\n');
|
|
||||||
this.setCaretOffset(offsetToLine(lineIdx + 1));
|
|
||||||
}
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '0':
|
|
||||||
e.preventDefault();
|
|
||||||
this.setCaretOffset(offsetToLine(lineIdx));
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '$':
|
|
||||||
e.preventDefault();
|
|
||||||
this.setCaretOffset(offsetToLine(lineIdx) + lines[lineIdx].length);
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'gg':
|
|
||||||
e.preventDefault();
|
|
||||||
this.setCaretOffset(0);
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'G':
|
|
||||||
e.preventDefault();
|
|
||||||
this.setCaretOffset(text.length);
|
|
||||||
this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Escape':
|
|
||||||
e.preventDefault();
|
|
||||||
this.mode = 'normal';
|
|
||||||
this.keyBuffer = '';
|
|
||||||
this.cmdLine.style.display = 'none';
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// allow up to 2 chars for combos
|
|
||||||
if (this.keyBuffer.length > 2) this.keyBuffer = '';
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
this.setMode('normal');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('njet-editor', NjetEditor);
|
// Normal mode handling
|
||||||
export {NjetEditor}
|
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) {
|
||||||
|
case 'i':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setMode('insert');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setCaretOffset(this.getCaretOffset() + 1);
|
||||||
|
this.setMode('insert');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'v':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setMode('visual');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ':':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setMode('command');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'yy':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.yankedLine = lines[lineIndex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dd':
|
||||||
|
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;
|
||||||
|
|
||||||
|
case 'p':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
const lineToPaste = this.yankedLine || this.lastDeletedLine;
|
||||||
|
if (lineToPaste) {
|
||||||
|
lines.splice(lineIndex + 1, 0, lineToPaste);
|
||||||
|
this.editor.innerText = lines.join('\n');
|
||||||
|
this.setCaretOffset(lineStartOffset + lines[lineIndex].length + 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '0':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setCaretOffset(lineStartOffset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '$':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setCaretOffset(lineStartOffset + lines[lineIndex].length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'gg':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setCaretOffset(0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'G':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
this.setCaretOffset(this.editor.innerText.length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'h':
|
||||||
|
case 'ArrowLeft':
|
||||||
|
this.keyBuffer = '';
|
||||||
|
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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Clear buffer if it gets too long or contains invalid sequences
|
||||||
|
if (this.keyBuffer.length > 2 ||
|
||||||
|
(this.keyBuffer.length === 2 && !['dd', 'yy', 'gg'].includes(this.keyBuffer))) {
|
||||||
|
this.keyBuffer = '';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('njet-editor', NjetEditor);
|
||||||
|
export { NjetEditor }
|
||||||
|
Loading…
Reference in New Issue
Block a user