Hatena::Groupvimperator

vimpめも

 | 

2009-04-02

modes.jsを読む

21:39 | はてなブックマーク - modes.jsを読む - vimpめも

modesのプライベート変数一覧

最初の6つは同名のgetter, setterあり。

    var main = 1;     // NORMAL
    var extended = 0; // NONE, set extended(value) { this.set(null, value) }
    var passNextKey = false;
    var passAllKeys = false;
    var isRecording = false;
    var isReplaying = false; // playing a macro

    var lastShown = null;
    var modeStack = [];
    var mainModes = [self.NONE];
    var lastMode = 0;
    var modeMap = {};

モード一覧

    // main modes, only one should ever be active
    self.addMode("NORMAL", false, -1);
    self.addMode("INSERT");
    self.addMode("VISUAL", false, function () "VISUAL" + (extended & modes.LINE ? " LINE" : ""));
    self.addMode("COMMAND_LINE");
    self.addMode("CARET"); // text cursor is visible
    self.addMode("TEXTAREA"); // text cursor is in a HTMLTextAreaElement
    self.addMode("MESSAGE"); // Muttatorで使う
    self.addMode("COMPOSE"); // Muttatorで使う
    self.addMode("CUSTOM", false, function () plugins.mode);
    // extended modes, can include multiple modes, and even main modes
    self.addMode("EX", true);
    self.addMode("HINTS", true);
    self.addMode("INPUT_MULTILINE", true); // commandline.inputMultiline()の実行時に開始されるモード。複数行入力モード。
    self.addMode("OUTPUT_MULTILINE", true); // commandline.echo()やg<などの実行時に開始されるモード。複数行出力モード。
    self.addMode("SEARCH_FORWARD", true);
    self.addMode("SEARCH_BACKWARD", true);
    self.addMode("MENU", true); // a popupmenu is active
    self.addMode("LINE", true); // TEXTAREAモードでVを入力した時にこのモードになる。ビジュアル行モード。
    self.addMode("PROMPT", true);

modesのパブリックなメンバー一覧

        NONE,
        get all() mainModes.slice(),
        get inputMode() main & (this.COMMAND_LINE | this.INPUT | this.TEXTAREA | this.COMPOSE), // このthis.INPUTが謎。バグ?

        addMode: function (name, extended, display){
            let disp = name.replace("_", " ", "g");
            this[name] = 1 << lastMode++;
            modeMap[name] = modeMap[this[name]] = { // モード名と数字を使ってアクセスできる。
                extended: extended,   // true/false
                mask: this[name],       // 数字(bit mask)
                name: name,
                display: display || function () disp   //表示名
            };
            if (!extended)
                mainModes.push(this[name]);
        },

        getMode: function (name) modeMap[name],
        show: function ()         // show the current mode string in the command line
        add: function (mode){ extended |= mode; this.show(); },         // extendedモードの追加。set(null, extended | mode)を実行。

        // extended, mainにモードをsetし、mode changeをハンドリングする。silent: モード名を表示しない
        // if silent == true, you also need to take care of the mode handling changes yourself
        set: function (mainMode, extendedMode, silent, stack){
            silent = (silent || main == mainMode && extended == extendedMode); // &&のほうが優先度高い
            let oldMain = main, oldExtended = extended;
            if (typeof extendedMode === "number") extended = extendedMode;
            if (typeof mainMode === "number"){
                main = mainMode;
                if (!extendedMode) extended = modes.NONE;
                if (main != oldMain) handleModeChange(oldMain, mainMode, oldExtended);
            }
	    // 自分でモードの変化をハンドリングする時に使う?(liberator.registerObserver('modeChange', func)のようにする)
            liberator.triggerObserver("modeChange", [oldMain, oldExtended], [main, extended], stack);
            if (!silent) this.show();
        },

        push: function (mainMode, extendedMode, silent){
                modeStack.push([main, extended]);
                this.set(mainMode, extendedMode, silent, { push: modeStack[modeStack.length - 1] });
        },
        pop: function (silent){
            let a = modeStack.pop();
            if (a){ this.set(a[0], a[1], silent, { pop: a }); }else{ this.reset(silent); }
        }

        setCustomMode: function (modestr, oneventfunc, stopfunc){ // 廃止される可能性あり
            plugins.mode = modestr;
            plugins.onEvent = oneventfunc;
            plugins.stop = stopfunc;
        },

        reset: function (silent){
            modeStack = [];
            this.set(modes.NORMAL, modes.NONE, silent);
        },
        remove: function (mode){
            if (extended & mode){
                extended &= ~mode;
                this.show();
            }
        },

