root/lang/javascript/userscripts/minibuffer.user.js

Revision 36543, 54.3 kB (checked in by snj14, 7 years ago)

fix createDocumentFromString

Line 
1// ==UserScript==
2// @name           Minibuffer
3// @namespace      http://white.s151.xrea.com/
4// @description    Minibuffer
5// @include        *
6// ==/UserScript==
7
8var VERSION = "2009.12.06";
9
10var Class = function(){return function(){this.initialize.apply(this,arguments)}};
11
12// string key
13var Key = new Class();
14
15//  32-40 space pageup pagedown end home left up right down
16Key.keyCodeStr = {
17        8:  'BAC',
18        9:  'TAB',
19        10: 'RET',
20        13: 'RET',
21        27: 'ESC',
22        33: 'PageUp',
23        34: 'PageDown',
24        35: 'End',
25        36: 'Home',
26        37: 'Left',
27        38: 'Up',
28        39: 'Right',
29        40: 'Down',
30        45: 'Insert',
31        46: 'Delete',
32        112: 'F1',
33        113: 'F2',
34        114: 'F3',
35        115: 'F4',
36        116: 'F5',
37        117: 'F6',
38        118: 'F7',
39        119: 'F8',
40        120: 'F9',
41        121: 'F10',
42        122: 'F11',
43        123: 'F12'
44};
45Key.whichStr = {
46        32: 'SPC'
47};
48Key.specialKeys = values(Key.keyCodeStr).concat(values(Key.whichStr));
49
50Key.getKeyIdentifier = function(aEvent){
51  // http://www.w3.org/TR/DOM-Level-3-Events/keyset.html
52  return ((aEvent.keyCode in this.keyCodeStr) && this.keyCodeStr[aEvent.keyCode]) ||
53                ((aEvent.which in this.whichStr) && this.whichStr[aEvent.which]) ||
54                String.fromCharCode(aEvent.which);
55};
56
57Key.prototype = {
58  initialize: function(){
59          this.orig_string = arguments[0];
60          this.key = this.orig_string.replace(/[ACMS]-/g,'');
61    this.special = !!~Key.specialKeys.indexOf(this.key);
62  },
63  has: function(modifier){return this.orig_string.indexOf(modifier) > -1},
64  equal: function (e, ch){
65          return (this.key == ch &&
66                  this.has('C-') == e.ctrlKey &&
67                  ((this.special)? this.has('S-') == e.shiftKey : true) &&
68                  (e.metaKey || e.altKey) == (this.has('A-') || this.has('M-')))
69  }
70};
71
72var ShortcutKey = new Class();
73ShortcutKey.prototype = {
74  initialize: function(){
75          this.hash = {};
76          this.state_available = false;
77          this.prevent_event = true;
78          this.through_input_elements = false;
79          this.parent = null;
80          this.html = null;
81          this.descriptions = [];
82  },
83  setParent: function(parent){this.parent = parent; return this},
84  isAvailable: function(){return this.state_available},
85  addCommand: function(opt){
86          var lst  = opt.key.split(' ');
87          var last = lst.last();
88          if(this.id) this.addDescription(opt);
89          var self = this;
90          var idx = 0;
91          var last_idx = lst.length - 1;
92          lst.forEach(function(k){
93                  if(idx++ != last_idx){
94                          var new_shortcutkey;
95                          if(self.hash[k]){
96                                  new_shortcutkey = self.hash[k][1];
97                          }else{
98                                  new_shortcutkey = new ShortcutKey().setParent(self);
99                                  new_shortcutkey.prevent_event = self.prevent_event;
100                                  new_shortcutkey.setParameter(self.target, self.event, function(e){new_shortcutkey.listener(e)}, self.capture);
101                          }
102                          self.hash[k] = [new Key(k), new_shortcutkey];
103                          self = new_shortcutkey;
104                  }else{
105                          self.hash[k] = [new Key(k), opt.command];
106                  }
107          });
108  },
109  addDescription: function(opt){
110          var getKeyHTML = function(key, description){
111                  return $N('div',{},
112                            [$N('kbd',{},key),
113                             $N('div',{},description)]);
114          };
115          var div = getKeyHTML(opt.key, opt.description);
116          this.html.appendChild(div);
117          this.descriptions.push({key:opt.key, html:div});
118  },
119  removeCommand: function(key){
120          delete this.hash[key];
121  },
122  findByEvent: function(e, ch){return values(this.hash).find(function(kf){return kf[0].equal(e, ch) && kf})},
123  removeEventListener: function(){
124          this.disable();
125          this.setParameter(null, null, null, null);
126  },
127  addEventListener: function(target, event, capture){ // (document, 'keypress', true)
128          var self = this;
129          this.setParameter(target, event, function(e){self.listener(e)}, capture);
130          this.enable();
131  },
132  setParameter: function(target, event, observer, capture){
133          this.target   = target;
134          this.event    = event;
135          this.observer = observer;
136          this.capture  = capture;
137  },
138
139// enable/disable temporary
140  enable: function(){this.state_available = true;this.target.addEventListener(this.event, this.observer, this.capture);},
141  disable: function(){this.state_available = false; this.target.removeEventListener(this.event, this.observer, this.capture)},
142
143  throughEvent: function(){this.prevent_event = false; return this},
144  throughInputElements: function(){this.through_input_elements = true; return this},
145
146  getAllKeys: function(){return keys(this.hash)},
147
148  backToRoot: function(){
149          if(!this.parent) return;
150          var tmp = this;
151          while(tmp.parent){
152                  tmp.disable();
153                  tmp = tmp.parent;
154          }
155          tmp.enable();
156  },
157  listener: function(aEvent){
158          if(!this.capture &&
159             this.through_input_elements &&
160             /^(?:input|textarea)$/.test(aEvent.target.nodeName.toLowerCase())) return;
161          var ch = Key.getKeyIdentifier(aEvent);
162          var kf = this.findByEvent(aEvent, ch);
163          var preventDefault = this.prevent_event;
164//        log(aEvent.keyCode, aEvent.which, ch,kf, aEvent.shiftKey);
165          if(kf){
166                  var fn = kf[1];
167                  if(ShortcutKey.prototype.isPrototypeOf(fn)){
168                          this.disable();
169                          fn.enable();
170                  }else{
171                          fn(aEvent);
172                          this.backToRoot();
173                  }
174          }else{
175                  this.backToRoot();
176                  preventDefault = false;
177          }
178          if(preventDefault) {
179                  aEvent.preventDefault();
180                  aEvent.stopPropagation();
181          }
182  },
183  initHelp: function(id){
184          var box = $N('div',{id:'gm_minibuffer_'+id}, [$N('h1',{},'Shortcut Keys')]);
185          this.html = box;
186          this.id = box.id;
187          var id = 'div#' + box.id;
188          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;';
189          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',
190                       id,' div{', inherit, 'opacity:1.0;','text-align:center;','}',
191                       id,' > div{', inherit, 'margin: 0px 20px 20px 0px;', 'opacity:1.0;','text-align:center;','}',
192                       id,' kbd{', inherit, 'font-size: 120%;','font-weight: bold;', 'color: #B83E3B;', 'text-align: right;', 'width:50%;','float:left;','}\n',
193                       id,' kbd + div{', 'margin-left:50%;', 'text-align:left;','}\n',
194                       id,' kbd + div:before{', 'content:": ";' ,'}\n',
195                       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',
196                       ].join(''));
197          return this;
198  },
199  bindHelp: function(){return (this.hideHelpMessage() || this.showHelpMessage())},
200  showHelpMessage: function(){document.body.appendChild(this.html)},
201  hideHelpMessage: function(){
202          var help = document.getElementById(this.id);
203          if(!help) return false;
204          document.body.removeChild(this.html);
205          return true;
206  },
207};
208
209// to use history/alias in Minibuffer, define getter/setter like below
210// history is Array of string
211//   e.g.  ["foo", "bar"]
212// alias is hash of alias:expand
213//   e.g.  {"foo":"bar111|bar222", "baz":"qux111|qux222"}
214//
215// obj.setHistoryGetter(function() {
216//     var res = eval(GM_getValue('history', '[]'));
217//     return res;
218// });
219// obj.setHistorySetter(function(new_history) {
220//     GM_setValue('history',uneval(new_history));
221// });
222//
223
224var Minibuffer = new Class();
225Minibuffer.prototype = {
226  MAX_CANDIDATES: 20,
227  KEYBIND: {
228          // move caret
229          'C-a'      : 'bindBeginningOfLine',
230          'Home'     : 'bindBeginningOfLine',
231          'C-e'      : 'bindEndOfLine',
232          'End'      : 'bindEndOfLine',
233          'C-f'      : 'bindForwardChar',
234          'C-b'      : 'bindBackwardChar',
235          'M-b'      : 'bindBackwardWord',
236          'M-f'      : 'bindForwardWord',
237          // delete character
238          'C-d'      : 'bindDeleteForwardChar',
239          'C-h'      : 'bindDeleteBackwardChar',
240          'BAC'      : 'bindDeleteBackwardChar',
241          'C-w'      : 'bindDeleteBackwardWord',
242          'M-h'      : 'bindDeleteBackwardWord',
243          'M-d'      : 'bindDeleteForwardWord',
244          'C-u'      : 'bindDeleteAllStrings',
245          // history
246          'C-r'      : 'bindSearchHistoryBackward',
247          'C-s'      : 'bindSearchHistoryForward',
248          // alias
249          'C-c'      : 'bindRegisterOrUnregisterAlias',
250          'M-c'      : 'bindExpandAlias',
251          // other
252          'ESC'      : 'bindExit',
253          'C-g'      : 'bindExit',
254          'C-['      : 'bindExit',
255          'C-m'      : 'bindDecision',
256          'RET'      : 'bindDecision',
257          'C-n'      : 'bindSelectNext',
258          'Down'     : 'bindSelectNext',
259          'C-p'      : 'bindSelectPrevious',
260          'Up'       : 'bindSelectPrevious',
261          'C-v'      : 'bindScrollNext',
262          'PageDown' : 'bindScrollNext',
263          'M-v'      : 'bindScrollPrev',
264          'PageUp'   : 'bindScrollPrev',
265          'TAB'      : 'bindComplete',
266          'C-i'      : 'bindComplete',
267          'C-/'      : 'bindCompleteAndPipe',
268  },
269  initialize: function(){
270          this.separator = '|';
271
272          this.state_available = false;
273          this.candidates = [];
274
275          this.last_completed_string = '';
276          this.current = -1;
277
278          this.initHTML(); // html
279
280          this.shortcutkey = new ShortcutKey();
281          this.shortcutkey.addEventListener(this.html.input, 'keypress', true);
282          var self = this;
283          var chars = "!\"#$%&'()=~|-^_[]{}:;+*@`?\\".split('');
284
285          for(var i=48; i<58; i++){ // 0-9
286                  chars[chars.length] = String.fromCharCode(i);
287          }
288          for(var i=65; i<91; i++){ // A-z
289                  chars[chars.length] = String.fromCharCode(i); // == chars.push(String.fromCharCode(i))
290                  chars[chars.length] = String.fromCharCode(i+32);
291          }
292          chars.forEach(function(ch){
293                  self.shortcutkey.addCommand({
294                        key: ch,
295                        command: function(){self.bindInputChar(ch)}
296                  });
297          });
298          for(var key in self.KEYBIND){
299                  (function(a){
300                          self.shortcutkey.addCommand({key:key, command:function(){a.call(self)}});
301                  })(self[self.KEYBIND[key]]);
302          }
303  },
304  isAvailable: function(){return this.state_available},
305
306  initHistoryVariable: function(){
307          if(!this.hasOwnProperty('history')) return;
308          this.history_max = 100;
309          this.history_search_count = -1;
310          this.history_search_regexp = null;
311  },
312// setter
313  setSeparator: function(arg){
314          this.separator = arg;
315          return this;
316  },
317  setPrompt: function(str){
318          this.html.prompt.innerHTML = str;
319          return this;
320  },
321  setCandidates: function(lst){
322          this.candidates = lst;
323          return this;
324  },
325  setHistorySetter: function(fn){this.__defineSetter__('history',fn); return this;},
326  setHistoryGetter: function(fn){this.__defineGetter__('history',fn); return this;},
327  setAliasSetter: function(fn){this.__defineSetter__('alias',fn); return this;},
328  setAliasGetter: function(fn){this.__defineGetter__('alias',fn); return this;},
329
330// html
331  initHTML: function(){
332          var INPUT_ID      = 'gm_minibuffer_input_area';
333          var COMPLETION_ID = 'gm_minibuffer_completion';
334          var CONTAINER_ID  = 'gm_minibuffer_container';
335          this.html = {};
336          this.html.completion = $N('ul',{id:COMPLETION_ID});
337          this.html.message = $N('div');
338          this.html.prompt = $N('span',{},"$");
339          this.html.input = $N('input',{id:INPUT_ID});
340          this.html.container = $N('div',{id:CONTAINER_ID, style:"background-color:#000;"},
341                                   [this.html.completion,
342                                    this.html.message,
343                                    this.html.prompt,
344                                    this.html.input]);
345
346          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;';
347          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',
348                       '#', CONTAINER_ID, ' > span',INPUT_ID, '{', 'color: #CB6161;','display:inline;','}',
349                       '#', CONTAINER_ID, ' > span {', inherit, 'color: #ccc;', 'display:inline;','margin-right: 5px;','}\n',
350                       '#', INPUT_ID, '{', inherit, 'width:90%;','}',
351                       '#', COMPLETION_ID, '{', inherit, 'margin-bottom: 20px;','border-bottom: 1px dotted #444;' ,'}\n',
352                       '#', COMPLETION_ID, ' > span {', inherit, 'color: #ccc;', 'margin: 10px;','display:block;','}\n',
353                       '#', COMPLETION_ID, ' > li {', inherit, 'color: #ccc;', 'padding: 2px;','margin-bottom:10px;','margin-left: 10px;','}\n',
354                       '#', COMPLETION_ID, ' > li.gm_minibuffer_selected{', inherit, 'color: #CB6161;', 'padding: 2px;','margin-bottom:10px;','margin-left: 10px;','}\n',
355                       ].join(''));
356  },
357
358  updateComplationList: function(scroll){
359          var c = this.html.completion,
360          i = this.html.input,
361          old_lst = this.candidates,
362          new_lst = [];
363          if(this.hasOwnProperty('alias')) old_lst = old_lst.concat(keys(this.alias)); // alias
364          var input_str = i.value.slice(0,i.selectionStart).match(new RegExp("[^"+this.separator+"]*$"))[0].replace(/^\s+/,'');
365          var test = function(str, l){
366                  var regexp = new RegExp(str, str.toLowerCase() == str ? 'i' : '');
367                  return l.filter(function(el){return el.match(regexp)});
368          };
369          // all candidates
370          if(input_str == '') new_lst = old_lst;
371          // prefix match
372          if(!new_lst.length) new_lst = test('^' + input_str.replace(/^\^/, '').escapeRegexp(), old_lst);
373          // substring match
374          if(!new_lst.length) new_lst = test(input_str.escapeRegexp(), old_lst);
375          // to check whether list has change by STRING
376          var completed_str = new_lst.join('\n');
377          var len = new_lst.length;
378          if(len && this.last_completed_string != completed_str){
379                  c.innerHTML = '';
380                  for(var i=0,l=Math.min(new_lst.length, this.MAX_CANDIDATES); i<l; i++){
381                          c.appendChild($N('li',{}, new_lst[i]));
382                  }
383                  if(old_lst.length > this.MAX_CANDIDATES){
384                          this.html.message.innerHTML = 'Page 1/' + (Math.floor(new_lst.length / this.MAX_CANDIDATES) +1);
385                  }
386          }else if(len == 0){
387                  c.innerHTML = '';
388          }
389          this.last_completed_string = completed_str;
390          this.current = -1;
391          this.initHistoryVariable();
392  },
393  scrollPageNext: function(stop){ // stop at the bottom
394          var c = this.html.completion;
395          var lst = this.last_completed_string.split(/\r?\n|\r/);
396          if(this.MAX_CANDIDATES < lst.length){
397                  var txt = c.lastChild.innerHTML;
398                  var pos = lst.position(txt) + 1;
399                  if(pos == lst.length){
400                          if(stop) return false;
401                          pos = 0;
402                  }
403                  var next_candidates = lst.slice(pos, pos + this.MAX_CANDIDATES);
404                  c.innerHTML = '';
405                  next_candidates.forEach(function(e){
406                          c.appendChild($N('li',{}, e));
407                  });
408                  this.html.message.innerHTML = 'Page ' + (Math.floor(pos / this.MAX_CANDIDATES) +1) + '/' + (Math.floor(lst.length / this.MAX_CANDIDATES)+1);
409          }
410          return true;
411  },
412  scrollPagePrev: function(stop){ // stop at the top
413          var c = this.html.completion;
414          var lst = this.last_completed_string.split(/\r?\n|\r/);
415          if(this.MAX_CANDIDATES < lst.length){
416                  var txt = c.firstChild.innerHTML;
417                  var pos = lst.position(txt) - this.MAX_CANDIDATES;
418                  if(pos < 0){
419                          if(stop) return false;
420                          pos = lst.length - (lst.length % this.MAX_CANDIDATES);
421                  }
422                  var next_candidates = lst.slice(pos, pos + this.MAX_CANDIDATES);
423                  c.innerHTML = '';
424                  next_candidates.forEach(function(e){
425                          c.appendChild($N('li',{}, e));
426                  });
427                  this.html.message.innerHTML = 'Page '+ (Math.floor(pos / this.MAX_CANDIDATES) +1) + '/' + (Math.floor(lst.length / this.MAX_CANDIDATES)+1);
428          }
429          return true;
430  },
431  getCompletedString: function(){
432          var lst = this.last_completed_string.split(/\r?\n|\r/);
433          var fn = function(a,b){
434                  if(a.length==0) return a;
435                  var tmp=0, i=1, len=a.length;
436                  while(tmp = b.indexOf(a.slice(0,i)) == 0) if(len == i++) break;
437                  return a.slice(0,--i);
438          };
439          // ["ab1", "ab2", "ab3"] => "ab"
440          return lst.reduce(fn);
441  },
442  deleteAllStrings: function(){
443          this.last_completed_string = '';
444          this.html.input.value = '';
445          this.html.completion.innerHTML = '';
446  },
447  exit: function(result){
448          this.html.input.blur();
449          this.deleteAllStrings();
450          this.initHistoryVariable();
451          this.current = -1;
452
453          document.body.removeChild(this.html.container);
454          this.callback(result);
455          this.dispatchEvent("hide_minibuffer");
456          this.state_available = false;
457  },
458  complete: function(callback){
459          this.initHistoryVariable();// history
460
461          this.callback = callback;
462          this.keepSelection();
463          document.body.appendChild(this.html.container);
464          this.html.input.focus();
465          this.dispatchEvent("show_minibuffer", null);
466          this.state_available = true;
467  },
468  selectCandidate: function(newNode, oldNode){ // highlight node
469          var i = this.html.input;
470          if(newNode){
471                  i.value = (i.value.match(new RegExp('.*'+this.separator.escapeRegexp()+'\\s*')) || '') + newNode.innerHTML;
472                  newNode.setAttribute('class', 'gm_minibuffer_selected');
473          }
474          if(oldNode){
475                  oldNode.removeAttribute('class',0);
476          }
477  },
478  keepSelection: function(arg){
479          this.selection = {};
480          // selected text
481          this.selection.text = getSelectionText();
482          // selected node
483          this.selection.node = getSelectionNode();
484  },
485
486// event (hook)
487
488// usage:
489//
490//      var minibuffer = new Minibuffer();
491//      var obj = {
492//              'show_minibuffer': function(){alert('show')},
493//              'hide_minibuffer': function(){alert('hide')}
494//      };
495//      minibuffer.addEventListener(obj);
496
497  listeners: [],
498  removeEventListener: function(obj){
499          this.listeners = this.listeners.remove(obj);
500  },
501  addEventListener: function(obj){
502          this.listeners[this.listeners.length] = obj;
503  },
504  dispatchEvent: function(event_name, data){
505          this.listeners.forEach(function(listener){
506                  if(event_name in listener){
507                          try{
508                                  listener[event_name].apply(listener, [data]);
509                          }catch(e){ log(e); }
510                  }
511          });
512  },
513
514// ShortcutKey
515  bindSelectNext: function(){
516          var c = this.html.completion;
517          if(!this.last_completed_string) this.updateComplationList();
518          var last = this.current != -1 && c.childNodes[this.current];
519          if(++this.current >= c.childNodes.length){
520                  if(this.MAX_CANDIDATES <= this.last_completed_string.split(/\r?\n|\r/).length){
521                          this.scrollPageNext();
522                  }
523                  this.current = 0;
524          }
525          this.selectCandidate(c.childNodes[this.current], last);
526  },
527  bindSelectPrevious: function(){
528          var c = this.html.completion;
529          if(!this.last_completed_string) this.updateComplationList();
530          var last = this.current != -1 && c.childNodes[this.current];
531          if(--this.current < 0) {
532                  if(this.MAX_CANDIDATES <= this.last_completed_string.split(/\r?\n|\r/).length){
533                          this.scrollPagePrev();
534                  }
535                  this.current = c.childNodes.length - 1;
536          }
537          this.selectCandidate(c.childNodes[this.current], last);
538  },
539  bindScrollNext: function(){
540          var c = this.html.completion;
541          var last_position = this.current;
542          var last = this.current != -1 && c.childNodes[this.current];
543          var res = this.scrollPageNext(true);
544          if(!res) return;
545          this.current = 0;
546          this.selectCandidate(c.childNodes[this.current]);
547  },
548  bindScrollPrev: function(){
549          var c = this.html.completion;
550          var last_position = this.current;
551          var last = this.current != -1 && c.childNodes[this.current];
552          var res = this.scrollPagePrev(true);
553          if(!res) return;
554          this.current = 0;
555          this.selectCandidate(c.childNodes[this.current]);
556  },
557  bindComplete: function(){
558          var self = this;
559          var setString = function(str){
560                  if(!str) return;
561                  var i=self.html.input, b=i.selectionStart, e=i.selectionEnd;
562                  // "a | b | c" => "a | b | "
563                  var pstr = i.value.slice(0,b).match(new RegExp('.*'+self.separator.escapeRegexp()+'\\s*'));
564                  i.value = (pstr ? pstr[0] : '') + str;
565                  var l=i.value.length;
566                  i.setSelectionRange(l,l);
567                  return i.value;
568          };
569          var getUniqueCandidate = function(){
570                  var lst = self.last_completed_string.split(/\r?\n|\r/);
571                  return lst.length == 1 ? lst[0] : false;
572          };
573          var str = self.getCompletedString();
574          if(!str){
575                  this.updateComplationList();
576                  str = getUniqueCandidate();
577          }
578          setString(str);
579          return getUniqueCandidate();
580  },
581  bindDecision: function(){
582          // history
583          if(this.hasOwnProperty('history')){ // ensure to set setter/getter
584                  var history = this.history,
585                  string = this.html.input.value;
586                  history = history.remove(string);// to avoid duplicated
587                  history.unshift(string);         //
588                  if(history.length > this.history_max) history.pop();
589                  this.history = history;
590          }
591          this.exit(this.html.input.value);
592  },
593  bindExit: function(){
594          this.exit();
595  },
596// move caret
597  bindBeginningOfLine: function(){this.html.input.setSelectionRange(0, 0)},
598  bindEndOfLine: function(){var i=this.html.input, l=i.value.length; i.setSelectionRange(l,l)},
599  bindForwardChar: function(){var i=this.html.input, p=i.selectionEnd+1; i.setSelectionRange(p,p)},
600  bindBackwardChar: function(){var i=this.html.input, p=i.selectionStart-1; i.setSelectionRange(p,p)},
601  bindForwardWord: function(){
602          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);
603          i.setSelectionRange(l,l);
604  },
605  bindBackwardWord: function(){
606          var i=this.html.input, l=i.value.slice(0,i.selectionStart).replace(/[a-zA-Z0-9]+$|[^a-zA-Z0-9]+$/,'').length;
607          i.setSelectionRange(l,l);
608  },
609// delete character
610  bindDeleteBackwardChar: function(){
611          var i= this.html.input, b=i.selectionStart, e=i.selectionEnd;
612          if(b==e) b--;
613          i.value = i.value.slice(0,b)+i.value.slice(e);
614          i.setSelectionRange(b,b);
615          this.updateComplationList();
616  },
617  bindDeleteBackwardWord: function(){
618          var i=this.html.input, b=i.selectionStart, e=i.selectionEnd;
619          var tx = i.value, tr=tx.slice(0,b-1).replace(/[^a-zA-Z0-9]+$/,'').replace(/[a-zA-Z0-9]+$/,''), l=tr.length;
620          i.value = tr+tx.slice(e);
621          i.setSelectionRange(l,l);
622          this.updateComplationList();
623  },
624  bindDeleteForwardChar: function(){
625          var i=this.html.input, b=i.selectionStart, e=i.selectionEnd;
626          if(b == e) e++;
627          i.value=i.value.slice(0,b)+i.value.slice(e+1);
628          i.setSelectionRange(b,b);
629          if(i.value=='') this.updateComplationList();
630  },
631  bindDeleteForwardWord: function(){
632          var i=this.html.input, b=i.selectionStart, e=i.selectionEnd;
633          var t=i.value,  m=t.slice(e).match(/[a-zA-Z0-9]+|[^a-zA-Z0-9]+/);
634          i.value = t.slice(0,b)+t.slice(!!m?e+m[0].length:e);
635          i.setSelectionRange(b,b);
636          if(i.value == '') this.updateComplationList();
637  },
638  bindDeleteAllStrings: function(){
639          this.deleteAllStrings();
640          this.updateComplationList();
641  },
642// insert
643  bindInputChar: function(key){
644          var i=this.html.input, b=i.selectionStart, e=i.selectionEnd, t=i.value;
645          i.value = t.slice(0,b++) + key + t.slice(e);
646          i.setSelectionRange(b,b);
647          this.updateComplationList();
648  },
649  bindCompleteAndPipe: function(){
650          if(this.separator!='|' ||
651             (this.current<0 && !this.bindComplete())) return;
652          var i=this.html.input, b=i.selectionStart, str=i.value;
653          var trim = function(str){
654                  return str.replace(/^\s|\s$/g,'');
655          };
656          i.value = trim(str.slice(0,b)) + ' ' + this.separator + ' ' + trim(str.slice(b));
657          var p = i.selectionEnd + this.separator.length;
658          i.setSelectionRange(p,p);
659          // eliminate highlight
660          var c = this.html.completion;
661          var last = this.current != -1 && c.childNodes[this.current];
662          this.selectCandidate(null, last);
663
664          this.updateComplationList();
665  },
666// history
667  bindSearchHistoryBackward: function(){
668          if(!this.hasOwnProperty('history')) return;
669          var history = this.history, self = this, i = this.html.input;
670          this.history_search_regexp = this.history_search_regexp || new RegExp('^' + i.value);
671          var count = history.position(function(e, n){
672                  return e.match(self.history_search_regexp) && n > self.history_search_count;
673          });
674          if(typeof count != 'number') count = this.history_search_count;
675          if(count > -1) i.value = history[count];
676          this.history_search_count = count;
677  },
678  bindSearchHistoryForward: function(){
679          if(!this.hasOwnProperty('history') || !this.history_search_regexp) return;
680          var history = this.history, self = this;
681          var count = history.slice(0,this.history_search_count).reverse().position(function(e, i){return e.match(self.history_search_regexp)});
682          if(typeof(count) == 'number') this.html.input.value = history[this.history_search_count -= count+1];
683  },
684// alias
685  bindRegisterOrUnregisterAlias: function(){
686          if(!this.hasOwnProperty('alias')) return;
687          var alias = this.alias;
688          var t = this.html.input.value;
689          if(typeof alias[t] == 'undefined'){
690                  // register as alias
691                  var a = prompt('input alias of '+t+'');
692                  alias[a] = t;
693                  this.alias = alias;
694          }else{
695                  // unregister from alias
696                  delete alias[t];
697                  this.alias = alias;
698          }
699  },
700  bindExpandAlias: function(){
701          var i = this.html.input;
702          var alias = this.alias[i.value];
703          if(alias) i.value = alias;
704          this.updateComplationList();
705  }
706};
707
708var Shell = {
709  TT: {
710        arg: 'arg',
711        control: 'control'
712  },
713  Parser: {
714        buffer: null,
715        init: function (buffer) {
716                this.buffer = buffer;
717        },
718        get_token: function (  ) {
719                // surround('foo', '[]') => '[foo]'
720                // surround('foo', '/')  => '/foo/'
721                var surround = function (s, c) {
722                        var d = (c.length > 1 ? c[1] : c) ;
723                        return c[0] + s + d;
724                };
725
726                var quote_chars = '"' + "'";
727                var meta_chars = "|";
728                // ((["'])(.+?)(\3))
729                var quoted_arg = '(([' + quote_chars + '])(.+?)(\\3))';
730                // [^|<>;"'\s]+
731                var bare_arg = surround( '^' + meta_chars + quote_chars + '\\s', '[]') + '+';
732                var job_controlers = meta_chars;
733
734                var exp = '^\\s*';
735                exp += surround( [
736                        quoted_arg, surround(bare_arg, "()"),
737                        surround( surround(meta_chars, "[]"), "()" )
738                        ].join("|"), "()" );
739                var re = new RegExp(exp);
740                if ( re.test(this.buffer) ) {
741                        // huuum, we need to count parenthesis index from constructed expression....
742                        // ^\s*(((["'])(.+?)(\3))|([^|<>;"']+)|([|<>;]))
743                        //4 or 6 or 7
744                        var token = RegExp.$4 ? {type: Shell.TT.arg, literal: RegExp.$4} :
745                        RegExp.$6 ? {type: Shell.TT.arg, literal: RegExp.$6} :
746                        {type: Shell.TT.control, literal: RegExp.$7 };
747                        this.buffer = RegExp.rightContext;
748                        return token;
749                } else {
750                        return null;
751                }
752        },
753  },
754  Command: {
755        commands: [],
756        state: null,
757        current_command: null,
758        init: function () {
759                this.commands = [];
760                this.state = this.need_command;
761        },
762        add_token: function (token) {
763                this.state.apply(this, [token]);
764        },
765        need_command: function (token) {
766                if ( token.type != Shell.TT.arg ) {
767                        trace("syntax error", token);
768                } else {
769                        this.current_command = { name: token.literal, args: [] };
770                        this.state = this.search_for_end;
771                }
772        },
773        search_for_end: function (token) {
774                if ( token.type == Shell.TT.control ) {
775                        this.end();
776                } else {
777                        this.current_command.args.push( token.literal );
778                }
779        },
780        end: function () {
781                this.commands.push(this.current_command);
782                this.current_command = null;
783                this.state = this.need_command;
784        }
785  },
786
787  buffer: null,
788  parse: function (buffer) {
789          this.Parser.init(buffer);
790          this.Command.init();
791
792          var token;
793          while ( token = this.Parser.get_token() ) {
794                  this.Command.add_token ( token );
795          }
796          this.Command.end();
797
798          return this.Command.commands;
799  },
800  execute: function (commands) {
801          var obj = null;
802          commands.forEach( function ( command ) {
803                  var proc = Bin[command.name];
804                  if ( proc ) {
805                          obj = proc.apply( this, [command.args, obj] );
806                  } else {
807                          trace("command not found.", command.name);
808                  }
809          } );
810          return stdin;
811  }
812};
813
814// copied from FLASH KEY (c) id:brazil
815// http://userscripts.org/scripts/show/11996
816// slightly modified.
817var FlashMessage = new function(){
818        GM_addStyle(<><![CDATA[
819                #FLASH_MESSAGE{
820                        position : fixed;
821                        font-size : 500%;
822                        z-index : 10000;
823
824                        padding : 50px;
825                        left : 50%;
826                        top : 50%;
827                        margin : -1em;
828
829                        background-color : #444;
830                        color : #FFF;
831                        -moz-border-radius: 0.3em;
832                        border-radius: 0.3em;
833                        min-width : 1em;
834                        text-align : center;
835                }
836        ]]></>)
837        var opacity = 0.9;
838        var flash = $N('div',{id:'FLASH_MESSAGE'});
839        hide(flash);
840        document.body.appendChild(flash);
841        var canceler;
842        this.showFlashMessageWindow = function (string, duration) {
843                duration = duration || 400;
844                canceler && canceler();
845                flash.innerHTML = string;
846                flash.style.opacity = opacity;
847                show(flash);
848                flash.style.marginLeft = (-(flash.offsetWidth/2))+'px';
849
850                canceler = callLater(function(){
851                        canceler = tween(function(value){
852                                flash.style.opacity = opacity * (1-value);
853                        }, 100, 5);
854                }, duration);
855        };
856
857        // ----[Utility]-------------------------------------------------
858        function callLater(callback, interval){
859                var timeoutId = setTimeout(callback, interval);
860                return function(){
861                        clearTimeout(timeoutId)
862                }
863        }
864        function tween(callback, span, count){
865                count = (count || 20);
866                var interval = span / count;
867                var value = 0;
868                var calls = 0;
869                var intervalId = setInterval(function(){
870                        callback(calls / count);
871
872                        if(count == calls){
873                                canceler();
874                                return;
875                        }
876                        calls++;
877                }, interval);
878                var canceler = function(){
879                        clearInterval(intervalId)
880                        hide(flash)
881                }
882                return canceler;
883        }
884        function hide(target){
885                target.style.display='none';
886        }
887        function show(target, style){
888                target.style.display=(style || '');
889        }
890};
891
892var Status = new Class();
893Status.id = 'gm_minibuffer_flash_status';
894Status.hash = {};
895Status.prototype = {
896  initialize: function(){
897          this.initContainer();
898          var [name,status,time_limit] = arguments;
899          var hash = this.getHash();
900          var del = function(){
901                  delete hash[name];
902          }
903          if(typeof status != "string"){
904                  if(hash[name]){
905                          this.fadeout(hash[name]);
906                          del();
907                  }
908          }else{
909                  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"});
910                  var lst = typeof time_limit == 'number' ? [status]: [img ,status];
911                  var div = $N('div', {}, lst);
912                  if(hash[name]){
913                          this.replace(div, hash[name]);
914                  }else{
915                          this.add(div);
916                  }
917                  hash[name] = div;
918                  if(typeof time_limit == 'number'){
919                          this.fadeout.later(time_limit).call(this, hash[name]);
920                          del.later(time_limit)();
921                  }
922          }
923  },
924  initContainer: function(){
925          if(!document.getElementById(Status.id)){
926                  GM_addStyle(<><![CDATA[
927                          #gm_minibuffer_flash_status{
928                                position : fixed;
929                                font-size: 150%;
930                                z-index : 10000;
931                                right : 20px;
932                                bottom : 0px;
933                                opacity: 0.9;
934                                background-color : #000;
935                                padding: 10px;
936                                color : #FFF;
937                                -moz-border-radius: 0.3em;
938                                border-radius: 0.3em;
939                          }
940                          #gm_minibuffer_flash_status img {
941                                  margin-right: 10px;
942                          }
943                          ]]></>);
944                  var container = $N('div',{id:Status.id, style:'display:block;'});
945                  document.body.appendChild(container);
946          }
947  },
948  getHash: function(){return Status.hash},
949  add: function(node){
950          var container = document.getElementById(Status.id);
951          if(container){
952                  container.appendChild(node);
953          }
954          if(container.style.display == 'none'){
955                  container.style.display = 'block';
956          }
957  },
958  fadeout: function(node){
959          var setOpacity = function(node, opacity){
960                  node.style.opacity = opacity;
961          }
962          var max = 15;
963          var base = 1000;
964          for(var i=0; i<max; i++){
965                  setOpacity.later(i/max*base)(node, 1-i/max);
966          }
967          this.remove.later(base*1.2)(node);
968  },
969  remove: function(node){
970          var container = document.getElementById(Status.id);
971          container.removeChild(node);
972          if(!container.hasChildNodes()){
973                  container.style.display = 'none';
974          }
975  },
976  replace: function(new_node, old_node){
977          var container = document.getElementById(Status.id);
978          container.replaceChild(new_node, old_node);
979  }
980};
981
982var Command = new Class();
983Command.prototype = {
984  initialize: function(){
985          this.command = {};
986  },
987  get selection(){
988          return this.minibuffer.isAvailable() ?
989                  this.minibuffer.selection :
990                {
991                  node: getSelectionNode(),
992                  text: getSelectionText()
993                }
994  },
995
996  /* argument of addCommand
997   * name:        "string",
998   * command:     function(stdin){ return stdout; },
999   *
1000   * todo:
1001   * // description: "string",
1002   * // argument:    function(){ return ["option1", "option2"]; },
1003   */
1004  addCommand: function(hash){
1005          // to keep compatibility
1006          if(typeof hash.name == 'undefined'){
1007                  for(var name in hash){
1008                          this.command[name] = hash[name];
1009                  }
1010                  return;
1011          }
1012
1013//        var description = hash['description'];
1014//        var argument = hash['argument'];
1015          this.command[hash['name']] = hash['command'];
1016  },
1017  addShortcutkey: function(opt){
1018          this.shortcutkey.addCommand(opt);
1019  },
1020  attachEvent: function(){
1021          var self = this;
1022          var fn = function(){
1023                  self.shortcutkey.disable();
1024                  self.minibuffer.setCandidates(keys(self.command));
1025                  self.minibuffer.complete(function(a){self.callback(a)});
1026          }
1027          this.addShortcutkey({ key:'M-x', description:'Open Minibuffer', command:fn});
1028          this.addShortcutkey({ key:':', description:'Open Minibuffer', command:fn});
1029          this.addShortcutkey({
1030                key:'?',
1031                description: 'Toggle help',
1032                command: function(){self.shortcutkey.bindHelp.call(self.shortcutkey)},
1033          });
1034  },
1035  hoge: function(){
1036          var self = this;
1037          var s = new ShortcutKey();
1038          s.throughEvent();
1039          s.addEventListener(document, 'keypress', false);
1040          s.addCommand({key: 'Up Up Down Down Left Right Left Right b a', command:function(){FlashMessage.showFlashMessageWindow.eachLater(1000).apply(this,atob("NSA0IDMgMiAxIEJPTUIhISE=").split(' ').map(function(e){return [e,800]}))}});
1041          s.addCommand({key: 'Up x Down b l y r a', command: function(){var tmp='';FlashMessage.showFlashMessageWindow.eachLater(500).apply(this,'\u30ab \u30ab \u30ed \u30c3 \u30c8 \u30fb \u30fb \u30fb'.split(' ').map(function(e){return [tmp+=e, 500]}))}});
1042          s.addCommand({key: 'Down r Up l y b x a', command: function(){
1043                  var h=self.minibuffer.html.container;
1044                  var f=function(){return Math.floor(Math.random()*256).toString(16)};
1045                  var c='#'+f()+f()+f();
1046                  FlashMessage.showFlashMessageWindow(c,1000);
1047                  h.style.backgroundColor=c;
1048          }});
1049  },
1050  detachEvent: function(){
1051          this.shortcutkey.removeCommand('M-x');
1052  },
1053  execute: function(commandline, stdin){
1054          if(!commandline) return;
1055          var alias = this.alias_getter()[commandline];
1056          var commands = Shell.parse(alias ? alias : commandline);
1057          var self = this;
1058          if(typeof stdin == 'undefined') stdin = [];
1059          var ret = commands.forEach(function(command){
1060                  var fn = self.command[command.name];
1061                  if(!fn) return null;
1062                  var cmd = {
1063                        func: fn,
1064                        args: command.args,
1065                        name: command.name
1066                  };
1067                  stdin = cmd.func(stdin);
1068          });
1069          return stdin;
1070  },
1071  callback: function(commandline){
1072          this.shortcutkey.enable();
1073          if(!commandline) return;
1074          this.execute(commandline);
1075  },
1076  setup: function(){
1077          // setup minibuffer
1078          var define_setter = function(type){return function(arg){ GM_setValue(type, uneval(arg))}};
1079          this.alias_getter = function(){return eval(GM_getValue('alias', '({})'))};
1080          this.minibuffer = new Minibuffer()
1081                  .setHistoryGetter(function(){return eval(GM_getValue('history', '[]'))})
1082                  .setAliasGetter(this.alias_getter)
1083                  .setHistorySetter(define_setter('history'))
1084                  .setAliasSetter(define_setter('alias'));
1085          // setup shortcut key
1086          this.shortcutkey = new ShortcutKey()
1087                  .initHelp('command')
1088                  .throughInputElements();
1089          this.shortcutkey.addEventListener(document, 'keypress', false);
1090          this.attachEvent();
1091          this.hoge();
1092  },
1093};
1094
1095function $N(name, attr, childs) {
1096        var ret = document.createElement(name);
1097        for (var k in attr) if (attr.hasOwnProperty(k)) {
1098                var v = attr[k];
1099                if (k == "class") ret.className = v;
1100                else ret.setAttribute(k, v);
1101        }
1102        switch(typeof childs){
1103          case "string":
1104                ret.appendChild(document.createTextNode(childs));
1105                break;
1106          case "object":
1107                for (var i=0, len=childs.length; i<len; i++) {
1108                        var child = childs[i];
1109                        if (typeof child == "string") {
1110                                ret.appendChild(document.createTextNode(child));
1111                        } else {
1112                                ret.appendChild(child);
1113                        }
1114                }
1115        }
1116        return ret;
1117}
1118
1119// via http://gist.github.com/283040
1120function createDocumentFromString(source){
1121        var doc;
1122        try {
1123                doc = document.cloneNode(false);
1124                doc.appendChild(doc.importNode(document.documentElement, false));
1125        } catch(e) {
1126                doc = document.implementation.createHTMLDocument ?
1127                                document.implementation.createHTMLDocument('hogehoge') :
1128                                document.implementation.createDocument(null, 'html', null);
1129        }
1130        var range = document.createRange();
1131        range.selectNodeContents(document.documentElement);
1132        var fragment = range.createContextualFragment(source);
1133        var headChildNames = {title: true, meta: true, link: true, script: true, style: true, /*object: true,*/ base: true/*, isindex: true,*/};
1134        var child, head = doc.getElementsByTagName('head')[0] || doc.createElement('head'),
1135                   body = doc.getElementsByTagName('body')[0] || doc.createElement('body');
1136        while ((child = fragment.firstChild)) {
1137                if (
1138                        (child.nodeType === doc.ELEMENT_NODE && !(child.nodeName.toLowerCase() in headChildNames)) ||
1139                        (child.nodeType === doc.TEXT_NODE &&/\S/.test(child.nodeValue))
1140                   )
1141                        break;
1142                head.appendChild(child);
1143        }
1144        body.appendChild(fragment);
1145        doc.documentElement.appendChild(head);
1146        doc.documentElement.appendChild(body);
1147        return doc;
1148}
1149
1150
1151
1152// $X on XHTML
1153// @target Freifox3, Chrome3, Safari4, Opera10
1154// @source http://gist.github.com/184276.txt
1155function $X (exp, context) {
1156    context || (context = document);
1157    var _document = context.ownerDocument || context,
1158        documentElement = _document.documentElement,
1159        isXHTML = documentElement.tagName !== 'HTML' && _document.createElement('p').tagName === 'p',
1160        defaultPrefix = null;
1161    if (isXHTML) {
1162        defaultPrefix = '__default__';
1163        exp = addDefaultPrefix(exp, defaultPrefix);
1164    }
1165    function resolver (prefix) {
1166        return context.lookupNamespaceURI(prefix === defaultPrefix ? null : prefix) ||
1167            documentElement.namespaceURI || "";
1168    }
1169
1170    var result = _document.evaluate(exp, context, resolver, XPathResult.ANY_TYPE, null);
1171    switch (result.resultType) {
1172        case XPathResult.STRING_TYPE : return result.stringValue;
1173        case XPathResult.NUMBER_TYPE : return result.numberValue;
1174        case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
1175        case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
1176            // not ensure the order.
1177            var ret = [], i = null;
1178            while (i = result.iterateNext()) ret.push(i);
1179            return ret;
1180    }
1181}
1182// XPath 式中の接頭辞のない名前テストに接頭辞 prefix を追加する
1183// e.g. '//body[@class = "foo"]/p' -> '//prefix:body[@class = "foo"]/prefix:p'
1184// http://nanto.asablo.jp/blog/2008/12/11/4003371
1185function addDefaultPrefix(xpath, prefix) {
1186    var tokenPattern = /([A-Za-z_\u00c0-\ufffd][\w\-.\u00b7-\ufffd]*|\*)\s*(::?|\()?|(".*?"|'.*?'|\d+(?:\.\d*)?|\.(?:\.|\d+)?|[\)\]])|(\/\/?|!=|[<>]=?|[\(\[|,=+-])|([@$])/g;
1187    var TERM = 1, OPERATOR = 2, MODIFIER = 3;
1188    var tokenType = OPERATOR;
1189    prefix += ':';
1190    function replacer(token, identifier, suffix, term, operator, modifier) {
1191        if (suffix) {
1192            tokenType =
1193                (suffix == ':' || (suffix == '::' && (identifier == 'attribute' || identifier == 'namespace')))
1194                ? MODIFIER : OPERATOR;
1195        } else if (identifier) {
1196            if (tokenType == OPERATOR && identifier != '*')
1197                token = prefix + token;
1198            tokenType = (tokenType == TERM) ? OPERATOR : TERM;
1199        } else {
1200            tokenType = term ? TERM : operator ? OPERATOR : MODIFIER;
1201        }
1202        return token;
1203    }
1204    return xpath.replace(tokenPattern, replacer);
1205}
1206
1207// Usage:: with (D()) { your code }
1208// JSDefeered 0.2.1 (c) Copyright (c) 2007 cho45 ( www.lowreal.net )
1209// See http://coderepos.org/share/wiki/JSDeferred
1210function D () {
1211function Deferred () { return (this instanceof Deferred) ? this.init(this) : new Deferred() }
1212Deferred.prototype = {
1213        init : function () {
1214                this._next    = null;
1215                this.callback = {
1216                        ok: function (x) { return x },
1217                        ng: function (x) { throw  x }
1218                };
1219                return this;
1220        },
1221
1222        next  : function (fun) { return this._post("ok", fun) },
1223        error : function (fun) { return this._post("ng", fun) },
1224        call  : function (val) { return this._fire("ok", val) },
1225        fail  : function (err) { return this._fire("ng", err) },
1226
1227        cancel : function () {
1228                (this.canceller || function () {})();
1229                return this.init();
1230        },
1231
1232        _post : function (okng, fun) {
1233                this._next =  new Deferred();
1234                this._next.callback[okng] = fun;
1235                return this._next;
1236        },
1237
1238        _fire : function (okng, value) {
1239                var self = this, next = "ok";
1240                try {
1241                        value = self.callback[okng].call(self, value);
1242                } catch (e) {
1243                        next  = "ng";
1244                        value = e;
1245                }
1246                if (value instanceof Deferred) {
1247                        value._next = self._next;
1248                } else {
1249                        if (self._next) self._next._fire(next, value);
1250                }
1251                return this;
1252        }
1253};
1254
1255Deferred.parallel = function (dl) {
1256        var ret = new Deferred(), values = {}, num = 0;
1257        for (var i in dl) if (dl.hasOwnProperty(i)) {
1258                (function (d, i) {
1259                        d.next(function (v) {
1260                                values[i] = v;
1261                                if (--num <= 0) {
1262                                        if (dl instanceof Array) {
1263                                                values.length = dl.length;
1264                                                values = Array.prototype.slice.call(values, 0);
1265                                        }
1266                                        ret.call(values);
1267                                }
1268                        }).error(function (e) {
1269                                ret.fail(e);
1270                        });
1271                        num++;
1272                })(dl[i], i);
1273        }
1274        if (!num) Deferred.next(function () { ret.call() });
1275        ret.canceller = function () {
1276                for (var i in dl) if (dl.hasOwnProperty(i)) {
1277                        dl[i].cancel();
1278                }
1279        };
1280        return ret;
1281};
1282
1283Deferred.wait = function (n) {
1284        var d = new Deferred(), t = new Date();
1285        var id = setTimeout(function () {
1286                clearTimeout(id);
1287                d.call((new Date).getTime() - t.getTime());
1288        }, n * 1000)
1289        d.canceller   = function () { try { clearTimeout(id) } catch (e) {} };
1290        return d;
1291};
1292
1293Deferred.next = function (fun) {
1294        var d = new Deferred();
1295        var id = setTimeout(function () { clearTimeout(id); d.call() }, 0);
1296        if (fun) d.callback.ok = fun;
1297        d.canceller   = function () { try { clearTimeout(id) } catch (e) {} };
1298        return d;
1299};
1300
1301Deferred.call = function (f, args) {
1302        args = Array.prototype.slice.call(arguments);
1303        f    = args.shift();
1304        return Deferred.next(function () {
1305                return f.apply(this, args);
1306        });
1307};
1308
1309Deferred.loop = function (n, fun) {
1310        var o = {
1311                begin : n.begin || 0,
1312                end   : n.end   || (n - 1),
1313                step  : n.step  || 1,
1314                last  : false,
1315                prev  : null
1316        };
1317        var ret, step = o.step;
1318        return Deferred.next(function () {
1319                function _loop (i) {
1320                        if (i <= o.end) {
1321                                if ((i + step) > o.end) {
1322                                        o.last = true;
1323                                        o.step = o.end - i + 1;
1324                                }
1325                                o.prev = ret;
1326                                ret = fun.call(this, i, o);
1327                                if (ret instanceof Deferred) {
1328                                        return ret.next(function (r) {
1329                                                ret = r;
1330                                                return Deferred.call(_loop, i + step);
1331                                        });
1332                                } else {
1333                                        return Deferred.call(_loop, i + step);
1334                                }
1335                        } else {
1336                                return ret;
1337                        }
1338                }
1339                return Deferred.call(_loop, o.begin);
1340        });
1341};
1342
1343Deferred.register = function (name, fun) {
1344        this.prototype[name] = function () {
1345                return this.next(Deferred.wrap(fun).apply(null, arguments));
1346        };
1347};
1348
1349Deferred.wrap = function (dfun) {
1350        return function () {
1351                var a = arguments;
1352                return function () {
1353                        return dfun.apply(null, a);
1354                };
1355        };
1356};
1357
1358Deferred.register("loop", Deferred.loop);
1359Deferred.register("wait", Deferred.wait);
1360
1361Deferred.define = function (obj, list) {
1362        if (!list) list = ["parallel", "wait", "next", "call", "loop"];
1363        if (!obj)  obj  = (function () { return this })();
1364        list.forEach(function (i) {
1365                obj[i] = Deferred[i];
1366        });
1367        return Deferred;
1368};
1369
1370
1371
1372function xhttp (opts) {
1373        var d = Deferred();
1374        if (opts.onload)  d = d.next(opts.onload);
1375        if (opts.onerror) d = d.error(opts.onerror);
1376        opts.onload = function (res) {
1377                d.call(res);
1378        };
1379        opts.onerror = function (res) {
1380                d.fail(res);
1381        };
1382        GM_xmlhttpRequest(opts);
1383        return d;
1384}
1385xhttp.get  = function (url)       { return xhttp({method:"get",  url:url}) };
1386xhttp.post = function (url, data) { return xhttp({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) };
1387
1388
1389function http (opts) {
1390        var d = Deferred();
1391        var req = new XMLHttpRequest();
1392        req.open(opts.method, opts.url, true);
1393        if (opts.headers) {
1394                for (var k in opts.headers) if (opts.headers.hasOwnProperty(k)) {
1395                        req.setRequestHeader(k, opts.headers[k]);
1396                }
1397        }
1398        req.onreadystatechange = function () {
1399                if (req.readyState == 4) d.call(req);
1400        };
1401        req.send(opts.data || null);
1402        d.xhr = req;
1403        return d;
1404}
1405http.get  = function (url)       { return http({method:"get",  url:url}) };
1406http.post = function (url, data) { return http({method:"post", url:url, data:data, headers:{"Content-Type":"application/x-www-form-urlencoded"}}) };
1407
1408Deferred.Deferred = Deferred;
1409Deferred.http     = http;
1410Deferred.xhttp    = xhttp;
1411return Deferred;
1412}// End of JSDeferred
1413
1414function keys(hash){
1415        var tmp = [];
1416        for(var key in hash)tmp.push(key);
1417        return tmp;
1418}
1419function values(hash){
1420        var tmp = [];
1421        for(var key in hash)tmp.push(hash[key]);
1422        return tmp;
1423}
1424var getSelectionText = function(){
1425        return String(window.getSelection()).split(/\r?\n|\r/).remove("");
1426};
1427var getSelectionNode = function(){
1428        var s=window.getSelection(), res=[], len=s.rangeCount;
1429        for(var i=0; i<len; i++){
1430                var ret = document.createElement('root');
1431                ret.appendChild(s.getRangeAt(i).cloneContents());
1432                res[res.length] = ret;
1433        }
1434        return res;
1435};
1436
1437String.prototype.escapeRegexp = function(){
1438        return this.replace(/^(?=[?*])/, '.').replace(/(?=[|+])/g, '\\').replace(/\([^)]*$/, '').replace(/\[[^\]]*$/, '');
1439};
1440Array.prototype.position = function(obj){
1441        var test = (typeof(obj) == 'function') ? obj : function(a){return a == obj};
1442        for(var i=0;i<this.length; i++) if(test(this[i], i)) return i;
1443        return false;
1444};
1445Array.prototype.last = function(){
1446        return this[this.length-1];
1447};
1448Array.prototype.find = function(obj){
1449        var i = this.position(obj);
1450        return typeof(i) == 'number' ? this[i] : false;
1451};
1452Array.prototype.remove = function(obj){
1453        var test = (typeof(obj) == 'function') ? obj : function(a){return a == obj};
1454        return this.filter(function(e){return !test(e)})
1455};
1456Array.prototype.reduce = function(fn ,initial){
1457        var len = this.length;
1458        if(typeof fn != "function" || (len == 0 && arguments.length == 1)) throw new TypeError();
1459        var i = 0;
1460        if(arguments.length >= arguments.callee.length){
1461                var rv = arguments[1];
1462        }else{
1463                do{
1464                        if(i in this){
1465                                rv = this[i++];
1466                                break;
1467                        }
1468                        if(++i >= len)throw new TypeError();
1469                }while (true);
1470        }
1471        for (;i<len;i++) if(i in this) rv=fn.call(null, rv, this[i], i, this);
1472        return rv;
1473};
1474Function.prototype.later = function(ms){
1475        var self = this;
1476        return function(){
1477                var args = arguments;
1478                var thisObject = this;
1479                var res = {
1480                        arg: args,
1481                        complete: false,
1482                        cancel: function(){clearTimeout(PID);},
1483                        notify: function(){clearTimeout(PID);later_func()}
1484                };
1485                var later_func = function(){
1486                        self.apply(thisObject,args);
1487                        res.complete = true;
1488                };
1489                var PID = setTimeout(later_func,ms);
1490                return res;
1491        };
1492};
1493// usage:
1494//   var lst = (function(e){console.log(e)}).eachLater(500)([1],[2],[3],[4],[5],[6],[7]);
1495//   (function(){lst.forEach(function(e){e.complete || e.cancel()})}).later(2000)();
1496Function.prototype.eachLater = function(ms){
1497        var self = this;
1498        return function(){
1499                var tmp=0, lst=[];
1500                for(var i=0;i<arguments.length; i++) lst[lst.length] = self.later(tmp+=ms).apply(this,arguments[i]);
1501                return lst;
1502        }
1503};
1504
1505function log(){console.log.apply(console, Array.slice(arguments));}
1506
1507//// register command
1508if(document.body){
1509        var command = new Command();
1510        window.Minibuffer = {
1511          getMinibuffer  : function(){return new Minibuffer()}
1512        , getShortcutKey : function(){return new ShortcutKey()}
1513
1514        ,  addShortcutkey : function(a){command.addShortcutkey(a)}
1515        ,  addCommand     : function(a){command.addCommand(a)}
1516
1517        ,  execute        : function(a, stdin){return command.execute(a, stdin)}
1518        ,  message        : FlashMessage.showFlashMessageWindow
1519        ,  status         : function(name, status, timelimit){new Status(name, status,timelimit)}
1520
1521        ,  $X             : $X
1522        ,  $N             : $N
1523        ,  D              : D
1524        ,  createDocumentFromString : createDocumentFromString
1525        };
1526
1527        window.Minibuffer.addCommand({
1528          name: 'Minibuffer::Exit',
1529          command : command.detachEvent,
1530        });
1531
1532        // nothing => list of current URL
1533        window.Minibuffer.addCommand({
1534                name: 'location',
1535                command: function(){return [location.href]},
1536        });
1537
1538        // nothing => list of string (divided by \n)
1539        window.Minibuffer.addCommand({
1540                name: 'selected-text',
1541                command: function(){return command.selection.text},
1542        });
1543
1544        // nothing => list of selected node
1545        window.Minibuffer.addCommand({
1546                name: 'selected-node',
1547                command: function(){return command.selection.node}
1548        });
1549
1550        // node list => list of string
1551        window.Minibuffer.addCommand({
1552                name: 'innerHTML',
1553                command: function(stdin){return stdin.map(function(a){return a.innerHTML})}
1554        });
1555
1556        // list of node or nothing => list of node
1557        // args: 'tag'
1558        window.Minibuffer.addCommand({
1559                name: 'filter-by-tag-name',
1560                command: function(stdin){
1561                        var tag = this.args.shift();
1562                        if(stdin.length == 0) stdin.push(document);
1563                        var res = [];
1564                        for(var i=0,k=stdin.length; i<k; i++){
1565                                var lst = stdin[i].getElementsByTagName(tag);
1566                                for(var j=0,l=lst.length; j<l; j++){
1567                                        res.push(lst[j]);
1568                                }
1569                        }
1570                        return res;
1571                }
1572        });
1573
1574        // list of URL or nothing => list of URL
1575        // args: count
1576        window.Minibuffer.addCommand({
1577                name: "upper-directory",
1578                command: function(stdin){
1579                        var urls  = stdin.length ? stdin : [location.href];
1580                        var count = this.args.shift() || 1;
1581                        var rep   = new RegExp('[^/]+/?$');
1582                        var host  = new RegExp('[a-z]+://[^/]+/');
1583                        return urls.map(function(url){
1584                                for(var i=0; i<count; i++){
1585                                        var newurl = url.replace(rep, '');
1586                                        if(newurl.match(host)) url = newurl;
1587                                }
1588                                return url;
1589                        });
1590                }
1591        });
1592
1593        // object => object
1594        window.Minibuffer.addCommand({
1595                name: 'echo',
1596                command: function(obj){
1597                        log(obj);
1598                        return obj;
1599                }
1600        });
1601
1602        // list of node or nothing => list of node
1603        // args: 'XPath'
1604        window.Minibuffer.addCommand({
1605                name: 'xpath',
1606                command: function(stdin) {
1607                        var exp = this.args.shift();
1608                        if(stdin.length == 0) stdin.push(document);
1609                        var res = [];
1610                        for(var i=0,l=stdin.length; i<l; i++){
1611                                var lst = window.Minibuffer.$X(exp, stdin[i]);
1612                                for(var j=0,l=lst.length; j<l; j++){
1613                                        res.push(lst[j]);
1614                                }
1615                        }
1616                        return res;
1617                }
1618        });
1619
1620        // list of node or list of string => list of node or list of string
1621        // args: 'regexp' 'attribute' 'flag'
1622        window.Minibuffer.addCommand({
1623                name: 'grep',
1624                command: function (stdin) {
1625                        var regexp = this.args.shift();
1626                        var attr = this.args.shift();
1627                        var flag = this.args.shift();
1628                        var re = new RegExp(regexp, typeof(flag) != 'undefined' ? flag : regexp.toLowerCase() == regexp ? 'i':'');
1629                        return stdin.filter(function(obj) {
1630                                if(typeof(obj) == 'string'){
1631                                        return obj.match(re);
1632                                }else if(obj.nodeType == 3){
1633                                        return obj.nodeValue.match(re);
1634                                }else if(obj.nodeType == 1 && attr && obj.getAttribute(attr)){
1635                                        return obj.getAttribute(attr).match(re);
1636                                }else if(obj.nodeType == 1 && obj.text){
1637                                        return obj.text.match(re);
1638                                }
1639                        });
1640                }
1641        });
1642
1643        // list of anchor node or list of URL
1644        // args: 'target'
1645        window.Minibuffer.addCommand({
1646                name: 'open',
1647                command: function(stdin){
1648                        var target = this.args.shift();
1649                        if(target == 'top' || target == 'blank') target = '_' + target;
1650                        stdin.forEach(function(url){
1651                                if(target){
1652                                        window.open(url, target);
1653                                }else{
1654                                        GM_openInTab(url);
1655                                }
1656                        });
1657                        return stdin;
1658                }
1659        });
1660
1661        // list => reversed list
1662        window.Minibuffer.addCommand({
1663                name: 'reverse',
1664                command: function(stdin){
1665                        return stdin.reverse();
1666                }
1667        });
1668
1669        // list of URL => list of URL
1670        window.Minibuffer.addCommand({
1671                name: 'web-archive',
1672                command: function(stdin){
1673                        return stdin.map(function(url){return 'http://web.archive.org/web/*/' + url})
1674                }
1675        });
1676
1677        // list of URL => list of URL
1678        window.Minibuffer.addCommand({
1679                name: 'google-cache',
1680                command: function(stdin){
1681                        return stdin.map(function(url){return 'http://www.google.com/search?q=cache:' + url})
1682                }
1683        });
1684
1685        // list of URL => list of URL
1686        window.Minibuffer.addCommand({
1687                name: 'web-gyotaku',
1688                command: function(stdin){
1689                        return stdin.map(function(url){return 'http://megalodon.jp/?url=' + url})
1690                }
1691        });
1692
1693        // list => list
1694        window.Minibuffer.addCommand({
1695                name: 'scrollto-top',
1696                command: function(stdin){
1697                        window.scrollTo(0,0);
1698                        return stdin;
1699                }
1700        });
1701        // list => list
1702        window.Minibuffer.addCommand({
1703                name: 'scrollto-bottom',
1704                command: function(stdin){
1705                        window.scrollTo(0, window.scrollMaxY);
1706                        return stdin;
1707                }
1708        });
1709
1710//      // tako3
1711//      window.Minibuffer.addCommand({
1712//              name: 'tako3',
1713//              command: function(stdin){
1714//                      return stdin.map(function(url){return "http://tako3.com/" + url})
1715//              }
1716//      });
1717
1718        // setup
1719        command.setup();
1720
1721        // shortcut key sample
1722
1723//      window.Minibuffer.addShortcutkey({
1724//        key: 'C-o',
1725//        description: 'Open Google cache',
1726//        command: function(){
1727//                      window.Minibuffer.execute('pinned-or-current-link | google-cache | open | clear-pin');
1728//              }
1729//      });
1730//      // vi like
1731//      window.Minibuffer.addShortcutkey({
1732//        key: 'g g',
1733//        description: 'scroll to top',
1734//        command: function(){window.Minibuffer.execute('scrollto-top')}
1735//      });
1736//      window.Minibuffer.addShortcutkey({
1737//        key: 'G',
1738//        description: 'scroll to bottom',
1739//        command: function(){window.Minibuffer.execute('scrollto-bottom')}
1740//      });
1741        var ev = document.createEvent('Events');
1742        ev.initEvent('GM_MinibufferLoaded', false, true);
1743        window.dispatchEvent(ev);
1744}
1745
Note: See TracBrowser for help on using the browser.