Hatena::Groupvimperator

vimpめも

2010-03-07

CSS関連まとめ

09:56 | はてなブックマーク - CSS関連まとめ - vimpめも

vimperator2.2のCSSに関するコマンド、JavaScript関数をまとめた。


参考情報

ヘルプ chrome://liberator/locale/styling.html

CSSに関する処理を記述したvimperator本体のソースファイル common/content/style.js



コマンド

CSSに関して、colorscheme, highlight, styleという3つのコマンドがある。


vimperatorの内部においては、CSSに関して、StylesとHighlightsという2つのクラスが存在する。

このエントリでは、前者が受け持つ機能をスタイル、後者が受け持つ機能をハイライトと呼称する。


colorscheme設定ファイルを読み込むコマンドスタイルとハイライトを設定
highlighthighlight属性のついた要素に対して、CSSで装飾するコマンドハイライトを設定
styleURLごとのCSS設定、ブラウザのCSS設定を行うコマンドスタイルを設定

ちなみに、sourceコマンドを用いてcssファイルを読み込むことも出来る。

source ~/vimperator/colors/xulstyle.css

ただし、一度設定したCSSをデフォルトに戻したり、異なるCSSファイルに差し替えたりするのには向かない。

sourceコマンドを使うよりも、colorscheme, highlight, styleコマンドを利用したほうが良いと思う。


では、その3つのコマンドを説明していく。



colo[rscheme]コマンド

スタイル設定を記述したファイルを読み込む。

:colo hoge // 'runtimepath'(pluginディレクトリなどのあるディレクトリ)下のcolors/hoge.vimpが読み込まれる。

colorscheme設定ファイルの書式

/lang/javascript/vimperator-plugins/trunk/colors – CodeRepos::Share – Trac

http://coderepos.org/share/browser/lang/javascript/vimperator-plugins/trunk/colors?order=date&desc=1

vimperatorrcと同じ書式で書く。基本的に、hilightコマンド、styleコマンドを並べて書く。


オートコマンド

colorschemeが読み込み終わると、ColorSchemeオートコマンドが呼ばれる。


ハイライトの消去

:colo[rscheme] default

これでハイライトはデフォルトに戻る。このコマンドでは、スタイルのデフォルト化は出来ない。



hi[ghlight]コマンド

highlight属性のついた要素に対して、CSSで装飾する。

:hi[ghlight][!] [-append] {group} [[{selector}] {css}]

{group}には、TabClose, CmdLine, StatusLine, Hint, Functionなどの決まった文字列を用いる。これはhighlight属性値にあたる。

グループの一覧→chrome://liberator/locale/styling.html#%3Ahi

本エントリ末尾のおまけも参照。


{selector}は省略できる。


appendオプション

-appendまたは-aをつけると、CSSを上書きせずに追加できる(-appendが無いと上書きする)。

:hi CmdLine -append font-size: 16px // コマンドラインの文字サイズを指定できる。

ハイライトの一覧表示

{selector}, {css}を略すと、その{group}にセットされているハイライトをリストアップできる。

この時、{group}は部分的に一致していてもマッチされる。

:hi Number // Number, TabNumber, TabIconNumberという3つのグループに割り当てられているハイライトが表示される。

ハイライトの除去

:hi[ghlight] clear {group} {selector} // ハイライトを消去しデフォルト値に戻す。

highlightコマンドではhereDoc(ヒアドキュメント)を利用できる。

:hi CmdLine<<E
font-size: 18px;
E


sty[le]コマンド

ブラウザやページにスタイルを追加する。

:sty[le][!] [-name={name}] [-append] {filter} [{css}]

{filter}について

コンマ区切りでURLを指定。cssはセレクタとCSS本体を合わせたもの。http://は書かない。


styleコマンドをファイルに書いて、colorschemeコマンドで読み込むことが出来る。ファイルの拡張子は.vimpにする。

ファイル内で複数行にわたって書きたい時は次のように記述する。

style mixi.jp <<EOM
.adBanner{ visibility: hidden !important }
#adBanner, #prContentsArea, #mixiRadioArea, #calendar, #prSepecial{ display: none !important }
EOM

ちなみに、vimperatorrcにもこのような記述が可能だが、vimperatorrcには出来れば記述せずに、

CSSのファイルはCSSのファイルだけでまとめたほうが良いと思う。


スタイルの命名

-nameまたは-nオプションにより、スタイルに名前をつけられる。

