/ 最近 .rdf 追記 設定 本棚

脳log[SakuraEditor: 2020-08-23~]



2020年08月23日 (日)

最終更新: 2020-08-26T10:30+0900

[SakuraEditor] 上書き禁止と排他制御と編集禁止

 CDocLocker とは

ある時点でのファイルへの書き込みアクセスの可否を保存し、エディタの「上書き禁止モード」を体現するクラス。CDocLocker::IsDocWritable が第一義。

  1. 書き込みアクセスができた
  2. CDocLocker.IsDocWritable(); // true
  3. 上書き禁止モードではない

 上書き禁止モードと排他制御

排他制御を行うのに書き込みアクセスは必須ではない。しかし書き込みアクセスができることをエディタは条件にしている。自身が書き込みできないファイルに対して「お前らこのファイルは俺のものだぞ。勝手に読んだり書いたりするなよ」と主張することのナンセンスを考えれば納得できる。

書き込みアクセスがないなら排他制御はやめとこか、くらいの温度感なので、CDocLocker.IsDocWritable() に基づいて判断を下している。今現在の瞬間の書き込みアクセスを条件にしているわけではない。

 上書き禁止モードと編集禁止(※編集禁止は状態としてはビューモードと同じ)

「上書き禁止検出時は編集禁止にする」というオプションによって、上書き禁止モードがより制限の強い編集禁止状態へと格上げされる。

(別のファイルとして保存するために)上書きできなくても編集はしたいという考えも、上書きできないのなら編集できても意味がないという考えもどちらもあるだろう。そこはどちらでもいい。

 上書き不可の検出タイミングについて考えたい

これは CDocLocker.IsDocWritable() の値が変化しうるタイミングと同義。次のようになっている。

  • ファイルを開いたあと
  • ファイルを開き直したあと
  • ファイルを保存したあと
  • 排他オプションの設定(しない/上書きを禁止する/読み書きを禁止する)を変更したとき
  • ビューモードのオンオフを切り替えたとき
  • タイマーで外部からのファイル更新を監視するついで(※)

※ 最後だけは「書込禁止の監視を廃止(復活させるなら「更新の監視」付随ではなく別オプションにしてほしい)」というコメントとともに無効化されている。

上書き禁止モードの変化が排他制御を試みるかどうかと編集禁止モードのオンオフに影響するのはすでに書いた。

  1. 排他オプションを変更したときに上書き可否を改めて検知しているのは理に適っている
  2. 「上書き禁止検出時は編集禁止にする」にチェックを入れたときに上書き禁止モードのオンオフを改めようとしないのは、排他オプションの場合と比較してちぐはぐ。
  1. ファイルを開き直したあとの上書き禁止モード改め時には「すでに編集できない状態ならファイルロックのメッセージを表示しない」という再メッセージ抑制策がとられている。
  2. ビューモードをオフにしたときには、何度でも「ファイルがロックされている(上書きが禁止されている)」というようなメッセージが出る。モードの切り替えとファイルの開き直しを比較して、メッセージ抑制策の有無の分かれ目とは?

2組の比較を挙げたけども、どうにも扱いがちぐはぐで行き当たりばったり感がある。上書き禁止がいつ検知・再検知されるのか、すっきり説明できるようにしたい。

 再検知により上書き禁止モードがオフからオンになることについて

オプションにより編集禁止モードと連動する。ファイルを開いたときに上書き可否を検知し編集が禁止されるのは、ユーザーの選択でもありなんの問題もない。

しかし一度編集を開始したファイルに対して、アンドゥバッファが溜まり更新フラグが立ったファイルに対して、先ほど挙げた再検知タイミングを挟んで、上書き不可が検知され編集が禁止される事態が起こりうる。これはユーザーの望む動作であろうか。いたずらにユーザーの操作を制限しているだけではないのか。

ビューモードをオンオフするタイミング次第でエディタが編集可能になったり不可能になったりするようなことを誰が望んでいるのか。結局のところ編集禁止の根拠となっている上書き禁止モード、つまりは書き込みアクセスができたかどうかは、過去のある時点ではそうだったというだけなのに。

 上書きができないことと上書き禁止モードの差

ファイルの保存をしようとしてその直前のテストで書き込みアクセスが拒否されたところに、こういうコメントがあるのがおもしろい。「たとえ上書き保存の場合でもここでの失敗では書込み禁止へは遷移しない

上書き禁止モードを体現する CDocLocker.IsDocWritable() の値が変化しうるタイミングをいくつかすでに挙げたけども、実際に上書き保存をする直前というタイミングがそのリストから明示的に除外されていることになる。

実際上の理由はわかる。最初の上書き失敗を理由にして2回目以降のトライを勝手に諦めてもらっては困るからだ(上書き禁止モードで上書き保存は選べない)。

上書き禁止モードが何ではないのかがよくわかるコメントではないか。

 ビューモードと上書き禁止モード(と編集モード)の差

  • ビューモードはユーザーがオンオフできるが、上書き禁止モードは自動で発動する。

なお、自動で発動する上書き禁止モードをビューモード相当の制限に格上げすることができる(「上書き禁止は編集禁止」オプション)。

 上書き禁止モードをユーザーがオンオフできてもいいのでは?

上書き禁止モードを仮にユーザーがオフにしたところで、上書きできないときには上書き保存に失敗するだけのことだ。ついさっき挙げた、この失敗から上書き禁止に遷移はしない、というコメントの状況が発生するだけのことだ。

ビューモードほど強い制限でなく、ファイルシステムからの要請でもなく、ユーザーが自分の意思でこのファイルには上書きしないと宣言するモード(上書き禁止モード)があってもいいのでは?

 ビューモードをオフにする行為とは?

派生して、ビューモードをオフにするタイミングで書き込みアクセスの可否を再判定して、上書きモードのオンオフを更新する現在の挙動についても再考する。

上書き禁止モードは結局のところエディタやユーザーの選択の結果でしかない。書き込みアクセスが拒絶されたとて、そうあらねばならないモードではない。

ビューモードのオンオフ、上書き禁止モードのオンオフという操作によりユーザーが3つのモードを自在に行き来する状況を想定すれば、ビューモードのオフは編集モードへの移行であるべきでは?

 上書き禁止は編集禁止、あらため、上書き禁止はビューモード

ファイルを開いた際にユーザーの便宜のために自動で上書き禁止モードやビューモードを適用する機能があっていい。今は何が違うかというと……

  • 上書き禁止モードへの移行を拒否できない。
  • 上書き禁止モードが任意でオフにできない。
  • 「上書き禁止は編集禁止」オプションが文字通りの意味であり、上書き禁止モード(編集は可能)が上書き禁止モード(編集も禁止)によって上書きされている。
  • 上書き可否検出タイミングが場合により不合理(一度テキスト編集を開始したあとで編集を禁止されることになると……)。

 サクラエディタでファイル(テキストファイル等)を開くとファイルの更新日時が変更されてしまいます。

せっかくなので理解した内容を長々書いてきたけどもそちらは本題ではなく、そもそもは標記の現象のための作業をしていた>コード。それで何か嬉しいことができたかというとそういうことはない。現在のステータス……

  • 書き込みアクセスの要求が原因だとは決まっていない。
  • 予備的に書き込みアクセスを要求しないようにコードを修正しても、排他制御の判断と、上書き禁止は編集禁止オプションと、文字列展開のためにはやっぱり必要。
  • 書き込みアクセスを確認する、より穏当な代替手段を探しては?
  • ファイルを保護するというお節介ソフトの挙動をそういうものだと受け入れては?

2018年04月27日 (金)

最終更新: 2018-04-28T11:31+0900

[SakuraEditor] Admin 不在で死に体のサクラエディタ

2年前>[8163] OSDNへの移行の検討

sourceforge.net の変節は耳にしてる。当時は、そのうち osdn.net へ移行するんだろう、そうしたら自分のアカウントは無効になるからパッチ投稿だけ継続しようか、音沙汰のない人間にリポジトリの鍵を預けっぱなしにしておくなんてリスクだからちょうどいいんじゃないの、って気楽に考えていた。せめて支持は表明すべきだった。正当性をもった移行だと認められて人が付いてこなければ、結果的に個人フォークや不成功に終わった乗っ取りと同じことになるのだから。

移行先は名前が挙がってる GitHub より osdn.net の方がずっといいだろう。GitHub は開発者にはおなじみだろうが、掲示板が動かないことにはバグ報告も質問も途絶えてしまう。ただの利用者に英語の画面を使わせたり発言するのにアカウント登録を求めたりするのは、門前払いに等しい行為だよ。

いくつか問題が挙げられてるが、移行してからなんとでもなることを無視すると、究極的には人の問題が残る。開発者の問題が。すでに活動していない人であっても、かつて主要な貢献をしたその名前がリストされていることが、プロジェクトの正統性を裏付けるという側面があるだろう。2年前は誤解していたけど、今こうしてほとんど人が去ったプロジェクトの現状を目の当たりにすると、お願いして名前を掲載させてもらいたい立場であることがわかる。この現状で過去を清算するような移行は、プロジェクトにとって致命傷になりかねない、と思う。

開発の活発化を企図するなら、個人個人でリポジトリを持って自由にコミットできる Git の採用が必要だと思う。登録された開発メンバーという意味ではなく開発ができる利用者個人という意味ね。git svn コマンドのおかげでプロジェクトのリポジトリが Subversion であっても自分の Git リポジトリを持てることを考えると、よりカジュアルにフォークできる GitHub 上の Git リポジトリが本当に必要なものだという気もする。GitHub にプッシュされた私家版パッチは見えるし、Pull Request もあって本家取り込みもやりやすいだろう。

こうなると osdn.net への移行がわからなくなる。けどたぶん考える前に行動することが必要なんだと思う。sourceforge.net に留まり続けるプロジェクトは見捨てられたプロジェクトなんだと、俺はもう受け取るようになっている。移行してしまえば、少なくともそう決めてしまえば自分はついて行くし(「こっち来んな」って?)、チケットの移行など手を動かすこともするんだけど、問題は自分が「ついて行く」人間であって引っ張っていく人間ではないことなんだな。sourceforge.net のサクラエディタプロジェクトに対して自分ができることって svn commit だけだというのもあるし。


2017年12月16日 (土)

最終更新: 2018-02-15T12:43+0900

[SakuraEditor] insert_datetime.patch(-44行/+29行)

 F5で現在の日付と時刻を挿入したい。

メモ帳でできるように……と書くために今試してみたのだけど、挿入されたのが「00:49 2017-12-17」だった。なんだこのキモい並びは。年月日も時刻もフォーマットこそ OSの地域と言語のオプションに従ってはいるものの、その前後関係が決定的にYMD文化圏のそれではない。

 しかし日付は日付、時刻は時刻。

メモ帳はどうでもいい。サクラエディタには「日付を挿入」コマンドと「時刻を挿入」コマンドがある。それぞれのフォーマットをカスタマイズできる。日付を挿入するコマンドを F5 に割り当てて、そのフォーマットを「yyyy-MM-dd HH:mm」にすればいいと考える。できない。日付書式と時刻書式が分かれていて同時に使用できないせい。

ソースを見れば一目瞭然だけど、Windowsが GetDateFormatと GetTimeFormatという2つの APIを公開していて、サクラエディタのコマンドがその薄い被せ物だというのが、おそらくこの不便な分離の理由。くっつけたっていいでしょ。

 注釈(パッチの上から順に)

  1. TCHAR* で受け取った引数を const TCHAR* で返すのは筋が通らない。constを外した。
  2. MyGetDateFormat/MyGetTimeFormat には 3引数のものとフラグとポインタの 2引数が追加された 5引数のオーバーロードが存在するが、追加された 2引数は過度に複雑で目的が不明瞭なので 1引数にまとめた。CFormatManagerは DLLSHAREDATAから切り出された過去があるらしく、DLLSHAREDATAのメンバ構成がそのまま引数になっていたのだろう。しかし過去は過去。
  3. 省かれたコロンコロンについては以前に書いた>「namespaceが導入されたからグローバルな名前空間を明示するために :: を付けましょう、これまでただ Func(); と呼んでいたものを ::Func(); と書きましょう、なんてのはなんの冗談かと思う。譲るのは当然後から来たほうだし、衝突しないような名前を選ぶ、衝突が避けられないときにしぶしぶ :: を付けて区別する、くらいの態度で(20161214)
  4. GetDateFormat/GetTimeFormat APIが書き込みを行わなかった場合に戻り値となる文字列が不定になるが、呼び出し元がそれを知る(すべ)がない。CFormatManagerの責任で戻り値をターミネイトする。
  5. カスタムフォーマットの場合に限り GetDateFormatと GetTimeFormatの両方の APIを呼ぶ。そうでないと OSの設定で日付フォーマットを「'hh' yyyy'年'M'月'd'日'」みたいにしたときに hh がそのまま hh とは表示されなくなる。

    書式文字列と書き込み先が同じポインタでも問題ないみたい。フォーマットに対して書き込み文字数の方が多い場合があるとしたら問題になるだろうか。ANSI版で日本語の曜日が書き込まれるときとか?

  6. CFormatManagerのメンバ変数をポインタから参照へ。ポインタであるならそれは nullable であるので NULLチェックをしなければいけない。それをしていないのなら、その必要がないとわかっているのなら、それを表明できる参照型を使わない理由がない。

 「前」と「後」でも良く解らんが [2005/8/10] - 晶紀の館

GetTimeFormatは時刻だけしか対応していなくて、日付を整形したい場合はGetDateFormatというAPIを使うことになります。これが2つに分かれている所為で、両方を一度に整形したい場合に実に厄介な問題が発生するんですが、まぁそれは置いておいて………。

置いておかないで。知りたい。

「tt」が「AM」になるのか「午前」になるのかは、実行したOSの設定次第です(日本語設定なら「午前」とか)。

それでは、日本語環境で「t」はどうやって表示されるのでしょう。

「AM」が「A」、「PM」が「P」に対応するみたいで、なんとなく「t」は「tt」の短縮形じゃないかと想像付くんですけど、果たして、「午前」「午後」はどのように短縮されるんでしょうかね…。

wwwwww

 コンストラクタからコンストラクタを呼ぶ - 役に立たないプログラミングTip - 晶紀の館

20170908p02で書いた明示的デストラクタ呼び出しと placement new の組み合わせがここでも。そんなにありふれてるかなあ。

20170908p02で結論が出てるけど、一時オブジェクトとスワップするのが最適解。ナンセンスに思えた途中の試行が実はいいセンいっていたという。さらに最近の C++ なら素直に移譲コンストラクタが使える(らしい)。

 インラインの基準@2018-02-06

約200行ほどの関数です。別にどうと言うことの無い関数なんですが(個人的には幾つかに分割したいんですけど)、何故にinlineになっているのかっ!

インライン展開するには大きすぎるから。って言うか、そもそもインライン展開する必要ないでしょ。プログラムの起動時に1回だけ呼ばれる初期化用の関数なんだし。

自分がわりと inline を、とりあえず付けておこうっていう人間かな。このファイル(脳log[20170908p01] ツールチップ>a.patch)の中を inline で検索すると知れると思うけど。

基準は、今考えてるんだけど、以下のいずれか。

  • 小さいこと(1~3行)
  • フラグによる分岐と関数呼び出しばかりで、実質的に小さいこと
  • コードブロックの目的と入出力を明示するために名前を付けて関数化したけれど、1か所からしか呼び出されないこと

最近 C++ In Depthシリーズを読み返してるんだけど、そして実はシリーズに見えてシリーズとは書かれていないんだけど、『Efficient C++』の中に「シングルトン関数」という呼び名で3番目の関数のインライン考が載せられていた。同意するかは読み手に任されるとしても、そういう考えのもとにインライン化することもあるってことで。

2番目の「実質的に小さい」は、よく考えると嘘かもわからんね。呼び出す関数の引数が多かったり、呼び出すつもりの関数がインライン展開されたりすると、ソースコード上の見た目以上に大きいということがわりにありそう。

他の関数を真似してinlineをつけたんだと思うけど、「訳がわからんけど動くからそれで良いや」的にコンパイラを誤魔化すだけのテクニックなんて使用厳禁っ!

最近知ったんだけど、inlineを付けるだけで関数の多重定義によるリンクエラーが避けられるらしいですねえ>20170410。そういうことをしだすと、「道具として使う」を通り越して乱用の域に達してると思う。

pHを測る機械があって、ピッと簡単に数字が出るのかは実際に使わなかったから知らないけど、「原理も知らずに……(もにょもにょ)」というぼやきが聞こえてきたのが印象に残ってる。原理を知らないと手段の優劣も限界も条件にあった適切な選択もわからんもんね。

道具ってのは元をたどれば誰かが目的のために最適な手段を求めた結果として生み出された経緯があると思う。予め用意された道具を使っているだけではアマチュアの域を出られないのではなくて? 知の高速道路に関係する話。開拓者とフォロワーの違い。大多数にはハイアマチュアも届かない存在かもしれんけど。

 表示する日付時刻のフォーマットをカスタマイズ - Tablacus Explorer - タブファイラー開発ブログ

日付と時刻を同じひとつのフォーマットで指定できる。そのソースがたぶんここ>「HRESULT tePSFormatForDisplay(PROPERTYKEY *ppropKey, VARIANT *pv, DWORD pdfFlags, LPWSTR *ppszDisplay)

[hHmst]が時刻を表すフォーマット文字としてリストでき、[dMyg]が日付の文字としてリストできる。時刻文字が見つかると日付文字が見つかるまで、日付文字が見つかると時刻文字が見つかるまで API 呼び出しを遅延し、切り替わる部分まで(もしくは文字列末尾まで)をフォーマット文字列として、対応する API を呼び出している。どんなフォーマットでも最低1回は GetDateFormat を呼び出してるっぽいのは、2値だと if 文の条件がシンプルに書けるからか。必ず1度は、それも最初に日付フォーマット文字に出くわすだろうという推定は、すごく妥当だと思います。

