213 lines
6.8 KiB
JavaScript
213 lines
6.8 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
|
|
const D2_SHORTCUT_DEFAULT_COMMENT = "ctrl+/";
|
|
|
|
/**
|
|
* ショートカットキーを作るクラス
|
|
*/
|
|
class D2_ShortcutKeyControl {
|
|
shortcutKeys = {
|
|
comment: [],
|
|
};
|
|
|
|
constructor() {
|
|
// 設定読み込み
|
|
this.changeKeySetting("comment", app.ui.settings.getSettingValue("D2.shortcutKey.comment", D2_SHORTCUT_DEFAULT_COMMENT));
|
|
|
|
// イベントハンドラをバインド
|
|
this._onKeyDown = this._onKeyDown.bind(this);
|
|
|
|
// キーボードイベントリスナーを設定
|
|
document.addEventListener('keydown', this._onKeyDown);
|
|
}
|
|
|
|
/**
|
|
* キーボードイベントハンドラ
|
|
* @param {KeyboardEvent} e キーボードイベント
|
|
*/
|
|
_onKeyDown(e) {
|
|
// アクティブな要素がテキストエリアでない場合は処理しない
|
|
if (!(document.activeElement instanceof HTMLTextAreaElement)) {
|
|
return;
|
|
}
|
|
|
|
// 押されたキーの配列を取得
|
|
const pressedKeys = this._getPressedKeys(e);
|
|
|
|
// コメント
|
|
if (this.shortcutKeys.comment.length > 0 &&
|
|
this._compareKeyArrays(pressedKeys, this.shortcutKeys.comment)) {
|
|
e.preventDefault();
|
|
this.comment();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* キーボードイベントから押されたキーの配列を生成
|
|
* @param {KeyboardEvent} e キーボードイベント
|
|
* @returns {string[]} 押されたキーの配列
|
|
*/
|
|
_getPressedKeys(e) {
|
|
const keys = [];
|
|
if (e.ctrlKey) keys.push('ctrl');
|
|
if (e.altKey) keys.push('alt');
|
|
if (e.shiftKey) keys.push('shift');
|
|
if (e.metaKey) keys.push('meta');
|
|
|
|
// キーコードを追加(特殊キーの場合は名前、それ以外は小文字)
|
|
let key = e.key.toLowerCase();
|
|
if (key === ' ') key = 'space';
|
|
if (key === '/') key = '/';
|
|
if (key.length === 1 || ['enter', 'tab', 'space', 'backspace', 'delete', 'escape', 'arrowup', 'arrowdown', 'arrowleft', 'arrowright'].includes(key)) {
|
|
keys.push(key);
|
|
}
|
|
|
|
return keys;
|
|
}
|
|
|
|
/**
|
|
* 2つのキー配列が同じ要素を含むか比較
|
|
* @param {string[]} arr1 キー配列1
|
|
* @param {string[]} arr2 キー配列2
|
|
* @returns {boolean} 同じ要素を含む場合はtrue
|
|
*/
|
|
_compareKeyArrays(arr1, arr2) {
|
|
if (arr1.length !== arr2.length) return false;
|
|
|
|
// 配列をソートしてから比較
|
|
const sorted1 = [...arr1].sort();
|
|
const sorted2 = [...arr2].sort();
|
|
|
|
return sorted1.every((key, index) => key === sorted2[index]);
|
|
}
|
|
|
|
/**
|
|
* ショートカットキー設定を変更
|
|
*/
|
|
changeKeySetting(param, value) {
|
|
this.shortcutKeys[param] = value ? value.toLowerCase().split('+').map(k => k.trim()).filter(k => k) : [];
|
|
}
|
|
|
|
/**
|
|
* コメント処理
|
|
* 選択範囲がない場合:
|
|
* 1. マルチラインコメントの探索を行い、存在するなら削除
|
|
* 2. 行頭にシングルラインコメントがあれば削除
|
|
* 3. シングルラインコメントを付与
|
|
* 選択範囲がある場合:
|
|
* - マルチラインコメントを付与
|
|
*/
|
|
comment() {
|
|
const textarea = document.activeElement;
|
|
if (!(textarea instanceof HTMLTextAreaElement)) {
|
|
return;
|
|
}
|
|
|
|
const text = textarea.value;
|
|
const selStart = textarea.selectionStart;
|
|
const selEnd = textarea.selectionEnd;
|
|
|
|
// 選択範囲があるかどうか
|
|
const hasSelection = selStart !== selEnd;
|
|
|
|
let newText;
|
|
let newSelStart;
|
|
let newSelEnd;
|
|
|
|
if (hasSelection) {
|
|
// 選択範囲がある場合は /* */ を付与
|
|
const selectedText = text.substring(selStart, selEnd);
|
|
const beforeText = text.substring(0, selStart);
|
|
const afterText = text.substring(selEnd);
|
|
|
|
newText = beforeText + '/*' + selectedText + '*/' + afterText;
|
|
// 選択範囲を解除し、/* の後ろにカーソルを移動
|
|
newSelStart = selStart + 2;
|
|
newSelEnd = selStart + 2;
|
|
} else {
|
|
// 選択範囲がない場合
|
|
|
|
// 1. /* */ の探索
|
|
const beforeText = text.substring(0, selStart);
|
|
const afterText = text.substring(selEnd);
|
|
const lastCommentStartPos = beforeText.lastIndexOf('/*');
|
|
const firstCommentEndPos = afterText.indexOf('*/');
|
|
|
|
if (lastCommentStartPos !== -1 && firstCommentEndPos !== -1) {
|
|
// /* */ が見つかった場合、コメント部分を削除して内容を保持
|
|
const commentStartPos = lastCommentStartPos;
|
|
const commentEndPos = selEnd + firstCommentEndPos + 2; // +2 for '*/'
|
|
const commentContent = text.substring(commentStartPos + 2, commentEndPos - 2);
|
|
|
|
newText = text.substring(0, commentStartPos) + commentContent + text.substring(commentEndPos);
|
|
newSelStart = commentStartPos;
|
|
newSelEnd = commentStartPos;
|
|
} else {
|
|
// 2. & 3. 行コメントの処理
|
|
|
|
// 現在のカーソル位置を含む行の開始位置と終了位置を取得
|
|
let lineStart = text.lastIndexOf('\n', selStart - 1) + 1;
|
|
let lineEnd = text.indexOf('\n', selStart);
|
|
if (lineEnd === -1) lineEnd = text.length;
|
|
|
|
// 現在の行のテキストを取得
|
|
const line = text.substring(lineStart, lineEnd);
|
|
|
|
// 行頭に「//」があるかチェック
|
|
const hasComment = line.startsWith('//');
|
|
|
|
// 新しい行のテキスト
|
|
let newLine;
|
|
if (hasComment) {
|
|
// コメントを削除
|
|
newLine = line.substring(2).trimStart();
|
|
} else {
|
|
// コメントを追加
|
|
newLine = '// ' + line;
|
|
}
|
|
|
|
// テキストエリアの内容を更新
|
|
newText = text.substring(0, lineStart) + newLine + text.substring(lineEnd);
|
|
|
|
// カーソル位置を調整(選択範囲を解除)
|
|
if (hasComment) {
|
|
// コメントを削除した場合、カーソルを3文字前に移動
|
|
newSelStart = selStart - 3;
|
|
newSelEnd = newSelStart;
|
|
} else {
|
|
// コメントを追加した場合
|
|
const cursorOffset = 3; // "// " の長さ
|
|
newSelStart = selStart + cursorOffset;
|
|
newSelEnd = newSelStart;
|
|
}
|
|
}
|
|
}
|
|
|
|
// テキストエリアの内容を更新
|
|
textarea.value = newText;
|
|
|
|
// 選択範囲を調整
|
|
textarea.selectionStart = newSelStart;
|
|
textarea.selectionEnd = newSelEnd;
|
|
|
|
// 変更イベントを発火
|
|
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
}
|
|
}
|
|
|
|
|
|
(function () {
|
|
const shortcutKeyControl = new D2_ShortcutKeyControl();
|
|
|
|
app.ui.settings.addSetting({
|
|
id: "D2.shortcutKey.comment",
|
|
name: `Comment (default: ${D2_SHORTCUT_DEFAULT_COMMENT})`,
|
|
type: "string",
|
|
defaultValue: D2_SHORTCUT_DEFAULT_COMMENT,
|
|
onChange(value) {
|
|
shortcutKeyControl.changeKeySetting("comment", value);
|
|
},
|
|
});
|
|
})();
|