title: $:/plugins/local/org-gtd/codemirror.js type: application/javascript module-type: codemirror /*\ title: $:/plugins/local/org-gtd/codemirror.js type: application/javascript module-type: codemirror \*/ (function() { "use strict"; var CodeMirror = require("$:/plugins/tiddlywiki/codemirror/lib/codemirror.js"), org = require("$:/plugins/local/org-gtd/library.js"), JUMP_STATE_TITLE = "$:/state/org-gtd/jump", KEY_MAP_TITLE = "$:/config/org-gtd/extraKeys"; function isOrgEditor(cm) { return cm.getMode().name === "org-gtd"; } function readOrgGtdKeyMap(cm) { var widget = cm.getOption("editWidget"), text, keyMap; if(!widget || !widget.wiki) { return null; } text = widget.wiki.getTiddlerText(KEY_MAP_TITLE,"{}"); try { keyMap = JSON.parse(text); } catch(error) { console.error("Org GTD shortcut configuration is invalid JSON",error); return null; } if(!keyMap || typeof keyMap !== "object" || Array.isArray(keyMap) || Object.keys(keyMap).length === 0) { return null; } try { return CodeMirror.normalizeKeyMap(keyMap); } catch(error) { console.error("Org GTD shortcut configuration contains an invalid key name",error); return null; } } function syncOrgGtdKeyMap(cm) { var keyMap; if(cm.state.orgGtdKeyMap) { cm.removeKeyMap(cm.state.orgGtdKeyMap); cm.state.orgGtdKeyMap = null; } if(!isOrgEditor(cm)) { return; } keyMap = readOrgGtdKeyMap(cm); if(keyMap) { cm.addKeyMap(keyMap); cm.state.orgGtdKeyMap = keyMap; } } function clearFolds(cm) { var marks = cm.state.orgGtdFoldMarks || []; marks.forEach(function(mark) { if(mark.find()) { mark.clear(); } }); cm.state.orgGtdFoldMarks = []; } function replaceDocument(cm,lines,cursorLine,cursorCh) { var lastLine = cm.lastLine(), lastCh = cm.getLine(lastLine).length; clearFolds(cm); cm.operation(function() { cm.replaceRange(lines.join("\n"),CodeMirror.Pos(0,0),CodeMirror.Pos(lastLine,lastCh),"+org-gtd"); cm.setCursor(CodeMirror.Pos(cursorLine,cursorCh || 0)); }); } function currentHeading(cm) { var parsed = org.parse(cm.getValue()), heading = org.headingAtLine(parsed,cm.getCursor().line); return {parsed: parsed,heading: heading}; } CodeMirror.defineMode("org-gtd",function() { return { startState: function() { return {heading: false,planning: false}; }, token: function(stream,state) { if(stream.sol()) { state.heading = /^\*+\s+/.test(stream.string); state.planning = /^\s*(?:SCHEDULED|DEADLINE):/.test(stream.string); } if(state.heading) { if(stream.match(/^\*+\s+/)) { return "header"; } if(stream.match(/^(?:TODO|NEXT|WAIT|DONE)\b/)) { return "keyword"; } if(stream.match(/^\s+:[A-Za-z0-9_@#%+.-]+(?::[A-Za-z0-9_@#%+.-]+)*:\s*$/)) { return "tag"; } } if(state.planning) { if(stream.match(/^\s*(?:SCHEDULED|DEADLINE):/)) { return "def"; } if(stream.match(/^\s*<[0-9]{4}-[0-9]{2}-[0-9]{2}(?:\s+[^>]*)?>/)) { return "atom"; } } if(stream.sol() && stream.match(/^\s*#.*$/)) { return "comment"; } stream.next(); return null; }, lineComment: "# " }; }); CodeMirror.defineMIME("text/x-org","org-gtd"); CodeMirror.commands.orgGtdCycleState = function(cm) { var cursor, line, result; if(!isOrgEditor(cm)) { return CodeMirror.Pass; } cursor = cm.getCursor(); line = cm.getLine(cursor.line); result = org.cycleState(line); if(!result) { return CodeMirror.Pass; } cm.replaceRange(result.line,CodeMirror.Pos(cursor.line,0),CodeMirror.Pos(cursor.line,line.length),"+org-gtd"); cm.setCursor(CodeMirror.Pos(cursor.line,Math.min(cursor.ch + (result.line.length - line.length),result.line.length))); }; function changeLevel(cm,direction) { var context, lines; if(!isOrgEditor(cm)) { return CodeMirror.Pass; } context = currentHeading(cm); if(!context.heading || (direction < 0 && context.heading.level === 1)) { return CodeMirror.Pass; } lines = context.parsed.lines.slice(0); for(var index = context.heading.line; index <= context.heading.subtreeEndLine; index++) { if(/^\*+\s+/.test(lines[index])) { lines[index] = direction > 0 ? "*" + lines[index] : lines[index].substring(1); } } replaceDocument(cm,lines,context.heading.line,Math.max(0,cm.getCursor().ch + direction)); } CodeMirror.commands.orgGtdPromote = function(cm) { return changeLevel(cm,-1); }; CodeMirror.commands.orgGtdDemote = function(cm) { return changeLevel(cm,1); }; function moveSubtree(cm,direction) { var context, heading, headings, index, other, lines, before, current, after, newLine; if(!isOrgEditor(cm)) { return CodeMirror.Pass; } context = currentHeading(cm); heading = context.heading; if(!heading) { return CodeMirror.Pass; } headings = context.parsed.headings; index = headings.indexOf(heading); if(direction < 0) { for(index = index - 1; index >= 0; index--) { if(headings[index].level < heading.level) { return CodeMirror.Pass; } if(headings[index].level === heading.level) { other = headings[index]; break; } } if(!other) { return CodeMirror.Pass; } lines = context.parsed.lines; before = lines.slice(0,other.line); current = lines.slice(heading.line,heading.subtreeEndLine + 1); after = lines.slice(other.line,heading.line).concat(lines.slice(heading.subtreeEndLine + 1)); newLine = other.line; replaceDocument(cm,before.concat(current,after),newLine,cm.getCursor().ch); } else { for(index = index + 1; index < headings.length; index++) { if(headings[index].line <= heading.subtreeEndLine) { continue; } if(headings[index].level < heading.level) { return CodeMirror.Pass; } if(headings[index].level === heading.level) { other = headings[index]; break; } } if(!other) { return CodeMirror.Pass; } lines = context.parsed.lines; before = lines.slice(0,heading.line); current = lines.slice(heading.line,heading.subtreeEndLine + 1); after = lines.slice(other.line,other.subtreeEndLine + 1); newLine = heading.line + after.length; replaceDocument(cm,before.concat(after,current,lines.slice(other.subtreeEndLine + 1)),newLine,cm.getCursor().ch); } } CodeMirror.commands.orgGtdMoveUp = function(cm) { return moveSubtree(cm,-1); }; CodeMirror.commands.orgGtdMoveDown = function(cm) { return moveSubtree(cm,1); }; CodeMirror.commands.orgGtdToggleFold = function(cm) { var context, heading, marks, existing, marker, mark; if(!isOrgEditor(cm)) { return CodeMirror.Pass; } context = currentHeading(cm); heading = context.heading; if(!heading || cm.getCursor().line !== heading.line || heading.subtreeEndLine === heading.line) { return CodeMirror.Pass; } marks = cm.state.orgGtdFoldMarks = cm.state.orgGtdFoldMarks || []; existing = marks.filter(function(item) { var range = item.find(); return range && range.from.line === heading.line; })[0]; if(existing) { existing.clear(); cm.state.orgGtdFoldMarks = marks.filter(function(item) {return item !== existing;}); return; } marker = cm.getWrapperElement().ownerDocument.createElement("span"); marker.className = "cm-org-gtd-fold-marker"; marker.textContent = " ..."; mark = cm.markText( CodeMirror.Pos(heading.line,cm.getLine(heading.line).length), CodeMirror.Pos(heading.subtreeEndLine,cm.getLine(heading.subtreeEndLine).length), {collapsed: true,replacedWith: marker,atomic: true} ); marks.push(mark); marker.addEventListener("click",function() { mark.clear(); cm.state.orgGtdFoldMarks = marks.filter(function(item) {return item !== mark;}); }); }; CodeMirror.commands.orgGtdInsertSibling = function(cm) { var context, heading, lines, newLine; if(!isOrgEditor(cm)) { return CodeMirror.Pass; } context = currentHeading(cm); heading = context.heading; if(!heading) { return CodeMirror.Pass; } lines = context.parsed.lines.slice(0); newLine = heading.subtreeEndLine + 1; lines.splice(newLine,0,new Array(heading.level + 1).join("*") + " TODO "); replaceDocument(cm,lines,newLine,heading.level + 6); }; CodeMirror.defineInitHook(function(cm) { var widget = cm.getOption("editWidget"), request, currentTitle, tiddler, originalTitle; syncOrgGtdKeyMap(cm); cm.on("optionChange",function(instance,option) { if(option === "mode") { syncOrgGtdKeyMap(instance); } }); if(!widget || !widget.wiki) { return; } request = $tw.utils.parseJSONSafe(widget.wiki.getTiddlerText(JUMP_STATE_TITLE,""),null); if(!request) { return; } currentTitle = widget.editTitle || widget.getVariable("currentTiddler",""); tiddler = widget.wiki.getTiddler(currentTitle); originalTitle = tiddler && tiddler.fields["draft.of"] ? tiddler.fields["draft.of"] : currentTitle; if(originalTitle !== request.title) { return; } setTimeout(function() { var line = Math.max(0,Math.min(Number(request.line || 1) - 1,cm.lastLine())), position = CodeMirror.Pos(line,0); cm.setCursor(position); cm.scrollIntoView(position,100); cm.focus(); widget.wiki.deleteTiddler(JUMP_STATE_TITLE); },0); }); })();