既存のスタイルの名前を指定すると、上書きされる。

上書きをせずに、既存のスタイルに追加するには、-appendオプションを使う。


スタイルの一覧

:sty[le]  // 全てのスタイルの一覧
:sty[le] {filter} // {filter}に対するスタイルの一覧

この時に各スタイルに付いているindexを確認できる。


styleコマンドでも、hereDocを利用できる。


スタイルの消去

:dels[tyle] [-name={name}] [-index={index}] [{filter}] [{css}]

名前を指定する、またはスタイルのindexを指定する。


スタイルの無効、有効、トグル

:styled[isable]
:styee[nable]
:stylet[oggle]

引数はdels[tyle]と同じ。




JavaScriptからのアクセス

グローバル変数highight, stylesが、ハイライトとスタイルに関するAPIである。

以下では、これらの持つメソッドその他を見ていく。



highlight.get(class)

ハイライトを取得する。classはコマンドの項目で登場した{group}にあたる。

:hi CmdLine -a font-size: 18px
:ec highlight.get('CmdLine')

class: "CmdLine"
default: "font-family: monospace; padding: 1px;"
filter: "chrome://liberator/content/buffer.xhtml,chrome://browser/content/browser.xul"
selector: "[liberator|highlight~=CmdLine]>*"
value: "font-family: monospace; padding: 1px; font-size: 18px;"


highlight.set(groupName, newStyle, force, append)

ハイライトをセットする。



highlight.selector(class)

指定したクラスのセレクタを得る。

:ec highlight.selector('CmdLine')

[liberator|highlight~=CmdLine]


highlight.clear

ハイライトをリセットする。



highlight.loadCSS

ハイライトのCSSを読み込む。highlightコマンドとは違って、上書きを阻止できないみたい。

highlight.loadCSS('CmdLine font-size: 30px') // コマンドラインの文字が大きくなる。
// highlight.loadCSS('-a CmdLine font-size: 30px') // これでは動かなかった。

このloadCSSは、上書きを阻止できないどころか、デフォルトのCSSまでも書き換えるらしい。

:ec highlight.get('CmdLine').default // font-size: 30pxと表示される。デフォルトで設定されていたfont-familyやpaddingは上書きされた。

ちなみに、vimperatorの起動時(style.js内でのhighlightオブジェクト作成時)に

    this.loadCSS(this.CSS);

が実行される。このthis.CSSは次で説明する。



highlight.CSS

グループ名の一覧とそれらに割り当てられたデフォルトのCSSについて書かれた1つの長い文字列。

これをパースすれば、グループ名の一覧を簡単に取得できるはず。(もっと簡単な方法があるかも)



styles.userSheets

ユーザ定義のスタイル(sheetと呼ぶ)の配列をIterator()で包んだもの。

:ec var str = ''; for(let [, v] in styles.userSheets){ str += [v.id, v.name, v.sites, v.css].join(' | ') + '\n';} log(str);


styles.systemSheets

デフォルトのsheetsをIterator()で包んだもの。



styles.userNames, styles.systemNames

スタイル名をインデックスにしたオブジェクトをIterator()で包んだもの。

:ec var str = ''; for(let [k, ] in styles.userNames){ str += k + '\n';} al(str); // ユーザ定義で、名前のついたスタイルの名前を一覧表示。


styles.addSheet(system, name, filter, css, agent)

sheet(スタイル)を追加する。

system引数は、デフォルトのスタイルかどうかを指定する論理値。

name引数は、スタイル名。

filter引数は、前述のstyleコマンドの{filter}引数と同じで、スタイルを適用するURLのフィルター(文字列)。

css引数は、セレクタとCSS本体からなる。複数のCSSを指定できる。

// vimperatorグループの文字サイズが大きくなり、ヘッダが見えなくなるスタイル。
styles.addSheet(0, 'test', 'vimperator\.g\.hatena\.ne\.jp', '*{ font-size: 20px } #simple-header{ display: none }')
:styletoggle -name test

とすれば、スタイルの有効/無効が切り替わる。



styles.get(system, identifier)

スタイル(sheet)を得るメソッド。identifierには、スタイルのindexまたはスタイル名を指定する。

sheetオブジェクトには、agent, css, id, name, sites, systemというプロパティがある。

styles.get(0, 'test'); // 先ほど作成したtestスタイルのオブジェクトを得る。


styles.findSheets(system, name, filter, css, index)

