Skip to content

Instantly share code, notes, and snippets.

@sonota88
Created December 3, 2022 05:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sonota88/47ec37ef761ac4290ac88ee28e3c1621 to your computer and use it in GitHub Desktop.
Save sonota88/47ec37ef761ac4290ac88ee28e3c1621 to your computer and use it in GitHub Desktop.
HTML + JavaScript で Emacs の align-regexp
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>align-regexp</title>
<style>
* {
font-family: Firge, monospace;
}
body {
padding: 1rem 4%;
}
input, textarea {
margin: 0.2rem 0;
}
textarea {
width: 90%;
height: 40vh;
}
</style>
<script>
function puts(...args) {
// console.log(...args);
}
function getEl(sel) {
return document.querySelector(sel);
}
function handleEv(el, ev, fn) {
let els;
if (typeof el === "string") {
els = document.querySelectorAll(el);
} else {
els = [el];
}
els.forEach(el => el.addEventListener(ev, fn));
}
function debounce(fn, msec) {
const self = debounce;
if (self.map[fn] != null) {
clearTimeout(self.map[fn]);
}
self.map[fn] = setTimeout(
() => {
fn();
self.map[fn] = null;
},
msec
);
}
debounce.map = {};
// --------------------------------
function getPattern() {
return getEl("#pattern").value;
}
function getIndex() {
return Math.trunc(getEl("#index").value);
}
function getInput() {
return getEl("#input").value;
}
function setInput(text) {
getEl("#input").value = text;
}
function getPreview() {
return getEl("#preview").value;
}
function setPreview(text) {
getEl("#preview").value = text;
}
// --------------------------------
function isHankaku(c) {
const cc = c.charCodeAt(0);
return (
(32 <= cc && cc <= 126) // (SPC) ... '~'
|| (65377 <= cc && cc <= 65439) // '。' ... '゚'
);
}
function charWidth(c) {
return isHankaku(c) ? 1 : 2;
}
function lengthInHankaku(s) {
let len = 0;
for (let i = 0; i < s.length; i++) {
len += charWidth(s.charAt(i));
}
return len;
}
/*
* left sep right
* ^m.index == left length
* ^^^m[0]
*/
function matchLine(line, re, index) {
let rest = line;
let found = false;
let pos = 0;
let i = -1;
while (true) {
i++;
if (1000 < i) {
throw new Error("too many iteration");
}
const m = rest.match(re);
if (m == null) {
break;
}
pos += m.index; // add left
if (index <= i) {
found = true;
break;
}
pos += m[0].length; // add separator
rest = rest.substring(m.index + m[0].length);
}
return found ? pos : null;
}
function getAlignPos(pairs) {
const headLenList =
pairs
.map(pair => pair[0])
.map(lengthInHankaku);
return Math.max(...headLenList);
}
function toPair(line, re, index) {
const pos = matchLine(line, re, index);
if (pos == null) {
return [line, ""];
} else {
const head = line.substring(0, pos);
const rest = line.substring(pos);
return [head, rest];
}
}
function alignRegexp(input, re, index) {
const lines = input.split("\n");
const pairs = lines.map(line => toPair(line, re, index));
const alignPos = getAlignPos(pairs);
const newLines =
pairs.map(pair => {
const [head, rest] = pair;
if (rest === "") {
return head;
} else {
const numSpaces = alignPos - lengthInHankaku(head);
return head + " ".repeat(numSpaces) + " " + rest;
}
});
return newLines.join("\n");
}
function _refreshPreview() {
const pattern = getPattern();
if (pattern === "") {
setPreview("");
return;
}
const re = new RegExp(pattern);
const input = getInput();
const index = getIndex();
const aligned = alignRegexp(input, re, index);
setPreview(aligned);
}
function refreshPreview() {
try {
_refreshPreview();
} catch(e) {
setPreview(e.message);
throw e;
}
}
function doApply() {
const text = getPreview();
setInput(text);
}
function oninput_text() {
debounce(refreshPreview, 100);
}
function onclick_apply() {
doApply();
}
function onkeydown(ev) {
if (ev.ctrlKey && ev.key === "Enter") {
doApply();
}
}
function init() {
refreshPreview();
["#pattern", "#input", "#index"]
.forEach(sel =>
handleEv(sel, "input", oninput_text)
);
handleEv("#btn_apply", "click", onclick_apply)
handleEv(window, "keydown", (ev) => onkeydown(ev));
}
window.addEventListener("pageshow", init);
</script>
</head>
<body>
pattern: <input id="pattern" style="width: 50%;" />
<br />
position: <input id="index" type="number" value="0" min="0" />
<br />
<textarea id="input"></textarea>
<hr />
preview
<button id="btn_apply">apply (C-RET)</button>
<br />
<textarea id="preview" style="color: #444;"></textarea>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment