diff --git a/content/buffer.js b/content/buffer.js index b2e64768..3844661b 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -449,7 +449,7 @@ vimperator.Buffer = function () //{{{ vimperator.commands.add(["st[op]"], "Stop loading", - function() { BrowserStop(); }); + function () { BrowserStop(); }); vimperator.commands.add(["vie[wsource]"], "View source code of current document", diff --git a/content/mail.js b/content/mail.js index c22950c7..7a4a94a9 100644 --- a/content/mail.js +++ b/content/mail.js @@ -32,6 +32,64 @@ vimperator.Mail = function () ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + // used for asynchronously selecting messages after wrapping folders + var selectMessageKeys = []; + var selectMessageCount = 1; + var selectMessageReverse = false; + + var folderListener = { + OnItemAdded: function(parentItem, item) { }, + OnItemRemoved: function(parentItem, item) { }, + OnItemPropertyChanged: function(item, property, oldValue, newValue) { }, + OnItemIntPropertyChanged: function(item, property, oldValue, newValue) { }, + OnItemBoolPropertyChanged: function(item, property, oldValue, newValue) { }, + OnItemUnicharPropertyChanged: function(item, property, oldValue, newValue) { }, + OnItemPropertyFlagChanged: function(item, property, oldFlag, newFlag) { }, + + OnItemEvent: function(folder, event) + { + var eventType = event.toString(); + if (eventType == "FolderLoaded") + { + if (folder) + { + var msgFolder = folder.QueryInterface(Components.interfaces.nsIMsgFolder); + dump (msgFolder.prettiestName + " loaded\n"); + + // Jump to a message when requested + var indices = []; + if (selectMessageKeys.length > 0) + { + for (var j = 0; j < selectMessageKeys.length; j++) + indices.push([gDBView.findIndexFromKey(selectMessageKeys[j], true), selectMessageKeys[j]]); + + indices.sort(); + let index = selectMessageCount - 1; + if (selectMessageReverse) + index = selectMessageKeys.length - 1 - index; + + gDBView.selectMsgByKey(indices[index][1]); + selectMessageKeys = []; + } + } + } + /*else if (eventType == "ImapHdrDownloaded") { } + else if (eventType == "DeleteOrMoveMsgCompleted") { } + else if (eventType == "DeleteOrMoveMsgFailed") { } + else if (eventType == "AboutToCompact") { } + else if (eventType == "CompactCompleted") { } + else if (eventType == "RenameCompleted") { } + else if (eventType == "JunkStatusChanged") { }*/ + } + } + + var mailSession = Components.classes[mailSessionContractID] + .getService(Components.interfaces.nsIMsgMailSession); + var nsIFolderListener = Components.interfaces.nsIFolderListener; + var notifyFlags = nsIFolderListener.intPropertyChanged | nsIFolderListener.event; + mailSession.AddFolderListener(folderListener, notifyFlags); + + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// OPTIONS ///////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -50,7 +108,7 @@ vimperator.Mail = function () var modes = vimperator.config.mailModes || [vimperator.modes.NORMAL]; vimperator.mappings.add(modes, ["", "i"], - "Focus message", + "Inspect (focus) message", function () { content.focus(); }); vimperator.mappings.add(modes, ["d", ""], @@ -59,20 +117,24 @@ vimperator.Mail = function () vimperator.mappings.add(modes, ["j", ""], "Select next message", - function () { goDoCommand("cmd_nextMsg"); }); - + function (count) { vimperator.mail.selectMessage(function (msg) { return true; }, false, false, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + vimperator.mappings.add(modes, ["J", ""], "Select next unread message", - function () { goDoCommand("cmd_nextUnreadMsg"); }); + function (count) { vimperator.mail.selectMessage(function (msg) { return !msg.isRead; }, true, false, count); }, + { flags: vimperator.Mappings.flags.COUNT }); vimperator.mappings.add(modes, ["k", ""], "Select previous message", - function () { goDoCommand("cmd_previousMsg"); }); - + function (count) { vimperator.mail.selectMessage(function (msg) { return true; }, false, true, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + vimperator.mappings.add(modes, ["K"], "Select previous unread message", - function () { goDoCommand("cmd_previousUnreadMsg"); }); - + function (count) { vimperator.mail.selectMessage(function (msg) { return !msg.isRead; }, true, true, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + vimperator.mappings.add(modes, ["r"], "Reply to sender", function () { goDoCommand("cmd_reply"); }); @@ -81,12 +143,185 @@ vimperator.Mail = function () "Get new messages", function () { goDoCommand("cmd_getNewMessages"); }); + vimperator.mappings.add([vimperator.modes.NORMAL], + ["c"], "Change folders", + function () { vimperator.commandline.open(":", "goto ", vimperator.modes.EX); }); + + vimperator.mappings.add(modes, ["]f"], + "Select next flagged message", + function (count) { vimperator.mail.selectMessage(function(msg) { return msg.isFlagged; }, true, false, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + + vimperator.mappings.add(modes, ["[f"], + "Select previous flagged message", + function (count) { vimperator.mail.selectMessage(function(msg) { return msg.isFlagged; }, true, true, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + + vimperator.mappings.add(modes, ["]a"], + "Select next message with an attachment", + function (count) { vimperator.mail.selectMessage(function(msg) { return gDBView.db.HasAttachments(msg.messageKey); }, true, false, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + + vimperator.mappings.add(modes, ["[a"], + "Select previous message with an attachment", + function (count) { vimperator.mail.selectMessage(function(msg) { return gDBView.db.HasAttachments(msg.messageKey); }, true, true, count); }, + { flags: vimperator.Mappings.flags.COUNT }); + + + + vimperator.mappings.add(modes, [""], + "Get new messages", + function (count) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.forward, true); }, + { flags: vimperator.Mappings.flags.COUNT }); + + vimperator.mappings.add(modes, [""], + "Get new messages", + function (count) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.back, true); }, + { flags: vimperator.Mappings.flags.COUNT }); + + vimperator.mappings.add(modes, ["gg"], + "Get new messages", + function (count) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.firstMessage, true); }, + { flags: vimperator.Mappings.flags.COUNT }); + + vimperator.mappings.add(modes, ["G"], + "Get new messages", + function (count) { if (count < 1) count = 1; while (count--) GoNextMessage(nsMsgNavigationType.lastMessage, false); }, + { flags: vimperator.Mappings.flags.COUNT }); + + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// COMMANDS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + vimperator.commands.add(["go[to]"], + "Select a folder", + function (args, special, count) + { + args = args || "Inbox"; + count = count > 0 ? (count - 1) : 0; + + var folder = vimperator.mail.getFolders(args)[count]; + if (!folder) + vimperator.echoerr("Folder \"" + args + "\" does not exist"); + else + SelectFolder(folder.URI); + }); + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ return { + getFolders: function(filter) + { + var folders = []; + if (!filter) + filter = ""; + + var tree = GetFolderTree(); + for (let i = 0; i < tree.view.rowCount; i++) + { + var resource = GetFolderResource(tree, i).QueryInterface(Components.interfaces.nsIMsgFolder); + if (/*!resource.isServer && */resource.prettiestName.toLowerCase().indexOf(filter.toLowerCase()) >= 0) + folders.push(resource); + } + return folders; + }, + + // XXX: probably refactored with another method + getTotalUnread: function() + { + var folders = this.getFolders(); + + var unread = 0, flagged = 0; + for (var i = 0; i < folders.length; i++) + { + var msgs = folders[i].getMessages(msgWindow); + while (msgs.hasMoreElements()) + { + var msg = msgs.getNext().QueryInterface(Components.interfaces.nsIMsgDBHdr); + if (!msg.isRead) + unread++; + if (msg.isFlagged) + flagged++; + } + } + return unread; + }, + + selectMessage: function(validatorFunc, canWrap, reverse, count) + { + if (typeof validatorFunc != "function") + return; + + if (typeof count != "number" || count < 1) + count = 1; + + // first try to find in current folder + if (gDBView) + { + // FIXME: doesn't work with collapsed threads + for (var i = gDBView.selection.currentIndex + (reverse ? -1 : 1); + (reverse ? ( i >= 0) : (i < gDBView.rowCount)); reverse ? i-- : i++) + { + var key = gDBView.getKeyAt(i); + var msg = gDBView.db.GetMsgHdrForKey(key); + if (validatorFunc(msg)) + count--; + + if (count == 0) + { + gDBView.selectMsgByKey(key); + return; + } + } + } + + // then in other folders + if (canWrap) + { + selectMessageReverse = reverse; + + var folders = this.getFolders(); + var ci = GetFolderTree().currentIndex; + for (var i = 1; i <= folders.length; i++) + { + let index = (i + ci) % folders.length; + if (reverse) + index = folders.length - 1 - index; + + var folder = folders[index]; + if (folder.isServer) + continue; + + selectMessageCount = count; + selectMessageKeys = []; + + var msgs = folder.getMessages(msgWindow); + while (msgs.hasMoreElements()) + { + var msg = msgs.getNext().QueryInterface(Components.interfaces.nsIMsgDBHdr); + if (validatorFunc(msg)) + { + count--; + selectMessageKeys.push(msg.messageKey); + } + } + + if (count <= 0) + { + // SelectFolder is asynchronous, message is selected in folderListener + SelectFolder(folder.URI); + return; + } + } + } + + // TODO: finally for the "rest" of the current folder + + vimperator.beep(); + } }; //}}} }; diff --git a/content/muttator.js b/content/muttator.js index d797ead6..f6c47d8e 100644 --- a/content/muttator.js +++ b/content/muttator.js @@ -29,7 +29,7 @@ the terms of any one of the MPL, the GPL or the LGPL. vimperator.config = { /*** required options, no checks done if they really exist, so be careful ***/ name: "Muttator", - hostApplication: "Thunderbird", // TODO: can this be found out otherwise? + hostApplication: "Thunderbird", // TODO: can this be found out otherwise? gBrandBundle.getString("brandShortName"); /*** optional options, there are checked for existance and a fallback provided ***/ features: ["hints", "mail", "marks"],