引数名は、これまでと同様の意味。getメソッドと違って、複数のスタイルを得る。

戻り値は、sheetオブジェクトの配列。



styles.removeSheet(system, name, filter, css, index)

引数名は、これまでと同様。filterが指定された時はマッチする全てのスタイル(sheet)を削除する。



styles.registerSheet(uri, agent, reload)

与えられたCSSURIに対して、スタイルを登録する。reloadがtrueなら、既存のスタイルをリロードする。

sourceコマンドでCSSファイルをロードできるのも、このメソッドによる。

内部的にはnsIStyleSheetServiceが使われている。

    const sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);

Using the Stylesheet Service - MDC

https://developer.mozilla.org/en/Using_the_Stylesheet_Service



styles.unregisterSheet(uri, agent)

登録していたCSSを解除する。




おまけ

ハイライトがvimperator本体で使われている箇所(ソースファイルをhighlight=で検索)。

基本的に、echoする時や補完候補などを表示する時か、タブやページ要素にスタイルを与える時にハイライトが使われている。

以下のリストの大文字から始まる単語は、highlight属性値(CmdLineなど)にあたる。



  • bindings.xml
    • FrameIndicator
    • TabIcon, TabIconNumber, TabNumber, TabText, TabClose,
  • buffer.js
    • Indicator, FrameIndicator(']f'と入力してフォーカスするフレームを移動した時に赤くフラッシュするもの)
  • completion.jsのcompletion.listCompleter
    • Completions
  • events.jsのevents.list
    • Title
  • finder.jsのhighlightDoc
    • Search
  • hints.jsのヒントにスタイルを与える部分
    • Hint
  • io.jsのrunコマンドで実行するコマンドのアウトプットに関する部分。(例えば、:hi -a CmdOutput color: blueとしてから:run echo hogeとすると、青字になる)
    • CmdOutput
  • liberator.jsextensionsコマンド(enabled/disabledの文字の色)、timeコマンド
    • Enabled, Disabled, Title
  • liberator.xul
    • Bell, CmdLine, Normal, StatusLine
  • options.js
    • CmdOutput
  • ui.js
    • PreView, Message, CmdOutput, Normal
    • Completions, CompItem, CompGroup, CompMsg, CompLess, CompMore,
    • Title
    • NonText
  • template.jsのいろんなメソッド
    • CompResult, CompDesc, CompIcon→補完関係
    • URL→bmarksコマンド実行時の右側に表示される文字、またはpageinfoコマンドの表示文字
    • Filter(補完候補にフィルターをかけて表示する時の、マッチ部分のハイライト。例えば、:o vimp入力時にデフォルトでは'Smart Completions'と表示され'vimp'が黒字になる)
    • Gradient(後述)
    • Number, String, Boolean, Function, Null, Object, NonText (各種補完候補の表示、options.list, options.listPrefs, util.objectToString)
    • Title(jumpsコマンドによる履歴の一覧表示, options.list, options.listPrefs, g<C-g>の時のページ情報表示、コマンドやオプションなどの一覧表示、helpのindex.htmlの表示)
  • ui.js
    • Gradient, GradientLeft, GradientRight(補完候補のピンク色の横線に関するもの)
  • util.js
    • NonText
    • TitleObject
    • Key

:ec commandline.echo('<span highlight="Number">12345</span>', null, commandline.FORCE_MULTILINE) // デフォルトでは青だが:hi Number -a color: redとすると赤に。


追記 2010/03/07 12:19:52

vimperator2.2のdels[tyle]が動作していないかもしれない。

            action: function (sheet) styles.removeSheet(sheet)

ではなく

            action: function (sheet) styles.removeSheet(false, sheet.name)

が正しいのでは。

2010-03-04

無変換キー、CapsLockキーなどをmap出来るようにするプラグインをvimp2.2に対応させた

01:31 | はてなブックマーク - 無変換キー、CapsLockキーなどをmap出来るようにするプラグインをvimp2.2に対応させた - vimpめも

Re: vimperatorrcでキーコードを指定してmapする - hogelogの日記 - vimperatorグループ

http://vimperator.g.hatena.ne.jp/hogelog/20090510/1241970179

の方法がvimp2.2では使えなかったので、自分で書いた。既出かも。

ちなみにvimp2.2リリース以降のnightlyは未試用。


コード