これが真摯な対応というべきものか。俺は2割のコーナーケースを捨てて過度にシンプルな解法を選びがち。

 datetime - Win32: GetDateFormat and GetTimeFormat exist. GetDateTimeFormat? - Stack Overflow

先に挙げたコードの通りの回答。たぶん AM とか PM とか Thu とか Sat とか Oct とかに展開された文字列が再度フォーマット文字として解釈されてしまうんだろう。まじめに挙げると問題のあるロカールでは問題ばかりだった。


2017年09月08日 (金) [SakuraEditor] ツールチップがおかしいよね。選択した単語のヘルプがマウスカーソルのそばに出る。そこじゃないよ。■メニュー>設定>キーワードヘルプ自動表示するを選ぶと(※この設定をソースコードで見つけて、どこで設定できるのか探した。iniには書き込まれない揮発性の設定みたいだ)、選択しなくてもキャレット付近の単語の説明が出るんだけど、ポインタの先の単語じゃないんだよね。ポインタのそばに説明が出るのに……。■ポインタがビューの上にないときには表示しない処理もあるんだよね。表示するのはポインタではなくキャレットのそばの単語の説明なのに。■誰かのこだわりの結果なのかなんなのか。普通じゃないね。■2.3.1.0で補完をすると、例えば サクラ を入力してから補完ウィンドウを出して サクラエディタ を選ぶと、サクラサクラエディタ みたいに補完されるのだけど、trunkをデバッグモードでコンパイルしたものがたまたまあるので試してみてもそんなことは起こらないから、何かの設定のせいなのか、よくわからないうちに直ったのか。■単語検索といえば昔のはてなダイアリーは豪快だった。サクラエディタも Trieとか接尾辞配列とかを導入したら、予め決めた単語境界に基づいて抽出した単語を登録済みキーワードと比較するのではなく、もっとアグレッシブに編集中のテキストから登録済みのキーワードを見つけ出せるのに。……とかいって俺には難しすぎるデータ構造だから他人事(ひとごと)だよ。手を動かして理解する手もある?

最終更新: 2017-10-01T01:05+0900

[SakuraEditor] ツールチップ>a.patch

  • ベースは https://svn.code.sf.net/p/sakura-editor/code/sakura -r 4196
  • +1007行, -760行。増えた……
    • CEditView.h, CEditView.cpp, CEditView_Search.cpp, CEditView_Mouse.cpp に限定すると +28行, -429行。圧倒的に減った(ファイルが2つ増えてるんだから多少はね)。
    • 新ファイル(AsWithHelpTooltip.h, AsWithHelpTooltip.cpp)合計が 714行。
    • 目的は減量ではなく機能単位での CEditViewからの切り出しだから……。
  • class CEditViewから消えたメンバー一覧
    • BOOL KeySearchCore(...); publicだが privateにできる。
    • bool MiniMapCursorLineTip(...); publicだが privateにできる。
    • enum LID_SKH {...} publicだが privateにできる。
    • BOOL KeyWordHelpSearchDict(...); publicだが privateにできる。
    • bool ShowKeywordHelp(...); public
    • DWORD m_dwTipTimer; public
    • CTipWnd m_cTipWnd; public
    • POINT m_poTipCurPos; publicだが privateにできる。
    • CDicMgr m_cDicMgr; publicだが privateにできる。
  • 代わりに継承元がひとつ増えた
    • class CEditView: public AsWithHelpTooltip<CEditView>
      • この名前ちょっと微妙? AsSelectable みたいなのを想定した命名規則なんだけど。
    • 基底クラスだけど仮想のデストラクタはない。インターフェイスではないし、public継承されたのもたまたまで、想定される使い方では必要ないはず。
      • 「public継承されたのもたまたま」

        理想を追求する人は private 継承して public 部に using AsWithHelpTooltip::XXX を並べるなり、継承ではなくコンポジションを選び一行だけの委譲関数を書くなりすると思う。というわけで、public 継承している現状に対応して AsWithHelpTooltip::OnTimerAsWithHelpTooltip::OnMouseMove をクラス外部から隠す目的で public から protected に変更するのは間違い。public と private と protected が混在することになるという一事をもって即座に間違いだと気付くべきところ。

      • protected 継承のことは知らない。virtual だとか菱形だとかの継承も難しすぎてわからない。
  • [新機能] メニュー>設定>キーワードヘルプ自動表示する を有効にすると登録キーワードをポイントするだけでヘルプツールチップが出る。
    • その際ツールチップの X座標は単語が始まる場所で、Y座標は注目している行の1.5行下。
    • キャレットを動かしたときも同じ位置に出せるんだけど、そこは互換性。知っていればポインタという定位置にヘルプが出るのも悪くないかもしれないし。
  • [違う所] ビューの境界ギリギリでツールチップを表示したあとポインタをビューの外に持っていくとツールチップが消えないことが本家ではあったがそれがない。
    • ミニマップ(これもビューの一種)のツールチップは必ず消えるんだけど、どういう仕組みによるのかはわからなかった。
    • ミニマップのツールチップはメインメニューが展開しているときにも出る。これは m_bInMenuLoopTRUE にならないからなんだけど、なぜミニマップだけなのか。
  • [気付いた点] CTipWnd::ComputeWindowSize()CTipWnd::DrawTipText() の中で DrawText() の引数 pszWorknew してるけど、DrawTextはサイズを指定する引数も受け取るから _T('\0') を埋める必要も new する必要もないよね。
    • というのが DrawTipText() というひとつの static 関数に反映されてる。

 何がしたかったのか

CEditViewの問題は(ANSI版よりずっと改善したとはいえ)あらゆる機能がひとつのクラスに同居していることなので、publicが privateにできたからといってなんの安心材料にもなりはしない。試したかったのは1機能1クラスで CEditViewを拡張する方法。20091129p01とか20131130。新機能はただの余録。そういうのも簡単にできるよねっていうお試し。

個別機能クラスが結局は CEditViewのすべて(継承元である他の個別機能クラスを含む)に依存できそうなのがちょっと予想外だった。機能と機能の絡み合いがだんだんと窮まってきそう。privateデータメンバー(※)を狭い範囲に閉じ込めて守りやすいのだけが利点か。

※publicデータメンバーもメンバーアクセスを代行するだけのセッタゲッタもありませんよ。それを当然の前提にして privateデータメンバーに注目してる。

 述懐

たまたま目についたひとつのメンバ変数 m_cTipWnd を分離してやろうと思って初めてキーワードヘルプや補完機能をセットアップしてから、コードの絡み合いを解きほぐして再構成するのに一週間ちかくかかってる。これでは、とりあえず一か所に、とりあえず public, friend にしたくなるのもわかる。あえてクラスを分けてアクセスを制限して、その結果に頭を悩ませたい人間がいるだろうか。いやいるし必要なんだけど、結局自分の能力でできる範囲のやり方しかできない。この a.patch だって自信満々で晒してるわけではなくて、むしろクラス設計は迷いまくりで、AsWithHelpTooltip.GetLastHitKeywordDictionary() なんて行き場のない妥協の産物である。正しい設計なんてのは与えられればそれ以外に考えられないくらい当たり前のものに思えるのに、自分でそこにたどり着くのは難しかったりする。高校数学の問題とかがそう。解法をチラ見すれば解ける。しかし独力ではなんのとっかかりも見つけられない。


2016年12月16日 (金)

最終更新: 2018-03-14T23:15+0900

[SakuraEditor] poor-awk.js (15.8KiB, 2017-01-04)

行単位で加工するマクロのための定型。そのままで同時に標準入出力にもファイルドロップ(SendToを含む)にも対応している。Perl譲りなんだろうけど、Rubyの -p スイッチって便利だし実際のニーズに基づいてるよねって。

 @2018-03-10 使用例:大文字小文字を区別しない昇順ソート (ソート(case-insensitive).js)

マクロに登録しても良し、ソートしたいテキストファイルをドロップしても良し。

よく知らないけどプラグインだとメインメニューにコマンドを登録できたりするんだろうか。こういうものを C++で書いてエディタ本体に内蔵したくはないよね。

掲示板に貼り付けるには長いし、Wikiのマクロ投稿ページはログインを要求してきて書き込みさせてくれないしで、返信できずにここに書いてるんですよ。>「[8316] ソートの挙動の切り替えはサクラエディタ内で可能になりませんでしょうか

Meryのソートマクロを見たら実質ワンライナーだった。連打するたぐいのマクロではないとしても、自分のはゼロオーバーヘッドルールから離れすぎていた。


2016年11月29日 (火)

最終更新: 2017-11-01T20:40+0900

[SakuraEditor] TSV

