@bhsd/codemirror-mediawiki
Version:
Modified CodeMirror mode based on wikimedia/mediawiki-extensions-CodeMirror
108 lines (107 loc) • 3.92 kB
JavaScript
import { keymap } from '@codemirror/view';
import { EditorSelection } from '@codemirror/state';
import { indentMore, indentLess } from '@codemirror/commands';
import { getLSP } from '@bhsd/browser';
import { CodeMirror6, menuRegistry } from './codemirror';
const entity = { '"': 'quot', "'": 'apos', '<': 'lt', '>': 'gt', '&': 'amp', ' ': 'nbsp' };
/**
* 根据函数转换选中文本
* @param func 转换函数
* @param cmd 原命令
*/
const convert = (func, cmd) => (view) => {
if (view.state.selection.ranges.some(({ empty }) => !empty)) {
CodeMirror6.replaceSelections(view, func);
return true;
}
return cmd(view);
};
// eslint-disable-next-line @typescript-eslint/no-misused-spread
export const escapeHTML = (str) => [...str].map(c => {
if (c in entity) {
return `&${entity[c]};`;
}
const code = c.codePointAt(0);
return code < 256 ? `&#${code};` : `&#x${code.toString(16)};`;
}).join(''), escapeURI = (str) => {
if (str.includes('%')) {
try {
return decodeURIComponent(str);
}
catch { }
}
return encodeURIComponent(str);
};
const escapeWiki = (cm) => {
const view = cm.view, { state } = view, { ranges } = state.selection, lsp = getLSP(view, false, cm.getWikiConfig);
if (lsp && 'provideRefactoringAction' in lsp && ranges.some(({ empty }) => !empty)) {
(async () => {
const replacements = new WeakMap();
for (const range of ranges) {
// eslint-disable-next-line no-await-in-loop
const [action] = await lsp.provideRefactoringAction(state.sliceDoc(range.from, range.to));
replacements.set(range, action?.edit.changes[''][0].newText);
}
view.dispatch(state.changeByRange(range => {
const insert = replacements.get(range);
if (insert === undefined) {
return { range };
}
return {
range: EditorSelection.range(range.from, range.from + insert.length),
changes: { from: range.from, to: range.to, insert },
};
}));
})();
return true;
}
return false;
};
const handlerBase = (view, e) => {
e.stopPropagation();
view.focus();
};
let items;
menuRegistry.push({
name: 'escape',
isActionable({ lang, view }) {
return lang === 'mediawiki' && view.state.selection.ranges.some(({ empty }) => !empty);
},
getItems(cm) {
if (!items) {
const view = cm.view, btnHTML = document.createElement('div'), btnURI = document.createElement('div');
btnHTML.textContent = 'HTML escape';
btnHTML.addEventListener('click', e => {
CodeMirror6.replaceSelections(view, escapeHTML);
handlerBase(view, e);
});
btnURI.textContent = 'URI encode/decode';
btnURI.addEventListener('click', e => {
CodeMirror6.replaceSelections(view, escapeURI);
handlerBase(view, e);
});
items = [btnHTML, btnURI];
const lsp = getLSP(view, false, cm.getWikiConfig);
if (lsp && 'provideRefactoringAction' in lsp) {
const btnWiki = document.createElement('div');
btnWiki.textContent = 'Escape with magic words';
btnWiki.addEventListener('click', e => {
escapeWiki(cm);
handlerBase(view, e);
});
items.unshift(btnWiki);
}
}
return items;
},
});
export default (cm) => keymap.of([
{ key: 'Mod-[', run: convert(escapeHTML, indentLess) },
{ key: 'Mod-]', run: convert(escapeURI, indentMore) },
{
key: 'Mod-\\',
run() {
return escapeWiki(cm);
},
},
]);