// extraKeysForMapping.js
{
const extra_keys = [
        [ 28, ["Henkan"]],
        [ 29, ["Muhenkan"]],
        [ 242, ["Katakana"]],
        [ 240, ["CapsLock", "Caps"]]
];
let [_code_key, _key_code] = liberator.eval('[code_key, key_code]', events.destroy);
extra_keys.forEach(function([code, names]){
        _code_key[code] = names[0];
        for(let [, name] in Iterator(names))
            _key_code[name.toLowerCase()] = code;
});
let extra = uneval([_code_key, _key_code]);
liberator.eval('[code_key, key_code] = ' + extra, events.destroy);
}

動作

map <C-Caps> :js alert('ok')<CR> // Ctrl+CapsLockを入力した時に、'ok'というアラートが表示されるようになる
unmap <C-CapsLock>
map <C-Caps><C-Caps> :js alert('successful')<CR> // Ctrl+CapsLockを二回入力した時に、'successful'というアラートが表示されるようになる
map <C-Caps>foo :js alert('foo')<CR> // 同様

2010-03-03

複数行入力可能なプロンプトを出す util.openMultilinePrompt を作った

09:58 | はてなブックマーク - 複数行入力可能なプロンプトを出す util.openMultilinePrompt を作った - vimpめも

firefox標準のpromptだと複数行入力できないので作った。


このメソッドを実行すると、テキストエリアが出現する。

テキストを入力してOKボタンを押すと、テキスト内容を引数にしてコールバックが実行される。


TODO

空のページで実行しないと表示がおかしくなる可能性がある。

ページ内にpromptを作ると、ページのCSSに左右されるデメリットがある。新しいミニウィンドウにpromptを作ると、読み込みに時間がかかるデメリットがある。

新しいタブにpromptを作るのは表示してたページが見えなくなってしまうデメリットがある。

サイドバーにpromptを作るのは横幅が通常は狭いデメリットがある。スマートな方法はないか?


// 複数行入力可能なプロンプトを出す。
util.openMultilinePrompt = function(callback, title, defaultVal, rows, cols, top, left){
	var thisMethodName = 'util.openMultilinePrompt';
	var rows = rows || 20;
	var cols = cols || 50;
	var _top = parseInt(top) || 50;
	var _left = parseInt(left) || 50;
	var defaultVal = defaultVal || '';
	var html = '<div id="' + thisMethodName + '_div">'
		+ '<h2>' + (title ? title + '<br/>': '') + '</h2>'
		+ '<textarea id="' + thisMethodName + '_textarea" rows="' + rows + '" cols="' + cols + '"></textarea>'
		+ '<br/>'
		+ '<input id="' + thisMethodName + '_submit" type="button" value="ok"/>'
	+'</div>';
	var div = content.document.createElement('div');
	div.innerHTML = html;
	div.id = 'multilinePrompt';
	div.style.opacity = '0.85';
	div.style.position = 'fixed';
	div.style.top = _top + 'px';
	div.style.left = _left + 'px';
	content.document.body.appendChild(div);
	var textarea = content.document.getElementById(thisMethodName + '_textarea');
	textarea.focus();
	textarea.value = defaultVal;

	var button = content.document.getElementById(thisMethodName + '_submit');
	var submit = function(){
		callback( textarea.value );
		div.parentNode.removeChild(div);
		button.removeEventListener('click', submit, false);
		mappings.remove(modes.INSERT, '<Esc>');
	};
	button.addEventListener(
		'click',
		submit,
		false
	);

	mappings.addUserMap([modes.INSERT], ['<Esc>'], 'Esc for ' + thisMethodName, function(){
		div.parentNode.removeChild(div);
		button.removeEventListener('click', submit, false);
		mappings.remove(modes.INSERT, '<Esc>');
		modes.reset();
	});
};

利用例

ブックマークを検索し、一覧され、選択し、編集して、確定すると、登録されるコマンド。

複数行の編集エリアなのでbookmarkletの編集がしやすくなった。

bookmarks.addByObj = function(o, starOnly){
	var success = bookmarks.add(starOnly || false, o.title, o.url, o.keyword, o.tags);
	return success;
};
String.prototype.fromUTF8Octets = function(){
	return decodeURIComponent(
			this.replace(/[%\x80-\xFF]/g, function(c)'%' + c.charCodeAt(0).toString(16))
	);
};