ただの表データを扱うのに Excelを起動したくはなくて、エディタの TSVモードを使った(※世の中には関連づけのままにフォトショップを使って画像をプレビューする人もいますがね<鷹揚な人だ見習おう)。タブ文字って、列の幅がタブの幅の倍数をまたぐたびに上下で列がずれるのが微妙で、タブを複数連続で使って揃えたとしても、列を編集して長さが変わったら限界まで圧縮されていたタブがぴょっこり復元して後ろの列を押し出したりするし、1タブでなんとかしようとタブの幅を想定最大列幅まで大きくするなら3つか4つの列しか画面に収まらないしで、どうやっても手間がかかる。タブ文字の拡張として TSVモードは良い。

 ここが気になる

TSVモードとは関係なく、いつもと違う使い方をしたのでこれまで気付かなかったことに気がついたり、新バージョンのバグに当たったりした。

  1. タブの幅を大きくするとあるレベル以上で幅が固定されて大きくならない
  2. 拡張子 .tsvで TSVファイル設定を作成したのに .tsvファイルを開いたときに TSVファイルタイプにならない
  3. (その状態で)タイプ別設定の内容を変更して決定しても反映されない
  4. (TSVモード) 列の余白が広すぎて収まるものまで画面の右にはみ出る
  5. (TSVモード) タブストップの再計算はいつ? (Alt、F、W、W で再読み込みするしかないの?<頻繁なので覚えた)

 1. タブの幅を大きくするとあるレベル以上で幅が固定されて大きくならない

文字の大きさや折り返し幅が関係してる。原因をつきとめてさあ報告しようかと既存のバグ報告を漁ろうとしたら、いきなり掲示板の3スレッド目がそれだった>開発掲示板(Unicode)[2370]。おまけ付きのパッチもある。脱力。

 2. 拡張子 .tsvで TSVファイル設定を作成したのに、.tsvファイルを開いたときに TSVファイルタイプにならない

MRUに前回の適用ファイルタイプが記録されていたせいだった。けっこう昔からある機能で、Ver.2系の最初のリリースぐらいからある。要望も昔からあるみたいで「Request/111 手動で変更したタイプを記憶 - SakuraEditorWiki」の投稿者は request.txtとなっている。

いらない機能です。「一時適用」したファイルタイプを一時でなく覚えておいた結果、ユーザー(俺)を欺いてしまっているのだから。先にばらしてしまうと、次に挙げる問題もこの機能に起因して引き起こされていたし、せめて、拡張子というルールに基づくファイルタイプ判別に引っかからなかった場合に限り、アドホックな一時適用ファイルタイプを採用するなどしてほしかった。

どちらを優先するのか一概に決められないところはある。アドホックな選択を記憶・復元するのがユーザーの意思を尊重した結果なら、ルールを新設して適用したいというのもユーザーの意思なのだから。お天気屋の原理主義者の今日の言い分は「ルールはルール(なので絶対)、一時は一時」ですがね。

※お天気屋たる所以は、かっこ書きの中身が普段は「(所詮はルール。人の作ったもの。文言の枝葉末節や不備に盲従せずその精神を忖度した運用でカバーし必要なら変えるべし)」だから。どちらへ転んでも自分に都合のいい主張になるという。

 3. タイプ別設定の内容を変更して決定しても反映されない

タイプ別設定一覧から選んで一時適用ボタンを押したときと、ファイルを開くときに MRUから記憶していたファイルタイプを復元したときでは、状態が違う。ついでにいうとコマンドラインの -TYPEスイッチでファイルタイプを任意に設定したときは、MRUから復元したときと同じ挙動を示す。

どういう挙動か。ファイルタイプの選択結果がロックされていないので、設定を変更したときに、拡張子に基づくルールベースの選択に上書きされる。

この結果どういうイライラ状況が発生したか。

  1. TSVファイル設定を作って拡張子 .tsvを登録(※「「ドット省略必須」「スペースorカンマorセミコロン区切り」のリスト(20100915p01)」であることはソースコードから既知)したのにテキストモードになる。なんで? 開き直してもエディタを再起動してもダメ。なんで?
  2. じゃあテキストモードでいいよ。テキストファイルの設定を TSVファイルの設定として使ってやる。設定変更、完了。全然反映されてないんだけど、なんで? (実はさっき複製して新設したばかりで未設定の TSVファイルタイプが拡張子に基づいて適用されていた)

適用されているファイルタイプってタイプ別設定一覧を表示するまでわからないんだよね。ちょっと覚えがあってヘルプを調べたらタイトルバーには表示できるみたいだけど(「$B タイプ別設定の名前 (sakura:2.0.7.0以降)」)、初期設定ではない。

ファイルタイプが見えないしすり替わってるしルールを無視するしで、気がつくまで(つまりソースコードを読むまで) 1と 2のあいだをぐるぐるぐるぐるしていたのだった。

 4. (TSVモード) 列の余白が広すぎて収まるものまで画面の右にはみ出る

どうも W の幅をひとつの基準にしているらしい(※高さの基準に x が、ハイフンの長さの基準に n や m が使われるように(<果たしてそれを聞き分けて区別できるだろうか)、もっとも幅広な文字として W が挙がるのかもしれない)。そして列の幅計算にはフォントを使わず、全角半角単位で数えてるっぽい。

たとえば最近愛用してる HG正楷書体-PRO フォントの場合、W の文字は全角半角で字形にゆらぎ程度の違いしかなく、基準に使う W は半角文字ながら全角の幅を持っている。その結果、列幅の想定と実際におよそ2倍の開きが生じた結果、ほぼ列の内容と同じ幅の余白が隣の列との間に確保されることになった。

そういうアルゴリズムで(=列幅の2倍の)タブストップを計算してるのだと思ったから、よーし好きに書き直しちゃうぞー、と CTsvModeInfo::CalcTabLength を頭から書き下していって途中で首を傾げてしまった(あれ? これ、俺が書こうとしてるそのまんまやん)。実際、列の内容が W のみだった場合は想定とのあいだに誤差はなく、計算された余白は適正と思えるものだった。

 5. (TSVモード) タブストップの再計算はいつ? (Alt、F、W、W で再読み込みするしかないの?)

実際どうなのだろう。閲覧を主に想定してるのだろうか。

 雑に解決します>TSVモードを富豪的に使いやすく。以前のファイルタイプを記憶しない。コマンドラインで指定されたファイルタイプをロックする。.rev2.patch

ファイル名の通り……ではないな。細かいけど「記憶しない」ではなく復元しないだし、一定以上のタブ幅が4に切り詰められることの修正も入ってる。

雑なパッチとは「Viewが前の行にさかのぼって再描画してくれない? 全部書き換えたらいいじゃない」の精神で。「Viewが持ってるフォント情報は古い、しょうがないので自分で作る」みたいなコメントがソースのどこかにはあって、フォントを利用した列幅計算はある状況では問題があるのかもしれないけど、「こまけぇこたぁ(略)」の精神で。

あと、TSVモードで現在無視されてるタブ幅の設定を、余白の量として扱った。タブ幅8なら隣の列とのあいだに最小の行で全角4個分の余白。

 @2016-12-09 やはり……雑!
  • 切り取ったときにゴミが残る
  • 横スクロールがときどき狂って右端が見えなくなる

我慢して使うよ。

 @2016-12-11 パッチを .rev2.patchに差し替えた
  • 雑な全体再描画誘導をアップデート(0を -1に置換しただけ)
  • 雑に折り返さないときの横スクロールに対応(常に最長行をフルスキャン)

下に書いた悪魔合体を進めると、横スクロール対応を雑でなくできる


P.S. histという用語は MoveHistPrev/MoveHistNext という前例に基づいたものだったらしい。マクロを書いていて気がついた。だからどうということではないが。

そして、マクロを書いていてアンドゥ履歴をまとめたいなとか、カーソルをコマンド連打以外の方法で操作したいなとか思って調べたら、そういう機能は 2.0.4.0とか 2.1.0.0で入っていたらしい。マクロ強化の動きは見ていたけど、これはたしかに必要だわ。理想的にはコマンド列挙の VBスタイルではなく JScript式のオブジェクトモデルが欲しいという考えは変わらないけど。コマンド群が不足なく整備されさえすれば、マクロの先頭で JScriptで書いたラッパーライブラリを読み込んで evalするという方法もありそうだけど。『C++のためのAPIデザイン』を読んだところでは、関数群を整備するのに比べてオブジェクトライブラリを用意することには特有の問題(依存とか修正とかに関してだっけ?)があって難易度が上がるとかなんとか(つまり最初は関数列挙で正解)。

書いていたマクロっていうのがカーソル行をファイル末尾に飛ばすものなんだけど、Ruby風に書くと File.lines.partition{...} というのをそのマクロを使ってやろうとしていたんだけど、どうしてもできないのがスクロール位置(縦と横)を動かさずにそれをすること。Jumpダメ。GoFileEndダメ。MoveHistPrevダメ。AddTailダメ。LineCutToStartダメ。DeleteLineは対象が折り返し行でダメ。使えるコマンドがなかった。「行削除って便利。(20100620p01)」のときから変わらない悩み。マクロ実行の前後でフォーカスを見失うのは我慢できない。

 PLAN TODO: 列を指定してソート

