diff --git a/NEWS b/NEWS index c9a804aa..83209995 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ special versions for the old behavior * IMPORTANT: renamed Startup and Quit autocmd events to VimperatorEnter and VimperatorLeave respectively + * add 'runtimepath' * allow ; hints to work in the multiline output widget * add :scriptnames * add commandline completion to 'activate', 'cpt', 'defsearch', 'pageinfo', diff --git a/content/events.js b/content/events.js index 9a883a9a..bbb64432 100644 --- a/content/events.js +++ b/content/events.js @@ -538,24 +538,43 @@ liberator.Events = function () //{{{ // load all macros inside ~/.vimperator/macros/ // setTimeout needed since liberator.io. is loaded after liberator.events. setTimeout (function () { + // FIXME: largely duplicated for loading plugins try { - var files = liberator.io.readDirectory(liberator.io.getSpecialDirectory("macros")); - for (let i = 0; i < files.length; i++) - { - var file = files[i]; - if (!file.exists() || file.isDirectory() || - !file.isReadable() || !/^[\w_-]+(\.vimp)?$/i.test(file.leafName)) - continue; + let dirs = liberator.io.getRuntimeDirectories("macros"); - var name = file.leafName.replace(/\.vimp$/i, ""); - macros.set(name, liberator.io.readFile(file).split(/\n/)[0]); - liberator.log("Macro " + name + " added: " + macros.get(name), 5); + if (dirs.length > 0) + { + for (let [,dir] in Iterator(dirs)) + { + if (liberator.options["verbose"] >= 2) + liberator.echo("Searching for \"macros/*\" in \"" + dir.path + "\"\n"); + + liberator.log("Sourcing macros directory: " + dir.path + "...", 3); + + let files = liberator.io.readDirectory(dir.path); + + files.forEach(function (file) { + if (!file.exists() || file.isDirectory() || + !file.isReadable() || !/^[\w_-]+(\.vimp)?$/i.test(file.leafName)) + return; + + let name = file.leafName.replace(/\.vimp$/i, ""); + macros.set(name, liberator.io.readFile(file).split(/\n/)[0]); + + liberator.log("Macro " + name + " added: " + macros.get(name), 5); + }); + } + } + else + { + liberator.log("No user macros directory found", 3); } } catch (e) { - liberator.log("Macro directory not found or error reading macro file", 9); + // thrown if directory does not exist + liberator.log("Error sourcing macros directory: " + e, 9); } }, 100); diff --git a/content/io.js b/content/io.js index d6a7fb79..0450819a 100644 --- a/content/io.js +++ b/content/io.js @@ -33,41 +33,19 @@ liberator.IO = function () //{{{ ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + const WINDOWS = navigator.platform == "Win32"; + const EXTENSION_NAME = liberator.config.name.toLowerCase(); // "vimperator" or "muttator" + var environmentService = Components.classes["@mozilla.org/process/environment;1"] .getService(Components.interfaces.nsIEnvironment); - const WINDOWS = navigator.platform == "Win32"; var cwd = null, oldcwd = null; - var extname = liberator.config.name.toLowerCase(); // "vimperator" or "muttator" var lastRunCommand = ""; // updated whenever the users runs a command with :! var scriptNames = []; - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// OPTIONS //////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////{{{ - + // default option values var cdpath = "," + (environmentService.get("CDPATH").replace(/[:;]/g, ",") || ","); - - liberator.options.add(["cdpath", "cd"], - "List of directories searched when executing :cd", - "stringlist", cdpath, - { - setter: function (value) - { - var values = value.split(","); - var expanded = ""; - - for (let i = 0; i < values.length; i++) - { - expanded += liberator.io.expandPath(values[i]); - if (i < values.length - 1) - expanded += ","; - } - - return expanded; - } - }); - + var runtimepath = "~/" + (WINDOWS ? "" : ".") + EXTENSION_NAME; var shell, shellcmdflag; if (WINDOWS) @@ -83,6 +61,48 @@ liberator.IO = function () //{{{ shellcmdflag = "-c"; } + function expandPathList(list) + { + var expanded = []; + + for (let [,path] in Iterator(list.split(","))) + expanded.push(liberator.io.expandPath(path)); + + return expanded.join(","); + } + + // TODO: why are we passing around so many strings? I know that the XPCOM + // file API is limited but... + function joinPaths(head, tail) + { + let pathSeparator = WINDOWS ? "\\" : "/"; + let sep = pathSeparator.replace("\\", "\\\\"); + + head = head.replace(RegExp(sep + "$"), ""); + tail = tail.replace(RegExp("^" + sep), ""); + + return head + pathSeparator + tail; + } + + + /////////////////////////////////////////////////////////////////////////////}}} + ////////////////////// OPTIONS //////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + liberator.options.add(["cdpath", "cd"], + "List of directories searched when executing :cd", + "stringlist", cdpath, + { + setter: function (value) expandPathList(value) + }); + + liberator.options.add(["runtimepath", "rtp"], + "List of directories searched for runtime files", + "stringlist", runtimepath, + { + setter: function (value) expandPathList(value) + }); + liberator.options.add(["shell", "sh"], "Shell to use for executing :! and :run commands", "string", shell, @@ -143,7 +163,7 @@ liberator.IO = function () //{{{ for (let i = 0; i < directories.length; i++) { - var dir = directories[i] + liberator.io.directorySeparator + args; + var dir = joinPaths(directories[i], args); if (liberator.io.setCurrentDirectory(dir)) { // FIXME: we're just overwriting the error message from @@ -184,7 +204,7 @@ liberator.IO = function () //{{{ { argCount: "0" }); // mkv[imperatorrc] or mkm[uttatorrc] - liberator.commands.add(["mk" + extname.substr(0, 1) + "[" + extname.substr(1) + "rc]"], + liberator.commands.add(["mk" + EXTENSION_NAME.substr(0, 1) + "[" + EXTENSION_NAME.substr(1) + "rc]"], "Write current key mappings and changed options to the config file", function (args, special) { @@ -193,7 +213,7 @@ liberator.IO = function () //{{{ if (args) filename = args; else - filename = "~/" + (WINDOWS ? "_" : ".") + extname + "rc"; + filename = "~/" + (WINDOWS ? "_" : ".") + EXTENSION_NAME + "rc"; var file = liberator.io.getFile(filename); if (file.exists() && !special) @@ -315,11 +335,6 @@ liberator.IO = function () //{{{ MODE_SYNC: 0x40, MODE_EXCL: 0x80, - get directorySeparator() - { - return WINDOWS ? "\\" : "/"; - }, - expandPath: function (path) { // TODO: proper pathname separator translation like Vim - this should be done elsewhere @@ -364,6 +379,7 @@ liberator.IO = function () //{{{ var file = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); + // FIXME: why aren't we using the "CurWorkD" special directory -- djk var dirs = [cwd, "$PWD", "~"]; for (let i = 0; i < dirs.length; i++) { @@ -385,7 +401,7 @@ liberator.IO = function () //{{{ } // just make sure we return something which always is a directory - return WINDOWS ? "C:\\" : "/"; + return WINDOWS ? "C:\\" : "/"; // XXX }, setCurrentDirectory: function (newdir) @@ -412,24 +428,20 @@ liberator.IO = function () //{{{ return ioManager.getCurrentDirectory(); }, - getSpecialDirectory: function (directory) + getRuntimeDirectories: function (specialDirectory) { - var pluginDir; + let dirs = liberator.options["runtimepath"].split(","); - if (WINDOWS) - pluginDir = "~/" + liberator.config.name.toLowerCase() + "/" + directory; - else - pluginDir = "~/." + liberator.config.name.toLowerCase() + "/" + directory; + dirs = dirs.map(function (dir) liberator.io.getFile(joinPaths(dir, specialDirectory))) + .filter(function (dir) dir.exists() && dir.isDirectory() && dir.isReadable()); - pluginDir = ioManager.getFile(ioManager.expandPath(pluginDir)); - - return pluginDir.exists() && pluginDir.isDirectory() ? pluginDir : null; + return dirs; }, getRCFile: function () { - var rcFile1 = ioManager.getFile("~/." + liberator.config.name.toLowerCase() + "rc"); - var rcFile2 = ioManager.getFile("~/_" + liberator.config.name.toLowerCase() + "rc"); + var rcFile1 = ioManager.getFile("~/." + EXTENSION_NAME + "rc"); + var rcFile2 = ioManager.getFile("~/_" + EXTENSION_NAME + "rc"); if (WINDOWS) [rcFile1, rcFile2] = [rcFile2, rcFile1]; @@ -461,7 +473,7 @@ liberator.IO = function () //{{{ let expandedPath = ioManager.expandPath(path); if (!/^([a-zA-Z]:|\/)/.test(expandedPath)) // doesn't start with /, C: - expandedPath = ioManager.getCurrentDirectory() + (WINDOWS ? "\\" : "/") + expandedPath; + expandedPath = joinPaths(ioManager.getCurrentDirectory(), expandedPath); file.initWithPath(expandedPath); } @@ -471,14 +483,14 @@ liberator.IO = function () //{{{ // TODO: make secure // returns a nsILocalFile or null if it could not be created - // FIXME: is there a reason the "TmpD" special file isn't being used? -- djk + // FIXME: is there a reason the "TmpD" special directory isn't being used? -- djk createTempFile: function () { let file = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); - let tmpName = extname + ".tmp"; + let tmpName = EXTENSION_NAME + ".tmp"; - switch (extname) + switch (EXTENSION_NAME) { case "muttator": tmpName = "mutt-ator-mail"; // to allow vim to :set ft=mail automatically @@ -487,7 +499,7 @@ liberator.IO = function () //{{{ try { if (window.content.document.location.hostname) - tmpName = extname + "-" + window.content.document.location.hostname + ".tmp"; + tmpName = EXTENSION_NAME + "-" + window.content.document.location.hostname + ".tmp"; } catch (e) {} break; @@ -615,20 +627,21 @@ liberator.IO = function () //{{{ lookup: for (let i = 0; i < dirs.length; i++) { - var path = dirs[i] + (WINDOWS ? "\\" : "/") + program; + var path = joinPaths(dirs[i], program); try { file.initWithPath(path); if (file.exists()) break; + // TODO: couldn't we just palm this off to the start command? // automatically try to add the executable path extensions on windows if (WINDOWS) { var extensions = environmentService.get("PATHEXT").split(";"); for (let j = 0; j < extensions.length; j++) { - path = dirs[i] + "\\" + program + extensions[j]; + path = joinPaths(dirs[i], program) + extensions[j]; file.initWithPath(path); if (file.exists()) break lookup; diff --git a/content/liberator.js b/content/liberator.js index 2072e80b..34114929 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -1059,11 +1059,12 @@ const liberator = (function () //{{{ // TODO: we should have some class where all this guioptions stuff fits well hideGUI(); - // finally, read a ~/.vimperatorrc + // finally, read a ~/.vimperatorrc and plugin/**.{vimp,js} // make sourcing asynchronous, otherwise commands that open new tabs won't work setTimeout(function () { var rcFile = liberator.io.getRCFile(); + if (rcFile) liberator.io.source(rcFile.path, true); else @@ -1071,20 +1072,28 @@ const liberator = (function () //{{{ if (liberator.options["loadplugins"]) { - // also source plugins in ~/.vimperator/plugin/ + // FIXME: largely duplicated for loading macros try { - var pluginDir = liberator.io.getSpecialDirectory("plugin"); - if (pluginDir) + let dirs = liberator.io.getRuntimeDirectories("plugin"); + + if (dirs.length > 0) { - var files = liberator.io.readDirectory(pluginDir.path); - liberator.log("Sourcing plugin directory...", 3); - files.sort(function (a, b) String.localeCompare(a.path, b.path)) - .forEach(function (file) + for (let [,dir] in Iterator(dirs)) { - if (!file.isDirectory() && /\.(js|vimp)$/i.test(file.path)) - liberator.io.source(file.path, false); - }); + // TODO: search plugins/**/* for plugins + if (liberator.options["verbose"] >= 2) + liberator.echo("Searching for \"plugin/*.{js,vimp}\" in \"" + dir.path + "\"\n"); + + liberator.log("Sourcing plugin directory: " + dir.path + "...", 3); + + let files = liberator.io.readDirectory(dir.path); + + files.sort(function (a, b) String.localeCompare(a.path, b.path)).forEach(function (file) { + if (!file.isDirectory() && /\.(js|vimp)$/i.test(file.path)) + liberator.io.source(file.path, false); + }); + } } else { @@ -1094,7 +1103,7 @@ const liberator = (function () //{{{ catch (e) { // thrown if directory does not exist - //liberator.log("Error sourcing plugin directory: " + e); + liberator.log("Error sourcing plugin directory: " + e, 9); } } diff --git a/locale/en-US/hints.txt b/locale/en-US/hints.txt index 432ffbd5..642e468c 100644 --- a/locale/en-US/hints.txt +++ b/locale/en-US/hints.txt @@ -8,13 +8,13 @@ INTRO TO BE WRITTEN... ||f\\{hint\\}|| ________________________________________________________________________________ Start QuickHint mode. In QuickHint mode, every hintable item (according to the -'hinttags' XPath query) is assigned a unique number. You can now either type +'hinttags' XPath query) is assigned a unique number. You can now either type this number or type any part of the URL which you want to follow, and it is -followed as soon as it can be uniquely identified. Often it is can be useful -to combine these techniques to narrow down results with some letters, and then -typing a single digit to make the match unique. Pressing [m][m] -(defaults to :let mapleader = "\") toggles "escape-mode", where numbers are -treated as normal text. + +followed as soon as it can be uniquely identified. Often it can be useful to +combine these techniques to narrow down results with some letters, and then +typing a single digit to make the match unique. Pressing [m][m] +(defaults to [c]:let mapleader = "\"[c]) toggles "escape-mode", where numbers +are treated as normal text. + [m][m] stops this mode at any time. ________________________________________________________________________________ diff --git a/locale/en-US/options.txt b/locale/en-US/options.txt index ee31dc59..debe3b47 100644 --- a/locale/en-US/options.txt +++ b/locale/en-US/options.txt @@ -391,10 +391,10 @@ ____ |\'hlss'| |\'hlsearchstyle'| -||'hlsearchstyle' 'hlss'|| +||'hlsearchstyle' 'hlss'|| string ____ -string (default: color: black; background-color: yellow; padding: 0; display: -inline;) + +(default: color: black; background-color: yellow; padding: 0; display: +inline;) CSS specification of highlighted search items ____ @@ -476,8 +476,10 @@ ____ |\'nextpattern'| -||'nextpattern'|| stringlist (default: \bnext,^>$,^(>>|»)$,^(>|»),(>|»)$,\bmore\b) +||'nextpattern'|| stringlist ____ +(default: \bnext,^>$,^(>>|»)$,^(>|»),(>|»)$,\bmore\b) + Patterns to use when guessing the 'next' page in a document sequence. Each pattern, in order, is matched against all links in the page with the first match being used. The patterns are case insensitive regular expressions. @@ -566,14 +568,31 @@ ____ //____ |\'previouspattern'| -||'previouspattern'|| stringlist (default: \bprev|previous\b,^<$,^(<<|«)$,^(<|«),(<|«)$) +||'previouspattern'|| stringlist ____ +(default: \bprev|previous\b,^<$,^(<<|«)$,^(<|«),(<|«)$) + Patterns to use when guessing the 'previous' page in a document sequence Each pattern, in order, is matched against all links in the page with the first match being used. The patterns are case insensitive regular expressions. ____ +|\'rtp'| |\'runtimepath'| +||'runtimepath' 'rtp'|| stringlist +____ +(default: Unix, Mac: "\~/.vimperator", Windows: "\~/vimperator") + +List of directories searched for runtime files: + +macros/ + +plugin/ + + +Example: [c]:set runtimepath=\~/myvimperator,\~/.vimperator[c] + +This will search for plugins in both "\~/myvimperator/plugin" and +"\~/.vimperator/plugin" +____ + + |\'scr'| |\'scroll'| ||'scroll' 'scr'|| number (default: 0) ____ @@ -761,8 +780,10 @@ ____ |\'wsp'| |\'wordseparators'| + -||'wordseparators' 'wsp'|| string (default: [\.,!\?:;/\\"\^\$%&§\(\)\[\]\\{\\}<>#\\*\+\\|=~ _\\-]) +||'wordseparators' 'wsp'|| string ____ +(default: [\.,!\?:;/\\"\^\$%&§\(\)\[\]\\{\\}<>#\\*\+\\|=~ _\\-]) + A regex which defines the word separators which are used for the 'hintmatching' types "wordstartswith" and "firstletters" to split the words in the text of a link. ____ diff --git a/locale/en-US/starting.txt b/locale/en-US/starting.txt index cb2441e4..0e7af76d 100644 --- a/locale/en-US/starting.txt +++ b/locale/en-US/starting.txt @@ -12,11 +12,15 @@ JavaScript files found in the plugin directory. The RC file may be named * Unix and Mac: [a]\~/.vimperatorrc[a] then [a]\~/_vimperatorrc[a] * Windows - [a]\~/_vimperatorrc[a] then [a]\~/.vimperatorrc[a] -The plugin directory is named: +The plugin directory can be in any of the directories in 'runtimepath'. Using +the default value of 'runtimepath' this would be: * Unix and Mac - [a]\~/.vimperator/plugin[a] * Windows - [a]\~/vimperator/plugin[a] +All directories in 'runtimepath' are searched for plugins and they are are all +loaded. + Plugins will not be sourced if 'noloadplugins' is set. The user's \'$HOME'(~) directory is determined as follows: diff --git a/vimperator.vim b/vimperator.vim index 61698991..0dea2575 100644 --- a/vimperator.vim +++ b/vimperator.vim @@ -46,11 +46,12 @@ syn region vimperatorSet matchgroup=vimperatorCommand start="\%(^\s*:\=\)\@<=\<\ syn keyword vimperatorOption activate act activelinkfgcolor alfc activelinkbgcolor albc cdpath cd complete cpt defsearch ds editor \ extendedhinttags eht eventignore ei guioptions go helpfile hf hintmatching hm hintstyle hs hinttags ht hinttimeout hto \ history hi hlsearchstyle hlss laststatus ls linkbgcolor lbc linkfgcolor lfc newtab nextpattern pageinfo pa - \ popups pps previewheight pvh previouspattern scroll scr shell sh shellcmdflag shcf showstatuslinks ssli showtabline stal - \ suggestengines titlestring urlseparator verbose vbs visualbellstyle t_vb wildignore wig wildmode wim wildoptions wop - \ wordseparators wsp + \ popups pps previewheight pvh previouspattern runtimepath rtp scroll scr shell sh shellcmdflag shcf showstatuslinks ssli + \ showtabline stal suggestengines titlestring urlseparator verbose vbs visualbellstyle t_vb wildignore wig wildmode wim + \ wildoptions wop wordseparators wsp \ contained nextgroup=vimperatorSetMod +" toggle options syn match vimperatorOption "\<\%(no\|inv\)\=\%(focuscontent\|fc\|fullscreen\|fs\|ignorecase\|ic\|incsearch\|is\)\>!\=" \ contained nextgroup=vimperatorSetMod syn match vimperatorOption "\<\%(no\|inv\)\=\%(insertmode\|im\|hlsearch\|hls\|linksearch\|lks\|loadplugins\|lpl\|more\)\>!\="