commands.addUserCommand(
	['editBookmark'],'Edit Bookmark in Multi Line',function(args){
		var bookmarkToText = function(bm){
			var url = bm.url.replace(/;/g, ';\n');
			return [bm.title, bm.keyword, bm.tags.join(','), url].join('\n');
		}

		// edit and add bookmark
		var editBookmark = function(value, oldurl){
			var addBookmark = function(str){
				var obj = {};
				var lines = str.split('\n');
				var url = lines.splice(3).join('');
				var [title, keyword, tags] = lines;
				tags = tags.split(/,\s*/g);
				obj.title = title;
				obj.keyword = keyword;
				obj.tags = tags;
				obj.url = url;
				var success = bookmarks.addByObj(obj);
				if(success) bookmarks.remove(oldurl);
			};
			util.openMultilinePrompt(
				addBookmark,
				'1,2,3行目と4行目以降にそれぞれtitle, keyword, tags, urlを書く'.fromUTF8Octets(),
				value,
				25, 90
			);
		};

		// get bookmarks
		var filter = args[0] == '0' ? '': args[0];
		var tags = [];
		if(args.length == 2)
			tags = args[1].split(',');
		else
			tags = args.splice(1);
		var bms = bookmarks.get(filter, tags);
		if(bms.length > 50){
			liberator.echoerr('Too much tags to display'); return;
		}
		if(bms.length == 1){
			editBookmark(bookmarkToText(bms[0]), bms[0].url);
			return;
		}

		// select bookmark
		var cursorPos = 0;
		var box = <div></div>
		// bmのキー: icon, id, keyword, title, url, tags
		bms.forEach(function(bm){
			var iconURL = bm['icon'].match(/.*?(((http|https|chrome):\/\/|data:image\/).+)/)[1] || '';
			var keyword = bm.keyword || ' - ';
			var tags = bm.tags.join(',') || ' - ';
			box.* += <><div class="matchedBookmark" style="padding:20px">
				<p style="font-size:18px"><b>{bm['title']}</b></p>
				<p><img src={iconURL}/> {bm['url']}</p>
				<p>#keyword# {keyword}, #tags# {tags}</p>
			</div></>;
		});
		box.@style = 'padding: 20px; height: 520; font-size:12px';
		var wid = plugins.makeFloatWidget(box, 520, 800, 120, 30, 'white');
		var div = wid.ref;
		var divs = div.getElementsByClassName('matchedBookmark');
		divs[0].style.backgroundColor = '#cff';
		div.style.overflow = 'scroll';
		wid.selectThis();
		wid.addKeyEvent(['j'], 'move next', function(){
			var nextPos = cursorPos + 1;
			if(nextPos == divs.length) nextPos = 0;
			divs[cursorPos].style.backgroundColor = '';
			divs[nextPos].style.backgroundColor = '#cff';
			divs[nextPos].scrollIntoView();
			cursorPos = nextPos;
		});
		wid.addKeyEvent(['k'], 'move previous', function(){
			let nextPos = cursorPos - 1;
			if(nextPos == -1) nextPos = divs.length - 1;
			divs[cursorPos].style.backgroundColor = '';
			divs[nextPos].style.backgroundColor = '#cff';
			divs[nextPos].scrollIntoView();
			cursorPos = nextPos;
		});
		wid.addKeyEvent(['D'], 'delete bookmark', function(){
			bookmark.remove(bms[cursorPos].url);
		});
		wid.addKeyEvent(['o'], 'edit this bookmark', function(){
			wid.destroy();
			var bm = bms[cursorPos];
			editBookmark(bookmarkToText(bm), bm.url);
		});
	},{},true
);

ウィジェットを作るためのプラグインを改良した - vimpめも - vimperatorグループプラグインを使用。


追記 2010/03/05 11:41:15

複数行入力可能にしたい時は、commandのextraInfo.hereDocがtrueなコマンドを定義すれば良いと気付いた。

commandline.inputMultiline(untilRegExp, callbackFunc)を使う手もある。


追記

外部エディタを使う方法もあった。

コマンドラインを外部エディタで編集 - Death to false Web browser! - vimperatorグループ

http://vimperator.g.hatena.ne.jp/nokturnalmortum/20100304/1267718311

2010-02-25

インサートモードでタブ変更するinoremap <C-n> <Esc><C-n>の問題点と、その解決方法

20:00 | はてなブックマーク - インサートモードでタブ変更するinoremap  の問題点と、その解決方法 - vimpめも

デフォルトのFirefoxでは、タブ変更してもフォーカスの状態は保持される。


ところで、vimperatorのインサートモードにおいて、タブ変更を行うには、