たしか秀丸にあったことで知った機能。今はソートの前に列を並び替えてるんだけど、ドロップをミスってデータを壊さないかはらはらしてる。気がつけばアンドゥできるけど、気がつかなかったときが怖い。

 PLAN TODO: 次のタブ(の前か後ろ)へ移動するコマンド

何度も[End]キーを押しては間違って行末へ飛んだ。どうやら TSVファイルの列を編集しているとき、無意識の[End]キーで飛んで行きたい末尾とは列の末尾だったらしい。

あと Tabキーで次の列へ移動するとか、Enterキーで下の行へ移動するとかしたい気がするけど、違和感なく適応できるかはわからない。試してみようにもマクロから CSV/TSVモードのオンオフを知る方法がないので。

 悪魔合体:CSVモードのための列幅キャッシュ + テキストを折り返さないときの最大行長キャッシュ (@2016-12-07)

できると思うんだけど。つまり、タブストップの随時再計算をそれなりの規模の CSVファイル(※ウチのPCだと富豪パッチバージョンで1MB・1万行ならもたつくけど現実的なレベルなので、それより大きいファイル)でやろうとして時間を空間で購った場合、横スクロールバーをドキュメントの最長行に対応させるためにやっていることっていうのはその一部になる。でもコストが整数2個から配列の配列と段違いに大きくなるので、CSVモードではない場合のコスト増を避けなければいけない。(折り返しモード)×(CSVモード)=4通りのコード分裂……。

レイアウトマネージャーの仕事の一部(※折り返さないときのための再計算を適宜実行すること)が外に漏れた結果あちこちにコピペコードが散らばってるなーとか、レイアウトはレイアウトマネージャーの持つパラメーターがないと幅計算できないしレイアウトマネージャーからは独立してる(参照を持っていない)んだから、レイアウトに計算結果を持たせるのは間違いだし値が意味を持たないよなーとか、レイアウトマネージャーからレイアウトを受けとってそのレイアウトにレイアウトマネージャーを引数として渡して幅計算をさせるっていうのは、直接レイアウトマネージャーに指定レイアウトの幅を聞けばいいし(※)、そうするならレイアウトマネージャーにはゲートウェイとして値をキャッシュして返す役割が期待できて、その値っていうのがつまり折り返さないときのために計算してレイアウトにこっそり(レイアウトの与り知らないところで)埋め込んだレイアウト幅のことなのだから、やっぱりレイアウトに値を埋め込むのは間違いだよなーとか、if (bFastMode) {...} で囲まれた複数の部分が幅計算の適宜実行だけじゃなく、CLayoutMgr::_DoLayout なんてアンダースコア付きで見るからにやばいメンバ関数を呼んでいて、レイアウトマネージャーの内臓に手を突っ込むがごとき癒着ぶりだなーとか、うーん、すでに悪魔の脳みそが必要な状態なんじゃないのこれ。

※こうなっていないのはレイアウトマネージャーが束ねるレイアウトっていうのがリンクリストになっていて、添え字で任意のレイアウトにランダムアクセスできないからだろうっていうのは想像できる。でも……。

落し所はレイアウトを必要なときに必要な分だけ作成して返すプロキシクラスに位置づけて、内部的にはレイアウトマネージャーの持つ情報に自由にアクセスすることだろうか。これだとレイアウトにレイアウトマネージャーへのポインタを持たせるオーバーヘッドを正当化できる、というか、それがレイアウトの持つ一番大事な情報ということになる。プロキシが自身が古くて無効だと知る方法はなんなのだろう。レイアウトマネージャーとレイアウトにバージョン番号を持たせて突き合わせるのだろうか。C++のイテレータはどうしてるかな……って考えると、俺だ! 俺が無効化されたイテレータを管理してた。デバッグモードでは違うけど。

でも……、レイアウトマネージャーの持つ情報のうち、レイアウト行ごとに分割できるものをまとめてレイアウトクラスとし、情報だけでなく行ごとに独立した機能もレイアウトのメンバーとしてレイアウトマネージャーからオフロードしたら、それって現状? プロキシクラスだなんだと言ってレイアウトマネージャーへのポインタを持たせる理屈をこねて、現状のレイアウトにポインタメンバを一個追加する言い訳を考えてただけ?

それもこれもレイアウトマネージャーを引数に取るレイアウトの一部のメンバ関数(CalcLayoutWidth, CalcLayoutOffset)と、レイアウトが自身で一切触れずにレイアウトマネージャーが操作するメンバ(m_nLayoutWidthとそのアクセサ)が気に入らないからだ。どちらもオフロード対象ではないということだから、レイアウトがレイアウトマネージャーへのポインタを持つか、レイアウトを通さず直接レイアウトマネージャーに尋ねて解決すべき問題。そもそも後者はメンバがメンバではないという一見してあり得ない状態。パブリックなメンバ変数しか持たない構造体ならそういう使われ方はあるが、じゃあお飾りでお為ごかしのアクセサを捨てよ。<こういうのって性格の反映なのかな。引きこもって外からの干渉を拒絶する姿勢、線を引いて私の仕事あなたの仕事と分ける姿勢って。人格のコアというか凝り固まってこじらせてるからこういう語調になるんだろうって。

……とまあこんな感じで脱線するから CSVモード(富豪パッチ版)の脱富豪化は進まないのだった。それにこういうのは実装前が一番楽しい段階だから。

<追記@2017-11-01>『[単行本(ソフトカバー)] 有野 和真【Androidを支える技術〈I〉──60fpsを達成するモダンなGUIシステム (WEB+DB PRESS plus)】 技術評論社』で解説される Androidのコードに、自分で使うわけではないレイアウトのためのメンバーが出てくる。よくあるらしい。レイアウトは親が子のサイズを知りたがり子が親のサイズを知りたがるので、すぱっと割り切れないし決めきれない。それを解決する方法も入り組んでいて難しい。</追記>

 @2017-08-18 矩形選択削除に問題

当然の利用法として、特定の列を矩形選択で一括削除したくなったりするだろう。ダメ。削除し過ぎたり削除し足りなかったりする。なんだろう。削除の途中でタブストップの再計算が走るのだろうか。

 @2017-08-28 TSV補助データのコスト

すぐ上で

タブストップの随時再計算をそれなりの規模の CSVファイル(※ウチのPCだと富豪パッチバージョンで1MB・1万行ならもたつくけど現実的なレベルなので、それより大きいファイル)でやろうとして時間を空間で購った場合、横スクロールバーをドキュメントの最長行に対応させるためにやっていることっていうのはその一部になる。でもコストが整数2個から配列の配列と段違いに大きくなるので、CSVモードではない場合のコスト増を避けなければいけない。(折り返しモード)×(CSVモード)=4通りのコード分裂……

と書いたけど、別に配列1個でもよさそう。その場合「横スクロールバーをドキュメントの最長行に対応させるためにやっていること」とは独立したデータになるのでコード分裂もない。どういうデータを持つか。

  • 1列目からN列目までのそれぞれで、最長の幅を持つのは何行目でその幅はなんぼか、というのを配列に記録する。
  • その記録された(列,行)の幅が縮んだときは、すべての行でその列の幅をスキャンして記録を更新する。
  • 列の編集によってある行が記録されている最長の幅を超えた場合は、その行と列の幅で上書きする。

スキャンの発生頻度はそこそこ低いのではないか。ネックは N番目の列の幅を網羅的にスキャンするときに、行の頭からスキャンして列番号をカウントしていくのでは後ろの方の列がつらいってこと。このへんはダブルクォーテーションの解釈と連携してさらなる補助データを用意することも考えられる。まあ、考えるだけで書かないんだけど……。


2014年11月16日 (日)

最終更新: 2014-11-17T11:19+0900

[SakuraEditor] 登録キーワード強調@2ch

結局プログラム用なんだよね
漢字も強調できるけど、山田、太郎 と登録しても山田太郎はマークしない
山田商店も山田学校も全部別に登録しなきゃいけない
単語区切りをどう扱うかは、言語によって違う問題だから、
拡張子によって動作を変えれるとうれしい。

他との整合性がどうなるかは知らないけど強調キーワードの単語判定は sakura_core/view/colors/CColor_KeywordSet.cpp のモジュール内インライン関数(とそこから呼ばれる sakura_core/charset/charcode.h の IS_KEYWORD_CHAR)がやってるから、その判定にテーブルを使うようにして外部から与えられるようにすればタイプ別対応も不可能ではないよ、と書き込もうとしたけど、今は CWordParse::SearchNextWordPosition4KW に文字の分類と単語の切れ目検索を任せているのだった。たしか前に、できないことができるようになったという一事をもって満足してしまうのでこういう丁寧な仕事は俺には無理だなあとかここに書いてた気がするが、それだろうか。影響の見極めが面倒ですね。しがらみとか、調整とか、最も苦手なことだ。ぼっちメンタリティがコードの構成に影響するんだろうか。


