【NYAGOS】Nyaos から Nyagos へ移行する
2018/12/01 追記
- シンタックスハイライトを適用しました。
前書き
以前Nyagosをインストールしたものの、Nyaosにあった便利機能が色々なかったので結局 Nyaos を使い続けていたんですが、Nyagos の方も大分アップデートが進んで色々便利になったようなので、そろそろ移行しようかなと。
移行しようと思ったものの、(多分、まだまだ破壊的変更が入るからでしょうか)今までの設定とかどーやって移行すればいいのかって言う説明が無いに等しいので、適当にメモしておきます。
インストールした Nyagos
4.0.9_10 の 64bit 版をインストールしました。
設定ファイルの場所
今までは_nya
に書いており、読み込む場所はこんな風な説明がありました。
# _nya は NYAOS の初期設定ファイルです. # (1) NYAOS.EXE のあるディレクトリ # (2) %HOME% の下(未定義であれば %USERPROFILE%) # (3) カレントディレクトリ
Nyagos の場合は.nyagos
を%HOME%
ないしは%USERPROFILE%
へ置く必要があります。nyagos.exe
のあるディレクトリの.nyagos
は読み込みません。
マニュアルの「起動処理」にはnyagos.exe
と同じディレクトリ内のnyagos.lua
も読み込むと書かれていますが、こちらのファイルには先頭にこんなことが書かれています。
-------------------------------------------------------------------------- -- DO NOT EDIT THIS. PLEASE EDIT ~\.nyagos OR ADD SCRIPT INTO nyagos.d\ -- --------------------------------------------------------------------------
と言うわけで、おとなしく.nyagos
を書いていきましょう。
環境変数の設定
Nyaos ではset
を使って環境変数を積んでいくことが可能でした。Nyagos でもset
を使って同様のことができますが、更に簡単に書けるようになっています。
デフォルトの.nyagos
に書かれている内容を見てみましょう。
set{ PROMPT='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G$_$$$s' }
この中にぼんぼん設定したい環境変数をぶちこめば OK です。(ここじゃなくて別のところに書いてもいいですけど。)
今後の設定でもそうですが、何らかのパスを記載するときはLua のヒアドキュメント記法である[[ ]]
でくくるとエスケープをしなくて済むので楽ちんです。
set{ PROMPT='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G$_$$$s', LESS='-gj3R --no-init --quit-if-one-screen', LESSCHARSET='japanese', EDITOR=[[C:\Program Files (x86)\vim\vim74\vim.exe]] }
プロンプトの文字列や色を変更する
若干ここがややこしくなっています。
Nyaos ではoption prompt
で指定していましたが、Nyagos ではset
のPROMPT
が表示する文字列、nyagos.prompt
で色付け、となっています。
デフォルト設定だとこんな感じになってますね。
-- Simple Prompt for CMD.EXE set{ PROMPT='$L'.. nyagos.getenv('COMPUTERNAME') .. ':$P$G$_$$$s' } -- Coloring Prompt for NYAGOS.exe local prompter=nyagos.prompt nyagos.prompt = function(this) return prompter('$e[36;40;1m'..this..'$e[37;1m') end
Nyaos(もとい、_nya
)と同じ設定にする場合は、$e~;1m
の部分をnyagos.prompt
に持ってきます。
nyagos.prompt = function(this) return prompter('$e[36;40;1m'..this..'$e[37;1m') end
あとはその他の部分をset
内のPROMPT
に設定してあげれば OK です。
set{ PROMPT='[$P]$_$$ ', }
alias の設定
基本的にはマニュアルに書いてあることをよく読んで.nyagos
に書けば OK です。
デフォルトのエイリアスがnyagos.d\aliases.lua
にあるので参考にしましょう。
alias{ ls='ls -oF $*', lua_e=function(args) assert(load(args[1]))() end, ["for"]='%COMSPEC% /c "@set PROMPT=$G & @for $*"', }
私はもともと_nya
にこんなエイリアスを作ってました。
set MSYS=C:\MinGW\msys\1.0\bin alias ls=$MSYS\ls.exe --color=auto --show-control-chars alias ll=$MSYS\ls.exe -agoFh --time-style=+"%Y/%m/%d %H:%M" --color=auto --show-control-chars alias find=$MSYS\find.exe alias sjis=nkf.exe -s alias git=git --no-pager
.nyagos
に書き換えるとこんな感じですね。
local MSYS=[[C:\MinGW\msys\1.0\bin]] alias{ ls=MSYS..[[\ls.exe --color=auto --show-control-chars]], ll=MSYS..[[\ls.exe -agoFh --time-style=+"%Y/%m/%d %H:%M" --color=auto --show-control-chars]], find=MSYS..[[\find.exe]], sjis="nkf.exe -s", git="git --no-pager", }
suffix の設定
マニュアルに設定方法が書いてあります。見出しはnyagos.argsfilter
ってなってますが…。
デフォルトのサフィックスはnyagos.d\suffix.lua
の一番下に書かれています。
suffix.pl="perl" if nyagos.which("ipy") then suffix.py="ipy" elseif nyagos.which("py") then suffix.py="py" else suffix.py="python" end suffix.rb="ruby" suffix.lua="lua" suffix.awk={"awk","-f"} suffix.js={"cscript","//nologo"} suffix.vbs={"cscript","//nologo"} suffix.ps1={"powershell","-file"}
これを見るだけでも何となくわかると思うので説明は割愛。
History の移行
Nyaos ではoption savehist
を設定することで History を保存できましたが、Nyagos ではデフォルトで保存されるようになりました。
場所は~\AppData\Roaming\NYAOS_ORG\nyagos.history
です。
Nyaos の History は先頭にタイムスタンプが付与されていましたが、Nyagos ではそれがなくなりました。
適当な正規表現でタイムスタンプを削って保存すればそれだけで移行完了です。
Lua スクリプトのロード
nyagos.lua
を読む限りloadfile
なる関数にファイルパスを突っ込めばよさそうですが、普通にnyagos.d
に Lua ファイルを入れておくのが一番いいと思います。
おまけ:gpath の移行
Nyagos には gpath がまだ実装されていません。
割と頻繁に使う機能なので是非とも欲しいのですが、そもそも Nyaos の gpath は Lua で書かれているので自分で移行してしまいます。
とは言え、流石に単純に_nya.d
からコピペしただけで移行するのは不可能なので、ちょっとだけ直します。
nyagos.alias("gpath", function(args) local argv=args local shell=nyagos.ole.create_object_utf8("WScript.Shell") local sysEnv=shell:Environment("System") local path={} for p in string.gmatch(sysEnv:Item("PATH"),"[^;]+") do table.insert( path , p ) end if argv[1] == "del" then if argv[2] then if string.match(argv[2],"^%d+$") then table.remove( path , argv[2] ) else print("gpath del [POSITION]") return end else table.remove( path ) end elseif argv[1] == "add" then if argv[2] then if argv[3] then if string.match(argv[2],"^%d+$") then table.insert( path , argv[2] , argv[3] ) else print("gpath add [POSITION] DIRECTORY") return end else table.insert( path , argv[2] ) end else print("gpath add [POSITION] DIRECTORY") return end elseif argv[1] == "swap" then local tmp1 = path[tonumber(argv[2])] local tmp2 = path[tonumber(argv[3])] if tmp1 and tmp2 then path[ tonumber(argv[2]) ] = tmp2 path[ tonumber(argv[3]) ] = tmp1 else print("gpath swap POSITION-1 POSITION-2") return end else print("Usage:") print(" gpath add [POSITION] DIRECTORY") print(" gpath del [POSITION]") print(" gpath swap POSITION-1 POSITION-2") print("") for i=1,#path do print(i,path[i]) end return end path = table.concat(path,";") sysEnv:__put__("Item","PATH",path) if sysEnv:__get__("Item","PATH") ~= path then print("Fail to set PATH="..path) end end)
どれぐらい「ちょっと」なのか diff をとってみましょう。
< nyagos.alias("gpath", function(args) < local argv=args < local shell=nyagos.ole.create_object_utf8("WScript.Shell") --- > function nyaos.command.gpath(...) > local argv={...} > local shell=nyaos.create_object("WScript.Shell") 62c62 < end) \ No newline at end of file --- > end
ざっとこんなもんです。
まとめ
と言うわけで、おおよそ 1 時間もあれば移行できました。
もっと色々カスタマイズしている人はさらに大変でしょうが頑張ってください。
【less】今更始める less の基本文法と tips(2) - mixin 編
2018/12/01 追記
- シンタックスハイライトを適用しました。
前書き
前回の続きです。
mixin
less の特色はほとんどこの mixin に集約されていると言っても過言ではありません。
mixin の宣言
宣言と言うほどでもなく、既に定義したセレクタを呼び出せる機能です。
// less .mixin { color: #000; } div { .mixin; }
/* css */ .mixin { color: #000; } div { color: #000; }
クラスではなく ID でも呼べますが、あんまりやらないっぽいですね。
// less #mixin { color: #000; } div { #mixin; }
/* css */ #mixin { color: #000; } div { color: #000; }
名前空間
mixin はネストされた要素もすべて継承します。
// less .mixin { span { color: #000; } .hoge { color: #fff; } } div { .mixin; }
/* css */ div span { color: #000; } div .hoge { color: #fff; }
ある特定の要素だけを mixin したい場合はセレクタ > セレクタ
とすることで可能です。
// less .mixin { span { color: #000; } .hoge { color: #fff; } } div { .mixin > .hoge; }
/* css */ div { color: #fff; }
この機能を使うことで、mixin に名前空間ライクなものを付与することができます。
// less // ベンダプレフィックスを吸収するためのmixin #vendor { .border-radius { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } } div { #vendor > .border-radius; }
/* css */ div { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
!important
の扱い
mixin 時に!important
をつけると mixin される全プロパティに!important
が付与されます。
// less #vendor { .border-radius { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; } } div { #vendor > .border-radius !important; }
/* css */ div { -moz-border-radius: 4px !important; -webkit-border-radius: 4px !important; border-radius: 4px !important; }
引数つきの mixin
mixin には引数をつけることが可能です。
// less #vendor { .border-radius(@param) { -moz-border-radius: @param; -webkit-border-radius: @param; border-radius: @param; } } div { #vendor > .border-radius(4px); }
/* css */ div { -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; }
複数の引数を渡す
引数は,
か;
で区切って宣言することができます。
less としては;
を推奨しています。理由は、引数にデフォルト値を与えるときに,
を使えるからです。(デフォルト値については後述)
// less #vendor { .border-radius(@left-top; @right-top; @right-bottom; @left-bottom) { -moz-border-radius: @left-top @right-top @right-bottom @left-bottom; -webkit-border-radius: @left-top @right-top @right-bottom @left-bottom; border-radius: @left-top @right-top @right-bottom @left-bottom; } } div { // 引数を渡す時も";"が使える #vendor > .border-radius(4px; 2px; 3px; 5px); }
しかしまぁ、上記の記述だとちょっと冗長すぎます。@arguments
と言う暗黙的に定義される変数を使うことでもうちょっと楽ができます。
// less #vendor { .border-radius(@left-top; @right-top; @right-bottom; @left-bottom) { -moz-border-radius: @arguments; -webkit-border-radius: @arguments; border-radius: @arguments; } } div { #vendor > .border-radius(4px; 2px; 3px; 5px); }
/* css */ div { -moz-border-radius: 4px 2px 3px 5px; -webkit-border-radius: 4px 2px 3px 5px; border-radius: 4px 2px 3px 5px; }
ぶっちゃけてしまうと、,
ないしは;
が含まれていなければ一つの引数とみなすので、例のようにベンダプレフィックスを吸収するだけとかであれば、わざわざ複雑なことをせずにこれでいいと思います。
// less #vendor { .border-radius(@param) { -moz-border-radius: @param; -webkit-border-radius: @param; border-radius: @param; } } div { // 半角スペースでわたす #vendor > .border-radius(4px 2px 3px 5px); }
/* css */ div { -moz-border-radius: 4px 2px 3px 5px; -webkit-border-radius: 4px 2px 3px 5px; border-radius: 4px 2px 3px 5px; }
ちょっと厄介なのが、パラメータとして,
を含める可能性があるもの。
例えばbox-shadow
ですかね。下記のようにするとコンパイル時に「引数の数が一致する mixin が見つからないよ」と怒られてしまいます。
// less #vendor { .box-shadow(@param) { -moz-box-shadow: @param; -webkit-box-shadow: @param; box-shadow: @param; } } div { // 複数の値を渡したいが…。 #vendor > .box-shadow(0 0 4px #f00, 0 0 12px #000); }
less はさりげなく可変長引数...
をサポートしているのでこれで…!と思うのですが、それだと,
が消滅してしまいます。
// less #vendor { .box-shadow(@param...) { -moz-box-shadow: @param; -webkit-box-shadow: @param; box-shadow: @param; } } div { #vendor > .box-shadow(0 0 4px #f00, 0 0 12px #000); }
/* css */ div { -moz-box-shadow: 0 0 4px #ff0000 /* ここの","が消滅 */ 0 0 12px #000000; -webkit-box-shadow: 0 0 4px #ff0000 0 0 12px #000000; box-shadow: 0 0 4px #ff0000 0 0 12px #000000; }
じゃあどうするのかですが、~
を使ってエスケープしてしまいましょう。
// less #vendor { .box-shadow(@param) { -moz-box-shadow: @param; -webkit-box-shadow: @param; box-shadow: @param; } } div { #vendor > .box-shadow(~"0 0 4px #F00, 0 0 12px #000"); }
/* css */ div { -moz-box-shadow: 0 0 4px #f00, 0 0 12px #000; -webkit-box-shadow: 0 0 4px #f00, 0 0 12px #000; box-shadow: 0 0 4px #f00, 0 0 12px #000; }
可変長引数
説明が前後しますが、less は...
を使うことで可変長引数とすることができます。
引数が可変長引数のみの場合は識別子をつけなくても構いません。その場合は@arguments
で取得しましょう。
// less .hoge(...) { border: @arguments; } .fuga(@color; @margin...) { color: @color; margin: @margin; } div { .hoge(solid, 10px, #00f); .fuga(#000, 0, 1px, 0, 0); }
/* css */ div { border: solid 10px #0000ff; color: #000000; margin: 0 1px 0 0; }
引数のデフォルト値
mixin の引数に@識別子: 値
としておくことでデフォルト値を設定できます。
このデフォルト値は他の引数の値でも問題ありません。
// less .hoge(@color: #000; @background-color: @color) { color: @color; background-color: @background-color; } div { .hoge(); } span { .hoge(#fff); } table { .hoge(#fff, #f00); }
/* css */ div { color: #000000; background-color: #000000; } span { color: #ffffff; background-color: #ffffff; } table { color: #ffffff; background-color: #ff0000; }
名前付き引数
mixin を作っていると引数がめちゃめちゃ多くなったり、デフォルト値があったりなかったりする mixin を作成したくなることもあります。
そう言うときに便利なのが名前付き引数です。
// less .hoge(@color: #000; @border; @background-color: @color) { // 第二引数にだけデフォルト値なし(こんなもん作らないようにしましょう) color: @color; border: @border; background-color: @background-color; } div { // 名前付き引数を使わない場合、 // @colorもちゃんと引数で渡さなくてはならない .hoge(#000, solid 10px #00f); } span { // 名前付き引数を使っているので // @colorと@background-colorはちゃんとデフォルト値になる .hoge(@border: solid 10px #00f); } table { // 名前付き引数の場合は順番がむちゃくちゃでもOK .hoge(@background-color: #f00, @color: #fff, @border: solid 10px #00f); }
/* css */ div { color: #000000; border: solid 10px #0000ff; background-color: #000000; } span { color: #000000; border: solid 10px #0000ff; background-color: #000000; } table { color: #ffffff; border: solid 10px #0000ff; background-color: #ff0000; }
ルールセットを引数として渡す
CSS のルールそのものを引数として渡すことも可能です。@keyframes
とかで便利です。
// less #vendor { .keyframes(@name, @prop) { @-moz-keyframes @name { @prop(); } @-webkit-keyframes @name { @prop(); } @-o-keyframes @name { @prop(); } @keyframes @name { @prop(); } } .animation(@param) { -moz-animation: @param; -webkit-animation: @param; -o-animation: @param; -ms-animation: @param; animation: @param; } } div { #vendor > .keyframes(hoge, {0% {background-color: #fff;} 100% {background-color: #000}}); #vendor > .animation(hoge 5s ease 0 infinite alternate); }
/* css */ div { -moz-animation: hoge 5s ease 0 infinite alternate; -webkit-animation: hoge 5s ease 0 infinite alternate; -o-animation: hoge 5s ease 0 infinite alternate; -ms-animation: hoge 5s ease 0 infinite alternate; animation: hoge 5s ease 0 infinite alternate; } @-moz-keyframes hoge { 0% { background-color: #fff; } 100% { background-color: #000000; } } @-webkit-keyframes hoge { 0% { background-color: #fff; } 100% { background-color: #000000; } } @-o-keyframes hoge { 0% { background-color: #fff; } 100% { background-color: #000000; } } @keyframes hoge { 0% { background-color: #fff; } 100% { background-color: #000000; } }
mixin と変数のスコープ
mixin 内で変数を宣言した場合、mixin を呼び出したスコープでその変数を使用することができます。
// less .hoge { @base-color: #fff; color: @base-color; } div { .hoge(); background-color: @base-color; // mixin内で宣言している変数を使用 }
/* css */ div { color: #ffffff; background-color: #ffffff; }
mixin で呼び出した変数を書き換えてもそのスコープ内でしか値が変わりません。
また、変数を書き換えても mixin 内の値は変わりません。
// less .hoge { @base-color: #fff; color: @base-color; } div { .hoge(); @base-color: #000; background-color: @base-color; } span { .hoge(); background-color: @base-color; }
/* css */ div { color: #ffffff; /* @base-colorを書き換えたが、mixin内なので反映されない */ background-color: #000000; } span { /* @base-colorを他スコープ(div)で書き換えられたが何も影響しない */ color: #ffffff; background-color: #ffffff; }
これを使うことで「mixin の返り値」を作成することができます。
// less .average(@x; @y) { @average: ((@x + @y) / 2); // そう言えば演算関連まだ説明してないですね…。 } div { .average(20px, 40px); margin: @average; padding: @average; }
/* css */ div { margin: 30px; padding: 30px; }
パターンマッチングと mixin のオーバーロード
同名かつ引数の数が違う mixin を作ることは可能なんですが、呼び出せる mixin はすべて呼び出すが基本ルールです。
// less .hoge(@color) { color: @color; } .hoge(@margin) { margin: @margin; } div { .hoge(#fff); // @colorの方に行ってほしいが… }
/* css */ div { color: #ffffff; margin: #ffffff; /* @marginの方も引数の数が一緒なので呼び出されてしまう */ }
上記の例を回避する方法はいくつかあります。まずはガードを使用し、引数の型をチェックする方法。
// less .hoge(@color) when (iscolor(@color)) { color: @color; } .hoge(@margin) when (isnumber(@margin)) { margin: @margin; } div { .hoge(#fff); }
/* css */ div { color: #ffffff; }
キーワードを入れる方法もあります。
// less .hoge(color, @color) { color: @color; } .hoge(margin, @margin) { margin: @margin; } div { .hoge(color, #fff); } span { .hoge(margin, 0); }
/* css */ div { color: #ffffff; } span { margin: 0; }
根本的な対処ではないですが、引数の数そのものが違ってもマッチングしないとみなしてくれます。
ただし、デフォルト値が設定されていると面倒なことになったりします。
// less .hoge(@color) when (iscolor(@color)) { color: @color; } .hoge(@margin) when (isnumber(@margin)) { margin: @margin; } .hoge(@color, @border) { colol: @color; border: @border; } div { .hoge(#fff); } span { .hoge(#fff, solid 10px #0000ff); }
/* css */ div { color: #ffffff; } span { color: #ffffff; border: solid 10px #0000ff; }
ガード
ガードはかなり細かく設定できます。面倒ですが。
複数のガードを設定するときはand
を使います。
// less .hoge(@color, @margin) when (iscolor(@color)) and (isnumber(@margin)) { color: @color; margin: @margin; } div { .hoge(#fff, 10px); }
/* css */ div { margin: 10px; color: #ffffff; }
否定する場合はnot
をつけます。
// less .hoge(@color, @margin) when (iscolor(@color)) and (isnumber(@margin)) { color: @color; margin: @margin; } .hoge(@color, @border) when (iscolor(@color)) and not(isnumber(@border)) { color: @color; border: @border; } div { .hoge(#fff, 10px); } span { .hoge(#fff, solid 10px #0000ff); }
/* css */ div { color: #ffffff; margin: 10px; } span { color: #ffffff; border: solid 10px #0000ff; }
or 条件を指定したい場合は,
を使います。
// less .hoge(@margin, @padding: @margin) when (isnumber(@margin)), (isnumber(@padding)) { margin: @margin; padding: @padding; } div { .hoge(10px); } span { .hoge(20px, fuga); // @marginがnumberなのでコンパイルが通る }
/* css */ div { margin: 10px; padding: 10px; } span { margin: 20px; padding: fuga; }
また、数値であれば<
や>
も使えますし、文字列やキーワードの判定なんかもできます。
// less .hoge(@margin, @padding) when (@padding <= 0px) { @padding: 5px; margin: @margin; padding: @padding; } .hoge(@margin, @padding) when (@padding > 10px) { @padding: 10px; margin: @margin; padding: @padding; } div { .hoge(10px, -1px); } span { .hoge(20px, 20px); }
/* css */ div { margin: 10px; padding: 5px; } span { margin: 20px; padding: 10px; }
// less // !=は用意されてないのでnotを使う .hoge(@keyword, @color) when not(@keyword=bg) { color: @color; } .hoge(@keyword, @color) when (@keyword=bg) { background-color: @color; } div { .hoge(hoge, #fff); } span { .hoge(bg, #000); }
/* css */ div { color: #ffffff; } span { background-color: #000000; }
どこにもマッチングしなかった場合のフォールバックとして、default()
を使うこともできます。
この辺を使いこなせるようになると本当にオーバーロードっぽくなるのでぜひマスターしましょう。
// less .hoge(@margin, @padding) when (default()) { margin: @margin; padding: @padding; } .hoge(@margin, @padding) when (@padding <= 0px) { .hoge(@margin, 5px); // when (default()) } .hoge(@margin, @padding) when (@padding > 10px) { .hoge(@margin, 10px); // when (default()) } div { .hoge(10px, -1px); // when (@padding <= 0px) } span { .hoge(20px, 20px); // when (@padding > 10px) } table { .hoge(2px, 2px); // when (default()) }
/* css */ div { margin: 10px; padding: 5px; } span { margin: 20px; padding: 10px; } table { margin: 2px; padding: 2px; }
まとめ
やはり長くなってしまった…。
次回はまだ説明してないあれこれを適当に説明していく予定です。
【less】今更始める less の基本文法と tips(1) - ネストと変数編
2018/12/01 追記
- シンタックスハイライトを適用しました。
前書き
お仕事の方で 5000 ステップほどある CSS を編集したりするんですが、人間が管理するにはあまりにも厳しいので、lessを導入することにしました。
流石に 1 から自分の手で書き直すのは辛いのでcss2lessを使って一度変換し、後は黙々と mixin なりなんなりを作るだけです。
CSS を知っていれば文法的に難しいところもほとんどなかったんですが、ちょっとわかりづらかったり、そのうち忘れそうなところもいくつかあったので適当にメモしていきます。
文法そのものの解説はぐぐるといっぱいでてきますが、公式のドキュメントがやはり最強でした。文章量もそんなにないので、下手にどこかの解説を読むよりこっちを全部読んだ方が早い気がします。
コンパイラのインストール
基本的にはnpm -g install less
だけでいいんですが、minify するために必要なプラグインが別途用意されているのでnpm install -g less-plugin-clean-css
もしておきましょう。
セレクタのネスト
基本形式
less ではセレクタをネストして書くことができます。例えばこんな CSS があったとします。
#header nav ul li { display: inline; } #header hoge { color: #000; }
こんな風に書けます。
#header { nav { ul { li { display: inline; } } } #hoge { color: #000; } }
疑似要素
:hover
や:after
などの疑似要素は&
を使うことで簡単に宣言できます。
// less #header { background-color: #fff; &:hover { // #header:hover background-color: #000; } div { &:hover { // #header div:hover background-color: #ff0; } } }
/* css */ #header { background-color: #fff; } #header:hover { background-color: #000; } #header div:hover { background-color: #ff0; }
&
は単純にセレクタ名を格納した擬似変数なので、疑似要素の定義以外にもこんなことができます。
// less #header { &-hoge { color: #fff; } &-fuga { color: #000; } }
/* css */ #header-hoge { color: #fff; } #header-fuga { color: #000; }
メディアクエリのネスト
ネストした要素内でそれぞれのメディアクエリを書くことができます。さらに、メディアクエリ自体のネストも可能です。
// less #header { font-size: 100%; @media screen and (max-width: 768px) { font-size: 90%; } @media screen and (max-width: 560px) { font-size: 80%; } div { @media screen and (max-width: 768px) { color: #ff0; @media screen and (min-width: 560px) { color: #f00; } } } }
/* css */ #header { font-size: 100%; } @media screen and (max-width: 768px) { #header { font-size: 90%; } } @media screen and (max-width: 560px) { #header { font-size: 80%; } } @media screen and (max-width: 768px) { #header div { color: #ff0; } } @media screen and (max-width: 768px) and screen and (min-width: 560px) { #header div { color: #f00; } }
出来上がった CSS は中々おぞましいものになりますが、まぁ、気にしないことにしましょう。
@keyframes
や@font-face
の扱い
自動で外に吐き出されますので、どんどんネスト内で書きましょう。
// less #header { background-color: #fff; div { @font-face { font-family: "hoge"; src: url("/hoge.ttf"); } font-family: "hoge"; } }
/* css */ #header { background-color: #fff; } #header div { font-family: "hoge"; } @font-face { font-family: "hoge"; src: url("/hoge.ttf"); }
変数
変数宣言
@識別子:値
と言う形で変数宣言が可能です。
// less @base-color: #fff; div { color: @base-color; }
/* css */ div { color: #ffffff; }
変数として宣言できるもの
割となんでもいけます。
// less // カラーコード @base-color: #fff; @rgba-color: rgba(0, 0, 0, 0.5); // RGBAもOK div { color: @base-color; background-color: @rgba-color; } // もちろん数値もOK @width: 100%; @height: 50px; @margin: 0; div { width: @width; height: @height; margin: @margin; } // URLなんかもOK @image-dir: "../images"; div { background-image: url("@{image-dir}/image.jpg"); } // 変数にCSSのルールセットを定義することも可能 @rules: { color: @base-color; background-color: @rgba-color; width: @width; height: @height; margin: @margin; background-image: url("@{image-dir}/image.jpg"); }; div { @rules(); // ルールセットを展開する場合は"()" }
/* css */ div { color: #ffffff; background-color: rgba(0, 0, 0, 0.5); } div { width: 100%; height: 50px; margin: 0; } div { background-image: url("../images/image.jpg"); } div { color: #ffffff; background-color: rgba(0, 0, 0, 0.5); width: 100%; height: 50px; margin: 0; background-image: url("../images/image.jpg"); }
変数として宣言できるけど使い道が微妙なもの
セレクタを宣言しておくことも可能です。複数のセレクタを一まとめにしたいとき便利かもしれません。
// less @copyright: #footer address#copyright; @{copyright} { color: #000; }
/* css */ #footer address#copyright { color: #000; }
また、プロパティ名も宣言できます。長いプロパティ名を省略するとき…とか?
// less @ff: font-family; div { @{ff}: monospace; }
/* css */ div { font-family: monospace; }
変数名そのものを宣言しておくことも可能です。この辺まで来ると普通に可読性が落ちそうです。
// less @var-name: "mono"; @mono: monospace; @ff: font-family; div { @{ff}: @@var-name; }
/* css */ div { font-family: monospace; }
メディアクエリを変数として宣言
メディアクエリの条件を変数として宣言しておくことも可能です。
// less @tablet: ~"screen and (max-width: 768px)"; // "()"が入っているものは"~"をつけてエスケープする @media @tablet { div { color: #fff; } }
/* css */ @media screen and (max-width: 768px) { div { color: #fff; } }
変数の型
厳密には less に型と言う概念はないんですが、型チェックっぽい関数が用意されています。
型 | 該当するもの | 該当しないもの |
---|---|---|
Number |
|
|
String |
|
|
Color |
|
|
Keyword |
|
|
Url |
|
|
Pixel |
|
|
Em |
|
|
Percentage |
|
|
Ruleset |
|
|
スコープ
変数にはスコープがあります。あるセレクタ内で宣言された変数は、他のセレクタから呼び出せません。
@global-color: #000; #hoge { @inner-color: #fff; color: @global-color; div { color: @inner-color; } } #fuga { color: @inner-color; // スコープ外なので呼び出せない }
他のスコープで宣言された変数を書き換えると、そのスコープ内でのみ値が書き換わります。
// less @global-color: #000; #hoge { color: @global-color; } #fuga { @global-color: #fff; // fugaで@global-colorの値を変更する color: @global-color; } #piyo { color: @global-color; }
/* css */ #hoge { color: #000000; } #fuga { color: #ffffff; } #piyo { color: #000000; /* piyoの@global-colorは変わらない */ }
ただし、同スコープ内で変数の値を書き換えると、変更した場所に関わらず後に宣言した値が適用されます。
// less @global-color: #000; #hoge { color: @global-color; } @global-color: #fff; // hogeの後に@global-colorを書き換える #fuga { color: @global-color; } #piyo { color: @global-color; }
/* css */ /* @global-colorの値はすべて#fffになる */ #hoge { color: #ffffff; } #fuga { color: #ffffff; } #piyo { color: #ffffff; }
なるべくであればグローバルなスコープでの変数は書き換えないようにしましょう。危なっかしいので…。
まとめ
すごい長くなりそうなので続き物にします。
次回は mixin 関連を予定。早ければ明日に。
(2015/08/30)書きました。:-)
【Atom】Windows + Proxy 環境下で Atom を使うための準備
前書き
最近 Web エンジニア化が激しく、VSCode を使って頑張っていろいろ作っていたんですが、流石にこう、「もうちょっといいエディタ絶対あるわ…。」と言う気分になってきました。
いやまぁ悪いエディタではないんですけどね。いいエディタでは決してないですからね。
じゃあ他にどんな選択肢があるよ?って言われても、Atom か Sublime Text か Brackets か、って話ですし、以前 Atom を触ってたこともあるので迷わず Atom を入れたんですが、プロキシ環境下だとクソ面倒だったって話をします。
厳密に言えば別にプロキシ環境下でも何の問題もなく動くんですが、パッケージの検索、インストール関連は何一つ動きません。やってくれるぜって感じですね。
パッケージの入れられない Atom なんてそれこそ使う理由が一ミリもないのでちゃんと動くように準備します。
ちなみに環境は Windows です。まぁ Mac も Linux もやらなきゃいけないことは大体一緒だと思います。多分。
準備 1:apm のプロキシ設定をする
ぐぐるといっぱい出てきます。ぶっちゃけこれ読むのが一番早いです。
この設定を入れてないとパッケージを検索しようとしても真っ赤な血のような色でconnect ETIMEDOUT
と言われてしまいます。
Atom をインストールし終わったら%USERPROFILE%\.atom\
内に.apmrc
ファイルを作成し、以下の記述を追加しましょう。
(※%USERPROFILE%\.atom\.apm\.apmrc
なるものがデフォルトで置かれているけど更新しても意味ないので無視するように。)
https-proxy = http://[UserName]:[Password]@[Host]:[Port]/ http-proxy = http://[UserName]:[Password]@[Host]:[Port]/ strict-ssl = false
記述し終わったらコマンドプロンプトからapm config list
と叩いて反映されているか確認しましょう。
準備 2:パッケージインストール時に出る謎のエラーを解消する
準備 1 を終えることでパッケージが検索できるようになりました。満を持してパッケージをインストールするとCompiler tools not found
とかPackages that depend on modules that contain C/C++ code will fail to install.
とか言われます。
なんでコンパイラが必要なんじゃボケ、と思うんですが、これもプロキシが原因だったりします。
これの解決方法が中々見つからず、それっぽい Issueを探して読んでいたら唐突に Close されました。Close の理由は別の Issue に書かれた対処法で大体対処可能だったからです。
と言うわけで、このコマンドを打ち込んで謎の URL を環境変数に追加しましょう。
setx ATOM_NODE_URL http://gh-contractor-zcbenz.s3.amazonaws.com/atom-shell/dist /M
この環境変数が設定されていればパッケージがインストールできるようになっているはずです。お疲れさまでした。
2016/08/20 追記
はてブに「1.9.1
だとダメだった」と書かれてますが、普通に上記設定で問題ないです。
ご丁寧にリンクまで張ってくれていたのでそちらを見てみたところ、setx
の/M
を外したら動いた、とのこと。
setx
の/M
は「ユーザ環境変数ではなくシステム環境変数に追加する」スイッチなので、単に管理者権限がないコマンドプロンプトから実行したのだと思われます。
そりゃそうだ以上の言葉はないので、/M
を入れるかどうかはお好きな方でやってください。
まとめ
最近なんだかエディタの話ばっかりですね。
参考
【Javascript】Knockuout.js で option のカスケード処理を実装する
2018/12/01 追記
- シンタックスハイライトを適用しました。
前書き
最近お仕事の方でKnockout.jsを使ってごにょごにょやっています。
で、あるフォーム画面を作るときにとあるselect
option
のカスケードをやりたいな、と思って色々試行錯誤したんですが、思ったより大変だったのでメモしておきます。
options
バインディングの使い方
先にoptions
バインディングについて簡単に説明しておきましょう。(日本語版)
ドキュメントを読めばわかると思うんですが、data-binding
で指定できるものは以下の通りです。
パラメータ | 概要 |
---|---|
options |
select 内のoption として使う配列。 |
optionsCaption |
select の初期表示として表示する文言。option の先頭に生成され、value は"" (空文字)になる。 |
optionsText |
options の中身がオブジェクトの場合にoption のラベルとして使用するプロパティ名。 |
optionsValue |
optionsText と同様に、options の中身がオブジェクトの場合にoption のvalue として使用するプロパティ名。 |
optionsIncludeDestroyed |
論理削除フラグ。boolean を指定する。 |
optionsAfterRender |
生成したoption に対し何らかの処理を加えるためのコールバック。 |
selectedOptions |
option の初期選択値。select にmultiple 属性を指定している場合は配列を指定すればよい。 |
valueAllowUnset |
value バインディング(日本語版)でoption 内に存在しない値が指定されたときの挙動を制御する。false (デフォルト値)だと、value でバインディングしたものにoption に指定されていない値が指定されると強制的にundifined へ上書きするが、true ならばちゃんとその値がvalue として保持される。(ただし、select の見た目は空白になる。)*1 |
公式で紹介されている方法(with
バインディング)
公式ページのデモの一つにCart editorなるものがあります。(日本語版)
ここで使われている方法を使えば比較的簡単かつ直感的にカスケード処理を実装することができます。と言うか、このデモでやってることがカスケードそのものです。
ただちょっと内容が豪華すぎて本当に知りたい部分がわかりにくいので、もうちょっと簡素な形で作ってみましょう。JSFiddleも用意してあるので動作確認はそちらでどうぞ。
まずはこんな ViewModel を用意します。
function Parent(label, value, children) { this.label = label; this.value = value; this.children = ko.observableArray(children); } function Child(label, value) { this.label = label; this.value = ko.observable(value); } function ViewModel() { var self = this; this.opts = ko.observableArray([ new Parent("Parent1", 1, [new Child("Child1", 1), new Child("Child2", 2)]), new Parent("Parent2", 2, [new Child("Child3", 3), new Child("Child4", 4)]) ]); this.parentSelected = ko.observable(); this.childSelected = ko.observable(); this.checkValue = function() { var vParent = self.parentSelected() ? self.parentSelected().value : "undifined"; var vChild = self.childSelected() ? self.childSelected() : "undifined"; alert("Parent: " + vParent); alert("Child: " + vChild); }; } ko.applyBindings(new ViewModel());
で、HTML はこんな感じです。
<table> <tr> <td>Parent</td> <td> <select name="parent" data-bind="options: opts , optionsText: 'label' , value: parentSelected , optionsCaption: '選択してください'" > </select> </td> </tr> <tr> <td>Children</td> <td data-bind="with: parentSelected"> <select name="child" data-bind="options: children , optionsText: 'label' , optionsValue: 'value' , optionsCaption: '選択してください' , value: $parent.childSelected" > </select> </td> </tr> </table> <button data-bind="click: checkValue">値確認</button>
流れとしてはこんな感じですね。
ParentのoptionがViewModel.optsの配列でセットされる (表示される文言はParent.label、値はParent.value) ↓ Parentで選択した値(Parentクラス)がViewModel.parentSelectedにセットされる ↓ select#childにparentSelectedが渡される ↓ optionsにParent.childrenがセットされ、表示される文言はChild.label、値はChild.valueの値になる。 **ここで選択された値をViewModel.childSelectedに反映させるため、valueは$parent.childSelectedにする。**
カスケードされるselect
を最初から表示する
上記の方法ではselect#parent
が選択されるまでselect#child
は非表示の状態になっています。
何らかの理由でselect#child
を先に表示させておきたい場合は、if
を使って初期表示用のselect
を html に書いてしまいましょう。
visible
でも見た目上同じことができますが、select#child
が DOM 上に二つ存在することになってしまいます。if
であれば DOM の再作成 / 削除を行ってくれるのでその心配もなくなります。form
使わないならどっちでもいいですが。
(JSFiddle)
<table> <tr> <td>Parent</td> <td> <select name="parent" data-bind="options: opts , optionsText: 'label' , value: parentSelected , optionsCaption: '選択してください'" > </select> </td> </tr> <tr> <td>Children</td> <!-- ko if: parentSelected() --> <td data-bind="with: parentSelected"> <select name="child" data-bind="options: children , optionsText: 'label' , optionsValue: 'value' , optionsCaption: '選択してください' , value: $parent.childSelected" > </select> </td> <!-- /ko --> <!-- ko ifnot: parentSelected() --> <td> <select name="child" disabled> <option value="">先にParentを選択してください</option> </select> </td> <!-- /ko --> </tr> </table> <button data-bind="click: checkValue">値確認</button>
親要素が選択されていない場合は全子要素を選択できるようにする
これも初期表示用のselect
とカスケード用のselect
を作ってしまいましょう。
あわせて事前に全子要素を持った配列を作ってもいいですが、ko.computed
を使った方がスマートですし変更にも強いです。
// ViewModelに以下を追加 this.allChildren = ko.computed(function() { // flatten return [].concat.apply( [], self.opts().map(function(x) { return x.children(); }) ); });
<!-- ko if: parentSelected() --> <td data-bind="with: parentSelected"> <select name="child" data-bind="options: children , optionsText: 'label' , optionsValue: 'value' , optionsCaption: '選択してください' , value: $parent.childSelected" > </select> </td> <!-- /ko --> <!-- ko ifnot: parentSelected() --> <td data-bind="ifnot: parentSelected()"> <select name="child" data-bind="options: allChildren , optionsText: 'label' , optionsValue: 'value' , optionsCaption: '選択してください' , value: childSelected" > </select> </td> <!-- /ko -->
form
のsubmit
と併用する
カスケードとかは Knockout.js で全部設定するけどsubmit
に関してはもうform
の機能でやってしまいたい、と言うこともあるでしょう。
もちろん可能ですが、with
バインディングを使う方法だとこれはできません…。
値の確認で使っているViewModel.checkValue
をフォームのvalue
で表示するようちょっと変更してみましょうか。(JSFiddle)
this.checkValue = function() { /* var vParent = self.parentSelected() ? self.parentSelected().value : "undifined"; var vChild = self.childSelected() ? self.childSelected() : "undifined"; */ var vParent = window.test.parent.value; var vChild = window.test.child.value; alert("Parent: " + vParent); alert("Child: " + vChild); };
で、実際に動かしてみると Parent の value は絶対にとれないと思います。
理由はこれ、非常に簡単で、select
のoptions
バインディングに Javascript のオブジェクトが指定されている場合、optionsValue
バインディングを指定しないとoption
のvalue
はすべて空文字になるからです。(実際に FireBug とかで見てみるとわかる)
んじゃあoptionsValue
を設定してやるかーとこんな感じにしてみます。(JSFiddle)
<td>Parent</td> <td> <select name="parent" data-bind="options: opts , optionsValue: 'value' , optionsText: 'label' , value: parentSelected , optionsCaption: '選択してください'" > </select> </td>
動かしてみるとどんな Parent を選択しても Children が出てこなくなります。
これまた理由は非常に簡単で、optionsValue
が設定されたことで、value
バインディングに設定したparentSelected
にはParent.value
が入ってくるからです。今回の例で言えば「1」とか「2」みたいなただの数字が渡されています。
となれば、with
バインディングされているselect#child
からしたら、options
に指定されたchildren
なんて取れるわけがない(そもそもそんなプロパティがない)ので、空白のコンボボックスを表示するしかない、と言うわけです。
じゃあどうすればいいのかってとこですが、選択された値から Array を検索してしまいます。ダサいですね。
ViewModel
にこんなコードを追加します。parentSelected
に値が入っていたらViewModel.opt.value
を検索して該当するParent
を返すようにします。(JSFiddle)
this.cascade = ko.computed(function() { var vParent = self.parentSelected(); return vParent ? self.opts().filter(function(x) { return x.value == vParent; })[0] : null; });
html はこんな感じ。
<form name="test"> <table> <tr> <td>Parent</td> <td> <select name="parent" data-bind="options: opts , optionsText: 'label' , optionsValue: 'value' , value: parentSelected , optionsCaption: '選択してください'" > </select> </td> </tr> <tr> <td>Children</td> <td data-bind="if: cascade"> <select name="child" data-bind="options: cascade().children , optionsText: 'label' , optionsValue: 'value' , optionsCaption: '選択してください' , value: childSelected" > </select> </td> </tr> </table> <button data-bind="click: checkValue">値確認</button> </form>
これで値確認用のボタンをクリックすればちゃんと Parent も Child も値が取得できます。
まとめ
と言うわけで、ここ最近得た知見をいろいろまとめました。
【VSCode】Visual Studio Code 0.5.0の新機能まとめ
前書き
またいつの間にかVisual Studio Codeがバージョンアップしていました。そーいや「これからは1ヶ月に1回ペースで更新するよ!」みたいなことがどこかに書いてあったような。
今回は0.5.0
になったみたいですね。リリースは奇数でやるってスタンスなのかしらん。
以前0.3.0
の機能をまとめたので、今回もまとめてみましょう。
アップデート方法
今回から自動アップデート機能がちゃんと動作するようです。(Linuxではサポートしてない模様。)
なぜか私の環境では一向に機能しなかったので、もっかいインストールしてきました。
新機能
またUpdatesの箇所を適当に訳していきます。
ただし、可能な限り省略するので、必要な情報だと思ったらちゃんと元ドキュメントを読みましょうね…。
要約
- ファイルの扱い方がアップデートされました。
- 末尾のwhite spaceを削除するオプションが追加されました。
- ワークスペース内の検索時に特定のファイルのみを検索できるようになりました。
- JavaScriptでES6をサポートするようになりました。また、
jsconfig.json
や///
による参照の改善、ワークスペース設定の追加などが行われました。 - 内蔵されているGitに以下の機能が追加されました。
- 認証プロンプトの表示
- 複数行のコミットメッセージ
- 自動フェッチ
- ユーザ独自のスニペットを作成できるようになりました。また、Dockerfiles、Python、Rustといった言語のスニペットが追加されました。
- デバッグ機能の強化として、ウォッチ式の追加や、Node.jsのブレークポイント、ソースマップのサポートなどが行われました。
- 他にも、プロキシのサポートや、MacやWindowsでの自動アップデートの提供、細かいバグフィックスが行われました。
ファイル
-r
と-g
の追加
- コマンドラインにて
-r
(--reuse-window
)オプションを使うことで最後にアクティブになったVisual Studio Codeのワークスペースにファイルを追加できるようになりました。存在しないファイル名が指定されていた場合はdirtyなファイルが作成されます。 - コマンドラインにて
-g
(--goto
)オプションを使うことでファイルの行番号を指定することができるようになりました。code -g file1:<line>:<column?> file2:<line>:<column?> file3:<line>:<column?>
column
を同時に指定することで位置も指定することができます。-
実際の指定例はこんな感じ。
code -g c:\mycode\HelloWorld.ts:10:17
-r
や-g
は外部ツールからVisual Studio Codeを呼び出す時に便利だよ!とのこと。
コマンドラインから呼び出す際の変更点
複数のファイルパスを渡してもちゃんと一つのインスタンスで開くようになりました。
例えばcode . HelloWorld.ts
とすれば、explorerにはカレントディレクトリとHelloWorld.tsが表示されます。(HelloWorld.tsがもしなければdirtyなファイルが作成される)
Macのdockのサポート
既にアクティブになっているdock上のVisual Studio Codeにファイル or フォルダをドロップしたら同じインスタンスで開くようになりました。
エディタのオプション
ファイル検索パターンの文法
Ctrl + Shift + F
で呼び出せるsearchで検索対象 / 非対象とするファイルパターンを指定できるようになりました。
また、settings.json
に予め非対象とするファイルのパターンを記述しておくことができます。(files.exclude
やsearch.exclude
)
パターンマッチの文法は以下の通り。
*
- 1文字以上の一致
?
- 1文字の一致
**
- 複数のパスセグメント
{}
- グループ指定(
{**/*.html,**/*.txt}
) []
- 範囲指定(
**/example_[0-9].txt)
)
末尾white spaceの自動削除
settings.json
でfiles.trimTrailingWhitespace
をtrue
にすると末尾のwhite spaceを自動で削除してくれます。
Working Filesの数のカスタマイズ
settings.json
のexplorer.workingFiles.maxVisible
でWorking Filesの表示数を制御できるようになりました。(デフォルトでは9)
また、explorer.workingFiles.dynamicHeight
をfalse
にすると新しいWorking Fileを操作しても自動で枠のサイズが広がらなくなります。(デフォルトではtrue
)
ファイル / フォルダの非表示
先述したfiles.exclude
に指定したファイル / フォルダは検索結果だけでなくexplorerでも表示されません。
リソースの非表示
files.exclude
の高度な使い方。元文章だとわかりにくいので勝手に解説します。
files.exclude
はデフォルトだとこんな設定になっています。
"files.exclude": { "**/.git": true, "**/.DS_Store": true }
なぜかファイルパターンに対する値がbooleanになっていますね。ここがミソで、このbooleanの部分にexpressionを記述することができます。
例えば「.ts
と同名の.js
は非表示とする」なんてことがしたかったらこんなexpressionを書けばOKです。
"files.exclude": { "**/*.js": { "when": "$(basename).ts"} }
このexpressionに関するドキュメントは見当たりませんでした。しっかりしろVSCodeチーム!
検索対象から特定ファイルの除外
search.exclude
にfiles.exclude
と同じようなことが記述できます。
今まであったsearch.excludeFolders
はもう使えなくなるし破壊的変更点となるので注意してね、とのこと。
検索対象ファイルの指定
searchの「files to include」にファイル検索パターンが使えるよと言うお話。
JavaScript
破壊的変更
以下の設定は非推奨となるようです。jsconfig.json
の方でサポートするから使わないでね、とのこと。
validate.scope
validate.baseUrl
validate.target
validate.module
validate.noLib
ES6のサポート
ECMAScript 6の文法を解釈してくれるようになりました。
ただし、superの参照が上手くいかない(?)らしく、コンパイルエラーが出るなら_surpressSuperWithoutSuperTypeError: [true|false]
を使って抑制してくれとのこと。
jsconfig.jsonによるプロジェクト設定
tsconfig.json
のサブセットとしてjsconfig.json
を作成することができます。
これにはどのjsファイルを使うかだとか、どのコンパイラを使うかなどを指定することができます。なぜか画像で表示されている例を書き写してみますか。
{ "compilerOptions": { "target": "ES6", "module": "commonjs" }, "files": [ "app.js", "model.js" ] }
これを設定しておくことで///
による参照が不要になるそうです。
シバンの色付け
#!
で始まる行を色付けしてくれるようになりました。これ、シバン(shebang)って言うんですね…。
スニペット
待望のユーザスニペットが導入されました。詳しいドキュメントはこちら。
ユーザスニペットの定義
メニューバーのFile | Preferences
に「User Snippets」なる項目が追加されています。
選択するとどの言語のスニペットを定義するか聞かれるので、適当に選びましょう。
で、公式の例はあんまり面白くないので、Visual StudioでC#を書いてる時に頻繁に使うprop
を定義してみましょう。
{ "Property": { "prefix": "prop", "body": "public ${1:int} ${2:MyProperty} { get; set; }", "description": "auto Property" } }
prefix
はスニペットを呼び出す時の文字列、description
はそのまんまスニペットの説明です。
body
で実際にスニペットを定義します。${id:text}
はタブストップです。idに数値、textにデフォルト値を指定しておくとベネです。また、body
は配列を受け取るので、複数行を定義することもできます。
Git
ぶっちゃけ機能追加 / 改善するよりVSCodeからgitのコマンドを直接叩けるようにしてほしいんですが。
認証プロンプト
「なんかVSCodeのGitが動かないんだけど…。」→「認証を求められている場所で固まってる」ってパターンがあまりにも多かったらしいので追加されました。
毎回毎回入力するのが面倒だったらgit config
で設定してくれよな!とのこと。
複数行のコミットメッセージ
コミットメッセージ入力中にEnter
を押すことで改行されるようになりました。送信する場合はCtrl + Enter
だそうです。
複数ファイルの選択
git add
する時に複数ファイルを選択できるようになりました。今までなかったのが不思議。
自動フェッチの制御
自動でフェッチするかどうかをドロップダウンで選択できるようになりました。
デバッグ
launch.json生成の改善
launch.json
を生成する時にpackage.json
のmain
属性を見るようにしたそうです。
ウォッチ式
ウォッチ式が追加されました。
って言うか…今までなかったんですね。未だにVSCodeのデバッグは使ったことないです。
デバッグ中のコード編集
流石にホット・コード置換は無理だけど、デバッグ中にコードが編集されたら自動でデバッガが再起動するようにしたとのこと。
Node.jsのブレークポイント改善
コールバックのようなクロージャの中にブレークポイントが置かれた場合、呼び出された行と実際に実行している行を表示するようになりました。
ブレークポイントのアクティブ / 非アクティブ化
ブレークポイントのアクティブ / 非アクティブが簡単に切り替えられるようになりました。
JavaScriptのソースマップ
生成(あるいはコンパイル)されたJavaScriptの元ソースが見つからない場合、接続設定(launch configuration)に記述されているoutDirから探すようになりました。
また、インラインで書かれたソースマップもサポートされるようになりました。(インラインで書かれたソースは無理、とのこと。)
Minifiedのデバッグ
圧縮 / 難読化されたJavaScriptでもデバッグできるようになりました。
その他諸々
出力への切り替え
Ctrl+Shift+U
で簡単にOutputを見ることができるようになりました。
OS XとWindowsでの自動アップデート
私は機能しませんでした。
プロキシのサポート
package/project/bower.jsonでHTTP通信をする際、以下のどちらかの方法でプロキシを経由できるようになりました。
- VSCode起動前に環境変数の
http_proxy
とhttps_proxy
に値をセットする。 - user settingsの
http.proxy
に値をセットする。
プロキシサーバへの認証(ID / パスワード)なんかはこんな風に指定することができます。VSCodeとは全く関係ない豆知識です。
http://{Username}:{Password}@{ProxyHost}:{PortNumber}
細かいバグフィックス
今回のアップデートに関わるissueが挙げられています。
- 16480: Git push/pull not working
- 16782: Fonts Are Blurry
- 17223: SourceMap debugging doesn't work with inlined source map
- 17079: Poor Scrolling in Explore Sidebar
まとめ
いやはや、まだまだ未完成度合いがすごいですね。
【Android】ブラウザからの Intent の送信とアプリがインストールされてない場合のフォールバック
2018/12/01 追記
- シンタックスハイライトを適用しました。
前書き
ここ半年ほどコードよりも日本語を書く仕事がメインになっていて、それはそれは退屈かつシビアなものだったのですが、最近はまたちょこちょこコードを書く仕事をしています。楽しいです。
色々な都合から以前のように一日に何本も記事を書いたりはできないんですが、現状、一ヶ月に一本ペースになってしまっているので、一週間に一本ぐらいのペースにしたいですね。
で、今回はタイトルの通り、Android のブラウザから Intent を送信する方法と、対応するアプリがインストールされていない場合のフォールバック方法について解説します。
Intent を送信するのは至極簡単なんですが、フォールバックは中々厄介です。
アプリ側でその Intent を受信する方法は以前書いたので、今回は特に説明しません。リンクぐらいは貼っておきますが。
Intent の送信方法
Chromeの資料にさらっと書かれています。syntax
の箇所を引用してみましょう。
HOST/URI-path // Optional host #Intent; package=[string]; action=[string]; category=[string]; component=[string]; scheme=[string]; end;
例がないとわかりにくいですね。アプリ側はこんな AndroidManifest.xml だとします。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="hoge.fuga.piyo" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="10" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="hoge.fuga.piyo.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name="hoge.fuga.piyo.IntentActivity"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="hogefuga" android:host="piyo"/> </intent-filter> </activity> </application> </manifest>
上記の例で設定した IntentActivity の intent-filter に引っかかるようにするためには、このような URI を用意してあげます。
intent://piyo#Intent;package=hoge.fuga.piyo;action=view;category=browsable;scheme=hogefuga;end;
また、一応こんな URI でも呼べますが、今はあまり推奨されていないようです。action や category も指定できませんしね。
hogefuga://piyo
アプリがインストールされていない場合のフォールバック
どちらかと言うと、これが本題。
「Web からネイティブアプリを Intent で起動させたい!」と言う要件。当然あると思います。
が、先ほどの例でのhogefuga://piyo
なんて URI だと、ネイティブアプリがインストールされていない端末では「ページが見つかりません」なんてつれないことを言われてしまいます。
ってなわけで、「インストールされていなかったら別のページに移動させたい!」となることも多いでしょう。どーやってやるかを解説していきます。
package を指定してインストールされていなかったら Google Play に飛ばす
Intent の URI でわざわざ package を指定していましたが、あれがフォールバックそのものになります。
Android 君は対象となる intent-filter が見つからないと自分で「インストールされてないっぽいから Google Play に飛ばすか!」とやってくれます。ありがたいですね。Google Play における各アプリのインストール画面はパッケージ名をクエリとして持っている(例えば Twitter ならcom.twitter.android)ため、このような芸当ができるわけですね。ありがたいことです。
が、この方法、何らかの事情で Google Play に公開していないアプリでは何の意味もありません。
むしろ意味なく Google Play に飛ばしてしまうので、野良アプリの場合は URI から package の指定を外しておいた方がいいです。(それでも Intent 自体は動いてくれます。)
S.browser_fallback_url を指定する
先ほどのドキュメントを読み返してみると、S.browser_fallback_url
と言う最早そのまんまな名前のパラメータに関する記述があります。
指定方法も簡単で、エンコード済みの URI を渡すだけです。先ほどの URI から package を外して S.browser_fallback_url を指定してみましょう。例えば、http://hoge.fuga.piyo/app-install.html
なんて場所に飛ばしたければこんな感じです。
intent://piyo#Intent;action=view;category=browsable;scheme=hogefuga;S.browser_fallback_url=http%3a%2f%2fhoge%2efuga%2epiyo%2fapp%2dinstall%2ehtml;end;
で、やってみると本当に飛ばしてくれます。最新版の Chrome なら。
この方法は非常にスマートなんですが…その…Chrome が対応したのが本当に最近で、今年の 3 ~ 4 月頃にリリースされた新機能だったりします。(細かいバージョンはちゃんと調べていません…。)
一応 Android の WebView も Chromium を使っていることには間違いないので、そのうち対応されるんでしょうが、現状では常に Chrome を最新版にしていてなおかつ普段のブラウジングも Chorome しか使わない人にしか機能しません。つまり、実質使えないってことですね。
JavaScript を書いてどうにかする
こうなったら JavaScript だ!ってことで、頑張って書いてみました。
function fallbackApp(uri, fallbackUri) { // 起動する瞬間の時間を取得しておく var startTime = new Date().getTime(); // 普通にlocation.hrefにuriを渡すとその後操作できないので // window.openを使って開く var w = window.open(uri, "intent"); setTimeout(function() { if(w) { // ブラウザに処理が戻ってきたタイミングの時間を取得する var endTime = new Date().getTime(); // アプリが起動しようとしまいと、ブラウザに処理が戻ってきた // タイミングでここのsetTimeoutは動く。 // そこで、起動時と起動後の時間の差分をとって無理矢理判定する。 // (そうしないと、アプリが起動したのにフォールバックしてしまう。) if((endTime - startTime) < 3000)) { w.location.href = fallbackUri; } } }, 1000); // ここの時間は多分もっと短くても良い。 // 1秒だと「ページが表示されませんでした」っぽいエラーが普通に見えてしまう。 }
見てもらえばわかる通り、相当な力技です。
Intent のための URI はどう頑張っても same origin policy に反するので(そもそも scheme が違うんだから打つ手がない)、このように setTimeout で逃げるしかありませんでした。せめてw.document
あたりが取得できればまた違ったのでしょうが…。
2015/12/11 追記:標準ブラウザでのフォールバック
上記のスクリプトですが、Chrome では動くものの、Android の標準ブラウザでは動作しません…。
試行錯誤の結果、更なるパワープレイで対処できました。
// intentを飛ばすためのa要素を作成し、bodyにappendする var a = document.createElement("a"); a.href = url; a.target = "_blank"; // 同ウィンドウ内でアプリを開くとフォールバックしないので無理矢理別ウィンドウに… var body = document.getElementsByTagName("body")[0]; body.appendChild(a); // a要素のクリックイベントをぶったたく var e = document.createEvent("MouseEvents"); e.initMouseEvent( "click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ); a.dispatchEvent(e); // 一定時間内にアプリが起動しなかったらフォールバック setTimeout(function() { var endTime = new Date().getTime(); if (endTime - startTime < 3000) { window.location = fallbackUrl; } }, 1000);
こうすると別ウィンドウでは「ページが見つかりません」みたいな表示がされ、リンク元のウィンドウでフォールバック、というあんまりイケてない感じにできます。文句は Google に言ってください。
まとめ
そんなわけで、Intent の送信は簡単なんですが、フォールバックさせるのは死ぬほどしんどいよ、と言うお話でした。