Fixes #13 by adding new outliner commands
This commit is contained in:
parent
9f082c83a9
commit
3ec2a74a53
@ -227,6 +227,30 @@ functions:
|
|||||||
command:
|
command:
|
||||||
name: "Upload: File"
|
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:
|
customFlashMessage:
|
||||||
path: editor.ts:customFlashMessage
|
path: editor.ts:customFlashMessage
|
||||||
command:
|
command:
|
||||||
|
225
plugs/editor/outline.ts
Normal file
225
plugs/editor/outline.ts
Normal file
@ -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
Block a user