generated at
ActionLock
>Howm の ActionLock って便利だったよね]


package.json
{ "name": "actionlock", "displayName": "ActionLock", "description": "ActionLock is a magical command inspired from [Howm](https://www.emacswiki.org/emacs/HowmMode).", "version": "1.2.0", "author": { "name": "Nobuhito SATO", "email": "nobuhito.sato@gmail.com", "url": "https://bulkus.net/" }, "publisher": "nobuhito", "engines": { "vscode": "^1.18.0" }, "categories": [ "Other" ], "repository": { "url": "https://github.com/nobuhito/vscode.actionlock" }, "activationEvents": [ "onLanguage:markdown", "onCommand:extension.doAction", "onCommand:extension.toggleTask" ], "license": "SEE LICENSE IN LICENSE", "icon": "icon.png", "main": "./src/extension", "contributes": { "configuration": { "type": "object", "title": "ActionLock configuration", "properties": { "actionlock.underlineColor": { "type": "string", "default": "#5b7e91", "description": "Underline color of the character that ActionLock fires (default color is 舛花色)" }, "actionlock.switchWords": { "type": "array", "default": [ [ "🚀 Rocket", "😺 Cat", "🐶 Dog" ], [ "true", "false" ] ], "description": "ActionLock switch words" } } }, "keybindings": [ { "command": "extension.doAction", "key": "Enter", "when": "actionlock.isTrue" } ], "commands": [ { "command": "extension.doAction", "title": "Execute ActionLock" }, { "command": "extension.toggleTask", "title": "Toggle task for mdtasks", "when": "actionlock.isInstalledMDTasks" } ] }, "scripts": { "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./node_modules/vscode/bin/test" }, "devDependencies": { "@types/mocha": "^2.2.42", "@types/node": "^7.0.0", "eslint": "^5.12.0", "mocha": "^5.2.0", "typescript": "^2.5.2", "vscode": "^1.1.26" }, "dependencies": { "moment": "^2.23.0" } }

src/extension.js
const vscode = require('vscode'); const ActionLock = require("./actionlock") const moment = require("moment"); var decorationTypes = []; function activate(context) { let isInstalledMdtasks = (vscode.extensions.all.filter((d) => { return d.id == "nobuhito.mdtasks"; }).length > 0); let ac = new ActionLock(isInstalledMdtasks); context.subscriptions.push(vscode.commands.registerTextEditorCommand("extension.doAction", editor => { doAction(ac, editor); })); vscode.commands.executeCommand("setContext", "actionlock.isInstalledMDTasks", false); if (isInstalledMdtasks) { vscode.commands.executeCommand("setContext", "actionlock.isInstalledMDTasks", true); context.subscriptions.push(vscode.commands.registerTextEditorCommand("extension.toggleTask", editor => { toggleTask(ac, editor); })); } var select = vscode.window.onDidChangeTextEditorSelection((event) => { triggerUpdate(event.textEditor); }); context.subscriptions.push(select); var timeout = null; function triggerUpdate(editor) { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { updateDecorations(ac, editor); vscode.commands.executeCommand("setContext", "actionlock.isTrue", false); let range = ac.getRangeAtCursor(editor); if (range != null) { vscode.commands.executeCommand("setContext", "actionlock.isTrue", true); } }, 100); } let editor = vscode.window.activeTextEditor; if (editor) { triggerUpdate(editor); } } exports.activate = activate; // this method is called when your extension is deactivated function deactivate() { } exports.deactivate = deactivate; function toggleTask(ac, editor) { let line = editor.selection.start.line; let position = ac.findCheckboxAtCursorLine(line); editor.selection = new vscode.Selection(position, position); doAction(ac, editor); } function updateDecorations(ActionLock, editor) { for (const decorationType of decorationTypes) { decorationType.dispose(); } decorationTypes = []; decorationTypes.push(ActionLock.updateDecorations(editor)); } function doAction(ac, editor) { let range = ac.getRangeAtCursor(editor); editor.selection = new vscode.Selection(range.start, range.end); let word = editor.document.getText(range); let isDate = ac.regexDate.test(word); if (isDate) { showQuickPick(ac); } else { let edits = []; let switchWords = ac.switchWords; for (let items of switchWords) { let index = items.indexOf(word); if (index > -1) { let dist = (items.length == index + 1) ? items[0] : items[index + 1]; edits.push({ range: range, dist: dist }); let isMdtasksItem = (["[x]", "[ ]"].indexOf(word) > -1) ? true : false; if (ac.isInstalledMdtasks && isMdtasksItem) { edits = ac.checkParentTasks(edits, editor.document.getText().split(/\r?\n/)); } var selection = new vscode.Position(range.start.line, range.start.character + dist.length); editor.selection = new vscode.Selection(selection, selection); break; } } editor.edit(edit => { for (const e of edits) { edit.replace(e.range, e.dist); } }).then(() => { if (ac.isInstalledMdtasks) { let lineAt = editor.document.lineAt(range.start.line); let doneDateReg = /\s\->\s\d{4}\-\d{2}\-\d{2}/; let newText = (doneDateReg.test(lineAt.text)) ? lineAt.text.replace(doneDateReg, "") : lineAt.text + " -> " + moment().format("YYYY-MM-DD"); editor.edit(edit => { edit.replace(lineAt.range, newText); }); } }); } } function showQuickPick(ac) { let items = ac.buildQuickPick(); let editor = vscode.window.activeTextEditor; var range = new vscode.Range(editor.selection.start, editor.selection.end); let options = { matchOnDescription: true, placeHolder: "Select date or Close with escape key" }; vscode.window.showQuickPick(items, options).then((select) => { if (select != undefined) { editor.edit((edit) => { edit.replace(range, select.label); }); } }); }