2013年06月24日 (月)

最終更新: 2013-07-14T00:41+0900

[C++][SakuraEditor] Sakura Editor / PatchUnicode / #618 Singletonの実装変更

静的変数の初期化とマルチスレッド安全性。C++11より前と VCに関してはお寒い状況らしいが、そもそものコードが素朴なものだった。

もともとマルチスレッド対応ではないので静的変数で実装するのは大差ないとして(マルチスレッド対応コードを自分自身で書く機会を放棄することにはなる?)、唯一性の保証を TSingleton利用者に期待するというなら TSingletonはただグローバルアクセサを定義する手段になってしまう。この assertで従来通りの保証ができるかなと思ったんだけど

TSingleton(){ assert(this == static_cast<TSingleton<T>*>(T::getInstance())); }

static変数の初期化にロックがかかってたらこれはデッドロックを引き起こすんでない?ロックせず素通りしてしまうと未初期化オブジェクトを使用してしまうおそれがあるよね? C++11に対応した gccはどうやってるんだろ。

パッチの目的はデストラクタが呼ばれるようになることにあるらしいが、一方で、任意のタイミングでオブジェクトを破棄することはできなくなる。それはシングルトンオブジェクトにはそぐわない扱いかもしれないけど。

本当に stackoverflowのコードを利用するなら継承をやめたらいい。そうすれば TSingletonの利用者などというものは存在せずシングルトンクラスの実装者しかいなくなる。実装を提供するだけならマクロでもできるし、あえて継承にする理由は一元的にインスタンス作成を補足することではないかと。偽りの名前を与えられた中途半端な実装を別ファイルに隔離するのはどうかと思う。


3回ぐらいコメントの下書きをしてるけどぐるぐるしてまとまらなくてこの日記になる。


 @2013-06-25

インスタンス変数を関数内staticではなくクラス内staticにするとアドレスを取得するのに getInstanceを呼ぶ必要がなくなって、getInstanceとコンストラクタ呼び出しがネストしなくなってロックの可能性(そんなものが実在するかは gccがないので知らない)が消える、と思ったんだけど、すべてのシングルトンオブジェクトの初期化がプログラム起動時に走ってしまいそうだ。これを避けるとインスタンスをポインタで保持することになって、これは現在のコードだ。インスタンスを関数内staticで保持したまま、getInstanceでそのアドレスだけをクラス内static変数にコピーする、とかいうのはトリッキーなわりに利がなくて目的を見失ってる感。

クラスで実装するシングルトンってなんのためにあるんだろうね。アプリケーションクラスだけがシングルトンでそれ以外はそのメンバでいいやん。Javaとか C#の Main関数みたいな居心地の悪さがあるかしらんけどさ。


 @2013-07-12 クリティカルセクションは他スレッドを閉め出すもの。

同じスレッドであれば同じCRITICAL_SECTIONを引数にして何度でも EnterCriticalSectionできるらしい(同じ回数の LeaveCriticalSectionが必要)。

各スレッドはクリティカルセクションの所有権を取得した後は、自らの実行をブロックすることなく、EnterCriticalSection または TryEnterCriticalSection 関数を追加で呼び出すことができます。この結果、スレッドが既に自ら所有しているクリティカルセクションを待機しようとしてデッドロックに陥ることを防止できます。

というわけで、コンパイラが静的変数の初期化を実際どう実装するかは知らんけど、あまり気にせず上の方に書いた assertを使っていいんじゃないかな。

イベントを使ったのはこのとき(20130416)が初めてで、クリティカルセクションはまだ使ったことがないのでした。たぶんその存在はペゾルドさんの本で読んだんだろうなあ。9年前


2013年05月01日 (水) spamにこんな URL >http://keisok.sakura.ne.jp/moodle/user/view.php?id=2351■.sakura.ne.jpだから目にとまったんだけど、keisok.sakura.ne.jp自体はたぶん真っ当なサイト。moodleを狙って書き込みを行い、その一見無害な URLをスパムとしてまき散らしてるみたい。他にも /moodle/を含む URLを内容とするスパムが大量にあった。■こういうのは SaaSを利用する圧力になるんかな。自分で管理するより管理されてる方が楽だもんね。それが面白くない。

最終更新: 2013-06-11T17:42+0900

[SakuraEditor] DirectWriteパッチ(by novice123)

https://sourceforge.net/p/sakura-editor/patchunicode/482/

これは期待せざるをえない。でもこれは、何がいけなかったんだろう。

trunk2>svn revert -R .
trunk2>svn up -r 2954
(サクラエディタで trunk2_directwrite.patchを、改行をCRLFに統一して、保存し直し)
trunk2>patch -p0 < trunk2_directwrite.patch
(VS2008EEで sakura/sakura.slnを開く。Release_Unicodeでリビルド)

1.起動してファイルを開いた直後。[EOF]マークが残ってる。日本語文字に半角サイズしか割り当てられてないのはウチの環境のせい(Consolas+Meiryoで GDIが全角文字を半角で描画するせいで全角半角判定(GDI依存)と DirectWriteによる描画が食い違ってる)。

2.スクロールしてまた先頭に戻ってきた。ゴミが残ってる。

3.最小化して復元した。ゴミしかない。

自分でもやってるけど目に見えて遅くなるから@2013-05-26常用はしてない。関連(古い順)>2011042320120201(参考画像豊富)、201202052012070720121018。DirectWriteをハンコにして得られるメリットは縦方向のアンチエイリアスがかけられることかなあ。GDI+ClearTypeでも横方向のサブピクセルレンダリングはやってるから、フォントと OS次第ではそれ以外に差が出ない。サクラエディタではサブピクセル単位での字間調整に意味がないという個別の事情もあるし。


 @2013-05-02 背景画像を指定してみた。


 @2013-05-03 更新パッチ:trunk2_directwrite_a.patch

フォントは相変わらず Consolas+Meiryoなんだけど、日本語にきちんと全角幅が割り当てられてるのが不思議。

曲線を観察するに縦方向のアンチエイリアスがかかってない気がする。それでは Consolas+Meiryo+ClearTypeに比して DirectWriteのアドバンテージがない。

 Postボタンを押す直前に臆病風に吹かれてここに書きますよ。モチベーション大事。

早々に気にすることではないかも知れませんが、この二つの適切な呼び分けは難しくないですか?

HRESULT DWriteContext::SetLOGFONT(const LOGFONTW &logFont, float fontSize);
void    DWriteContext::SetFont   (const LOGFONTW &logFont);

ひょっとして上の2つと下の1つは副作用を伴う一連の不可分な処理を3分割したもので、入れ替えたり飛ばしたりできないものだったりするのでしょうか?

void DWriteContext::SetFont(HFONT hFont, bool bold, bool underline)

それから、DWriteContext::SetFont(HFONT hFont, bool bold, bool underline) の hFontにも太字・下線情報が含まれますが、CEditView_Paint.cppでこのように

HFONT hFont = GetFontset().ChooseFontHandle(false, false);
DWriteContext_SetFont(s_dwc, hFont, info.m_bFatFont, info.m_bUnderLine);

あえて無視する(無視させる)理由についても、想像がつかないもので、知っておきたいと思うのですが、教えてもらえますか?

 @2013-05-13 vim-jpのログを読んでる。

デジャヴを感じるコメントの流れだ。

>DirectWriteで描画したい · Issue #262 · vim-jp/issues · GitHub

そんで、すぐ上の疑問に関して。たぶん novice123さんはここまで読んでいない(手が回っていない)。

koron commented 4 months ago

最新版ではGetObject()を使ってHFONTからLOGFONTを取得することで、必要なときに DWriteContext_SetFont() するように変更しました。合わせて太字と斜体の有無は、DrawTextの引数ではなくフォントの情報を利用するように変更しました。

しっかし、個別問題に対処するパッチがいくつか見られるだけでさっぱりコードの全貌が追いかけられん。リンク切れも多すぎる。


 @2013-05-04 trunk2_directwrite_c.patch

ピクセルジオメトリはダイアログで設定するものでも、できるものでもないと思う。マルチモニタ。デバッグモード?


 @2013-05-13 下線

あんまり PPDとか理解してないけど結果オーライ(※自分の環境限定)で、こんな感じでやってた。(static_cast<int>(f)って int(f)って書けるんかなあ)

  STDMETHODIMP DWGdiTextRenderer::DrawUnderline(
      __maybenull void* clientDrawingContext,
      FLOAT baselineOriginX,
      FLOAT baselineOriginY,
      __in DWRITE_UNDERLINE const* underline,
      IUnknown* clientDrawingEffect
      )
  {
      HDC hdcRT = pRenderTarget_->GetMemoryDC();
      FLOAT ppd = pRenderTarget_->GetPixelsPerDip();
      RECT rcUnderline = {static_cast<int>(baselineOriginX*ppd), static_cast<int>((baselineOriginY+underline->offset)*ppd),
                          static_cast<int>((baselineOriginX+underline->width)*ppd)+1, static_cast<int>((baselineOriginY+underline->offset+underline->thickness)*ppd)+1};
      HBRUSH hBrush = CreateSolidBrush(GetTextColor(hdcRT));

      FillRect(hdcRT, &rcUnderline, hBrush);

      DeleteObject(hBrush);
      return S_OK;
  }