<Esc><C-n>

といった操作を行う必要があり、少し面倒である。これを回避するには、

inoremap <C-n> <Esc><C-n>

とするのが一般的だと思う。


しかし、いずれにせよ、vimperatorでこうした設定を行えば、タブ変更時にテキスト入力エリアのフォーカス状態が保持されなくなる。

なぜなら、Escの実行によってインサートモードを脱出しているから。

これは、複数のタブを行き来しながら、テキストを入力していく時には、大きな問題となる。

例えば、あるタブで情報を見ながら、別のタブでそれに関するブログを書いている時に、ブログのタブに戻るたびに、入力エリアにフォーカスする必要が生じる。


そこで、

  • インサートモードでのタブ変更
  • フォーカスは維持する

という2つの要求をかなえる設定を考えた。


_vimperatorrcの記述

" C-1, ..., C-9, C-n, C-p, C-t in the INSERT mode
js <<EOM
{
for(let i = 0; i < 10; i++){
	mappings.addUserMap([modes.INSERT], ['<C-' + i + '>'], 'tab operation - keep tabs INSERT', function(){
		mappings.get(1, i + 'gt').execute();
	});
}
[
	['t', 'tabopen'],
	['n', 'tabnext'],
	['p', 'tabprevious']
].forEach(function([c, cmd]){
	mappings.addUserMap([modes.INSERT], ['<C-' + c + '>'], 'tab operation - keep tabs INSERT', function(){
		liberator.execute(cmd);
	});
});
}
EOM

ちなみに、インサートモードの時に他のタブをクリックした場合には、Escを実行しないので、

このような設定をせずとも、フォーカス状態を保持したままのタブ遷移が行える。


追記 2010/02/25 23:28:20

INSERT(menu) なモードでキーバインドが聞かない問題にたいする Hack - Death to false Web browser! - vimperatorグループ

の設定に伴って

inoremap <C-n> <Down>
inoremap <C-p> <Up>

と設定している場合は、C-n, C-pがバッティングしてしまう。

そこで、次のように変更したところ、うまく共存できた。

window.addEventListener(
  'keypress',
  function (event) {
    if (liberator.mode === modes.INSERT && modes.extended === modes.MENU) {
      let key = events.toString(event);

      if(key == '<C-n>' || key == '<C-p>'){
          event.preventDefault();
          event.stopPropagation();
      }
      if(key == '<C-n>'){
	  events.feedkeys('<Down>'); return;
      }else if(key == '<C-p>'){
	  events.feedkeys('<Up>'); return;
      }

      let map = mappings.get(modes.INSERT, key);
      if (map) {
        event.preventDefault();
        event.stopPropagation();
        map.execute();
      }
    }
  },
  false
);

開いているタブの情報を管理するプラグインを作った

19:10 | はてなブックマーク - 開いているタブの情報を管理するプラグインを作った - vimpめも

以前から、プラグイン制作をしている時などに、開いているタブの情報を管理できたら便利だと思っていたので、作った。


機能

  • 開いている全てのタブのcontnetWindowに、tabs.tabsinfoからアクセスできる。
  • 開いているタブに情報を付加できる(tabs.tabsinfo[i][1]。tabs.tabsinfoは[contentwin, infoObj]の配列)。
  • これらのデータは、他のタブに遷移しても、開いている間は永続化する(あくまで開いているタブの情報管理であり、閉じてしまったタブの管理はできない)。
  • おまけとして、tabs.addEvent, tabs.removeEventを付けた。これは単なる簡易メソッド。不要だったかも。

注意点

  • 扱いやすさを考えて、タブではなくcontentWindowを保存するようにしたけれども、何か不都合があるかもしれない。
  • おまけは、既存のautocommandsではコマンドという形でしかイベントハンドラを登録できないし、一般的なaddEventListenerだと書くのが面倒だったので作った。
  • おまけは、登録したイベントハンドラの管理が出来るわけではない。

使い方

tabs.tabsinfo.length // 開いているタブの数 (tabs.countと等しい)

// 左から2番目のタブにいるとする。
// 現在のタブのデータオブジェクトにキー:'hoge', 値:100を登録する。
tabs.setinfo(null, 'hoge', 100);

// 5番目のタブに移動したとする。
// 2番目のタブのデータオブジェクトの'hoge'の値を得るには次のようにする。
tabs.getinfo(2)['hoge'] // 100