src/actionlock.js
const vscode = require('vscode'); const moment = require("moment"); module.exports = class ActionLock { constructor(isInstalledMdtasks) { this.isInstalledMdtasks = isInstalledMdtasks; this.regexDate = /[12][90]\d{2}\-[01][0-9]\-[0-3][0-9]/g; this.decorationType = null; let myConf = vscode.workspace.getConfiguration("actionlock"); this.switchWords = myConf.get("switchWords"); if (isInstalledMdtasks) { this.switchWords.push(["[x]", "[ ]"]); } this.switchArray = []; for (const items of this.switchWords) { for (const item of items) { this.switchArray.push(item); } } this.regexWord = new RegExp(this.switchArray.map((d) => { return this.regexpEscape(d); }).join("|"), "g"); this.underlineColor = myConf.get("underlineColor"); this.ranges = []; } makeRanges(lines) { let match; this.ranges = []; for (let i = 0; i < lines.length; i++) { let line = lines[i]; // 日付 while ((match = this.regexDate.exec(line)) !== null) { let range = new vscode.Range(i, match.index, i, match.index + match[0].length); this.ranges.push(range); } // ユーザー定義 while ((match = this.regexWord.exec(line)) != null) { let range = new vscode.Range(i, match.index, i, match.index + match[0].length); this.ranges.push(range); } } } regexpEscape(regexpString) { return regexpString.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } getRanges(text) { this.makeRanges(text.split(/\r?\n/)); return this.ranges; } updateDecorations(editor) { let ranges = this.getRanges(editor.document.getText()); let decorationType = vscode.window.createTextEditorDecorationType({ "textDecoration": "underline " + this.underlineColor }); editor.setDecorations(decorationType, ranges); return decorationType; } getRangeAtCursor(editor) { let ranges = this.getRanges(editor.document.getText()); let selection = editor.selection; for (let i = 0; i < ranges.length; i++) { let range = ranges[i]; if (this.isWithInRange(selection, range)) { return range; } } return null; } isWithInRange(selection, range) { if (range.start.line == 44) { console.log(selection, range); } var ret = true; if (range.start.line > selection.start.line || range.end.line < selection.start.line) { ret = false; } if (range.start.character >= selection.start.character || range.end.character <= selection.start.character) { ret = false; } return ret; } buildQuickPick() { var days = {}; days[moment().format("YYYY-MM-DD")] = ["Today ."]; days[moment().add(1, "day").format("YYYY-MM-DD")] = ["Tomorrow"]; days[moment().add(7, "day").format("YYYY-MM-DD")] = ["NextWeek"]; days[moment().add(14, "day").format("YYYY-MM-DD")] = ["+2week", "+14day"]; days[moment().add(30, "day").format("YYYY-MM-DD")] = ["NextMonth", "+1month", "+30day"]; days[moment().add(60, "day").format("YYYY-MM-DD")] = ["+2month", "+60day"]; days[moment().add(90, "day").format("YYYY-MM-DD")] = ["+3month", "+90day"]; days[moment().add(180, "day").format("YYYY-MM-DD")] = ["+6month", "+180day"]; days[moment().add(360, "day").format("YYYY-MM-DD")] = ["NextYear", "+1year", "+12month", "+365day"]; for (var i = 1; i < 90; i++) { let _cd = moment().add(i, "day"); let cd = _cd.format("YYYY-MM-DD"); if (days[cd] == undefined) { days[cd] = []; } if (i < 8) { days[cd].push("Next" + _cd.format("dddd")); } days[cd].push("+" + i + "day"); } var items = []; items = Object.keys(days).map(key => { return { label: key, description: days[key].join(", ") }; }).sort((a, b) => { if (a.description.indexOf("Today") > -1) { return -1; } return (a.label > b.label) ? 1 : -1; }); return items; } // for MDTasks indentLevel(line) { return (/^(\s+)/.test(line)) ? RegExp.$1.length : 0; } findParentTasks(lines, startRow) { let currentIndentLevel = this.indentLevel(lines[startRow]); for (let i = startRow; i >= 0; i--) { if (currentIndentLevel > this.indentLevel(lines[i])) { return i; } } return -1; } isChildTasksAllDone(edits, lines, parentRow) { let firstChildRow = parentRow + 1; let childIndentLevel = this.indentLevel(lines[firstChildRow]); for (let i = firstChildRow; i < lines.length; i++) { let line = lines[i]; if (childIndentLevel > this.indentLevel(line)) { break; } if (childIndentLevel < this.indentLevel(line)) { continue; } let child = edits.filter(d => { return d.range.start.line == i; }); if (child.length > 0 && child[0].dist == "[x]") { } else if (child.length > 0 && child[0].dist == "[ ]") { return false; } else if (/^\s*\-?\s?\[\s\]\s/.test(line)) { return false; } } return true; } checkParentTasks(edits, lines) { let row = edits[edits.length - 1].range.start.line; let parentRow = this.findParentTasks(lines, row); if (parentRow == -1) { return edits; } let parentDone = this.isChildTasksAllDone(edits, lines, parentRow); this.makeRanges(lines); let range = this.ranges.filter((d) => { return d.start.line == parentRow; }); let dist = ""; if (range.length != 0) { dist = (parentDone) ? "[x]" : "[ ]"; edits.push({ range: range[0], dist: dist }); } if (parentRow != null && dist != "" && this.indentLevel(lines[parentRow]) != 0) { return this.checkParentTasks(edits, lines); } return edits; } findCheckboxAtCursorLine(line) { let ranges = this.ranges.sort((a, b) => { if (a.start.line > b.start.line) { return 1; } else if (a.start.line < b.start.line) { return -1; } else { return (a.start.character > b.start.character) ? 1 : -1; } }); let filteredRange = ranges.filter(d => { return d.start.line == line; }); if (filteredRange.length == 0) { return; } let position = new vscode.Position( filteredRange[0].start.line, filteredRange[0].start.character + 1 ); return position; } };