Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
<preference name="GradlePluginKotlinVersion" value="2.3.0" />
<preference name="fullscreen" value="false" />
<preference name="SplashScreen" value="none" />
<preference name="FadeSplashScreenDuration" value="150" />
<preference name="ShowTitle" value="true" />
<preference name="DisallowOverscroll" value="true" />
<preference name="BackgroundColor" value="0xFF313131" />
Expand Down
16 changes: 3 additions & 13 deletions rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,6 @@ module.exports = (env, options) => {
? ''.concat(devProto, '://', devHost, ':', devPort)
: '';

// Match the oldest WebView the app supports (MIN_WEBVIEW_MAJOR in
// www/index.html) instead of plain ES2015, so native async/await, classes
// and spread are kept. `entry` mode rewrites `import "core-js/stable"` to
// only the polyfills that WebView still needs.
const swcEnv = {
targets: 'chrome >= 67',
mode: 'entry',
coreJs: require('core-js/package.json').version,
};

const rules = [
{
test: /typescript[\\/]lib[\\/]lib\..*\.d\.ts$/,
Expand All @@ -40,8 +30,8 @@ module.exports = (env, options) => {
syntax: 'typescript',
tsx: false,
},
target: 'es2015',
},
env: swcEnv,
},
},
path.resolve(__dirname, 'utils/custom-loaders/html-tag-jsx-loader.js'),
Expand All @@ -65,8 +55,8 @@ module.exports = (env, options) => {
parser: {
syntax: 'ecmascript',
},
target: 'es2015',
},
env: swcEnv,
},
},
],
Expand All @@ -82,8 +72,8 @@ module.exports = (env, options) => {
syntax: 'ecmascript',
jsx: false,
},
target: 'es2015',
},
env: swcEnv,
},
},
path.resolve(__dirname, 'utils/custom-loaders/html-tag-jsx-loader.js'),
Expand Down
93 changes: 16 additions & 77 deletions src/cm/commandRegistry.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,6 @@ let cachedKeymap = [];
/** @type {Set<EditorView>} */
const commandViews = new Set();

/**
* Commands are often registered in bursts (a plugin adding several at once),
* so the keymap is rebuilt lazily on the next read instead of per command.
*/
let keymapDirty = true;

/** @type {Set<EditorView>} views waiting for the updated keymap */
const pendingKeymapViews = new Set();
let keymapRefreshScheduled = false;