なんでペンでなくブラシなんでしょうねえ。思い出せないからもう一度考えてみると、ペンの場合、線の太さが、始点と終点で示される線分に対してどう影響するのかが不明だったからだろう。線がどのように太るのかが。例えば幅2ピクセルの水平線を引く場合、線の始点・終点の Y座標に大小どちらを指定すべきだろう? その点 RECTで領域を指定して塗りつぶせるブラシは結果がピクセル単位で予想できる。

 @2013-05-16 DPI、DIP。ドットパーインチ、デバイスインディペンデントピクセル。

COMのところの2、3項目しか読んでなかったけど、常識レベルの基礎が身につく良いシリーズだったのだね。DirectWriteの APIドキュメントを読んでると出てくる DIPについても書いてあった。

 @2013-05-17 QueryInterface

DirectWriteのサンプルプログラムがそうで、自分のも同じように AddRefがなかったことに、コメントした後で気付いたんだけど、

ds14050 2013-05-01

QueryInterfaceが正常に返る前に AddRefしておかなくていいのでしょうか?

この疑問に対して同シリーズのこれは無関係ではないのではないか。

hr = pFileOpen->QueryInterface(IID_IFileDialogCustomize, 
    reinterpret_cast<void**>(&pCustom));
if (SUCCEEDED(hr))
{
    // インターフェイスを使用する  (省略)
    // ...

    pCustom->Release();
}
else
{
    // エラーを処理する
}

通常どおり、戻り値 HRESULT をチェックしてメソッドの失敗に備えます。メソッドが成功した場合、ポインターを使用し終えた時点で Release を呼び出す必要があります。詳細については、「オブジェクトの有効期間を管理する」を参照してください。

  1. QueryInterfaceが返す<インターフェイスポインタのコピー>ごとに Releaseが呼ばれる。
  2. QueryInterfaceの呼び出し元は AddRefを呼ばない。
  3. ということは……

あと、キャストせずに thisを代入して返してるけど、どういうキャストの場合に thisが変化するか熟知してない自分には不安なコード。

「QueryInterface キャスト」で検索した。そうそう、こういう懸念、

既に示したQueryInterfaceの問題点は、どのようなインターフェースが渡されてもオブジェクトのアドレスをそのまま返した点です。 このとき、適切なインターフェースでオブジェクトをキャストしていれば、 インターフェースのvptrは適切なvtblを識別するようになります。

サクラエディタで実装する、IDWriteTextRendererを継承した GdiTextRendererに真っ当な QueryInterfaceや、InterlockedIncrement/InterlockedDecrementを使った AddRef/Releaseが必要かといえばいらないんだろうけど(だから動いてる)、ざる実装でも構いはしないんだけど、一見それっぽいのはミスリーディングでよろしくない(問題があるとして、ね)。


 @2013-05-19 _d.patchをベースにしたパッチを投稿したよ。

寝る前の3時間でやっつけた!(つもりになってるだけで返り討ち……とか)。

 @2013-05-20 最初のダメージ

どういう思考をたどったのか、地震(※震源は前住んでたとこだ)に揺れる風呂場で気がついた。まだバージョン管理されていない新ファイル3つがパッチに含まれていなかった。サイズが半分になってるのは小さすぎると思ったんだ。以前指摘されたのと同じミス。

 @2013-05-25 コメントを頂戴しました。

https://sourceforge.net/p/sakura-editor/patchunicode/482/?limit=20#c9f7

 @2013-05-25 trunk2_directwrite_e.patch

無意味な(そう見える@2013-05-27)関数分割(20130501p01.02.01)によって

  1. トラップのようなエントリポイントが生じていること
  2. 分割した関数に同じ名前(オーバーロード)や似た名前を与えていることによって名が体を表していない(似た関数を区別するのに十分な名前を与えていない)こと。
  3. その必要がないのにメンバ関数にすると、関数全体を見渡して thisが大量にもたらす入力パラメータ(※非constメンバ関数の場合は出力パラメータを兼ねる)を見つけ出すのが面倒くさい

という問題に対処した点は汲んでもらえなかったようだ。Cインターフェイスという名の、DWriteContextの opaqueポインタ読み方(ハンドル)を操作する限られた関数群をとっぱらって、DWriteContextクラス自体をモジュールの外へエクスポートしたことで事態はさらに悪化した。

クラスっていうのは内にも外にも過度の依存性/コンテクスト/癒着をもたらすのだなあ。どれだけ良い点、Cからの改善点を並べようと一部のクソのために C++を忌避するという選択は理解できる。

 @2013-05-26 補記。ペンディング。

あ、エクスポートにあたって private化はされてました。トラップは対外的には隠されていたし、呼び出し階層が明確になっていた。でも DirectWrite/DirectX系の定数をモジュールの外から隠せてない。レンダリングパラメータを int, floatと変換関数のセットで再定義してる意味がない。


チケットが pendingされてしまいました。遅いのは vim-jpが DirectWriteをオプション扱いにする理由として挙げていてわかっていたことだし、描画方法と描画先を両方とも GDIから Direct2Dに完全移行できれば改善が期待できるし、遅くても試したい人はいっぱいいたと思うのになあ。


 @2013-05-28 いまさらだけど

互換インターフェイスを C++で定義するならヘッダに書く内容は DWriteContextそのままではなくこんなんになると思う。

class DirectWrite
{
public:
  static DirectWrite* Init();

  DrawText();
  SetFont();
  SetRenderingParam();
  ...
  //< Initと対になる後片付け関数。
private:
  DirectWrite(); // 呼ばせない。代わりに Init().
  //< たぶんコピーコンストラクタも禁止しないといけない。
  //< ポインタメンバがあるので代入演算子も禁止しないといけない。

  struct DataMember;
  DataMember* m;
};

ポリモーフィズムが必要なわけでないしファクトリメソッドの代わりにコンストラクタでもいいのかも。それでもこっそりメンバの追加はできるだろうし。


 @2013-06-11 DirectWrite採用への布石。待望のパッチ

Sakura Editor / PatchUnicode / #588 文字列を一度にExtTextOutで描画する

ただ口を開けて待ってたわけではなかったんだよ(いいわけ)。「ところで、DispTextって文字の数だけ呼ばれるんだけど、こんなんを仮想関数にしても良いものだろうか。9か月前の自分はできるだけ長い GLYPH_RUNを対象にすることで回数を減らそうとしてたみたいだけど(20120201)」と書いていた自分はというと、CEditView_Paint.cppの似たような所をいじるまではやっていて、そこから、どいつにどうやって一挙に文字を書き出させるのが(どこでどこまで決めるのが)いいんだろう?というところで停止していた。

@2013-05-26 もしかしたら違う理由かも。過去の日記を読む限りでは、べらぼうに遅い→まあまあ遅い→まともな速度、と2段の変化があったようで、2段階で改善したことは記憶してなかったから。表示品質があまり変わらなかったのと細かい詰めを放り投げた結果として常用してないのかも。

@2013-05-27 SetFontと SetFontに関しては同じ機能を提供しつつ、LOGFONT版でなく HFONT版を呼んだときには、前回の HFONTとの比較結果次第で以降の処理を省略する意図がありそう。みみっちい。ポインタ/ハンドルを比較してその先の値が同じかどうかわかるかいな。そんな目的のためだけにメンバ変数を増やしますか。しかし、効くのだろうか。自分のパッチでは std::basic_string<TCHAR>を毎回作って lfFaceNameの比較をしていた。手を抜きすぎだとは思う。

読み方 オゥペイクだってさ。オパックだと思ってたよ。思い込みは恐いよ。

本日のツッコミ(全2件) ツッコミを入れる

もかスクリーンショット拝見しました。ExtTextOutのETO_OPAQUEようするに"背景を塗りつぶす"に相当する処..