補足: modes.push(), modes.pop()を呼び出している箇所

概要

// modes.push

1. commandline.inputMultiline: 複数行入力させる時。hereDocオプションのついたコマンド(js, style, highlight)など。pushされたものは4でpopされる。

2. commandline.input: プロンプトモードに移行した時。extended hintなど。pushされたものはcallback内でpopされる(例えばhintを入力したら直ちにpopされる)

// modes.pop

3. commandline.onEvent: 複数行入力モード以外のコマンドラインモードで、EnterまたはBackSpaceを入力しコマンドラインから抜け出す時。

4. commandline.onMultilineInputEvent: 複数行入力モードの終了文字をタイプしてEnterした時。

5. commandline.onMultilineOutputEvent: 複数行出力モードでキー入力してMOWが閉じる時。


1. 複数行入力させる時。hereDocオプションのついたコマンドなど。

ui.js 1240

commandline.inputMultiline

	// 複数行入力させる
        inputMultiline: function inputMultiline(untilRegexp, callbackFunc){
            let cmd = !commandWidget.collapsed && this.command;
            modes.push(modes.COMMAND_LINE, modes.INPUT_MULTILINE); // ****
        /* snip */

この呼び出し箇所。

commands.js 174

Command#execute

        if (this.hereDoc){
            let matches = args.match(/(.*)<<\s*(\S+)$/);
            if (matches && matches[2]){
                commandline.inputMultiline(RegExp("^" + matches[2] + "$", "m"), // ****
                    function (args) { exec(matches[1] + "\n" + args); });
                return;
            }
        }
2. プロンプトモードに移行した時。extended hintなど。

ui.js 1207

commandline.input

	// プロンプトに入力させる
	// 例. commandline.input('type your name: ', function(name){alert(name)})
        input: function _input(prompt, callback, extra){
            extra = extra || {};
            input = {
                submit: callback,
                change: extra.onChange,
                complete: extra.completer,
                cancel: extra.onCancel
            };
            modes.push(modes.COMMAND_LINE, modes.PROMPT); // ****
            currentExtendedMode = modes.PROMPT;

            setPrompt(prompt, extra.promptHighlight || this.HL_QUESTION);
            setCommand(extra.default || "");
            commandlineWidget.collapsed = false;
            commandWidget.focus();
            completions = Completions(commandWidget.inputField);
        },

さらにこの呼び出し箇所。

hints.js 645

    mappings.add(myModes, [";"],
        "Start an extended hint mode",
        function (count)
        {
            extendedhintCount = count;
            commandline.input(";", null,  // ****
                {
                    promptHighlight: "Normal",
                    completer: function (context)
                    {
                        context.compare = function () 0;
                        context.completions = [[k, v.prompt] for ([k, v] in Iterator(hintModes))];
                    },
                    onChange: function () { modes.pop() }, // 1文字入力したらプロンプトモードから脱出する。 ****
                    onCancel: function (arg) { arg && setTimeout(function () hints.show(arg), 0); },
                });
        }, { flags: Mappings.flags.COUNT });
3. コマンドラインでEnterまたはBackSpaceを入力しコマンドラインから抜け出す時。

ui.js 1300

commandline.onEvent

	    // コマンドラインのイベントを処理する。
            else if (event.type == "keypress"){
		/* snip */
		// <Return>, <C-j>, <C-m>のどれか。つまりコマンドラインでコマンドを実行した時。
                if (events.isAcceptKey(key)) {
                    let mode = currentExtendedMode; // save it here, as modes.pop() resets it
                    keepCommand = true;
                    currentExtendedMode = null; // Don't let modes.pop trigger "cancel"
                    modes.pop(!this.silent); // ****
                    return liberator.triggerCallback("submit", mode, command);
                }
		/* snip */
                else if (key == "<BS>"){
                    if (command.length == 0){
                        liberator.triggerCallback("cancel", currentExtendedMode);
                        modes.pop(); // ****
                    }
                }

この呼び出し箇所。

liberator.xul 85

<window id="&liberator.mainWindow;"><stack orient="horizontal" align="stretch" class="liberator-container" liberator:highlight="CmdLine">
            <hbox id="liberator-commandline" hidden="false" collapsed="true" class="liberator-container" liberator:highlight="Normal">
                <label class="plain" id="liberator-commandline-prompt"  flex="0" crop="end" value="" collapsed="true"/>
                <textbox class="plain" id="liberator-commandline-command" flex="1" type="timed" timeout="100"
                    oninput="liberator.modules.commandline.onEvent(event);"
                    onkeyup="liberator.modules.commandline.onEvent(event);"
                    onfocus="liberator.modules.commandline.onEvent(event);"
                    onblur="liberator.modules.commandline.onEvent(event);"/> <!-- **** -->
            </hbox>

↓onkeypressはこっちで定義されている。

events.js 1350

events.onKeyPress

        // this keypress handler gets always called first, even if e.g. the commandline has focus
        onKeyPress: function (event){
	    if(...){
		/* snip */
            // if the key is neither a mapping nor the start of one
            else{
                if (key != "<Esc>" && key != "<C-[>"){
		    /* snip */
                    if (liberator.mode == modes.COMMAND_LINE){
			// コマンドラインモードで複数行入力モードではない時。
                        if (!(modes.extended & modes.INPUT_MULTILINE))
                            commandline.onEvent(event); // reroute event in command line mode ****
                    } else if (liberator.mode != modes.INSERT && liberator.mode != modes.TEXTAREA) liberator.beep();
                }
4. 複数行入力モードの終了文字をタイプしてEnterした時。

ui.js 1370

commandline.onMultilineInputEvent

	// 複数行入力モード。
        onMultilineInputEvent: function onMultilineInputEvent(event)
        {
            if (event.type == "keypress"){
                let key = events.toString(event);
		// Enterした時。
                if (events.isAcceptKey(key)){
                    let text = multilineInputWidget.value.substr(0, multilineInputWidget.selectionStart);
		    // EOMのような文字列でEnterした時。
                    if (text.match(multilineRegexp)){
                        text = text.replace(multilineRegexp, "");
                        modes.pop(); // ****
                        multilineInputWidget.collapsed = true;
                        multilineCallback.call(this, text);
                    }
                }else if (events.isCancelKey(key)){
                    modes.pop(); // ****
                    multilineInputWidget.collapsed = true;
                }
            }

さらにこの呼び出し箇所。

liberator.xul 95

<window id="&liberator.mainWindow;">
        <vbox class="liberator-container" hidden="false" collapsed="false">
            <textbox id="liberator-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true"
                onkeypress="liberator.modules.commandline.onMultilineInputEvent(event);"
                oninput="liberator.modules.commandline.onMultilineInputEvent(event);"
                onblur="liberator.modules.commandline.onMultilineInputEvent(event);"/> <!-- **** -->
        </vbox>
5. 複数行出力モードでキー入力してMOWが閉じる時。

ui.js 1410

commandline.onMultilineOutputEvent

	    // 複数行出力モードで、キー入力してMOWが閉じるような場合。
            if (passEvent || closeWindow){
                modes.pop(); // ****
                if (passEvent) events.onKeyPress(event);
            }else{
                commandline.updateMorePrompt(showMorePrompt, showMoreHelpPrompt);
            }

補足: NORMAL, CARETモード間で変化する仕組み

events.js 1325

events.onEscape

                case modes.VISUAL:
                    if (modes.extended & modes.TEXTAREA)
                        liberator.mode = modes.TEXTAREA;
                    else if (modes.extended & modes.CARET)
                        liberator.mode = modes.CARET;
                    break;
                case modes.CARET:
                    // setting this option will trigger an observer which will
                    // care about all other details like setting the NORMAL mode
                    options.setPref("accessibility.browsewithcaret", false);
                    break;

VISUALモードでEscすると、TEXTAREA or CARETに戻る。

CARETモードでEscすると、NORMALに戻る。


events.js 1770

events.observe

            observe: function (aSubject, aTopic, aData)
            {
                if (aTopic != "nsPref:changed")
                    return;

                // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
                // aData is the name of the pref that's been changed (relative to aSubject)
                switch (aData)
                {
                    case "accessibility.browsewithcaret":
                        let value = options.getPref("accessibility.browsewithcaret", false);
                        liberator.mode = value ? modes.CARET : modes.NORMAL;
                        break;
                }
             }

このようにオブザーバがついているので、about:configでprefをいじっても、モードがCARET, NORMAL間で変わる。

前面に表示されるウィジェットを作るためのプラグインを作った

20:54 | はてなブックマーク - 前面に表示されるウィジェットを作るためのプラグインを作った - vimpめも

位置・大きさ・色・innerHTMLorDOMを指定して、div要素(ウィジェット)を複数作成できる。

ヒントを使ってウィジェットを選択し、jkhldqで操作できる。


// floatWidget.js
var floatWidget = new function(){
	var count = 0;

	function Widget(html, height, width, x, y, color){
		var doc = content.document;
		count++;

		var ref = (function buildDOM(html, h, w, x, y, c){
			var ct = count;

			var div = doc.createElement('div');
			div.setAttribute('id', 'floatWidget' + ct);
			var a = doc.createElement('a');
			with(div.style){
				height = h + 'px';
				width = w + 'px';
				position = 'fixed';
				left = x + 'px';
				top = y + 'px';
				backgroundColor = c || 'white';
				zIndex = '1000';
			}
			with(a.style){
				height = h + 'px';
				width = w + 'px';
				position = 'relative';
				left = '0px';
				top = '0px';
				backgroundColor = c || 'white';
				zIndex = '1000';
			}
			if(typeof html == 'object'){
				a.appendChild(html);
			}else{
				a.innerHTML = html;
			}
			div.appendChild(a);

			function handleThis(e){
				var k = events.toString(e);
				if(k == 'q') modes.reset();
				if(k == 'h') div.style.left = (window.parseInt(div.style.left) - 50) + 'px';
				if(k == 'l') div.style.left = (window.parseInt(div.style.left) + 50) + 'px';
				if(k == 'k') div.style.top = (window.parseInt(div.style.top) - 50) + 'px';
				if(k == 'j') div.style.top = (window.parseInt(div.style.top) + 50) + 'px';
				if(k == 'd'){
					div.removeEventListener('click', selectThis, false);
					doc.body.removeChild(div);
					modes.reset();
				}
			}
			var selectThis = function(){
				modes.setCustomMode('floatwidget', handleThis, function(){return});
				modes.set(modes.CUSTOM);
			}
			div.addEventListener('click', selectThis, false);

			doc.body.appendChild(div);
			return div;
		})(html, height, width, x, y, color);

		this.ref = ref;
		this.id = count;
		this.height = ref.style.height;
		this.width = ref.style.width;
		this.x = ref.style.left;
		this.y = ref.style.top;
		this.color = ref.style.backgroundColor;
	}

	this.make = function(innerDOMorHTML, height, width, x, y, color){
		return new Widget(innerDOMorHTML, height, width, x, y, color);
	};
};
plugins.makeFloatWidget = function(html, h, w, x, y, c){ floatWidget.make(html, h, w, x, y, c) };

使用例。現在のモードを監視する。

:js var div=content.document.createElement('div'); plugins.makeFloatWidget(div,100,100,0,0); var timer1 = setInterval(function(){ div.innerHTML = liberator.mode }, 1000);

snaka72snaka722009/06/07 09:25このエントリ以外にもソース解説関連のエントリを Vimperator Wiki の方からリンク貼らせてもらいましたー。

 |