const CODEMIRROR_COMMAND_ENTRIES = Object.entries(cmCommands).filter(
([name, value]) =>
typeof value === "function" && CODEMIRROR_COMMAND_NAMES.has(name),
Expand All @@ -174,6 +164,7 @@ registerCoreCommands();
registerLspCommands();
registerLintCommands();
registerCommandsFromKeyBindings();
rebuildKeymap();

function registerCoreCommands() {
addCommand({
Expand Down Expand Up @@ -1523,34 +1514,19 @@ function buildResolvedKeyBindingsSnapshot() {
);
}

/**
* Resolve a command's effective description and key from the bindings.
* @returns {string|null} the key source
*/
function syncCommandBinding(command, name) {
const bindingInfo = resolveBindingInfo(name);
command.description = bindingInfo?.description || command.defaultDescription;
command.key =
bindingInfo && Object.prototype.hasOwnProperty.call(bindingInfo, "key")
? bindingInfo.key
: (command.defaultKey ?? null);
return command.key;
}

function invalidateKeymap() {
keymapDirty = true;
}

function ensureKeymap() {
if (keymapDirty) rebuildKeymap();
}

function rebuildKeymap() {
cachedResolvedKeyBindings = buildResolvedKeyBindingsSnapshot();
const candidates = [];
let order = 0;
commandMap.forEach((command, name) => {
const keySource = syncCommandBinding(command, name);
const bindingInfo = resolveBindingInfo(name);
command.description =
bindingInfo?.description || command.defaultDescription;
const keySource =
bindingInfo && Object.prototype.hasOwnProperty.call(bindingInfo, "key")
? bindingInfo.key
: (command.defaultKey ?? null);
command.key = keySource;
const combos = parseKeyString(keySource);
combos.forEach((combo) => {
const cmKey = toCodeMirrorKey(combo);
Expand All @@ -1575,15 +1551,9 @@ function rebuildKeymap() {
const conflicts = [];
for (const candidate of candidates) {
const canonicalKey = canonicalizeKeyBinding(candidate.key);
// First conflicting claim in insertion order, without copying the map
// for every candidate.
let claimed = null;
for (const entry of claimedKeys) {
if (keyBindingsConflict(entry[0], canonicalKey)) {
claimed = entry;
break;
}
}
const claimed = Array.from(claimedKeys.entries()).find(([key]) =>
keyBindingsConflict(key, canonicalKey),
);
if (claimed) {
const [claimedKey, owner] = claimed;
const appCommandShadowsCodeMirrorDefault =
Expand Down Expand Up @@ -1626,7 +1596,6 @@ function rebuildKeymap() {
cachedKeyBindingConflicts = conflicts;
cachedKeymap = bindings;
resolvedKeyBindingsVersion += 1;
keymapDirty = false;
return bindings;
}

Expand Down Expand Up @@ -1674,7 +1643,6 @@ export function executeCommand(name, view, args) {
}

export function getRegisteredCommands() {
ensureKeymap();
return Array.from(commandMap.values()).map((command) => ({
name: command.name,
description: command.description || command.defaultDescription,
Expand All @@ -1683,27 +1651,22 @@ export function getRegisteredCommands() {
}

export function getResolvedKeyBindings() {
ensureKeymap();
return cachedResolvedKeyBindings;
}

export function getEffectiveKeyBindings() {
ensureKeymap();
return cachedEffectiveKeyBindings;
}

export function getKeyBindingConflicts() {
ensureKeymap();
return cachedKeyBindingConflicts.map((conflict) => ({ ...conflict }));
}

export function getResolvedKeyBindingsVersion() {
ensureKeymap();
return resolvedKeyBindingsVersion;
}

export function getCommandKeymapExtension() {
ensureKeymap();
return commandKeymapCompartment.of(keymap.of(cachedKeymap));
}

Expand Down Expand Up @@ -1791,11 +1754,9 @@ export function registerExternalCommand(descriptor = {}) {
const stored = commandMap.get(name);
if (stored) {
stored.key = normalized.key ?? stored.key;
// The returned command reflects its final binding right away.
syncCommandBinding(stored, name);
}

invalidateKeymap();
rebuildKeymap();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Command bursts repeat keymap work Each external command registration now rebuilds the full keymap, and the subsequent refresh immediately reconfigures each affected editor pane. A plugin registering many commands repeats that work for every command instead of sharing one rebuild and dispatch, adding avoidable startup work.

Knowledge Base Used: Application bootstrap and runtime composition

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

return stored;
}

Expand All @@ -1804,34 +1765,13 @@ export function removeExternalCommand(name) {
const exists = commandMap.has(name);
if (!exists) return false;
commandMap.delete(name);
invalidateKeymap();
rebuildKeymap();
return true;
}

/**
* Apply the current keymap to a view. Calls made in the same task are applied
* together in a microtask, which always runs before the next key event.
*/
export function refreshCommandKeymap(view) {
const resolvedView = resolveView(view);
if (!resolvedView) return;
pendingKeymapViews.add(resolvedView);
if (keymapRefreshScheduled) return;
keymapRefreshScheduled = true;
Promise.resolve().then(flushKeymapRefresh);
}

function flushKeymapRefresh() {
keymapRefreshScheduled = false;
const views = Array.from(pendingKeymapViews);
pendingKeymapViews.clear();
for (const view of views) {
try {
applyCommandKeymap(view);
} catch (error) {
console.error("Failed to apply command keymap", error);
}
}
applyCommandKeymap(resolvedView);
}

function normalizeExternalCommand(descriptor) {
Expand Down Expand Up @@ -1887,9 +1827,8 @@ function normalizeExternalKey(bindKey) {
return combos.length ? combos.join("|") : null;
}

function applyCommandKeymap(view, bindings) {
function applyCommandKeymap(view, bindings = cachedKeymap) {
if (!view) return;
ensureKeymap();
commandViews.add(view);
view.dispatch({
effects: commandKeymapCompartment.reconfigure(
Expand Down
12 changes: 1 addition & 11 deletions src/cm/keyBindingUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,8 @@ export function toCodeMirrorKey(combo) {
return strokes.length ? strokes.join(" ") : null;
}

// Conflict checks compare every binding with every other one each time the
// keymap is rebuilt (once per registered command), so cache the parsed form.
const canonicalKeyCache = new Map();

export function canonicalizeKeyBinding(combo) {
if (typeof combo !== "string") {
return toCodeMirrorKey(combo)?.toLowerCase() || null;
}
if (canonicalKeyCache.has(combo)) return canonicalKeyCache.get(combo);
const canonicalKey = toCodeMirrorKey(combo)?.toLowerCase() || null;
canonicalKeyCache.set(combo, canonicalKey);
return canonicalKey;
return toCodeMirrorKey(combo)?.toLowerCase() || null;
}

/**
Expand Down
Loading
Loading