// ==UserScript== // @name Minibuffer // @namespace http://white.s151.xrea.com/ // @description Minibuffer // @include * // ==/UserScript== var VERSION = "2009.12.06"; var Class = function(){return function(){this.initialize.apply(this,arguments)}}; // string key var Key = new Class(); // 32-40 space pageup pagedown end home left up right down Key.keyCodeStr = { 8: 'BAC', 9: 'TAB', 10: 'RET', 13: 'RET', 27: 'ESC', 33: 'PageUp', 34: 'PageDown', 35: 'End', 36: 'Home', 37: 'Left', 38: 'Up', 39: 'Right', 40: 'Down', 45: 'Insert', 46: 'Delete', 112: 'F1', 113: 'F2', 114: 'F3', 115: 'F4', 116: 'F5', 117: 'F6', 118: 'F7', 119: 'F8', 120: 'F9', 121: 'F10', 122: 'F11', 123: 'F12' }; Key.whichStr = { 32: 'SPC' }; Key.specialKeys = values(Key.keyCodeStr).concat(values(Key.whichStr)); Key.getKeyIdentifier = function(aEvent){ // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html return ((aEvent.keyCode in this.keyCodeStr) && this.keyCodeStr[aEvent.keyCode]) || ((aEvent.which in this.whichStr) && this.whichStr[aEvent.which]) || String.fromCharCode(aEvent.which); }; Key.prototype = { initialize: function(){ this.orig_string = arguments[0]; this.key = this.orig_string.replace(/[ACMS]-/g,''); this.special = !!~Key.specialKeys.indexOf(this.key); }, has: function(modifier){return this.orig_string.indexOf(modifier) > -1}, equal: function (e, ch){ return (this.key == ch && this.has('C-') == e.ctrlKey && ((this.special)? this.has('S-') == e.shiftKey : true) && (e.metaKey || e.altKey) == (this.has('A-') || this.has('M-'))) } }; var ShortcutKey = new Class(); ShortcutKey.prototype = { initialize: function(){ this.hash = {}; this.state_available = false; this.prevent_event = true; this.through_input_elements = false; this.parent = null; this.html = null; this.descriptions = []; }, setParent: function(parent){this.parent = parent; return this}, isAvailable: function(){return this.state_available}, addCommand: function(opt){ var lst = opt.key.split(' '); var last = lst.last(); if(this.id) this.addDescription(opt); var self = this; var idx = 0; var last_idx = lst.length - 1; lst.forEach(function(k){ if(idx++ != last_idx){ var new_shortcutkey; if(self.hash[k]){ new_shortcutkey = self.hash[k][1]; }else{ new_shortcutkey = new ShortcutKey().setParent(self); new_shortcutkey.prevent_event = self.prevent_event; new_shortcutkey.setParameter(self.target, self.event, function(e){new_shortcutkey.listener(e)}, self.capture); } self.hash[k] = [new Key(k), new_shortcutkey]; self = new_shortcutkey; }else{ self.hash[k] = [new Key(k), opt.command]; } }); }, addDescription: function(opt){ var getKeyHTML = function(key, description){ return $N('div',{}, [$N('kbd',{},key), $N('div',{},description)]); }; var div = getKeyHTML(opt.key, opt.description); this.html.appendChild(div); this.descriptions.push({key:opt.key, html:div}); }, removeCommand: function(key){ delete this.hash[key]; }, findByEvent: function(e, ch){return values(this.hash).find(function(kf){return kf[0].equal(e, ch) && kf})}, removeEventListener: function(){ this.disable(); this.setParameter(null, null, null, null); }, addEventListener: function(target, event, capture){ // (document, 'keypress', true) var self = this; this.setParameter(target, event, function(e){self.listener(e)}, capture); this.enable(); }, setParameter: function(target, event, observer, capture){ this.target = target; this.event = event; this.observer = observer; this.capture = capture; }, // enable/disable temporary enable: function(){this.state_available = true;this.target.addEventListener(this.event, this.observer, this.capture);}, disable: function(){this.state_available = false; this.target.removeEventListener(this.event, this.observer, this.capture)}, throughEvent: function(){this.prevent_event = false; return this}, throughInputElements: function(){this.through_input_elements = true; return this}, getAllKeys: function(){return keys(this.hash)}, backToRoot: function(){ if(!this.parent) return; var tmp = this; while(tmp.parent){ tmp.disable(); tmp = tmp.parent; } tmp.enable(); }, listener: function(aEvent){ if(!this.capture && this.through_input_elements && /^(?:input|textarea)$/.test(aEvent.target.nodeName.toLowerCase())) return; var ch = Key.getKeyIdentifier(aEvent); var kf = this.findByEvent(aEvent, ch); var preventDefault = this.prevent_event; // log(aEvent.keyCode, aEvent.which, ch,kf, aEvent.shiftKey); if(kf){ var fn = kf[1]; if(ShortcutKey.prototype.isPrototypeOf(fn)){ this.disable(); fn.enable(); }else{ fn(aEvent); this.backToRoot(); } }else{ this.backToRoot(); preventDefault = false; } if(preventDefault) { aEvent.preventDefault(); aEvent.stopPropagation(); } }, initHelp: function(id){ var box = $N('div',{id:'gm_minibuffer_'+id}, [$N('h1',{},'Shortcut Keys')]); this.html = box; this.id = box.id; var id = 'div#' + box.id; var inherit = 'background:inherit; background-image:inherit; background-color:inherit; color:inherit; text-align:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; margin:inherit; opacity:inherit; text-decoration:inherit; border:0px; height:100%; padding:0; margin:inherit; font-family:inherit; vertical-align:inherit; line-height:inherit; font-stretch:inherit; font-variant:inherit; font-size-adjust:inherit; letter-spacing:inherit;'; GM_addStyle([id,'{', 'right: 10px;', 'left: 10px;', 'top: 10px;', 'line-height: 100%;', 'vertical-align: baseline;', 'border: 1px dotted #444;', 'font-family: sans-serif;', 'text-decoration: none;', 'font-weight: normal;', 'font-style: normal;', 'font-size: medium;', 'font-stretch: normal;', 'font-variant: normal;', 'font-size-adjust: none;', 'letter-spacing: normal;', 'background: none;', 'text-align: left;', 'position: fixed;', 'margin: 0;', 'padding: 20px;', 'background-color: #000;', 'background-image: none;', 'color: #aaa;', '-moz-border-radius: 10px;border-radius: 10px;', 'opacity:0.8;', 'z-index:1000;', '}\n', id,' div{', inherit, 'opacity:1.0;','text-align:center;','}', id,' > div{', inherit, 'margin: 0px 20px 20px 0px;', 'opacity:1.0;','text-align:center;','}', id,' kbd{', inherit, 'font-size: 120%;','font-weight: bold;', 'color: #B83E3B;', 'text-align: right;', 'width:50%;','float:left;','}\n', id,' kbd + div{', 'margin-left:50%;', 'text-align:left;','}\n', id,' kbd + div:before{', 'content:": ";' ,'}\n', id,' h1{', inherit, 'margin: 20px auto;','background-image: none;', "opacity:1.0;", 'font-weight: bold;', 'font-size: 150%;', 'color:#fff;','padding-left: 20px;', 'text-align: center;', '}\n', ].join('')); return this; }, bindHelp: function(){return (this.hideHelpMessage() || this.showHelpMessage())}, showHelpMessage: function(){document.body.appendChild(this.html)}, hideHelpMessage: function(){ var help = document.getElementById(this.id); if(!help) return false; document.body.removeChild(this.html); return true; }, }; // to use history/alias in Minibuffer, define getter/setter like below // history is Array of string // e.g. ["foo", "bar"] // alias is hash of alias:expand // e.g. {"foo":"bar111|bar222", "baz":"qux111|qux222"} // // obj.setHistoryGetter(function() { // var res = eval(GM_getValue('history', '[]')); // return res; // }); // obj.setHistorySetter(function(new_history) { // GM_setValue('history',uneval(new_history)); // }); // var Minibuffer = new Class(); Minibuffer.prototype = { MAX_CANDIDATES: 20, KEYBIND: { // move caret 'C-a' : 'bindBeginningOfLine', 'Home' : 'bindBeginningOfLine', 'C-e' : 'bindEndOfLine', 'End' : 'bindEndOfLine', 'C-f' : 'bindForwardChar', 'C-b' : 'bindBackwardChar', 'M-b' : 'bindBackwardWord', 'M-f' : 'bindForwardWord', // delete character 'C-d' : 'bindDeleteForwardChar', 'C-h' : 'bindDeleteBackwardChar', 'BAC' : 'bindDeleteBackwardChar', 'C-w' : 'bindDeleteBackwardWord', 'M-h' : 'bindDeleteBackwardWord', 'M-d' : 'bindDeleteForwardWord', 'C-u' : 'bindDeleteAllStrings', // history 'C-r' : 'bindSearchHistoryBackward', 'C-s' : 'bindSearchHistoryForward', // alias 'C-c' : 'bindRegisterOrUnregisterAlias', 'M-c' : 'bindExpandAlias', // other 'ESC' : 'bindExit', 'C-g' : 'bindExit', 'C-[' : 'bindExit', 'C-m' : 'bindDecision', 'RET' : 'bindDecision', 'C-n' : 'bindSelectNext', 'Down' : 'bindSelectNext', 'C-p' : 'bindSelectPrevious', 'Up' : 'bindSelectPrevious', 'C-v' : 'bindScrollNext', 'PageDown' : 'bindScrollNext', 'M-v' : 'bindScrollPrev', 'PageUp' : 'bindScrollPrev', 'TAB' : 'bindComplete', 'C-i' : 'bindComplete', 'C-/' : 'bindCompleteAndPipe', }, initialize: function(){ this.separator = '|'; this.state_available = false; this.candidates = []; this.last_completed_string = ''; this.current = -1; this.initHTML(); // html this.shortcutkey = new ShortcutKey(); this.shortcutkey.addEventListener(this.html.input, 'keypress', true); var self = this; var chars = "!\"#$%&'()=~|-^_[]{}:;+*@`?\\".split(''); for(var i=48; i<58; i++){ // 0-9 chars[chars.length] = String.fromCharCode(i); } for(var i=65; i<91; i++){ // A-z chars[chars.length] = String.fromCharCode(i); // == chars.push(String.fromCharCode(i)) chars[chars.length] = String.fromCharCode(i+32); } chars.forEach(function(ch){ self.shortcutkey.addCommand({ key: ch, command: function(){self.bindInputChar(ch)} }); }); for(var key in self.KEYBIND){ (function(a){ self.shortcutkey.addCommand({key:key, command:function(){a.call(self)}}); })(self[self.KEYBIND[key]]); } }, isAvailable: function(){return this.state_available}, initHistoryVariable: function(){ if(!this.hasOwnProperty('history')) return; this.history_max = 100; this.history_search_count = -1; this.history_search_regexp = null; }, // setter setSeparator: function(arg){ this.separator = arg; return this; }, setPrompt: function(str){ this.html.prompt.innerHTML = str; return this; }, setCandidates: function(lst){ this.candidates = lst; return this; }, setHistorySetter: function(fn){this.__defineSetter__('history',fn); return this;}, setHistoryGetter: function(fn){this.__defineGetter__('history',fn); return this;}, setAliasSetter: function(fn){this.__defineSetter__('alias',fn); return this;}, setAliasGetter: function(fn){this.__defineGetter__('alias',fn); return this;}, // html initHTML: function(){ var INPUT_ID = 'gm_minibuffer_input_area'; var COMPLETION_ID = 'gm_minibuffer_completion'; var CONTAINER_ID = 'gm_minibuffer_container'; this.html = {}; this.html.completion = $N('ul',{id:COMPLETION_ID}); this.html.message = $N('div'); this.html.prompt = $N('span',{},"$"); this.html.input = $N('input',{id:INPUT_ID}); this.html.container = $N('div',{id:CONTAINER_ID, style:"background-color:#000;"}, [this.html.completion, this.html.message, this.html.prompt, this.html.input]); var inherit = 'background:inherit; background-image:inherit; background-color:inherit; color:inherit; text-align:inherit; font-size:inherit; font-style:inherit; font-weight:inherit; margin:inherit; opacity:inherit; text-decoration:inherit; border:0px; height:100%; padding:0; margin:inherit; font-family:inherit; vertical-align:inherit; line-height:inherit; font-stretch:inherit; font-variant:inherit; font-size-adjust:inherit; letter-spacing:inherit;'; GM_addStyle(['#', CONTAINER_ID,'{', 'right: 0px;', 'left: 0px;', 'bottom: 0px;', 'line-height: 100%;', 'vertical-align: baseline;', 'border: 1px dotted #444;', 'font-family: sans-serif;', 'text-decoration: none;', 'font-weight: normal;', 'font-style: normal;', 'font-size: medium;', 'font-stretch: normal;', 'font-variant: normal;', 'font-size-adjust: none;', 'letter-spacing: normal;', 'background: none;', 'text-align: left;', 'position: fixed;', 'margin: 0;', 'padding: 20px;', 'background-image: none;', 'color: #aaa;', '-moz-border-radius: 10px 10px 0px 0px;border-radius: 10px 10px 0px 0px;', 'opacity:0.8;', 'z-index:999;', '}\n', '#', CONTAINER_ID, ' > span',INPUT_ID, '{', 'color: #CB6161;','display:inline;','}', '#', CONTAINER_ID, ' > span {', inherit, 'color: #ccc;', 'display:inline;','margin-right: 5px;','}\n', '#', INPUT_ID, '{', inherit, 'width:90%;','}', '#', COMPLETION_ID, '{', inherit, 'margin-bottom: 20px;','border-bottom: 1px dotted #444;' ,'}\n', '#', COMPLETION_ID, ' > span {', inherit, 'color: #ccc;', 'margin: 10px;','display:block;','}\n', '#', COMPLETION_ID, ' > li {', inherit, 'color: #ccc;', 'padding: 2px;','margin-bottom:10px;','margin-left: 10px;','}\n', '#', COMPLETION_ID, ' > li.gm_minibuffer_selected{', inherit, 'color: #CB6161;', 'padding: 2px;','margin-bottom:10px;','margin-left: 10px;','}\n', ].join('')); }, updateComplationList: function(scroll){ var c = this.html.completion, i = this.html.input, old_lst = this.candidates, new_lst = []; if(this.hasOwnProperty('alias')) old_lst = old_lst.concat(keys(this.alias)); // alias var input_str = i.value.slice(0,i.selectionStart).match(new RegExp("[^"+this.separator+"]*$"))[0].replace(/^\s+/,''); var test = function(str, l){ var regexp = new RegExp(str, str.toLowerCase() == str ? 'i' : ''); return l.filter(function(el){return el.match(regexp)}); }; // all candidates if(input_str == '') new_lst = old_lst; // prefix match if(!new_lst.length) new_lst = test('^' + input_str.replace(/^\^/, '').escapeRegexp(), old_lst); // substring match if(!new_lst.length) new_lst = test(input_str.escapeRegexp(), old_lst); // to check whether list has change by STRING var completed_str = new_lst.join('\n'); var len = new_lst.length; if(len && this.last_completed_string != completed_str){ c.innerHTML = ''; for(var i=0,l=Math.min(new_lst.length, this.MAX_CANDIDATES); i this.MAX_CANDIDATES){ this.html.message.innerHTML = 'Page 1/' + (Math.floor(new_lst.length / this.MAX_CANDIDATES) +1); } }else if(len == 0){ c.innerHTML = ''; } this.last_completed_string = completed_str; this.current = -1; this.initHistoryVariable(); }, scrollPageNext: function(stop){ // stop at the bottom var c = this.html.completion; var lst = this.last_completed_string.split(/\r?\n|\r/); if(this.MAX_CANDIDATES < lst.length){ var txt = c.lastChild.innerHTML; var pos = lst.position(txt) + 1; if(pos == lst.length){ if(stop) return false; pos = 0; } var next_candidates = lst.slice(pos, pos + this.MAX_CANDIDATES); c.innerHTML = ''; next_candidates.forEach(function(e){ c.appendChild($N('li',{}, e)); }); this.html.message.innerHTML = 'Page ' + (Math.floor(pos / this.MAX_CANDIDATES) +1) + '/' + (Math.floor(lst.length / this.MAX_CANDIDATES)+1); } return true; }, scrollPagePrev: function(stop){ // stop at the top var c = this.html.completion; var lst = this.last_completed_string.split(/\r?\n|\r/); if(this.MAX_CANDIDATES < lst.length){ var txt = c.firstChild.innerHTML; var pos = lst.position(txt) - this.MAX_CANDIDATES; if(pos < 0){ if(stop) return false; pos = lst.length - (lst.length % this.MAX_CANDIDATES); } var next_candidates = lst.slice(pos, pos + this.MAX_CANDIDATES); c.innerHTML = ''; next_candidates.forEach(function(e){ c.appendChild($N('li',{}, e)); }); this.html.message.innerHTML = 'Page '+ (Math.floor(pos / this.MAX_CANDIDATES) +1) + '/' + (Math.floor(lst.length / this.MAX_CANDIDATES)+1); } return true; }, getCompletedString: function(){ var lst = this.last_completed_string.split(/\r?\n|\r/); var fn = function(a,b){ if(a.length==0) return a; var tmp=0, i=1, len=a.length; while(tmp = b.indexOf(a.slice(0,i)) == 0) if(len == i++) break; return a.slice(0,--i); }; // ["ab1", "ab2", "ab3"] => "ab" return lst.reduce(fn); }, deleteAllStrings: function(){ this.last_completed_string = ''; this.html.input.value = ''; this.html.completion.innerHTML = ''; }, exit: function(result){ this.html.input.blur(); this.deleteAllStrings(); this.initHistoryVariable(); this.current = -1; document.body.removeChild(this.html.container); this.callback(result); this.dispatchEvent("hide_minibuffer"); this.state_available = false; }, complete: function(callback){ this.initHistoryVariable();// history this.callback = callback; this.keepSelection(); document.body.appendChild(this.html.container); this.html.input.focus(); this.dispatchEvent("show_minibuffer", null); this.state_available = true; }, selectCandidate: function(newNode, oldNode){ // highlight node var i = this.html.input; if(newNode){ i.value = (i.value.match(new RegExp('.*'+this.separator.escapeRegexp()+'\\s*')) || '') + newNode.innerHTML; newNode.setAttribute('class', 'gm_minibuffer_selected'); } if(oldNode){ oldNode.removeAttribute('class',0); } }, keepSelection: function(arg){ this.selection = {}; // selected text this.selection.text = getSelectionText(); // selected node this.selection.node = getSelectionNode(); }, // event (hook) // usage: // // var minibuffer = new Minibuffer(); // var obj = { // 'show_minibuffer': function(){alert('show')}, // 'hide_minibuffer': function(){alert('hide')} // }; // minibuffer.addEventListener(obj); listeners: [], removeEventListener: function(obj){ this.listeners = this.listeners.remove(obj); }, addEventListener: function(obj){ this.listeners[this.listeners.length] = obj; }, dispatchEvent: function(event_name, data){ this.listeners.forEach(function(listener){ if(event_name in listener){ try{ listener[event_name].apply(listener, [data]); }catch(e){ log(e); } } }); }, // ShortcutKey bindSelectNext: function(){ var c = this.html.completion; if(!this.last_completed_string) this.updateComplationList(); var last = this.current != -1 && c.childNodes[this.current]; if(++this.current >= c.childNodes.length){ if(this.MAX_CANDIDATES <= this.last_completed_string.split(/\r?\n|\r/).length){ this.scrollPageNext(); } this.current = 0; } this.selectCandidate(c.childNodes[this.current], last); }, bindSelectPrevious: function(){ var c = this.html.completion; if(!this.last_completed_string) this.updateComplationList(); var last = this.current != -1 && c.childNodes[this.current]; if(--this.current < 0) { if(this.MAX_CANDIDATES <= this.last_completed_string.split(/\r?\n|\r/).length){ this.scrollPagePrev(); } this.current = c.childNodes.length - 1; } this.selectCandidate(c.childNodes[this.current], last); }, bindScrollNext: function(){ var c = this.html.completion; var last_position = this.current; var last = this.current != -1 && c.childNodes[this.current]; var res = this.scrollPageNext(true); if(!res) return; this.current = 0; this.selectCandidate(c.childNodes[this.current]); }, bindScrollPrev: function(){ var c = this.html.completion; var last_position = this.current; var last = this.current != -1 && c.childNodes[this.current]; var res = this.scrollPagePrev(true); if(!res) return; this.current = 0; this.selectCandidate(c.childNodes[this.current]); }, bindComplete: function(){ var self = this; var setString = function(str){ if(!str) return; var i=self.html.input, b=i.selectionStart, e=i.selectionEnd; // "a | b | c" => "a | b | " var pstr = i.value.slice(0,b).match(new RegExp('.*'+self.separator.escapeRegexp()+'\\s*')); i.value = (pstr ? pstr[0] : '') + str; var l=i.value.length; i.setSelectionRange(l,l); return i.value; }; var getUniqueCandidate = function(){ var lst = self.last_completed_string.split(/\r?\n|\r/); return lst.length == 1 ? lst[0] : false; }; var str = self.getCompletedString(); if(!str){ this.updateComplationList(); str = getUniqueCandidate(); } setString(str); return getUniqueCandidate(); }, bindDecision: function(){ // history if(this.hasOwnProperty('history')){ // ensure to set setter/getter var history = this.history, string = this.html.input.value; history = history.remove(string);// to avoid duplicated history.unshift(string); // if(history.length > this.history_max) history.pop(); this.history = history; } this.exit(this.html.input.value); }, bindExit: function(){ this.exit(); }, // move caret bindBeginningOfLine: function(){this.html.input.setSelectionRange(0, 0)}, bindEndOfLine: function(){var i=this.html.input, l=i.value.length; i.setSelectionRange(l,l)}, bindForwardChar: function(){var i=this.html.input, p=i.selectionEnd+1; i.setSelectionRange(p,p)}, bindBackwardChar: function(){var i=this.html.input, p=i.selectionStart-1; i.setSelectionRange(p,p)}, bindForwardWord: function(){ var i=this.html.input, e=i.selectionEnd, t=i.value.slice(e).match(/[a-zA-Z0-9]+|[^a-zA-Z0-9]+/),l=i.value.slice(0,e).length + (!!t ? t[0].length : 1); i.setSelectionRange(l,l); }, bindBackwardWord: function(){ var i=this.html.input, l=i.value.slice(0,i.selectionStart).replace(/[a-zA-Z0-9]+$|[^a-zA-Z0-9]+$/,'').length; i.setSelectionRange(l,l); }, // delete character bindDeleteBackwardChar: function(){ var i= this.html.input, b=i.selectionStart, e=i.selectionEnd; if(b==e) b--; i.value = i.value.slice(0,b)+i.value.slice(e); i.setSelectionRange(b,b); this.updateComplationList(); }, bindDeleteBackwardWord: function(){ var i=this.html.input, b=i.selectionStart, e=i.selectionEnd; var tx = i.value, tr=tx.slice(0,b-1).replace(/[^a-zA-Z0-9]+$/,'').replace(/[a-zA-Z0-9]+$/,''), l=tr.length; i.value = tr+tx.slice(e); i.setSelectionRange(l,l); this.updateComplationList(); }, bindDeleteForwardChar: function(){ var i=this.html.input, b=i.selectionStart, e=i.selectionEnd; if(b == e) e++; i.value=i.value.slice(0,b)+i.value.slice(e+1); i.setSelectionRange(b,b); if(i.value=='') this.updateComplationList(); }, bindDeleteForwardWord: function(){ var i=this.html.input, b=i.selectionStart, e=i.selectionEnd; var t=i.value, m=t.slice(e).match(/[a-zA-Z0-9]+|[^a-zA-Z0-9]+/); i.value = t.slice(0,b)+t.slice(!!m?e+m[0].length:e); i.setSelectionRange(b,b); if(i.value == '') this.updateComplationList(); }, bindDeleteAllStrings: function(){ this.deleteAllStrings(); this.updateComplationList(); }, // insert bindInputChar: function(key){ var i=this.html.input, b=i.selectionStart, e=i.selectionEnd, t=i.value; i.value = t.slice(0,b++) + key + t.slice(e); i.setSelectionRange(b,b); this.updateComplationList(); }, bindCompleteAndPipe: function(){ if(this.separator!='|' || (this.current<0 && !this.bindComplete())) return; var i=this.html.input, b=i.selectionStart, str=i.value; var trim = function(str){ return str.replace(/^\s|\s$/g,''); }; i.value = trim(str.slice(0,b)) + ' ' + this.separator + ' ' + trim(str.slice(b)); var p = i.selectionEnd + this.separator.length; i.setSelectionRange(p,p); // eliminate highlight var c = this.html.completion; var last = this.current != -1 && c.childNodes[this.current]; this.selectCandidate(null, last); this.updateComplationList(); }, // history bindSearchHistoryBackward: function(){ if(!this.hasOwnProperty('history')) return; var history = this.history, self = this, i = this.html.input; this.history_search_regexp = this.history_search_regexp || new RegExp('^' + i.value); var count = history.position(function(e, n){ return e.match(self.history_search_regexp) && n > self.history_search_count; }); if(typeof count != 'number') count = this.history_search_count; if(count > -1) i.value = history[count]; this.history_search_count = count; }, bindSearchHistoryForward: function(){ if(!this.hasOwnProperty('history') || !this.history_search_regexp) return; var history = this.history, self = this; var count = history.slice(0,this.history_search_count).reverse().position(function(e, i){return e.match(self.history_search_regexp)}); if(typeof(count) == 'number') this.html.input.value = history[this.history_search_count -= count+1]; }, // alias bindRegisterOrUnregisterAlias: function(){ if(!this.hasOwnProperty('alias')) return; var alias = this.alias; var t = this.html.input.value; if(typeof alias[t] == 'undefined'){ // register as alias var a = prompt('input alias of '+t+''); alias[a] = t; this.alias = alias; }else{ // unregister from alias delete alias[t]; this.alias = alias; } }, bindExpandAlias: function(){ var i = this.html.input; var alias = this.alias[i.value]; if(alias) i.value = alias; this.updateComplationList(); } }; var Shell = { TT: { arg: 'arg', control: 'control' }, Parser: { buffer: null, init: function (buffer) { this.buffer = buffer; }, get_token: function ( ) { // surround('foo', '[]') => '[foo]' // surround('foo', '/') => '/foo/' var surround = function (s, c) { var d = (c.length > 1 ? c[1] : c) ; return c[0] + s + d; }; var quote_chars = '"' + "'"; var meta_chars = "|"; // ((["'])(.+?)(\3)) var quoted_arg = '(([' + quote_chars + '])(.+?)(\\3))'; // [^|<>;"'\s]+ var bare_arg = surround( '^' + meta_chars + quote_chars + '\\s', '[]') + '+'; var job_controlers = meta_chars; var exp = '^\\s*'; exp += surround( [ quoted_arg, surround(bare_arg, "()"), surround( surround(meta_chars, "[]"), "()" ) ].join("|"), "()" ); var re = new RegExp(exp); if ( re.test(this.buffer) ) { // huuum, we need to count parenthesis index from constructed expression.... // ^\s*(((["'])(.+?)(\3))|([^|<>;"']+)|([|<>;])) //4 or 6 or 7 var token = RegExp.$4 ? {type: Shell.TT.arg, literal: RegExp.$4} : RegExp.$6 ? {type: Shell.TT.arg, literal: RegExp.$6} : {type: Shell.TT.control, literal: RegExp.$7 }; this.buffer = RegExp.rightContext; return token; } else { return null; } }, }, Command: { commands: [], state: null, current_command: null, init: function () { this.commands = []; this.state = this.need_command; }, add_token: function (token) { this.state.apply(this, [token]); }, need_command: function (token) { if ( token.type != Shell.TT.arg ) { trace("syntax error", token); } else { this.current_command = { name: token.literal, args: [] }; this.state = this.search_for_end; } }, search_for_end: function (token) { if ( token.type == Shell.TT.control ) { this.end(); } else { this.current_command.args.push( token.literal ); } }, end: function () { this.commands.push(this.current_command); this.current_command = null; this.state = this.need_command; } }, buffer: null, parse: function (buffer) { this.Parser.init(buffer); this.Command.init(); var token; while ( token = this.Parser.get_token() ) { this.Command.add_token ( token ); } this.Command.end(); return this.Command.commands; }, execute: function (commands) { var obj = null; commands.forEach( function ( command ) { var proc = Bin[command.name]; if ( proc ) { obj = proc.apply( this, [command.args, obj] ); } else { trace("command not found.", command.name); } } ); return stdin; } }; // copied from FLASH KEY (c) id:brazil // http://userscripts.org/scripts/show/11996 // slightly modified. var FlashMessage = new function(){ GM_addStyle(<>) var opacity = 0.9; var flash = $N('div',{id:'FLASH_MESSAGE'}); hide(flash); document.body.appendChild(flash); var canceler; this.showFlashMessageWindow = function (string, duration) { duration = duration || 400; canceler && canceler(); flash.innerHTML = string; flash.style.opacity = opacity; show(flash); flash.style.marginLeft = (-(flash.offsetWidth/2))+'px'; canceler = callLater(function(){ canceler = tween(function(value){ flash.style.opacity = opacity * (1-value); }, 100, 5); }, duration); }; // ----[Utility]------------------------------------------------- function callLater(callback, interval){ var timeoutId = setTimeout(callback, interval); return function(){ clearTimeout(timeoutId) } } function tween(callback, span, count){ count = (count || 20); var interval = span / count; var value = 0; var calls = 0; var intervalId = setInterval(function(){ callback(calls / count); if(count == calls){ canceler(); return; } calls++; }, interval); var canceler = function(){ clearInterval(intervalId) hide(flash) } return canceler; } function hide(target){ target.style.display='none'; } function show(target, style){ target.style.display=(style || ''); } }; var Status = new Class(); Status.id = 'gm_minibuffer_flash_status'; Status.hash = {}; Status.prototype = { initialize: function(){ this.initContainer(); var [name,status,time_limit] = arguments; var hash = this.getHash(); var del = function(){ delete hash[name]; } if(typeof status != "string"){ if(hash[name]){ this.fadeout(hash[name]); del(); } }else{ var img = $N('img',{src:"data:image/gif;base64,R0lGODlhEAAQAOMIAAAAABoaGjMzM0xMTGZmZoCAgJmZmbKysv///////////////////////////////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgAIACwAAAAAEAAQAAAESBDJiQCgmFqbZwjVhhwH9n3hSJbeSa1sm5GUIHSTYSC2jeu63q0D3PlwCB1lMMgUChgmk/J8LqUIAgFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UKgmFqbpxDV9gAA9n3hSJbeSa1sm5HUMHTTcTy2jeu63q0D3PlwDx2FQMgYDBgmk/J8LqWPQuFRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YSgmFqb5xjV9gQB9n3hSJbeSa1sm5EUQXQTADy2jeu63q0D3PlwDx2lUMgcDhgmk/J8LqUPg+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+cagmFqbJyHV9ggC9n3hSJbeSa1sm5FUUXRTEDy2jeu63q0D3PlwDx3FYMgAABgmk/J8LqWPw+FRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+QihmFqbZynV9gwD9n3hSJbeSa1sm5GUYXSTIDy2jeu63q0D3PlwDx3lcMgEAhgmk/J8LqUPAOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+UqhmFqbpzHV9hAE9n3hSJbeSa1sm5HUcXTTMDy2jeu63q0D3PlwDx0FAMgIBBgmk/J8LqWPQOBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+YyhmFqb5znV9hQF9n3hSJbeSa1sm5EUAHQTQTy2jeu63q0D3PlwDx0lEMgMBhgmk/J8LqUPgeBRhV6z2q0VF94iJ9pOBAAh+QQBCgAPACwAAAAAEAAQAAAESPDJ+c6hmFqbJwDV9hgG9n3hSJbeSa1sm5FUEHRTUTy2jeu63q0D3PlwDx1FIMgQCBgmk/J8LqWPweBRhV6z2q0VF94iJ9pOBAA7"}); var lst = typeof time_limit == 'number' ? [status]: [img ,status]; var div = $N('div', {}, lst); if(hash[name]){ this.replace(div, hash[name]); }else{ this.add(div); } hash[name] = div; if(typeof time_limit == 'number'){ this.fadeout.later(time_limit).call(this, hash[name]); del.later(time_limit)(); } } }, initContainer: function(){ if(!document.getElementById(Status.id)){ GM_addStyle(<>); var container = $N('div',{id:Status.id, style:'display:block;'}); document.body.appendChild(container); } }, getHash: function(){return Status.hash}, add: function(node){ var container = document.getElementById(Status.id); if(container){ container.appendChild(node); } if(container.style.display == 'none'){ container.style.display = 'block'; } }, fadeout: function(node){ var setOpacity = function(node, opacity){ node.style.opacity = opacity; } var max = 15; var base = 1000; for(var i=0; i '//prefix:body[@class = "foo"]/prefix:p' // http://nanto.asablo.jp/blog/2008/12/11/4003371 function addDefaultPrefix(xpath, prefix) { var tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g; var TERM = 1, OPERATOR = 2, MODIFIER = 3; var tokenType = OPERATOR; prefix += ':'; function replacer(token, identifier, suffix, term, operator, modifier) { if (suffix) { tokenType = (suffix == ':' || (suffix == '::' && (identifier == 'attribute' || identifier == 'namespace'))) ? MODIFIER : OPERATOR; } else if (identifier) { if (tokenType == OPERATOR && identifier != '*') token = prefix + token; tokenType = (tokenType == TERM) ? OPERATOR : TERM; } else { tokenType = term ? TERM : operator ? OPERATOR : MODIFIER; } return token; } return xpath.replace(tokenPattern, replacer); } // Usage:: with (D()) { your code } // JSDefeered 0.2.1 (c) Copyright (c) 2007 cho45 ( www.lowreal.net ) // See http://coderepos.org/share/wiki/JSDeferred function D () { function Deferred () { return (this instanceof Deferred) ? this.init(this) : new Deferred() } Deferred.prototype = { init : function () { this._next = null; this.callback = { ok: function (x) { return x }, ng: function (x) { throw x } }; return this; }, next : function (fun) { return this._post("ok", fun) }, error : function (fun) { return this._post("ng", fun) }, call : function (val) { return this._fire("ok", val) }, fail : function (err) { return this._fire("ng", err) }, cancel : function () { (this.canceller || function () {})(); return this.init(); }, _post : function (okng, fun) { this._next = new Deferred(); this._next.callback[okng] = fun; return this._next; }, _fire : function (okng, value) { var self = this, next = "ok"; try { value = self.callback[okng].call(self, value); } catch (e) { next = "ng"; value = e; } if (value instanceof Deferred) { value._next = self._next; } else { if (self._next) self._next._fire(next, value); } return this; } }; Deferred.parallel = function (dl) { var ret = new Deferred(), values = {}, num = 0; for (var i in dl) if (dl.hasOwnProperty(i)) { (function (d, i) { d.next(function (v) { values[i] = v; if (--num <= 0) { if (dl instanceof Array) { values.length = dl.length; values = Array.prototype.slice.call(values, 0); } ret.call(values); } }).error(function (e) { ret.fail(e); }); num++; })(dl[i], i); } if (!num) Deferred.next(function () { ret.call() }); ret.canceller = function () { for (var i in dl) if (dl.hasOwnProperty(i)) { dl[i].cancel(); } }; return ret; }; Deferred.wait = function (n) { var d = new Deferred(), t = new Date(); var id = setTimeout(function () { clearTimeout(id); d.call((new Date).getTime() - t.getTime()); }, n * 1000) d.canceller = function () { try { clearTimeout(id) } catch (e) {} }; return d; }; Deferred.next = function (fun) { var d = new Deferred(); var id = setTimeout(function () { clearTimeout(id); d.call() }, 0); if (fun) d.callback.ok = fun; d.canceller = function () { try { clearTimeout(id) } catch (e) {} }; return d; }; Deferred.call = function (f, args) { args = Array.prototype.slice.call(arguments); f = args.shift(); return Deferred.next(function () { return f.apply(this, args); }); }; Deferred.loop = function (n, fun) { var o = { begin : n.begin || 0, end : n.end || (n - 1), step : n.step || 1, last : false, prev : null }; var ret, step = o.step; return Deferred.next(function () { function _loop (i) { if (i <= o.end) { if ((i + step) > o.end) { o.last = true; o.step = o.end - i + 1; } o.prev = ret; ret = fun.call(this, i, o); if (ret instanceof Deferred) { return ret.next(function (r) { ret = r; return Deferred.call(_loop, i + step); }); } else { return Deferred.call(_loop, i + step); } } else { return ret; } } return Deferred.call(_loop, o.begin); }); }; Deferred.register = function (name, fun) { this.prototype[name] = function () { return this.next(Deferred.wrap(fun).apply(null, arguments)); }; }; Deferred.wrap = function (dfun) { return function () { var a = arguments; return function () { return dfun.apply(null, a); }; }; }; Deferred.register("loop", Deferred.loop); Deferred.register("wait", Deferred.wait); Deferred.define = function (obj, list) { if (!list) list = ["parallel", "wait", "next", "call", "loop"]; if (!obj) obj = (function () { return this })(); list.forEach(function (i) { obj[i] = Deferred[i]; }); return Deferred; }; function xhttp (opts) { var d = Deferred(); if (opts.onload) d = d.next(opts.onload); if (opts.onerror) d = d.error(opts.onerror); opts.onload = function (res) { d.call(res); }; opts.onerror = function (res) { d.fail(res); }; GM_xmlhttpRequest(opts); return d; } xhttp.get = function (url) { return xhttp({method:"get", url:url}) }; xhttp.post = function (url, data) { return xhttp({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) }; function http (opts) { var d = Deferred(); var req = new XMLHttpRequest(); req.open(opts.method, opts.url, true); if (opts.headers) { for (var k in opts.headers) if (opts.headers.hasOwnProperty(k)) { req.setRequestHeader(k, opts.headers[k]); } } req.onreadystatechange = function () { if (req.readyState == 4) d.call(req); }; req.send(opts.data || null); d.xhr = req; return d; } http.get = function (url) { return http({method:"get", url:url}) }; http.post = function (url, data) { return http({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) }; Deferred.Deferred = Deferred; Deferred.http = http; Deferred.xhttp = xhttp; return Deferred; }// End of JSDeferred function keys(hash){ var tmp = []; for(var key in hash)tmp.push(key); return tmp; } function values(hash){ var tmp = []; for(var key in hash)tmp.push(hash[key]); return tmp; } var getSelectionText = function(){ return String(window.getSelection()).split(/\r?\n|\r/).remove(""); }; var getSelectionNode = function(){ var s=window.getSelection(), res=[], len=s.rangeCount; for(var i=0; i= arguments.callee.length){ var rv = arguments[1]; }else{ do{ if(i in this){ rv = this[i++]; break; } if(++i >= len)throw new TypeError(); }while (true); } for (;i list of current URL window.Minibuffer.addCommand({ name: 'location', command: function(){return [location.href]}, }); // nothing => list of string (divided by \n) window.Minibuffer.addCommand({ name: 'selected-text', command: function(){return command.selection.text}, }); // nothing => list of selected node window.Minibuffer.addCommand({ name: 'selected-node', command: function(){return command.selection.node} }); // node list => list of string window.Minibuffer.addCommand({ name: 'innerHTML', command: function(stdin){return stdin.map(function(a){return a.innerHTML})} }); // list of node or nothing => list of node // args: 'tag' window.Minibuffer.addCommand({ name: 'filter-by-tag-name', command: function(stdin){ var tag = this.args.shift(); if(stdin.length == 0) stdin.push(document); var res = []; for(var i=0,k=stdin.length; i list of URL // args: count window.Minibuffer.addCommand({ name: "upper-directory", command: function(stdin){ var urls = stdin.length ? stdin : [location.href]; var count = this.args.shift() || 1; var rep = new RegExp('[^/]+/?$'); var host = new RegExp('[a-z]+://[^/]+/'); return urls.map(function(url){ for(var i=0; i object window.Minibuffer.addCommand({ name: 'echo', command: function(obj){ log(obj); return obj; } }); // list of node or nothing => list of node // args: 'XPath' window.Minibuffer.addCommand({ name: 'xpath', command: function(stdin) { var exp = this.args.shift(); if(stdin.length == 0) stdin.push(document); var res = []; for(var i=0,l=stdin.length; i list of node or list of string // args: 'regexp' 'attribute' 'flag' window.Minibuffer.addCommand({ name: 'grep', command: function (stdin) { var regexp = this.args.shift(); var attr = this.args.shift(); var flag = this.args.shift(); var re = new RegExp(regexp, typeof(flag) != 'undefined' ? flag : regexp.toLowerCase() == regexp ? 'i':''); return stdin.filter(function(obj) { if(typeof(obj) == 'string'){ return obj.match(re); }else if(obj.nodeType == 3){ return obj.nodeValue.match(re); }else if(obj.nodeType == 1 && attr && obj.getAttribute(attr)){ return obj.getAttribute(attr).match(re); }else if(obj.nodeType == 1 && obj.text){ return obj.text.match(re); } }); } }); // list of anchor node or list of URL // args: 'target' window.Minibuffer.addCommand({ name: 'open', command: function(stdin){ var target = this.args.shift(); if(target == 'top' || target == 'blank') target = '_' + target; stdin.forEach(function(url){ if(target){ window.open(url, target); }else{ GM_openInTab(url); } }); return stdin; } }); // list => reversed list window.Minibuffer.addCommand({ name: 'reverse', command: function(stdin){ return stdin.reverse(); } }); // list of URL => list of URL window.Minibuffer.addCommand({ name: 'web-archive', command: function(stdin){ return stdin.map(function(url){return 'http://web.archive.org/web/*/' + url}) } }); // list of URL => list of URL window.Minibuffer.addCommand({ name: 'google-cache', command: function(stdin){ return stdin.map(function(url){return 'http://www.google.com/search?q=cache:' + url}) } }); // list of URL => list of URL window.Minibuffer.addCommand({ name: 'web-gyotaku', command: function(stdin){ return stdin.map(function(url){return 'http://megalodon.jp/?url=' + url}) } }); // list => list window.Minibuffer.addCommand({ name: 'scrollto-top', command: function(stdin){ window.scrollTo(0,0); return stdin; } }); // list => list window.Minibuffer.addCommand({ name: 'scrollto-bottom', command: function(stdin){ window.scrollTo(0, window.scrollMaxY); return stdin; } }); // // tako3 // window.Minibuffer.addCommand({ // name: 'tako3', // command: function(stdin){ // return stdin.map(function(url){return "http://tako3.com/" + url}) // } // }); // setup command.setup(); // shortcut key sample // window.Minibuffer.addShortcutkey({ // key: 'C-o', // description: 'Open Google cache', // command: function(){ // window.Minibuffer.execute('pinned-or-current-link | google-cache | open | clear-pin'); // } // }); // // vi like // window.Minibuffer.addShortcutkey({ // key: 'g g', // description: 'scroll to top', // command: function(){window.Minibuffer.execute('scrollto-top')} // }); // window.Minibuffer.addShortcutkey({ // key: 'G', // description: 'scroll to bottom', // command: function(){window.Minibuffer.execute('scrollto-bottom')} // }); var ev = document.createEvent('Events'); ev.initEvent('GM_MinibufferLoaded', false, true); window.dispatchEvent(ev); }