@replit/codemirror-emacs
Advanced tools
Comparing version 6.0.1 to 6.1.0
@@ -10,3 +10,3 @@ import { basicSetup, EditorView } from 'codemirror'; | ||
const doc = ` | ||
const doc = `// 🌞 אבג | ||
import { basicSetup, EditorView } from 'codemirror'; | ||
@@ -13,0 +13,0 @@ import { javascript } from '@codemirror/lang-javascript'; |
@@ -1,6 +0,62 @@ | ||
import { Extension } from '@codemirror/state'; | ||
import { Extension, ChangeDesc } from '@codemirror/state'; | ||
import { EditorView } from '@codemirror/view'; | ||
declare function emacs(options?: {}): Extension; | ||
declare type EmacsMark = number[] | null | undefined; | ||
declare class EmacsHandler { | ||
readonly view: EditorView; | ||
static bindKey(keyGroup: string, command: any): void; | ||
static getKey(e: KeyboardEvent): string[]; | ||
static commands: Record<string, any>; | ||
static addCommands(commands: any): void; | ||
static execCommand(command: any, handler: EmacsHandler, args: any, count?: number): any; | ||
handleKeyboard(e: KeyboardEvent): { | ||
command: string; | ||
args: string; | ||
count?: undefined; | ||
} | { | ||
command: string; | ||
args?: undefined; | ||
count?: undefined; | ||
} | { | ||
command: any; | ||
args: any; | ||
count: number; | ||
} | undefined; | ||
constructor(view: EditorView); | ||
$data: { | ||
count?: number | null; | ||
keyChain: string; | ||
lastCommand: string | null; | ||
}; | ||
findCommand([key, modifier, text]: string[]): { | ||
command: string; | ||
args: string; | ||
count?: undefined; | ||
} | { | ||
command: string; | ||
args?: undefined; | ||
count?: undefined; | ||
} | { | ||
command: any; | ||
args: any; | ||
count: number; | ||
} | undefined; | ||
showCommandLine(text: string): void; | ||
$emacsMarkRing: EmacsMark[]; | ||
$emacsMark?: EmacsMark; | ||
updateMarksOnChange(change: ChangeDesc): void; | ||
updateMark(mark: EmacsMark, change: ChangeDesc): number[] | null | undefined; | ||
emacsMark(): EmacsMark; | ||
setEmacsMark(p?: EmacsMark): void; | ||
pushEmacsMark(p?: EmacsMark, activate?: boolean): void; | ||
popEmacsMark(): EmacsMark; | ||
getLastEmacsMark(): EmacsMark; | ||
getCopyText(): string; | ||
clearSelection(): boolean; | ||
onPaste(text: string): void; | ||
selectionToEmacsMark(): number[]; | ||
} | ||
declare const emacsKeys: Record<string, any>; | ||
export { emacs, emacsKeys }; | ||
export { EmacsHandler, emacs, emacsKeys }; |
import { Prec, StateEffect, StateField, MapMode, EditorSelection } from '@codemirror/state'; | ||
import { EditorView, Direction, ViewPlugin, keymap, showPanel } from '@codemirror/view'; | ||
import * as View from '@codemirror/view'; | ||
import { EditorView, Direction, ViewPlugin, showPanel } from '@codemirror/view'; | ||
import * as commands from '@codemirror/commands'; | ||
import { startCompletion } from '@codemirror/autocomplete'; | ||
import { completionStatus, startCompletion } from '@codemirror/autocomplete'; | ||
import { openSearchPanel } from '@codemirror/search'; | ||
// backwards compatibility for old versions not supporting getDrawSelectionConfig | ||
let getDrawSelectionConfig = View.getDrawSelectionConfig || /*@__PURE__*/function () { | ||
let defaultConfig = { cursorBlinkRate: 1200 }; | ||
return function () { | ||
return defaultConfig; | ||
}; | ||
}(); | ||
class Piece { | ||
constructor(left, top, height, className, letter, partial) { | ||
constructor(left, top, height, fontFamily, fontSize, fontWeight, color, className, letter, partial) { | ||
this.left = left; | ||
this.top = top; | ||
this.height = height; | ||
this.fontFamily = fontFamily; | ||
this.fontSize = fontSize; | ||
this.fontWeight = fontWeight; | ||
this.color = color; | ||
this.className = className; | ||
@@ -27,3 +39,6 @@ this.letter = letter; | ||
elt.style.lineHeight = this.height + "px"; | ||
elt.style.color = this.partial ? "transparent" : ""; | ||
elt.style.fontFamily = this.fontFamily; | ||
elt.style.fontSize = this.fontSize; | ||
elt.style.fontWeight = this.fontWeight; | ||
elt.style.color = this.partial ? "transparent" : this.color; | ||
elt.className = this.className; | ||
@@ -33,11 +48,15 @@ elt.textContent = this.letter; | ||
eq(p) { | ||
return this.left == p.left && this.top == p.top && this.letter == p.letter && this.height == p.height && | ||
this.className == p.className; | ||
return this.left == p.left && this.top == p.top && this.height == p.height && | ||
this.fontFamily == p.fontFamily && this.fontSize == p.fontSize && | ||
this.fontWeight == p.fontWeight && this.color == p.color && | ||
this.className == p.className && | ||
this.letter == p.letter; | ||
} | ||
} | ||
class BlockCursorPlugin { | ||
constructor(view) { | ||
constructor(view, em) { | ||
this.view = view; | ||
this.rangePieces = []; | ||
this.cursors = []; | ||
this.em = em; | ||
this.measureReq = { read: this.readPos.bind(this), write: this.drawSel.bind(this) }; | ||
@@ -51,3 +70,5 @@ this.cursorLayer = view.scrollDOM.appendChild(document.createElement("div")); | ||
setBlinkRate() { | ||
this.cursorLayer.style.animationDuration = 1200 + "ms"; | ||
let config = getDrawSelectionConfig(this.view.state); | ||
let blinkRate = config.cursorBlinkRate; | ||
this.cursorLayer.style.animationDuration = blinkRate + "ms"; | ||
} | ||
@@ -59,2 +80,4 @@ update(update) { | ||
} | ||
if (configChanged(update)) | ||
this.setBlinkRate(); | ||
} | ||
@@ -69,3 +92,3 @@ scheduleRedraw() { | ||
let prim = r == state.selection.main; | ||
let piece = measureCursor(null, this.view, r, prim); | ||
let piece = measureCursor(this.em, this.view, r, prim); | ||
if (piece) | ||
@@ -94,2 +117,5 @@ cursors.push(piece); | ||
} | ||
function configChanged(update) { | ||
return getDrawSelectionConfig(update.startState) != getDrawSelectionConfig(update.state); | ||
} | ||
const themeSpec = { | ||
@@ -109,3 +135,4 @@ ".cm-line": { | ||
background: "none", | ||
outline: "solid 1px #ff9696" | ||
outline: "solid 1px #ff9696", | ||
color: "transparent !important", | ||
}, | ||
@@ -119,14 +146,55 @@ }; | ||
} | ||
function measureCursor(cm, view, cursor, primary) { | ||
function measureCursor(em, view, cursor, primary) { | ||
var _a, _b; | ||
let head = cursor.head; | ||
var hCoeff = 1; | ||
let pos = view.coordsAtPos(head, 1); | ||
if (!pos) | ||
return null; | ||
let base = getBase(view); | ||
let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); | ||
if (!letter || letter == "\n" || letter == "\r") | ||
letter = "\xa0"; | ||
let h = (pos.bottom - pos.top); | ||
return new Piece(pos.left - base.left, pos.top - base.top + h * (1 - hCoeff), h * hCoeff, primary ? "cm-fat-cursor cm-cursor-primary" : "cm-fat-cursor cm-cursor-secondary", letter, hCoeff != 1); | ||
let hCoeff = 1; | ||
if (em.$data.count || em.$data.keyChain) { | ||
hCoeff = 0.5; | ||
} | ||
{ | ||
let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); | ||
if (letter && (/[\uDC00-\uDFFF]/.test(letter) && head > 1)) { | ||
// step back if cursor is on the second half of a surrogate pair | ||
head--; | ||
letter = view.state.sliceDoc(head, head + 1); | ||
} | ||
let pos = view.coordsAtPos(head, 1); | ||
if (!pos) | ||
return null; | ||
let base = getBase(view); | ||
let domAtPos = view.domAtPos(head); | ||
let node = domAtPos ? domAtPos.node : view.contentDOM; | ||
while (domAtPos && domAtPos.node instanceof HTMLElement) { | ||
node = domAtPos.node; | ||
domAtPos = { node: domAtPos.node.childNodes[domAtPos.offset], offset: 0 }; | ||
} | ||
if (!(node instanceof HTMLElement)) { | ||
if (!node.parentNode) | ||
return null; | ||
node = node.parentNode; | ||
} | ||
let style = getComputedStyle(node); | ||
let left = pos.left; | ||
// TODO remove coordsAtPos when all supported versions of codemirror have coordsForChar api | ||
let charCoords = (_b = (_a = view).coordsForChar) === null || _b === void 0 ? void 0 : _b.call(_a, head); | ||
if (charCoords) { | ||
left = charCoords.left; | ||
} | ||
if (!letter || letter == "\n" || letter == "\r") { | ||
letter = "\xa0"; | ||
} | ||
else if (letter == "\t") { | ||
letter = "\xa0"; | ||
var nextPos = view.coordsAtPos(head + 1, -1); | ||
if (nextPos) { | ||
left = nextPos.left - (nextPos.left - pos.left) / parseInt(style.tabSize); | ||
} | ||
} | ||
else if ((/[\uD800-\uDBFF]/.test(letter) && head < view.state.doc.length - 1)) { | ||
// include the second half of a surrogate pair in cursor | ||
letter += view.state.sliceDoc(head + 1, head + 2); | ||
} | ||
let h = (pos.bottom - pos.top); | ||
return new Piece(left - base.left, pos.top - base.top + h * (1 - hCoeff), h * hCoeff, style.fontFamily, style.fontSize, style.fontWeight, style.color, primary ? "cm-fat-cursor cm-cursor-primary" : "cm-fat-cursor cm-cursor-secondary", letter, hCoeff != 1); | ||
} | ||
} | ||
@@ -154,3 +222,3 @@ | ||
this.em = new EmacsHandler(view); | ||
this.blockCursor = new BlockCursorPlugin(view); | ||
this.blockCursor = new BlockCursorPlugin(view, this.em); | ||
this.view.scrollDOM.classList.add("cm-emacsMode"); | ||
@@ -185,2 +253,8 @@ /*this.em.on("dialog", () => { | ||
eventHandlers: { | ||
keydown: function (e, view) { | ||
var result = this.em.handleKeyboard(e); | ||
if (result) | ||
this.blockCursor.scheduleRedraw(); | ||
return !!result; | ||
}, | ||
mousedown: function () { | ||
@@ -190,12 +264,2 @@ this.em.$emacsMark = null; | ||
}, | ||
provide: plugin => { | ||
return keymap.of([ | ||
{ | ||
any: function (view, e) { | ||
var _a; | ||
return !!((_a = view.plugin(plugin)) === null || _a === void 0 ? void 0 : _a.em.handleKeyboard(e)); | ||
} | ||
} | ||
]); | ||
} | ||
}); | ||
@@ -310,2 +374,4 @@ const showVimPanel = /*@__PURE__*/StateEffect.define(); | ||
var commandResult = undefined; | ||
if (count < 0) | ||
count = -count; | ||
if (typeof command === "function") { | ||
@@ -336,2 +402,4 @@ for (var i = 0; i < count; i++) | ||
var result = this.findCommand(keyData); | ||
if (/Up|Down/.test(keyData === null || keyData === void 0 ? void 0 : keyData[0]) && completionStatus(this.view.state)) | ||
return; | ||
if (result && result.command) { | ||
@@ -413,4 +481,5 @@ var commandResult = EmacsHandler.execCommand(result.command, this, result.args, result.count); | ||
} | ||
if (!command.readOnly && !command.isYank) | ||
if (!command.readOnly && !command.keepLastCommand) { | ||
data.lastCommand = null; | ||
} | ||
var count = data.count || 1; | ||
@@ -452,7 +521,7 @@ if (data.count) | ||
if (prevMark) | ||
this.$emacsMarkRing.push(prevMark); | ||
pushUnique(this.$emacsMarkRing, prevMark); | ||
if (!p || activate) | ||
this.setEmacsMark(p); | ||
else | ||
this.$emacsMarkRing.push(p); | ||
pushUnique(this.$emacsMarkRing, p); | ||
} | ||
@@ -512,4 +581,13 @@ ; | ||
} | ||
selectionToEmacsMark() { | ||
var selection = this.view.state.selection; | ||
return selection.ranges.map(x => x.head); | ||
} | ||
} | ||
EmacsHandler.commands = {}; | ||
function pushUnique(array, item) { | ||
if (array.length && array[array.length - 1] + "" == item + "") | ||
return; | ||
array.push(item); | ||
} | ||
const emacsKeys = { | ||
@@ -719,2 +797,3 @@ // movement | ||
if (args && args.count) { | ||
var newMark = handler.selectionToEmacsMark(); | ||
var mark = handler.popEmacsMark(); | ||
@@ -728,2 +807,3 @@ if (mark) { | ||
}); | ||
handler.$emacsMarkRing.unshift(newMark); | ||
} | ||
@@ -733,3 +813,3 @@ return; | ||
var mark = handler.emacsMark(); | ||
var rangePositions = ranges.map(function (r) { return r.from; }); | ||
var rangePositions = ranges.map(function (r) { return r.head; }); | ||
var hasNoSelection = ranges.every(function (range) { return range.from == range.to; }); | ||
@@ -756,3 +836,3 @@ // if transientMarkModeActive then mark behavior is a little | ||
exchangePointAndMark: { | ||
exec: function exchangePointAndMark$exec(handler, args) { | ||
exec: function (handler, args) { | ||
var view = handler.view; | ||
@@ -775,2 +855,3 @@ var selection = view.state.selection; | ||
if (args.count) { // replace mark and point | ||
markRing[markRing.length - 1] = handler.selectionToEmacsMark(); | ||
handler.clearSelection(); | ||
@@ -817,39 +898,53 @@ var newRanges = lastMark.map(x => { | ||
}, | ||
killLine: function (handler) { | ||
handler.pushEmacsMark(null); | ||
// don't delete the selection if it's before the cursor | ||
handler.clearSelection(); | ||
commands.selectLineEnd(handler.view); | ||
var view = handler.view; | ||
var state = view.state; | ||
var text = []; | ||
var changes = handler.view.state.selection.ranges.map(function (range) { | ||
var from = range.from; | ||
var to = range.to; | ||
var line = state.sliceDoc(from, to); | ||
text.push(line); | ||
// remove EOL if only whitespace remains after the cursor | ||
if (/^\s*$/.test(line)) { | ||
to += 1; | ||
text.push("\n"); | ||
killLine: { | ||
exec: function (handler) { | ||
handler.pushEmacsMark(null); | ||
// don't delete the selection if it's before the cursor | ||
handler.clearSelection(); | ||
var view = handler.view; | ||
var state = view.state; | ||
var text = []; | ||
var changes = state.selection.ranges.map(function (range) { | ||
var from = range.head; | ||
var lineObject = state.doc.lineAt(from); | ||
var to = lineObject.to; | ||
var line = state.sliceDoc(from, to); | ||
// remove EOL if only whitespace remains after the cursor | ||
if (/^\s*$/.test(line) && to < state.doc.length - 1) { | ||
to += 1; | ||
text.push(line + "\n"); | ||
} | ||
else { | ||
text.push(line); | ||
} | ||
return { from, to, insert: "" }; | ||
}); | ||
if (handler.$data.lastCommand == "killLine") { | ||
killRing.append(text.join("\n")); | ||
} | ||
return { from, to, insert: "" }; | ||
}); | ||
if (handler.$data.lastCommand == "killLine") | ||
killRing.append(text.join("\n")); | ||
else | ||
killRing.add(text.join("\n")); | ||
view.dispatch({ changes }); | ||
else { | ||
killRing.add(text.join("\n")); | ||
} | ||
handler.$data.lastCommand = "killLine"; | ||
view.dispatch({ changes }); | ||
}, | ||
keepLastCommand: true | ||
}, | ||
yank: function (handler) { | ||
handler.onPaste(killRing.get()); | ||
handler.$data.lastCommand = "yank"; | ||
yank: { | ||
exec: function (handler) { | ||
handler.onPaste(killRing.get()); | ||
handler.$data.lastCommand = "yank"; | ||
}, | ||
keepLastCommand: true | ||
}, | ||
yankRotate: function (handler) { | ||
if (handler.$data.lastCommand != "yank") | ||
return; | ||
commands.undo(handler.view); | ||
handler.$emacsMarkRing.pop(); // also undo recording mark | ||
handler.onPaste(killRing.rotate()); | ||
handler.$data.lastCommand = "yank"; | ||
yankRotate: { | ||
exec: function (handler) { | ||
if (handler.$data.lastCommand != "yank") | ||
return; | ||
commands.undo(handler.view); | ||
handler.$emacsMarkRing.pop(); // also undo recording mark | ||
handler.onPaste(killRing.rotate()); | ||
handler.$data.lastCommand = "yank"; | ||
}, | ||
keepLastCommand: true | ||
}, | ||
@@ -927,2 +1022,2 @@ killRegion: { | ||
export { emacs, emacsKeys }; | ||
export { EmacsHandler, emacs, emacsKeys }; |
{ | ||
"name": "@replit/codemirror-emacs", | ||
"version": "6.0.1", | ||
"version": "6.1.0", | ||
"description": "Emacs keybindings for CodeMirror 6", | ||
@@ -5,0 +5,0 @@ "scripts": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
109939
2130
16
1