ds14050背景画像を指定したスクリーンショットもとってみました。確かに仰るとおりでした。(novice123さんが背景画像を指..


2013年04月30日 (火) 警察と話をするときは新手の登場を黙って受け入れてはいけないな。途中から重量級を含めて3対1だよ。こちらを向かせる、座らせる、(自己)紹介をさせるなど、黙って耐えるのでなく、いろいろと試せる手はあったはずだ。相手だって威圧するのが目的で居るのではないはずだから(そういう建前がなかったらお手上げ)。ああ不愉快だ。

最終更新: 2013-05-02T16:45+0900

[SakuraEditor] 規制されたからここで。

830 :名無しさん@お腹いっぱい。:2013/04/30(火) 13:45:27.36 ID:ph+iG0mE0
>>829 
これってブロックコメントの色付け方法が変更されるの?違う? 
完全に閉じたときのみに色変更してほしいんだが……。 
現在の仕様のブロックコメント開始文字列があったらただちに色変更されるのは、(他人の)jsコード見るとき非常に困る。
>>830
829で指摘した変更は単なる最適化を目的としたものだよ。

>現在の仕様のブロックコメント開始文字列があったらただちに
これは ANSI板からの仕様みたいだし、自分としても、画面内の
情報だけに頼って色分けできたほうがどんなファイルを開いた場
合でも軽くていいと思う。対かっこ強調などもそういう割り切り
が多いよ。

せっかくこの日記に書いてるのだし、SHJS方式にも言及しておくと、あれでもやっぱり行末を超えた時点で、コメント終了マークが存在しなくても、コメント開始が確定してしまう。その取り消しを可能にするのがバックトラック版(20090808p01)なんだけど、あれができるのは Web上に載せるコード片が対象だからっていうのがある。IDEでもそれは許されるだろうけど、テキストエディタではどうだろう。バックグラウンドで色分けスレッドを走らせて非同期に画面上の文字の色を変えていくというのは可能だろうけど、わずかなラグ(ちらつき)が気になったりするだろうね。非同期だけど一瞬だけは待つことにして大体の場合は同期的に見えるというのだと、いいのかな? Operaだったか、ページの描画待ち時間を指定できたけど、あれはそういう意味だったのか。


予想外の展開。未完成の .jsに対する色分けかと思っていたら、XPathを表現した文字列に含まれる /* をコメント開始と誤認識するってな話だった。

832 :名無しさん@お腹いっぱい。:2013/04/30(火) 18:48:43.80 ID:ph+iG0mE0
>>831
?……そのプログラムって何を指すの?サクラ側の実行プログラムのこと?
例に挙げたjs(JavaScript)のこと?
jsでは「/*~*/」がブロックコメントという扱いだけど、「/*」の文字列はXPathなどコード上記述されることがままある。
このとき「*/」が以降のコードのどこかに記されてない限りコメント色が付いたまま。
だけどjsの実行上は問題ない。あくまで視認の問題。他人のコードを読むときに困ってるんだ。 

正規表現キーワードでは回避できないんだよね。既知の不具合です>サクラエディタBBS[7020]

掲示板のこのやりとりが 20090808p03のきっかけだった。自分では今でも使ってるんだけど、性能がときどき問題になる。バイナリファイル(=改行が少ない)を開いて検索したときに顕著。

もやもやするって書いちゃった(20130425)けど、このパッチ(#431 PHPヒアドキュメント/C++ RowString/C# quoted String等対応)が適用されたら、文字列の色分けを正規表現キーワードから組み込みのものにまた任せることができるし、それで解決すると思う。ただし、正規表現リテラルの中の /* は解決しない。


 @2013-05-02 vimだって?

834 :名無しさん@お腹いっぱい。:2013/05/02(木) 05:53:25.17 ID:8gLFxXSe0 
正規表現リテラルとか、htmlやphpみたいな文脈による言語変更とかを考えだすと限界があるんだよな・・・ 
vimのシンタックスエンジンを組み込みたい。あれかなり強力だし

どんなのだろうと検索したら見つかった。>syntax - vimdoc-ja< 難しすぎるでしょ。特定のキーワード(containとか contained)がフラットな記述に構造を与えてて余計に難しいってのもあるけど。


2013年04月14日 (日)

最終更新: 2013-04-15T03:23+0900

[SakuraEditor][Ruby] サクラエディタBBS[7601]

  • Editor定数の参照で落ちる。他の名前なら問題なし。
  • $Editorとすることで Editorオブジェクトが得られる。
  • マクロコマンドは先頭を小文字にしないと呼べない。(TraceOut→traceOutなど)
  • Editorオブジェクトに SCRIPTITEM_GLOBALMEMBERSフラグが付いてると、Rubyマクロのばあい ITypeLibが必要になる。(ITypeInfo->GetContainingTypeLibを実装する必要がある)
  • ITypeInfo->GetFuncDescがマクロコマンドの数(271)×3回、マクロを実行する前に呼ばれる。
  • 3回目に取得された ITypeInfoがリリースされない。
  • Editorオブジェクトもリリースが足りない。
  • 実行の成否にかかわらず一回につき 100KBぐらいメモリ使用量が増える。
  • リソースリークをなくしたければ参照カウントを捨てて、マクロ実行の前後ですべてのオブジェクトの作成・破棄を行うのが確実。
  • 12回目のマクロ呼び出しが必ず失敗する。例外が発生しているもよう。
  • TYPEATTR.cFuncsの数を減らしてマクロコマンドの数をごまかすと 11回を超えて成功するようになったり。リリースモードにしても回数は変化する。
  • 失敗しても繰り返しマクロを実行してるとときどき成功する。
  • マクロ実行に失敗するとき、IActiveScriptParse->ParseScriptText直前の IActiveScript->SetScriptState(SCRIPTSTATE_STARTED)呼び出しから足取りがつかめなくなる。
  • SetScriptStateを飛ばして ParseScriptTextを実行するようにすると例外はなく、正常にエラーメッセージ("実行に失敗しました")が表示される。なんにせよ失敗。
  • スクリプトエンジンをマクロ実行ごとにきれいに片付けられていれば、○回目から失敗みたいなことは起こらないと思うんだけど。なにが継続しているのか、あるいはこちらの何が破壊されていってるのか。

どうせ Rubyマクロはコマンドの先頭を小文字にする必要があって他のマクロと同じように書けないのだし、使われてないからこれまでに書かれたマクロ資産との互換性を図る必要もないし、例外とリークの元になり実行前の負荷も発生させる SCRIPTITEM_GLOBALMEMBERSフラグを落とすのがいいと思う。ScriptEngine名が "RubyScript." で始まるときとかに限って。

SCRIPTITEM_GLOBALMEMBERSフラグを落とすと $Editor.insTextだけのマクロは何十回でも問題なく実行できてるけど、それだけで安心してよいものか。Editorと書いたときのように(数字で始まるマクロコマンドを呼んだときも?)初回で確実に落ちるというだけならいいけど、何回も実行してるうちに運が悪ければ落ちるというのがあれば(たち)が悪い。


2012年10月23日 (火) 役に立つ機会があるかはともかく、least negative valueという表現。そんなに、ネガティブ(負)じゃない、値。-35と -35.5では -35が LNVらしい。※言及リンクは省略しました。

最終更新: 2012-10-24T00:56+0900

[SakuraEditor] またやらかしてたみたい。これ→20091026p01

矩形の選択中の未選択状態でコピーをすると矩形のままになる - ID: 3578282

IsBoxSelectingは IsTextSelectingに含まれる概念だと思われるから IsTextSelecting(0幅選択を含む選択状態)と IsTextSelected(1文字以上選択されている)の2つだけで判定条件を書きたい。0文字選択→選択キャンセル&移動。1文字以上選択→選択キャンセル。実際の処理は共通でいけるはず。

IsTextSelectingは BoxSelectが必ずロックを伴うことを当てにして今のように書かれてる気がするので追い追い修正が必要になりそう。>20090923p01

でも黙っとく。実際に正しく動くか検証もしてないし。


2012年10月02日 (火) すすぐ、というのは不純物の入った水を万倍の万倍の万倍に薄めて無いも同然になりました、ってこと? 理科の先生が一生懸命いつまでもこするより回数を増やした方がいいって言ってるのを見た記憶があるけど、万にかかる係数を増やすより指数を増やした方が効率がいいって事?■検索……。「洗浄の回数について - 質問・相談ならMSN相談箱」 同量の水を小分けにして回数を増やす。溶解の速度。廃液を減らす。初めて聞いた言葉>コンタミ(contamination)。

最終更新: 2012-10-02T16:53+0900

[SakuraEditor] マクロのお試しとかデバッグに。

選択された文字列かファイルの全体を JScriptマクロとして実行する。登録の手間なしにマクロを試してみたり、マクロのデバッグ&実行を登録や読み直しの手間なしに行うために。何個目の車輪になるのか数え切れないほどだと思ったので書いてなかったが、今日読んだ本*に同じようなアイディアが書いてあったので。2010年作。

/*
	InstantMacro.js
	選択テキストまたはファイル全体を JScriptマクロとして実行する。
*/

var jsmacro = Editor.GetSelectedString(0) || Editor_AllText();
try {
	eval(jsmacro);
} catch(e) {
	Editor.TraceOut(e.message);
}

function Editor_AllText()
{
	var text = "";
	for(var n = 1, n_max = Editor.GetLineCount(0); n <= n_max; ++n) {
		text += Editor.GetLineStr(n);
	}
	return text;
}

* リンク先は今日読み終わった本。このマンガを読んでも出てこない。