>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.jsconst 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.jsconst 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;
}
};