Fixes #13 by adding new outliner commands
parent
9f082c83a9
commit
3ec2a74a53
|
@ -227,6 +227,30 @@ functions:
|
|||
command:
|
||||
name: "Upload: File"
|
||||
|
||||
outlineMoveUp:
|
||||
path: ./outline.ts:moveItemUp
|
||||
command:
|
||||
name: "Outline: Move Up"
|
||||
key: "Alt-ArrowUp"
|
||||
|
||||
outlineMoveDown:
|
||||
path: ./outline.ts:moveItemDown
|
||||
command:
|
||||
name: "Outline: Move Down"
|
||||
key: "Alt-ArrowDown"
|
||||
|
||||
outlineIndent:
|
||||
path: ./outline.ts:indentItem
|
||||
command:
|
||||
name: "Outline: Move Right"
|
||||
key: "Alt-ArrowRight"
|
||||
|
||||
outlineOutdent:
|
||||
path: ./outline.ts:outdentItem
|
||||
command:
|
||||
name: "Outline: Move Left"
|
||||
key: "Alt-ArrowLeft"
|
||||
|
||||
customFlashMessage:
|
||||
path: editor.ts:customFlashMessage
|
||||
command:
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
import { editor } from "$sb/syscalls.ts";
|
||||
|
||||
export async function moveItemUp() {
|
||||
const cursorPos = await editor.getCursor();
|
||||
const text = await editor.getText();
|
||||
|
||||
try {
|
||||
const currentItemBounds = determineItemBounds(text, cursorPos);
|
||||
const previousItemBounds = determineItemBounds(
|
||||
text,
|
||||
currentItemBounds.from - 1,
|
||||
currentItemBounds.indentLevel,
|
||||
);
|
||||
|
||||
if (currentItemBounds.from === previousItemBounds.from) {
|
||||
throw new Error("Already at the top");
|
||||
}
|
||||
|
||||
const newText =
|
||||
ensureNewLine(text.slice(currentItemBounds.from, currentItemBounds.to)) +
|
||||
text.slice(previousItemBounds.from, previousItemBounds.to);
|
||||
const newCursorPos = (cursorPos - currentItemBounds.from) +
|
||||
previousItemBounds.from;
|
||||
|
||||
await editor.dispatch({
|
||||
changes: [
|
||||
{
|
||||
from: previousItemBounds.from,
|
||||
to: currentItemBounds.to,
|
||||
insert: newText,
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: newCursorPos,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
await editor.flashNotification(e.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
export async function moveItemDown() {
|
||||
const cursorPos = await editor.getCursor();
|
||||
const text = await editor.getText();
|
||||
|
||||
try {
|
||||
const currentItemBounds = determineItemBounds(text, cursorPos);
|
||||
const nextItemBounds = determineItemBounds(
|
||||
text,
|
||||
currentItemBounds.to + 1,
|
||||
currentItemBounds.indentLevel,
|
||||
);
|
||||
|
||||
if (currentItemBounds.from === nextItemBounds.from) {
|
||||
throw new Error("Already at the bottom");
|
||||
}
|
||||
|
||||
const nextItemText = ensureNewLine(
|
||||
text.slice(nextItemBounds.from, nextItemBounds.to),
|
||||
);
|
||||
const newText = nextItemText +
|
||||
text.slice(currentItemBounds.from, currentItemBounds.to);
|
||||
const newCursorPos = (cursorPos - currentItemBounds.from) +
|
||||
currentItemBounds.from + nextItemText.length;
|
||||
await editor.dispatch({
|
||||
changes: [
|
||||
{
|
||||
from: currentItemBounds.from,
|
||||
to: nextItemBounds.to,
|
||||
insert: newText,
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: newCursorPos,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
await editor.flashNotification(e.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
export async function indentItem() {
|
||||
const cursorPos = await editor.getCursor();
|
||||
const text = await editor.getText();
|
||||
|
||||
try {
|
||||
const currentItemBounds = determineItemBounds(text, cursorPos);
|
||||
const itemText = text.slice(currentItemBounds.from, currentItemBounds.to);
|
||||
const newText = itemText.split("\n").map((line) =>
|
||||
line ? " " + line : line
|
||||
).join("\n");
|
||||
const preText = text.slice(currentItemBounds.from, cursorPos);
|
||||
const newCursorPos = cursorPos + preText.split("\n").length * 2;
|
||||
await editor.dispatch({
|
||||
changes: [
|
||||
{
|
||||
from: currentItemBounds.from,
|
||||
to: currentItemBounds.to,
|
||||
insert: newText,
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: newCursorPos,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
await editor.flashNotification(e.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
export async function outdentItem() {
|
||||
const cursorPos = await editor.getCursor();
|
||||
const text = await editor.getText();
|
||||
|
||||
try {
|
||||
const currentItemBounds = determineItemBounds(text, cursorPos);
|
||||
const itemText = text.slice(currentItemBounds.from, currentItemBounds.to);
|
||||
if (!itemText.startsWith(" ")) {
|
||||
throw new Error("Cannot outdent further");
|
||||
}
|
||||
const newText = itemText.split("\n").map((line) =>
|
||||
line.startsWith(" ") ? line.substring(2) : line
|
||||
).join("\n");
|
||||
const preText = text.slice(currentItemBounds.from, cursorPos);
|
||||
const newCursorPos = cursorPos - preText.split("\n").length * 2;
|
||||
await editor.dispatch({
|
||||
changes: [
|
||||
{
|
||||
from: currentItemBounds.from,
|
||||
to: currentItemBounds.to,
|
||||
insert: newText,
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: newCursorPos,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
await editor.flashNotification(e.message, "error");
|
||||
}
|
||||
}
|
||||
|
||||
function ensureNewLine(s: string) {
|
||||
if (!s.endsWith("\n")) {
|
||||
return s + "\n";
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
function determineItemBounds(
|
||||
text: string,
|
||||
pos: number,
|
||||
minIndentLevel?: number,
|
||||
): { from: number; to: number; indentLevel: number } {
|
||||
// Find the start of the item marked with a bullet
|
||||
let currentItemStart = pos;
|
||||
let indentLevel = 0;
|
||||
while (true) {
|
||||
while (currentItemStart > 0 && text[currentItemStart - 1] !== "\n") {
|
||||
currentItemStart--;
|
||||
}
|
||||
// Check if the line is a bullet and determine the indent level
|
||||
indentLevel = 0;
|
||||
while (text[currentItemStart + indentLevel] === " ") {
|
||||
indentLevel++;
|
||||
}
|
||||
if (minIndentLevel !== undefined && indentLevel < minIndentLevel) {
|
||||
throw new Error("No item found at minimum indent level");
|
||||
}
|
||||
if (minIndentLevel !== undefined && indentLevel > minIndentLevel) {
|
||||
// Not at the desired indent level yet, let's go up another line
|
||||
currentItemStart--;
|
||||
if (currentItemStart <= 0) {
|
||||
// We've reached the top of the document, no bullet found
|
||||
throw new Error("No item found");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (["-", "*"].includes(text[currentItemStart + indentLevel])) {
|
||||
// This is a bullet line, found it, let's break out of this loop
|
||||
break;
|
||||
} else {
|
||||
// Not a bullet line, let's go up another line
|
||||
currentItemStart--;
|
||||
if (currentItemStart <= 0) {
|
||||
// We've reached the top of the document, no bullet found
|
||||
throw new Error("No item found");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, so at this point we have determine the starting point of our item
|
||||
// Relevant variables are currentItemStart and indentLevel
|
||||
// Now let's find the end point
|
||||
let currentItemEnd = currentItemStart + 1;
|
||||
while (true) {
|
||||
// Let's traverse to the end of the line
|
||||
while (currentItemEnd < text.length && text[currentItemEnd - 1] !== "\n") {
|
||||
currentItemEnd++;
|
||||
}
|
||||
// Check the indent level of the next line
|
||||
let nextIndentLevel = 0;
|
||||
while (text[currentItemEnd + nextIndentLevel] === " ") {
|
||||
nextIndentLevel++;
|
||||
}
|
||||
if (nextIndentLevel <= indentLevel) {
|
||||
// This is a line indentend less than the current item, found it, let's break out of this loop
|
||||
break;
|
||||
} else {
|
||||
// Not a bullet line, let's go up another line
|
||||
currentItemEnd++;
|
||||
if (currentItemEnd >= text.length) {
|
||||
// End of the document, mark this as the end of the item
|
||||
currentItemEnd = text.length - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
from: currentItemStart,
|
||||
to: currentItemEnd,
|
||||
indentLevel,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue