
I went to a PHP developer’s meetup recently, where the topic was IDE’s. As some of you may know, I’m a dedicated Vim user, but that’s because I’m most productive using Vim. I don’t have any extreme aversion to using an IDE. As a matter of fact, if I ever find one that really works for me, then I’d start using it. In any case, I’ve started trying to work Komodo Edit into my workflow. As far as I’m concerned, Komodo Edit is the most flexible IDE out there, and that’s part of the reason that I have trouble finding IDE’s. At any given moment, I may have a mix of perl, python, ruby, xml, yaml, javascript and html files open. No IDE that I’ve ever used has been able to handle that kind of mix. Language optimized IDE’s like PHPStorm or PyCharm are great for their intended language, and if all you do all day is work PHP, then that may work for you. For me, Komodo does a good job with almost every language that I work with.
One of komodo’s biggest strengths, is that it’s built on top of the Mozilla engine, so it’s extremely extensible. Basically, you can write Firefox extensions that work with Komodo. Of course, you’ll have to dig through a metric ton of documentation, not all of it up to date, in order to learn how to write an extension. I’m thinking of writing a “Hello World” tutorial, if for no other reason than to document what I’ve learned so far. But, never fear, there’s hope. Given the fact that Komodo Edit is basically a stripped down version of firefox with a specialized set of extensions, it has a really powerful javascript engine and API. It also allows you to create “Macros”, which can be pretty complex javascript applications. I’ve created a set of macros that allow me to work with my Todo.txt files (I highly recommend Gina Trapani’s implementation at http://todotxt.com/). So let’s take a look at what I’ve done.
First off, I created a new folder in my Komodo Edit toolbox to group the macros together in one place. Then I created a simple macro to open my todo.txt. This one was pretty simple. All it does is to open my todo.txt file in a new buffer:
1 2 3 4 | komodo.assertMacroVersion(3); if (komodo.view) { komodo.view.setFocus(); } komodo.openURI("file:///home/rhibbitts/Dropbox/todo/todo.txt"); |
Next, I wanted the ability to sort the tasks in the file according to priority. Priority in a Todo.txt file (at least, according to Gina’s format: https://github.com/ginatrapani/todo.txt-cli/wiki/The-Todo.txt-Format) is determined by an alphabetical character at the beginning of the line. So, if your top priority is denoted by ‘A’, then an alphabetical sort, should put that at the top of the line. And in fact, I found an existing Komodo macro on the Komodo forums that works out of the box. It was posted by ericp, who’s an ActiveState staff member (http://community.activestate.com/node/9740). Here it is in it’s base form:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | // All code below derived from Davide Ficano's Morekomodo extension var eolText = []; eolText[Components.interfaces.koIDocument.EOL_CR] = '\r'; eolText[Components.interfaces.koIDocument.EOL_CRLF] = '\r\n'; eolText[Components.interfaces.koIDocument.EOL_LF] = '\n'; function SortOptions() { this.removeDuplicate = false; this.ignoreCase = true; this.ascending = true; this.numeric = false; } function getSortedBuffer(arr, sortOptions, nl) { alert (JSON.stringify(arr)); var ltValue = sortOptions.ascending ? -1 : 1; var gtValue = sortOptions.ascending ? 1 : -1; function sortFn(a, b) { var sa = a; var sb = b; if (sortOptions.ignoreCase) { sa = a.toLowerCase(); sb = b.toLowerCase(); } if (sortOptions.numeric) { var na = parseFloat(sa); var nb = parseFloat(sb); if (!isNaN(na) && !isNaN(nb)) { sa = na; sb = nb; } } if (sa == sb) { return 0; } return sa < sb ? ltValue : gtValue; } arr.sort(sortFn); var s = ""; if (sortOptions.removeDuplicate) { var prevLine = null; for (var i = 0; i < arr.length; i++) { var lineData = arr[i]; if (prevLine != lineData) { s += lineData + nl; prevLine = lineData; } } // remove last newline s = s.substring(0, s.length - nl.length); } else { s = arr.join(nl); } return s; } function sortView(view, sortOptions) { var scimoz = view.scintilla.scimoz; var sel = view.selection; var useSelection = sel.length != 0; var firstLine, lastLine; if (useSelection) { var selStartPos = scimoz.selectionStart; var selEndPos = scimoz.selectionEnd; firstLine = scimoz.lineFromPosition(selStartPos); lastLine = scimoz.lineFromPosition(selEndPos); var firstColumnPos = scimoz.positionFromLine(lastLine); // if selection ends before first column means the line isn't involved if (firstColumnPos == selEndPos) { --lastLine; } } else { firstLine = 0; lastLine = scimoz.lineCount - 1; } if (firstLine == lastLine) { return; } var lines = []; for (var i = firstLine; i <= lastLine; i++) { var startPos = scimoz.positionFromLine(i); var endPos = scimoz.getLineEndPosition(i); lines.push(scimoz.getTextRange(startPos, endPos)); } var s = getSortedBuffer(lines, sortOptions, eolText[view.koDoc.new_line_endings]); if (useSelection) { scimoz.selectionStart = scimoz.positionFromLine(firstLine); scimoz.selectionEnd = scimoz.getLineEndPosition(lastLine); } else { scimoz.selectAll(); } scimoz.replaceSel(s); } var sortOptions = new SortOptions(); // see defaults above sortView(ko.views.manager.currentView, sortOptions); |
Another thing that’s nice to do is to be able to sort the tasks by context and project. That’s probably something that may differ quite a bit between users, so I’m not sure how usable these are for other people. But assuming that you set the context for a task using this syntax, @context, then it should work for you. Basically, it’s a reworking of the initial sort script, to account for contexts. I’m planning to add another one for sorting by project, which will basically be the same thing. In any case, here’s the one to sort based on context. Note that this does sort the individual tasks in a given context. So, you’ll have the tasks in a given context, in the correct order.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | // All code below derived from Davide Ficano's Morekomodo extension var eolText = []; eolText[Components.interfaces.koIDocument.EOL_CR] = '\r'; eolText[Components.interfaces.koIDocument.EOL_CRLF] = '\r\n'; eolText[Components.interfaces.koIDocument.EOL_LF] = '\n'; function SortOptions() { this.removeDuplicate = false; this.ignoreCase = true; this.ascending = true; this.numeric = false; } function getSortedBuffer(arr, sortOptions, nl) { var contextDict = {}; for (var i = 0; i < arr.length; i++) { if (arr[i].indexOf(" @") !== -1) { words = arr[i].split(' '); for (var j = 0; j < words.length; j++) { if (words[j].indexOf("@") == 0 ) { if (!contextDict[words[j]]) { contextDict[words[j]] = []; contextDict[words[j]].push(arr[i]); }else{ contextDict[words[j]].push(arr[i]); } } } }else{ if( contextDict['no_context'] ){ contextDict['no_context'].push(arr[i]); }else{ contextDict['no_context'] = []; contextDict['no_context'].push(arr[i]); } } } //ko.logging.getLogger("sort_by_context").warn(words[j]); var ltValue = sortOptions.ascending ? -1 : 1; var gtValue = sortOptions.ascending ? 1 : -1; function sortFn(a, b) { var sa = a; var sb = b; if (sortOptions.ignoreCase) { sa = a.toLowerCase(); sb = b.toLowerCase(); } if (sortOptions.numeric) { var na = parseFloat(sa); var nb = parseFloat(sb); if (!isNaN(na) && !isNaN(nb)) { sa = na; sb = nb; } } if (sa == sb) { return 0; } return sa < sb ? ltValue : gtValue; } for(var sortContext in contextDict){ contextDict[sortContext].sort(sortFn); } var s = ""; if (sortOptions.removeDuplicate) { var prevLine = null; for (var i = 0; i < arr.length; i++) { var lineData = arr[i]; if (prevLine != lineData) { s += lineData + nl; prevLine = lineData; } } // remove last newline s = s.substring(0, s.length - nl.length); } else { var context_array = []; for (var context in contextDict) { contextDict[context].join(nl); context_array.push(contextDict[context].join(nl)); } s = context_array.join(nl); } return s; } function sortView(view, sortOptions) { var scimoz = view.scintilla.scimoz; var sel = view.selection; var useSelection = sel.length != 0; var firstLine, lastLine; if (useSelection) { var selStartPos = scimoz.selectionStart; var selEndPos = scimoz.selectionEnd; firstLine = scimoz.lineFromPosition(selStartPos); lastLine = scimoz.lineFromPosition(selEndPos); var firstColumnPos = scimoz.positionFromLine(lastLine); // if selection ends before first column means the line isn't involved if (firstColumnPos == selEndPos) { --lastLine; } } else { firstLine = 0; lastLine = scimoz.lineCount - 1; } if (firstLine == lastLine) { return; } var lines = []; for (var i = firstLine; i <= lastLine; i++) { var startPos = scimoz.positionFromLine(i); var endPos = scimoz.getLineEndPosition(i); ko.logging.getLogger("Line text is:").warn(scimoz.getTextRange(startPos, endPos)); if (scimoz.getTextRange(startPos, endPos) !== "") { lines.push(scimoz.getTextRange(startPos, endPos)); } } //alert (JSON.stringify(lines)); var s = getSortedBuffer(lines, sortOptions, eolText[view.koDoc.new_line_endings]); if (useSelection) { scimoz.selectionStart = scimoz.positionFromLine(firstLine); scimoz.selectionEnd = scimoz.getLineEndPosition(lastLine); } else { scimoz.selectAll(); } scimoz.replaceSel(s); } var sortOptions = new SortOptions(); // see defaults above sortView(ko.views.manager.currentView, sortOptions); |
Note that you can also assign keyboard shortcuts to run these macros, so you never even have to leave the keyboard. Komodo’s extensibility is just awesome. It can literally be extended to do almost anything that you can imagine. I’m thinking about making this one my first screencast. When and if I do that, then I’ll post a link to the video here.