mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
add block comments with shortcut and breakpoint shortcut
This commit is contained in:
@ -43,7 +43,8 @@
|
|||||||
<input type="checkbox" id="qr--modal-syntax">
|
<input type="checkbox" id="qr--modal-syntax">
|
||||||
<span>Syntax highlight</span>
|
<span>Syntax highlight</span>
|
||||||
</label>
|
</label>
|
||||||
<small>Ctrl+Alt+Click to set / remove breakpoints</small>
|
<small>Ctrl+Alt+Click (or F9) to set / remove breakpoints</small>
|
||||||
|
<small>Ctrl+<span id="qr--modal-commentKey"></span> to toggle block comments</small>
|
||||||
</div>
|
</div>
|
||||||
<div id="qr--modal-messageHolder">
|
<div id="qr--modal-messageHolder">
|
||||||
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
<pre id="qr--modal-messageSyntax"><code id="qr--modal-messageSyntaxInner" class="hljs language-stscript"></code></pre>
|
||||||
|
@ -501,6 +501,7 @@ export class QuickReply {
|
|||||||
localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
|
localStorage.setItem('qr--syntax', JSON.stringify(syntax.checked));
|
||||||
updateSyntaxEnabled();
|
updateSyntaxEnabled();
|
||||||
});
|
});
|
||||||
|
navigator.keyboard.getLayoutMap().then(it=>dom.querySelector('#qr--modal-commentKey').textContent = it.get('Backslash'));
|
||||||
this.editorMessageLabel = dom.querySelector('label[for="qr--modal-message"]');
|
this.editorMessageLabel = dom.querySelector('label[for="qr--modal-message"]');
|
||||||
/**@type {HTMLTextAreaElement}*/
|
/**@type {HTMLTextAreaElement}*/
|
||||||
const message = dom.querySelector('#qr--modal-message');
|
const message = dom.querySelector('#qr--modal-message');
|
||||||
@ -515,6 +516,7 @@ export class QuickReply {
|
|||||||
message.addEventListener('keydown', async(evt) => {
|
message.addEventListener('keydown', async(evt) => {
|
||||||
if (this.isExecuting) return;
|
if (this.isExecuting) return;
|
||||||
if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) {
|
if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) {
|
||||||
|
// increase indent
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
const start = message.selectionStart;
|
const start = message.selectionStart;
|
||||||
const end = message.selectionEnd;
|
const end = message.selectionEnd;
|
||||||
@ -536,6 +538,7 @@ export class QuickReply {
|
|||||||
message.dispatchEvent(new Event('input', { bubbles:true }));
|
message.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
}
|
}
|
||||||
} else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) {
|
} else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) {
|
||||||
|
// decrease indent
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopImmediatePropagation();
|
evt.stopImmediatePropagation();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
@ -548,6 +551,7 @@ export class QuickReply {
|
|||||||
message.selectionEnd = end - count;
|
message.selectionEnd = end - count;
|
||||||
message.dispatchEvent(new Event('input', { bubbles:true }));
|
message.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
} else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {
|
} else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) {
|
||||||
|
// new line, keep indent
|
||||||
evt.stopImmediatePropagation();
|
evt.stopImmediatePropagation();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@ -560,10 +564,11 @@ export class QuickReply {
|
|||||||
message.selectionEnd = message.selectionStart;
|
message.selectionEnd = message.selectionStart;
|
||||||
message.dispatchEvent(new Event('input', { bubbles:true }));
|
message.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
} else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) {
|
} else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) {
|
||||||
evt.stopImmediatePropagation();
|
|
||||||
evt.stopPropagation();
|
|
||||||
evt.preventDefault();
|
|
||||||
if (executeShortcut.checked) {
|
if (executeShortcut.checked) {
|
||||||
|
// execute QR
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
const selectionStart = message.selectionStart;
|
const selectionStart = message.selectionStart;
|
||||||
const selectionEnd = message.selectionEnd;
|
const selectionEnd = message.selectionEnd;
|
||||||
message.blur();
|
message.blur();
|
||||||
@ -574,6 +579,45 @@ export class QuickReply {
|
|||||||
message.selectionEnd = selectionEnd;
|
message.selectionEnd = selectionEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (evt.key == 'F9' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey) {
|
||||||
|
// toggle breakpoint
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
preBreakPointStart = message.selectionStart;
|
||||||
|
preBreakPointEnd = message.selectionEnd;
|
||||||
|
toggleBreakpoint();
|
||||||
|
} else if (evt.code == 'Backslash' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) {
|
||||||
|
// toggle comment
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
// check if we are inside a comment -> uncomment
|
||||||
|
const parser = new SlashCommandParser();
|
||||||
|
parser.parse(message.value, false);
|
||||||
|
const start = message.selectionStart;
|
||||||
|
const end = message.selectionEnd;
|
||||||
|
const comment = parser.commandIndex.findLast(it=>it.name == '*' && (it.start <= start && it.end >= start || it.start <= end && it.end >= end));
|
||||||
|
if (comment) {
|
||||||
|
let content = message.value.slice(comment.start + 1, comment.end - 1);
|
||||||
|
let len = content.length;
|
||||||
|
content = content.replace(/^ /, '');
|
||||||
|
const offsetStart = len - content.length;
|
||||||
|
len = content.length;
|
||||||
|
content = content.replace(/ $/, '');
|
||||||
|
const offsetEnd = len - content.length;
|
||||||
|
message.value = `${message.value.slice(0, comment.start - 1)}${content}${message.value.slice(comment.end + 1)}`;
|
||||||
|
message.selectionStart = start - (start >= comment.start ? 2 + offsetStart : 0);
|
||||||
|
message.selectionEnd = end - 2 - offsetStart - (end >= comment.end ? 2 + offsetEnd : 0);
|
||||||
|
} else {
|
||||||
|
const lineStart = message.value.lastIndexOf('\n', start - 1) + 1;
|
||||||
|
const lineEnd = message.value.indexOf('\n', end);
|
||||||
|
const lines = message.value.slice(lineStart, lineEnd).split('\n');
|
||||||
|
message.value = `${message.value.slice(0, lineStart)}/* ${message.value.slice(lineStart, lineEnd)} *|${message.value.slice(lineEnd)}`;
|
||||||
|
message.selectionStart = start + 3;
|
||||||
|
message.selectionEnd = end + 3;
|
||||||
|
}
|
||||||
|
message.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const ac = await setSlashCommandAutoComplete(message, true);
|
const ac = await setSlashCommandAutoComplete(message, true);
|
||||||
@ -583,6 +627,11 @@ export class QuickReply {
|
|||||||
message.addEventListener('scroll', (evt)=>{
|
message.addEventListener('scroll', (evt)=>{
|
||||||
updateScrollDebounced();
|
updateScrollDebounced();
|
||||||
});
|
});
|
||||||
|
let preBreakPointStart;
|
||||||
|
let preBreakPointEnd;
|
||||||
|
/**
|
||||||
|
* @param {SlashCommandBreakPoint} bp
|
||||||
|
*/
|
||||||
const removeBreakpoint = (bp)=>{
|
const removeBreakpoint = (bp)=>{
|
||||||
// start at -1 because "/" is not included in start-end
|
// start at -1 because "/" is not included in start-end
|
||||||
let start = bp.start - 1;
|
let start = bp.start - 1;
|
||||||
@ -623,21 +672,38 @@ export class QuickReply {
|
|||||||
}
|
}
|
||||||
return { start:postStart, end:postEnd };
|
return { start:postStart, end:postEnd };
|
||||||
};
|
};
|
||||||
let preBreakPointStart;
|
/**
|
||||||
let preBreakPointEnd;
|
* @param {SlashCommandExecutor} cmd
|
||||||
message.addEventListener('pointerdown', (evt)=>{
|
*/
|
||||||
if (!evt.ctrlKey || !evt.altKey) return;
|
const addBreakpoint = (cmd)=>{
|
||||||
preBreakPointStart = message.selectionStart;
|
// start at -1 because "/" is not included in start-end
|
||||||
preBreakPointEnd = message.selectionEnd;
|
let start = cmd.start - 1;
|
||||||
});
|
let indent = '';
|
||||||
message.addEventListener('pointerup', async(evt)=>{
|
// step left until forward slash "/"
|
||||||
if (!evt.ctrlKey || !evt.altKey || message.selectionStart != message.selectionEnd) return;
|
while (message.value[start] != '/') start--;
|
||||||
|
// step left while whitespace (except newline) before start, collect the whitespace to help build indentation
|
||||||
|
while (/[^\S\n]/.test(message.value[start - 1])) {
|
||||||
|
start--;
|
||||||
|
indent += message.value[start];
|
||||||
|
}
|
||||||
|
// if newline before indent, include the newline
|
||||||
|
if (message.value[start - 1] == '\n') {
|
||||||
|
start--;
|
||||||
|
indent = `\n${indent}`;
|
||||||
|
}
|
||||||
|
const breakpointText = `${indent}/breakpoint |`;
|
||||||
|
const v = `${message.value.slice(0, start)}${breakpointText}${message.value.slice(start)}`;
|
||||||
|
message.value = v;
|
||||||
|
message.dispatchEvent(new Event('input', { bubbles:true }));
|
||||||
|
return breakpointText.length;
|
||||||
|
};
|
||||||
|
const toggleBreakpoint = ()=>{
|
||||||
const idx = message.selectionStart;
|
const idx = message.selectionStart;
|
||||||
let postStart = preBreakPointStart;
|
let postStart = preBreakPointStart;
|
||||||
let postEnd = preBreakPointEnd;
|
let postEnd = preBreakPointEnd;
|
||||||
const parser = new SlashCommandParser();
|
const parser = new SlashCommandParser();
|
||||||
parser.parse(message.value, false);
|
parser.parse(message.value, false);
|
||||||
const cmdIdx = parser.commandIndex.findLastIndex(it=>it.start <= idx && it.end >= idx);
|
const cmdIdx = parser.commandIndex.findLastIndex(it=>it.start <= idx);
|
||||||
if (cmdIdx > -1) {
|
if (cmdIdx > -1) {
|
||||||
const cmd = parser.commandIndex[cmdIdx];
|
const cmd = parser.commandIndex[cmdIdx];
|
||||||
if (cmd instanceof SlashCommandBreakPoint) {
|
if (cmd instanceof SlashCommandBreakPoint) {
|
||||||
@ -651,28 +717,22 @@ export class QuickReply {
|
|||||||
postStart = start;
|
postStart = start;
|
||||||
postEnd = end;
|
postEnd = end;
|
||||||
} else {
|
} else {
|
||||||
// start at -1 because "/" is not included in start-end
|
const len = addBreakpoint(cmd);
|
||||||
let start = cmd.start - 1;
|
postStart += len;
|
||||||
let indent = '';
|
postEnd += len;
|
||||||
// step left until forward slash "/"
|
|
||||||
while (message.value[start] != '/') start--;
|
|
||||||
// step left while whitespace (except newline) before start, collect the whitespace to help build indentation
|
|
||||||
while (/[^\S\n]/.test(message.value[start - 1])) {
|
|
||||||
start--;
|
|
||||||
indent += message.value[start];
|
|
||||||
}
|
|
||||||
// if newline before indent, include the newline
|
|
||||||
if (message.value[start - 1] == '\n') {
|
|
||||||
start--;
|
|
||||||
indent = `\n${indent}`;
|
|
||||||
}
|
|
||||||
const v = `${message.value.slice(0, start)}${indent}/breakpoint |${message.value.slice(start)}`;
|
|
||||||
message.value = v;
|
|
||||||
message.dispatchEvent(new Event('input', { bubbles:true }));
|
|
||||||
}
|
}
|
||||||
message.selectionStart = postStart;
|
message.selectionStart = postStart;
|
||||||
message.selectionEnd = postEnd;
|
message.selectionEnd = postEnd;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
message.addEventListener('pointerdown', (evt)=>{
|
||||||
|
if (!evt.ctrlKey || !evt.altKey) return;
|
||||||
|
preBreakPointStart = message.selectionStart;
|
||||||
|
preBreakPointEnd = message.selectionEnd;
|
||||||
|
});
|
||||||
|
message.addEventListener('pointerup', async(evt)=>{
|
||||||
|
if (!evt.ctrlKey || !evt.altKey || message.selectionStart != message.selectionEnd) return;
|
||||||
|
toggleBreakpoint();
|
||||||
});
|
});
|
||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
const resizeListener = debounce((evt) => {
|
const resizeListener = debounce((evt) => {
|
||||||
|
@ -489,8 +489,9 @@
|
|||||||
}
|
}
|
||||||
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
.popup:has(#qr--modalEditor) .popup-content > #qr--modalEditor > #qr--main > .qr--modal-messageContainer > .qr--modal-editorSettings {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em;
|
column-gap: 1em;
|
||||||
color: var(--grey70);
|
color: var(--grey70);
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
@ -560,8 +560,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
> .qr--modal-editorSettings {
|
> .qr--modal-editorSettings {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 1em;
|
column-gap: 1em;
|
||||||
color: var(--grey70);
|
color: var(--grey70);
|
||||||
font-size: smaller;
|
font-size: smaller;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
@ -231,6 +231,12 @@ export class SlashCommandParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BLOCK_COMMENT = {
|
||||||
|
scope: 'comment',
|
||||||
|
begin: /\/\*/,
|
||||||
|
end: /\*\|/,
|
||||||
|
contains: [],
|
||||||
|
};
|
||||||
const COMMENT = {
|
const COMMENT = {
|
||||||
scope: 'comment',
|
scope: 'comment',
|
||||||
begin: /\/[/#]/,
|
begin: /\/[/#]/,
|
||||||
@ -312,6 +318,9 @@ export class SlashCommandParser {
|
|||||||
begin: /{{/,
|
begin: /{{/,
|
||||||
end: /}}/,
|
end: /}}/,
|
||||||
};
|
};
|
||||||
|
BLOCK_COMMENT.contains.push(
|
||||||
|
BLOCK_COMMENT,
|
||||||
|
);
|
||||||
RUN.contains.push(
|
RUN.contains.push(
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
NAMED_ARG,
|
NAMED_ARG,
|
||||||
@ -362,6 +371,7 @@ export class SlashCommandParser {
|
|||||||
);
|
);
|
||||||
CLOSURE.contains.push(
|
CLOSURE.contains.push(
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
BLOCK_COMMENT,
|
||||||
COMMENT,
|
COMMENT,
|
||||||
ABORT,
|
ABORT,
|
||||||
KEYWORD,
|
KEYWORD,
|
||||||
@ -381,6 +391,7 @@ export class SlashCommandParser {
|
|||||||
keywords: ['|'],
|
keywords: ['|'],
|
||||||
contains: [
|
contains: [
|
||||||
hljs.BACKSLASH_ESCAPE,
|
hljs.BACKSLASH_ESCAPE,
|
||||||
|
BLOCK_COMMENT,
|
||||||
COMMENT,
|
COMMENT,
|
||||||
ABORT,
|
ABORT,
|
||||||
KEYWORD,
|
KEYWORD,
|
||||||
@ -678,7 +689,9 @@ export class SlashCommandParser {
|
|||||||
this.discardWhitespace();
|
this.discardWhitespace();
|
||||||
}
|
}
|
||||||
while (!this.testClosureEnd()) {
|
while (!this.testClosureEnd()) {
|
||||||
if (this.testComment()) {
|
if (this.testBlockComment()) {
|
||||||
|
this.parseBlockComment();
|
||||||
|
} else if (this.testComment()) {
|
||||||
this.parseComment();
|
this.parseComment();
|
||||||
} else if (this.testParserFlag()) {
|
} else if (this.testParserFlag()) {
|
||||||
this.parseParserFlag();
|
this.parseParserFlag();
|
||||||
@ -760,6 +773,30 @@ export class SlashCommandParser {
|
|||||||
return cmd;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testBlockComment() {
|
||||||
|
return this.testSymbol(/\/\*/);
|
||||||
|
}
|
||||||
|
testBlockCommentEnd() {
|
||||||
|
return this.testSymbol(/\*\|/);
|
||||||
|
}
|
||||||
|
parseBlockComment() {
|
||||||
|
const start = this.index + 1;
|
||||||
|
const cmd = new SlashCommandExecutor(start);
|
||||||
|
cmd.command = this.commands['*'];
|
||||||
|
this.commandIndex.push(cmd);
|
||||||
|
this.scopeIndex.push(this.scope.getCopy());
|
||||||
|
this.take(); // discard "/"
|
||||||
|
cmd.name = this.take(); //set "*" as name
|
||||||
|
while (!this.testBlockCommentEnd()) {
|
||||||
|
if (this.testBlockComment()) {
|
||||||
|
this.parseBlockComment();
|
||||||
|
}
|
||||||
|
this.take();
|
||||||
|
}
|
||||||
|
this.take(2); // take closing "*|"
|
||||||
|
cmd.end = this.index - 1;
|
||||||
|
}
|
||||||
|
|
||||||
testComment() {
|
testComment() {
|
||||||
return this.testSymbol(/\/[/#]/);
|
return this.testSymbol(/\/[/#]/);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user