最終更新: 2013-05-21T00:39+0900
<h4>と <h5>にもアンカー(<a name="..."></a>)を与える。href属性や中のテキスト( )は URLをコピーしやすくするためのおまけ。そもそも <h4>や <h5>に id属性を付加するのでなく <a>を利用しているところからが利便性目的以外のなにものでもない。<h4>と <h5>の中に <a>を置くことには、highlight.rbを修正しなくてもハイライトが機能するというメリットもある。デメリットは見出し語の先頭に余分な空白が挿入されている、ということ。セクションタイトル右端の編集用リンク「✍」は、<h3>に含まれないように気をつかっているのですよ。
def do_html4( date, idx, opt ) strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) } r = @html.lstrip r.sub!( %r!<h3>(.+?)</h3>!m ) do "<h3><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></h3>" end or r.sub!( %r!^<p>(.+?)</p>$!m ) do "<p><%= subtitle_proc( Time::at( #{date.to_i} ), #{strdump.call $1} ) %></p>" end serial = [nil, nil, nil, '%02d'%idx, '00', '00'] dumped_param = strdump.call "#{date.strftime( '%Y%m%d' )}#p#{'%02d' % idx}" header_t = Struct.new(:opentag, :level) bqlevel = 0 r.gsub!( %r!(<blockquote\b)|(</blockquote>)|<h(4|5)[^>]*>!i ) do bqlevel += 1 if $1 bqlevel -= 1 if $2 next $& if bqlevel != 0 or $3.nil? h = header_t.new($&, $3.to_i) serial[h.level, serial.length-h.level] = Array.new(serial.length-h.level){|i| i==0 ? serial[h.level].succ : '00' } frag = 'p' + serial[3 .. h.level].join('.') %!#{h.tag}<a name="#{frag}" href="<%=anchor(#{dumped_param}).gsub(/#p#{'%02d'%idx}/, #{strdump.call('#'+frag)}) %>"> </a>! end r.gsub( /<(\/)?tdiary-section>/, '<\\1p>' ) end
2段階に評価されるせいで、すんごく読みにくい。
ハッシュテーブルのキーがシンボルか文字列か気にしたくないのと、括弧や引用符やコロンがじゃまくさいので、. ひとつで済む Structに登場してもらった。
lambdaの .call() も嫌いだなあ。[] で () を代用するおぞましさよりはマシだが。
試してないけど、「end or r.sub!(...」を複数の行に分けるとシンタックスエラーになりそうなのも嫌。
破壊的メソッドが返す nilを利用するコードを初めて書いた!
H4のシリアルナンバーが増加したときに H5のシリアルをリセットするように変更した。以前はこういう連番だった。
最後が p01.02.01になるように。
URLフォーマットが変わると影響範囲が広くて困る。自分の日記へのリンクを検出する正規表現をあちこちで書き直した。
Index: core/tdiary/wiki_style.rb =================================================================== --- core/tdiary/wiki_style.rb (リビジョン 43193) +++ core/tdiary/wiki_style.rb (作業コピー) @@ -149,7 +149,7 @@ hikihtml = HikiDoc::HTMLOutput.new html.gsub!( %r!<a href="(.*?)">(.*?)</a>! ) do k, u = hikihtml.unescape_html($2), hikihtml.unescape_html($1) - if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)[^\d]*?#?([pct]\d+)?$/ =~ u then + if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)\D*([pct]\d+(?:\.\d+)*)?$/ =~ u then %Q[<%=my '#{$1}#{$2}', '#{escape_quote CGI.escapeHTML k}' %>] elsif /:/ =~ u scheme, path = u.split( /:/, 2 ) Index: core/plugin/00default.rb =================================================================== --- core/plugin/00default.rb (リビジョン 43193) +++ core/plugin/00default.rb (作業コピー) @@ -480,7 +480,7 @@ # make anchor string # def anchor( s ) - if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then + if /^([\-\d]+)#?([pct][\d\.])?$/ =~ s then if $2 then "?date=#$1##$2" else @@ -540,7 +540,7 @@ # make anchor tag in my diary # def my( a, str, title = nil ) - date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4}|)\D*([pct]\d+)?$/ )[0] + date, frag = a.scan( /^(\d{8}-\d+|\d{8}|\d{6}|\d{4})\D*([pct]\d+(?:\.\d+)*)?$/ )[0] anc = frag ? "#{date}#{frag}" : date index = /^https?:/ =~ @index ? '' : @conf.base_url index += @index.sub(%r|^\./|, '') Index: plugin/html_anchor.rb =================================================================== --- plugin/html_anchor.rb (リビジョン 43193) +++ plugin/html_anchor.rb (作業コピー) @@ -14,7 +14,7 @@ if @conf.index.empty? or /\/$/ =~ @conf.index def anchor( s ) - if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then + if /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then if $2 then "#$1.html##$2" else Index: plugin/my-sequel.rb =================================================================== --- plugin/my-sequel.rb (リビジョン 43193) +++ plugin/my-sequel.rb (作業コピー) @@ -487,7 +487,7 @@ alias :my_sequel_orig_my :my unless defined?(my_sequel_orig_my) def my(*args) if @my_sequel_active and @my_sequel_date and @my_sequel_anchor and @mode != 'preview' then - dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+))?$/)[0] + dst_date, frag = args[0].scan(/^(\d{8})\D*(?:p(\d+)(?:\.\d+)*)?$/)[0] if dst_date and dst_date < @my_sequel_date then dst_anchor = "#{dst_date}#{frag ? "#p%02d" % frag.to_i : ''}" @my_sequel.add(@my_sequel_anchor, dst_anchor) Index: plugin/my-ex.rb =================================================================== --- plugin/my-ex.rb (リビジョン 43193) +++ plugin/my-ex.rb (作業コピー) @@ -13,9 +13,10 @@ unless @conf.mobile_agent? +alias :my_overwritten_by_my_ex :my def my( a, str, title = nil ) m = /^(\d{8})\D*(?:([pct])(\d+))?$/.match( a ) - return '' unless m + return my_overwritten_by_my_ex( a, str, title ) unless m _, date, place, frag = *m if title.nil? and date and frag and @diaries[date] then
misc/plugin/html_anchor.rbと plugin/00default.rbプラグインを再び修正。
元々 | if /^([\-\d]+)#?([pct]\d*)?$/ =~ s then |
修正ミス | if /^([\-\d]+)#?([pct]\d+(?:\.\d+)*)?$/ =~ s then |
OK | if /^([\-\d]+)#?([pct][\d\.]*)?$/ =~ s then |
「ツッコミを入れる」リンクは YYYYMMDD#c へのリンクなので、cの後ろに数字が続いていない。これをうっかり除外してしまっていた。
最終更新: 2014-12-18T13:09+0900
すぐ上のセクションで出てきた(俺が書いたんだけど)こういうの、
alias :my_overwritten_by_my_ex :my def my ... end
こういう「エイリアス&上書き」が嫌いで、不完全ながらこういうものを書いたことがある。(今でも使っている。セミコロンを抑制する努力をしていない頃に書いたもののようだ。Rubyのバージョンで nameをシンボルにするか文字列にするか振り分けているが、今ならやらない。先頭要素を取り出してそれがシンボルか文字列かを調べる。そして、代入を ifで囲うのではなく ifの結果を代入する)
class ::Object def overwrite(name, &block) if('1.9.0'<=RUBY_VERSION); name = name.to_sym; else name = name.to_s; end if(self.singleton_methods(false).member?(name)) $dirty = [name, block]; class <<self name, block = *$dirty; old = instance_method(name); remove_method(name); # define_method(name){|*params| # block.call(old.bind(self), *params); # ブロックは渡せない? 1.9ならできる? # } define_method("__overwrite__#{name}__#{block.object_id}", &block); define_method(name){|*params| send("__overwrite__#{name}__#{block.object_id}", old.bind(self), *params); } end else if(true != self.class.ancestors.each{|klass| if(klass.methods(false).member?(name)) klass.instance_eval{ old = method(name); remove_method(name); # define_method(name){|*params| # block.call(old, *params); # ブロックは渡せない? 1.9ならできる? # } define_method("__overwrite__#{name}__#{block.object_id}", &block); define_method(name){|*params| send("__overwrite__#{name}__#{block.object_id}", old, *params); } } break true; end }) # failure raise; end end end private :overwrite end
グローバル変数は赤色でよく目立つね。$dirtyっていう変数はスコープの断絶を乗り越えるために使ってるんだろう。汚いやり方だ。
もはや自分でも読み解けないが、こうして使う(んだったかな? define_methodしたときはブロックで returnが使えた気がする)。
overwrite(:my){|old, *params| # 新しいmyを以下に…… # 必要なら古い定義も呼び出せる。 return old.call(*params) }
継承による overrideであれば、隠された同名のメソッドを superで呼び出せるけど、メソッドの上書きではそれができなくて古い定義を呼び出せない。かといって「alias :foo_bar :foo」「alias :foo_baz :foo」式の無秩序なやり方は見るに堪えない。overwrite.rbがやってることも機械的に名前を付ける以外は同じではあるけども(まだましでしょ?)。
<追記@2009-12-19>コメント部分がなんでコメントアウトされているのかやっと思い出せた気がする。奇しくも自分でその理由を書いていたりするのだが。>「define_methodしたときはブロックで returnが使えた気がする」つまり、新メソッド定義(overwriteメソッドに与えられたブロック)を define_methodでメソッド化しておかなければ returnが使えないからだ。かといって古いメソッド定義を渡すために一段余計な処理を挟まなければいけないから、新メソッド定義(ブロック)をそのままの名前でメソッドにすることもできない。ブロックが保持する bindingに変数(旧メソッドを指している)を注入する方法があれば、ダミーの長ったらしい名前を持つメソッドをこっそり定義する今のやり方から抜け出せるかも。</追記>
俺が知らないだけで 1.9以降では解決策があるんだろうか。こういう現実の問題に対処してほしいなあ。わりと見かける aliasの使い方で、問題含みだと思うんだけど。
一人見つけた。
super=承継元クラスの同名メソッドを呼び出す=とは別に、 overridden=上書きされたメソッドを呼び出す=というのを作るというのはどうでしょう?
こちらはちょっと違う。
aliasによるメソッドの再定義は危険なのでUnboundMethodかextendを使おう - (rubikitch loves (Emacs Ruby CUI))
再ロード対策は考えていなかった。
extendは知らないではないけど……(これが求めていたものなんだろうか?)。
新しい名前を導入しないように extendでやってみた。エラーは出ないけどリンク先のサブタイトルが取得できていない(my-exによる拡張が機能していない)。instance_evalを module_evalにしてみてもだめ。
extend Module.new{|mod| mod.instance_eval{ define_method(:my){|*params| a, str, title = *params # myプラグインを拡張し、リンク先が自分の日記のとき、 # 可能ならサブタイトルやコメントを取得して title属性にセットします。 } }}
オーソドックスにやってみても同じこと。
module MyEx def my( a, str, title = nil ) end end extend MyEx
extendする対象を間違えてるんだろうか。Pluginのインスタンスに特異メソッドを定義してるつもりでいるんだけど。いずれにしろ答えにたどり着けないのでは使えないよ。def...end と書いてメソッドの定義を上書きするよりも安全で洗練された代替が欲しいんだから、def...endと書く以上の複雑さは受け容れられない。
なんでも、aliasするときにメソッド名にコロンを付けてはいけないらしい。
http://www.loveruby.net/w/RubyCodingStyle.html
なぜ付けるかといえば、ほとんどすべてが「実行される」Rubyにおいて、クラスやメソッドの定義(class...end, def...end)ですら実行時の条件によってふりわけられる Rubyにおいて、構文であること、特別扱いされることが許せないからだ。「alias はメソッド呼び出しではなくて構文である。だからこそ引数の間にカンマがないのだ。そこはわかっていながらなぜエセメソッドのような使いかたをするのか。」それは構文であることへの抵抗なのです(俺の場合)。
「ちなみにどうしてもコロンが付けたければ alias_method を使えばいいんではないかと思う。」
そいつ( alias_method )は知らなかった。コロンだけでなくメソッド名とメソッド名の間にカンマを置くこともできるんだから、喜んでそちらを使いますとも。alias_methodと aliasは使える場所や対象が完全には一致していないけども、aliasでしかできないことをした経験はないから問題はなし。
「aliasでしかできないことをした経験はないから問題はなし」なんて書いたそばからこんな例が……
特異メソッドをalias_methodを使用して別名をつけようとするとエラーになる。(中略) 特異メソッドに別名をつけて退避したい場合は、alias_method (_chain)メソッドではなくてaliasをinstance_evalのブロック内で使用するとよい。
特異メソッドは別、でした。想像だけど extendでプラグインメソッドの上書きができなかったのも特異メソッドがらみだと思うな。
昨日の文章はおかしいな。class...endや def...endという構文を認めながら aliasだけを白眼視する説明になっていないような。
class MyClass; endと書いた場合、MyClassは Classオブジェクトの入った定数(という言語のビルディングブロック)だと認識している。def my_method; endと書いた場合、my_methodはメソッド(という言語の(略)であり、以後、my_methodや my_method()と書いた場合それはメソッドの呼び出しである、というのが Rubyという言語の約束だと認識している。「alias alias_of_my_method my_method」という構文は他の構文との共通性がなく例外的なルールに感じられ、(同じコンテクストで用いられる)includeや private、attr_accessorが Moduleのプライベートメソッドとしてあるなかで構文である必然性が認められず、メソッド名に関するルールを破ってさえいる。そこが許せない。aliasの(メソッドではないから正確には違うが)引数の「alias_of_my_method」や「my_method」を言語としてどう位置づけるのか、そこををはっきりさせないのならばそれは使えない、という主張が aliasのメソッド名部分にコロンを付ける理由。
undefや defined?にも aliasと同じ疑問がある(ただし、undefは defの対称として容易に受け入れられるし、defined?の場合、必然性だけは認められる)。JavaScriptの typeof演算子や delete演算子にすら同じ違和感を覚えている*。コンパイル型言語のように、コンパイル時に評価されるのだという言い訳ができないインタプリタ型言語の弱みだろうか。こういうものを見ると Perlに感じたように言語が突然平板なものに見えてしまって嫌なのだ。そこには対象となるメモリやオブジェクトが存在せず、ソースコードの字面しかないでしょう。
* きちんと定義はされています >「[[8.7 Reference 型 (Reference Type)|http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/8_Types.html#section-8.7]]」
最終更新: 2009-12-28T23:38+0900
もうひとつ、最初のセクションからネタ拾い。こんなのや、
strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) }
別の場所から持ってきたこんなの
jsstr = lambda{|s| s.gsub(/["\\]/){|x| "\\#{x}" } }
を、自分はときどき書くようだ。[1,2,3].map{|x| ... } みたいなのを書くことはもっとある。「|x| x.」の部分が冗長だね、っていう話。
省きたいけど、単純に考えるとブロックの中が x. で始まっていなければいけない。適用範囲が狭いんじゃないか?と疑問がわく。Rubyは「オブジェクト指向スクリプト言語」をうたっているからメソッドチェインを促す意味でサポートを行う名目はあるだろう。
メソッド呼び出しの構文をとりながら自身を引数にしてよそのメソッドを呼べるようにしたり(C#3.0の extension method風)、Hikiがすでにやっているように
class String def escapeHTML CGI::escapeHTML(self) end end
本当にインスタンスメソッドにしてしまったり。CGI::escapeHTML("str") より "str".escapeHTML にしておいた方が(1引数ラムダでの利用に)便利ですよ、という動機付けを言語として行ってもいいと思う。あとは引数の束縛やらラムダの .callと []以外の呼び出し方、ブロックやラムダを用いて定義したブロックを引数にしてメソッドを呼び出したりできれば何かができそうな気がしなくもない予感がなきにしもあらず。
全然関係あるのかないのか、Haskellのポイントフリースタイル(初めて目にしたのは 12月15日頃)を検索してみた。$ で順々に処理をつなげているものがあれば . を使って、途中の処理で必要な引数を押し出していくようにしながら一つの処理に書き換えるのだろうか。ポイントフリーなのにポイント塗れとはこれ如何に? 「firstNLines = (.lines).((unlines.).take)」のように明示されなくなった引数 Nはわかりにくくない? 2引数の関数はポイントフリースタイルで書けるの?『ふつける』を通し読みしただけで、モナドもわからず、一行たりとも Haskellコードを書いたことのない人間には疑問がいっぱいです。
最終更新: 2010-01-15T21:20+0900
上前津伏見くんの写真の中に、トラ技を背景にする Interface誌を見つけた。その数日前に「ときどきの雑記帖 i戦士篇 2009年11月(上旬)」で同誌の連載について触れられているのを読んでいた。こういう巡り合わせには積極的に乗っかることにしている。
家の本棚にはトランジスタ技術があったけど、読んだのは C言語特集だけ。volatileというキーワードをそこで発見して、十年以上前の Cにはそんなキーワードがあったのかと思ったら、単なる自分の知識の穴であって、その後 volatileを付けないと期待通りに動作しないケースに自分で遭遇することもあったり。ともかく、ハードウェアはわからない。
あらら、今月は隔月連載の載ってない月だ。来月号も買って、それで一年はお腹いっぱい。
「ラプラス変換がラブプラス変換に見えて困る」(某所にて) に対して、ラプラス変換なんて見たことねーよ、と心中で突っ込んでいたら、モーターのところで出てきた。
最終更新: 2010-01-15T21:20+0900
読みやすくも正確にも書けないのでリンクだけ。
サクラエディタの肥大化する一方の CEditView(描画、ダイアログの表示、文字列検索、単語判定、URL判定、ドラッグアンドドロップ対応、などなどなんでもござれ)を機能ごとに分割したいなあ、と考えていて、モデルとして想定していたのが Rubyの Enumerableモジュールのミックスイン。Enumerableを ミックスイン(include)するクラスが eachメソッドを提供するだけで、およそシーケンシャルアクセス可能なコレクションに対して行いたい操作(max, min, find, map,...)のすべてが可能になるという仕組み。
C++でやるにはどうするのかな、と想像していたときに「これってあれじゃね?」と結びついたのが「奇妙に再帰したテンプレートパターン(CRTP)」。このパターンは「[単行本(ソフトカバー)] ビョルン・カールソン【Boost C++をチューンアップする最先端ライブラリ】 ピアソンエデュケーション」で仕入れた。
仮に Enumerableモジュールのようなものをこのパターンで実現できたとして、その Enumerableテンプレート(eachメソッドが定義されたクラスをテンプレートパラメータとして受け取る)が配列っぽいあれやこれやのクラスにミックスイン(継承)されたらオブジェクトファイルがすごく肥大化しそうだという気がする。
純粋仮想関数にした eachに依存する形で Enumerableの各メソッドを実装すれば、テンプレートを使ったときと違って実装の共有ができるだろうか。
というようなことを想像してるだけ。
eachが yieldする型は何になるんだ?(C++で yieldなんてできないという話はおいておいて) 結局、型安全性を求めたらテンプレートになって、型ごとに実装(テンプレートのインスタンス)が作られるんじゃないか。
と、ここで boost::anyを使ってはどうか。Enumerableのメソッドの実装は要素の型に依存しないから anyのまま扱う。Enumerableのメソッドを呼び出す側が要素の型を知っているから anyから正しい型のオブジェクトを取り出せる。
と思ったら、「Enumerableのメソッドの実装は要素の型に依存しない」は嘘だ。比較できないと最大値や最小値が求まらん。Enumerableがコレクションに対して求めてるのは eachだけだけど、その要素に対しては比較できることや加算できることなど、いろいろ要求している。これじゃあテンプレートでないとやってられない。
いやいやいや、min, maxをはじめ Enumerableのほとんどのメソッドは要素を引数にとるブロックを受け付けて、その結果を基に処理を行っている。ブロックのコンテキストは Enumerableのメソッドを呼び出す側だから要素の型を知っている。やっぱり Enumerableの実装は要素の型を知らなくてもいい。
ブロックの代わりになるのは関数オブジェクトか traitsかな。
とまあ、かように面倒くさそうなことになるから CEditViewクラスになんでも突っ込む結果になるのも当然。言語の不備だ、とか言ってみる。
あるクラス(CEditDoc, CEditView)にいろいろな特性(検索可能性、ドラッグアンドドロップ可能性、範囲選択可能性、……)を、簡単にクラス外から付加する確立した手順が必要。……と、あくまで想像するだけ。
それに分割してしまうと機能間での連携(依存ともいう)に苦労することになるのが目に見えている。ANSI版の CEditViewはなんでも一カ所につっこめる限界点を超えているとは思うが。
mix-inや traitsは、使われる場所でいろいろ意味が違うみたい。自分は mix-inといえば Rubyのしか知らないし、traitsといえば std::stringの char_traitsしか知らない(それすら知っているとはいえない)。
最終更新: 2011-01-02T16:57+0900
/* 現在位置より前の位置を検索する */ bool found = false; CLogicRange matchLogicRange = CLogicRange( CLogicPoint( -1, -1 ), CLogicPoint( -1, -1 ) ); CLayoutRange matchLayoutRange = CLayoutRange( CLayoutPoint( -1, -1 ), CLayoutPoint( -1, -1 ) ); const SearchPos originalSearchPos = searchPos; if( pLayout ) { bool retried = false; ///< 末尾から再検索したかどうか。 for(;;) { found = 0 != GetDocument()->m_cLayoutMgr.SearchWord( searchPos.y, searchPos.x, // 検索開始位置 m_pCommanderView->m_szCurSrchKey, // 検索条件 SEARCH_BACKWARD, // 0==前方検索 1==後方検索 m_pCommanderView->m_sCurSearchOption, // 検索オプション &matchLayoutRange, // マッチレイアウト範囲 &m_pCommanderView->m_CurRegexp // 正規表現コンパイルデータ ); if( retried ) { break; } if( ! found && GetDllShareData().m_Common.m_sSearch.m_bSearchAll ) { // From Here 2002.01.26 hor 見つからなかったので末尾から再検索する。 searchPos.y = GetDocument()->m_cLayoutMgr.GetLineCount() - 1; searchPos.x = static_cast<Int>( MAXLINEKETAS ); // とにかく大きければよい。 retried = true; continue; } if( found ) { this->GetDocument()->m_cLayoutMgr.LayoutToLogic( matchLayoutRange, &matchLogicRange ); // 検索開始位置での幅0マッチはなかったことにして一つ前から探す。 if( matchLayoutRange.GetFrom() == matchLayoutRange.GetTo() && matchLayoutRange.GetFrom().GetY2() == originalSearchPos.y && matchLogicRange.GetFrom().GetX2() == originalSearchPos.x ) { searchPos.x -= 1; found = false; continue; } } break; } }
20090808p03の @2009-10-19に書いた*結果の一部が上のコード。@2009-10-20でちらりと書いた、現在の選択範囲が幅0かどうかで検索のその場足踏み対策を行っていたのは不十分だということがわかったので、対処するために CViewCommander::Command_SEARCH_NEXT()を同じように書き換えられたら楽だけど、テストがないからそれは(私家版でしか)できないんだよね。
gotoを使っていたり、bRedoや bFlag1という名前の変数があるコードをいじるのは嫌だなあ。(だからこそ Command_SEARCH_PREV()は全面書き直しになった)
まあ、bRedoはわからなくもない。いや、使われている部分を確認しないとわからないのだが、retriedだって他人にとっては似たり寄ったりかもしれないので許容しよう(ただし、スコープが狭まったことで使用部分の確認がしやすいのは明らかに前進している)。書き換えたっていうけど、ループだって単に飼い馴らされた gotoじゃないかと言われるかもしれない。しかも上の例では繰り返しを前提としていない(一回で抜けるのが通常パスだ)から意図が不明確になったおそれもある。でもこの関数で使われていたもう一つの gotoのように、変数が初期化されない云々のエラーが出るために変数宣言を関数の頭に置くことを要求してしまう、そんな使い方はできないことが担保されている。(goto end_of_func; のことですよ)
あ、上のコード、キャレットが行頭にあるときのことを考慮してない(searchPos.x -= 1; の部分)。一応、問題なく機能してるみたいだけど。
もうひとつ問題が見つかって、現状が問題ありありならばとザックリ Command_SEARCH_PREV()と Command_SEARCH_NEXT()を書き直した。どうせ問題続出するんだろうなあ、とこっそりアップロード。
CViewSelectの修正がないと、マッチの始点と選択開始点が重なったとき(選択範囲が空になるとき)に選択ロックが外れてしまう。
範囲選択中でないときは幅0マッチなどによる検索のやり直しをしないようにした。範囲選択中かどうかというのは条件として不十分なんだけど、すくなくとも検索前と後で状態が変わるということはいえる。Command_REPLACE()が事前に選択をキャンセルしてくれているので、とりあえずは置換が一つ飛ばしになることがなくなる。
「aaaaaaa...」というテキストを、F6による選択ロックを利用して上方向に選択中に「aaa」を下検索する。3文字ずつ選択範囲が縮小していくはずのところが、1文字ずつしか縮んでいなかったのを元通りにした。
誤) SEARCH_PREV, SEARCH_NEXT
正) Command_SEARCH_PREV, Command_SEARCH_NEXT
すべて置換の無限置換対策として「m_pCommanderView->GetDrawSwitch() // 全て置換の実行中じゃない」という条件(見落としていた)を元のソースからコピってきた。
CViewSelectの変更がステータスバーメッセージの不要な表示につながっていた。メッセージ表示部にそういうチェックをさせて、カーソル位置による選択範囲変更で幅0選択を可能にする(昨日の修正)部分は残したいけど、影響範囲を延々たたいていくようなことになったらいやなので CViewSelectの変更は取り消し。
F6でロックしてキャレットを動かしまた元の位置にもどす。この状態でダイアログを出して検索をすると選択範囲の拡大縮小ではなく、マッチ結果が選択されてしまう。こんなことが起こるならやはり昨日の修正を温存したくなる。不思議なのは ANSI版も同じ挙動だったのだが、UNICODE版ではきちんと選択範囲の拡大縮小になっていたこと。<<< どうやら「カーソル位置の単語文字列をデフォルトの検索文字列にする」の設定が違っていただけみたい。
CViewSelect.IsTextSelected()には、0文字選択を含むのか含まないのかはっきりしてほしい。定義⁑からは 0文字選択を含んでいるが、IsTextSelecting()というものの存在からは、意図としては 0文字選択を含まないようにもとれる。なんにせよ CViewSelect.ChangeSelectAreaByCurrentCursorTEST()が幅0選択を特別扱いして選択解除するのは意図がわからないし、仮に目的があったとしても不適切、あるいは不完全な処置だろうと思う。問題は選択解除によって IsTextSelected()が trueから falseに変わることで、IsTextSelected()は本当にあちこちから呼ばれているので ChangeSelectAreaByCurrentCursorTEST()の変更は影響範囲が広すぎる。(ステータスバーメッセージもそのひとつ)
そもそも、keepCurrentSelectionの条件に CViewSelect.IsTextSelected()は不要に思えるのでこれを外すことで対処。元のソースをできるだけ尊重するつもりでこうしてあったんだけど。
「末尾から再検索しました」が表示されないのは、条件が「先頭から再検索しました」と同じになっていたからでした。(不等号が逆向き)
「CViewSelect.cppの修正は、幅0なら何もしないのではなく、 (略)」 else節で「pSelect->Set(ptCaretPos);」と同じことをしていたつもりです。また「m_nLastSelectedByteLen = 0; // 前回選択時の選択バイト数」の扱い方がわかっていないのですが、選択解除もしていないのに「前回選択時の~」というのはあたらないのではないかと。
コメントで指摘されて rev2で導入した sel.IsTextSelected()を sel.IsTextSelecting()に変更したのは、CViewSelectの変更を取り消したことによって、検索結果による選択範囲の拡大(縮小)の結果が幅0になったときに sel.IsTextSelected()が falseになってしまうことへの対策。(選択開始点から先へ検索が進まなくなってしまうので)
「本の虫: シンタックスシュガーとしてのlambdaの解説」
ifの条件文に && や || が 3つも 4つも連なってくると 1つの Predicateにまとめてしまって名前を付けたくもなる。でも struct{ bool operator()() const {...} } IsHoge; と書くだけでも面倒なのに、スコープが断絶してしまうために、判断に必要な変数にアクセスするためにはコンストラクタで参照を受け取ってメンバ変数に保存しなければならない。コンストラクタを書くために型名も付けなければならない。もうね、やってられない。どうせコンパイラの最適化で消えてしまうコードを長々と書くのは無駄。消えなければもっと無駄。はやく lambdaを。
http://sakura-editor.svn.sourceforge.net/viewvc/sakura-editor?view=rev&revision=1724
とにかく無限ループで強制終了しかできなくなるのは良くない。
* 「# 幅0の上検索がいつまでもその場足踏みしてないで上へ進むように修正。 # キャレットより前の、幅0の行頭マッチがハイライトできていなかったのを修正。(あんまりやりにくいので CViewCommander::Command_SEARCH_PREV()から gotoを取り除いて、不必要に選択範囲を保存するのをやめた)」
⁑ CViewSelect.IsTextSelected() -> return CLayoutRange m_sSelect.IsValid() -> return CLayoutPoint m_ptFrom.BothNatural() && CLayoutPoint m_ptTo.BothNatural()。BothNaturalという名前だけど、実際は xと yが共に 0以上かどうかをテストしている。選択範囲の始点と終点の X,Y座標がすべて 0以上のとき CViewSelect.IsTextSelected()は trueを返す。
♭ ななしfix_searchword_selection_with_selectinglock_on 置換前:$ 置換後:..
♭ ななしてけとー。 CViewCommander.cpp 【追加】 3116: const bool wasSelected..
♭ ななしや、 3158: (sel.IsTextSelected() && matchLayoutRange.GetF..
♭ ななしrev2 「先頭(末尾)から再検索する」をONにしているのに、 末尾から再検索しても「▲末尾から再検索しました」 が..
♭ ななし「選択幅ゼロ」は自明なので一切表示しなくていいと思います。
♭ ななしCViewSelect.cppの修正は、 幅0なら何もしないのではなく、 以下のようにしたほうが良くないですか? ..
♭ ななしrev2です。 abc というテキストで、 置換前:abc 置換後:xyz 先頭(末尾)から再検索する:ON 置換対..
♭ ななし※同じく、置換後を abcxyz、置換対象を[選択文字(0)]としても起きた