add block comments with shortcut and breakpoint shortcut

This commit is contained in:
LenAnderson
2024-07-14 18:58:13 -04:00
parent 82dd53f166
commit db1094e391
5 changed files with 135 additions and 35 deletions

View File

@ -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>

View File

@ -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) => {

View File

@ -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;

View File

@ -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;

View File

@ -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(/\/[/#]/);
} }