// 5番目のタブにいながら、3番目のタブに情報を追加する。
tabs.setinfo(3, 'fuga', 200);

// 3番目のタブに移動したとする。
// 引数無しでgetinfoを呼び出しても、現在のタブの情報を取得できる。
tabs.getinfo()['fuga'] // 200

// ContentWindowオブジェクトも引数にとれる。
tabs.getinfo(content)['fuga'] // 200

おまけの使い方

// 新しいタブが開かれた時に、新しいタブのURLをアラートする。addEventはイベントハンドラを返す。イベント名は'TabOpen'でも'open'でも良い。
var handler=tabs.addEvent('open', function(event){ alert(event.target.linkedBrowser.contentWindow.location) }, false);
// 従来なら、
// var handler = function(event){ alert(event.target.linkedBrowser.contentWindow.location) };
// gBrowser.tabContainer.addEventListener('TabOpen', handler, false);
// と書くところ。

// イベントハンドラを除去する。従来の方法で登録したものも除去できる。
tabs.removeEvent('open', handler, false)


// tabsinfo.js
{
var contentwins = []; // [contentwin, infoObj]の配列。

tabs.__defineGetter__('tabsinfo', function(){
	return contentwins;
});

let getContentWindows = function(aTabBrowser) {
	let contentwins = [];
	let result = aTabBrowser.ownerDocument.evaluate(
    		'descendant::*[local-name()="tab"]',
    		aTabBrowser.mTabContainer,
    		null,
    		XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
		null
	);
	for(let i = 0, L = result.snapshotLength; i < L; i++)
		contentwins.push(gBrowser.getBrowserForTab(result.snapshotItem(i)).contentWindow);
	return contentwins;
}

// argは数字(tabindex)またはContentWindowオブジェクトまたはundefined
let getinfo = function(arg){
	function echoError(){
		liberator.echoerr('illegal arg for tabs.getinfo');
	}
	if(typeof arg == 'number'){
		let tab;
		try{
			tab = tabs.getTab(arg - 1);
		}catch(e){
			echoError(); return null;
		}
		if(tab === undefined){
			echoError(); return null;
		}
		arg = tab.linkedBrowser.contentWindow;
	}
	if(
		(arg instanceof Window && arg.document.body) || arg === undefined
	){
		arg = arg || content;
		for(let i = 0, L = contentwins.length; i < L; i++){
			if(contentwins[i][0] == arg)
				return contentwins[i][1]; // 正しい引数の時の戻り値
		}
		return null;
	}else{
		echoError(); return null;
	}
};
tabs.getinfo = getinfo;

let setinfo = function(arg, key, value){
	function echoError(){
		liberator.echoerr('illegal arg for tabs.setinfo');
	}
	if(typeof arg == 'number'){
		let tab;
		try{
			tab = tabs.getTab(arg - 1);
		}catch(e){
			echoError(); return;
		}
		if(tab === undefined){
			echoError(); return;
		}
		arg = tab.linkedBrowser.contentWindow;
	}
	if(
		(arg instanceof Window && arg.document.body) || arg == null
	){
		arg = arg || content;
		for(let i = 0, L = contentwins.length; i < L; i++){
			if(contentwins[i][0] == arg){
				contentwins[i][1][key] = value; // 正しい引数の時の動作
				return;
			}
		}
		return;
	}else{
		echoError(); return;
	}
};
tabs.setinfo = setinfo;


// 起動時に全てのタブをcontentwinsに格納する
var startListener = function(){
	contentwins = getContentWindows(gBrowser).map(function(cwin) [cwin, {}]);
};
autocommands.add('VimperatorEnter', '.*', 'js liberator.plugins.tabsinfo.startListener()');

//let tabChangeListener = function(event){
//	let newContentwin = event.target.contentWindow;
//	let oldContentwin = tabs.alternate.linkedBrowser.contentWindow;
//};
//document.addEventListener('TabSelect', tabChangeListener, false);

let tabOpenListener = function(event){
	let contentwin = event.target.linkedBrowser.contentWindow;
	contentwins.push([contentwin, {}]);
};
gBrowser.tabContainer.addEventListener('TabOpen', tabOpenListener, false);

let tabCloseListener = function(event){
	let lastContentWin = event.target.contentWindow;
	contentwins = contentwins.filter(function(cwin) cwin[0] != lastContentWin);
};
document.addEventListener('TabClose', tabCloseListener, false);



tabs.addEvent = function(type, listener, useCapture){
	switch(type){
		case 'TabSelect':
		case 'select':
			document.addEventListener('TabSelect', listener, useCapture);
			return listener;
			break;
		case 'TabOpenPre':
		case 'openpre':
			document.addEventListener('TabOpen', listener, useCapture);
			return listener;
			break;
		case 'TabOpen':
		case 'open':
			//event.target.linkedBrowser.contentWindow: 新しいタブのcontentwin
			gBrowser.tabContainer.addEventListener('TabOpen', listener, useCapture);
			return listener;
			break;
		case 'TabClose':
		case 'close':
			document.addEventListener('TabClose', listener, useCapture);
			return listener;
			break;
	}
};
tabs.removeEvent = function(type, listener, useCapture){
	switch(type){
		case 'TabSelect':
		case 'select':
			document.removeEventListener('TabSelect', listener, useCapture);
			break;
		case 'TabClose':
		case 'close':
			document.removeEventListener('TabClose', listener, useCapture);
			break;
		case 'OpenPre':
		case 'openpre':
			document.removeEventListener('TabOpen', listener, useCapture);
			break;
		case 'TabOpen':
		case 'open':
			gBrowser.tabContainer.removeEventListener('TabOpen', listener, useCapture);
			break;
	}
};

}

追記 2010/02/26 01:52:48

tabs.browserが各タブのbrowser要素を生成するジェネレータになっていることに気付いた。これを利用したほうが良かったかも。

js var br=tabs.browsers; var ar=[]; try{ while(1) ar.push(br.next()[1].contentWindow.location); }catch(e){} alert(ar.join('\n')); // 全てのタブのURLを表示

ジェネレータについて

New in JavaScript 1.7 - MDC

https://developer.mozilla.org/en/New_in_JavaScript_1.7#Generators_and_iterators

New in JavaScript 1.8 - MDC

https://developer.mozilla.org/en/New_in_JavaScript_1.8#Generator_expressions


追記 2010/03/04

tabs.getTab(index).linkedBrowser.contentWindowでi番目のタブのcontentWindowを取ってこれることに気付いた。

上のコードはかなり冗長で無駄骨だった。

tabs.getTab()で現在のタブが取れる。

2009-12-29

日付時刻を挿入するコマンドを作った

02:41 | はてなブックマーク - 日付時刻を挿入するコマンドを作った - vimpめも

最近vimperatorをいじっていなかったので、作法におかしな所があるかも。

commands.addUserCommand(
	['insertDate'],'Insert Date String',function(args){
		//一桁なら0をつける
		function convertTimeStringHelper(mo, d, h, m, s){
			return [mo, d, h, m, s].map(function(t){
				t = String(t);
				if(t.length == 1) t = '0' + t;
				return t;
			});
		}

		//現在時刻を"yy/mm/dd hh:mm:ss"に加工する
		var now = new Date();
		var [y, mo, d, h, m, s] = [now.getFullYear(), now.getMonth() + 1, now.getDate(),
			now.getHours(), now.getMinutes(), now.getSeconds()];
		[mo, d, h, m, s] = convertTimeStringHelper(mo, d, h, m, s);
		now = [y, mo, d].join('/') + ' ' + [h, m, s].join(':');

		util.copyToClipboard(now, true);
		editor.pasteClipboard();
	},{},true
);

使い方

上のコマンドに適当なマッピングを割り当てる。例えば、次のようなものをvimperatorrcに記述。

" insert date string
js <<EOM
mappings.addUserMap([modes.INSERT], ['<C-d>d'], 'Insert Date String', function(){
	liberator.execute('insertDate', null, true);
});
EOM

インサートモードでは、通常のa-zA-Zなどのキーは文字入力に用いられるので、最初のキーは<C-d>のようなCtrl, Altのついたキーにする。


動作

インサートモードの時に、

<C-d>d

と入力すると、日付時刻が挿入される。

2009/12/30 02:39:28

ただし、OSの時計をjavascriptで取ってきているので、実際の時刻と比べて多少の誤差がある可能性がある。


追記

1/16

秒数が一桁の場合に十の位のゼロが表示されない問題を修正

hogeloghogelog2010/01/30 16:52使い方はvimperatorrcに
inoremap <C-d>d :insertDate<CR>
で良いのではないでしょうか

blue_ringblue_ring2010/02/25 15:57試してみましたが、それだと入力エリアに':insertDate\n'が入力されてしまいます。