2019-12-03 (Tue) [長年日記]
_ Textbringerの名前の由来
※Textbringer Advent Calendar 2019の参加記事です。
Textbringerという名前はStormbringerに由来している。 StormbringerといえばDeep Purple第3期の佳作(邦題『嵐の使者』)を思い出す人も多いと思うが、エルリックサーガ( メルニボネの皇子をはじめとするマイクルムアコックの小説群)に登場する魔剣Stormbringerの方である。 殺した相手の魂を吸い取って、持ち主である病弱なエルリックに活力を与えてくれる強力な剣であり、我々定命の者の能力を拡張するテキストエディタの名前としてふさわしい。
なお、ムアコックの読者からはしばしば不吉な名前だと言われるが、Stormbringerが危険なのはstormをbringするからであって、Textbringerがbringするのはtextなので安心してほしい。
_ tDiary 5.1.0へのアップデート
tDiary 5.1.0へアップデートした。
リリースノートに従ってAmazonプラグインの設定をしたが、429 Too Many Requestsでどうにもならない感じだったので、以下のスクリプトでキャッシュを元にリンクするようにして、Amazonプラグインは使わないことにした。
#!/usr/bin/ruby
require 'rexml/document'
class AmazonItem
  def initialize(xml, parser = :rexml, label = nil)
    @parser = parser
    @label = label
    if parser == :oga
      @doc = Oga.parse_xml(xml)
      @item = @doc.xpath('*/*/Item')[0]
    else
      @doc = REXML::Document::new( REXML::Source::new( xml ) ).root
      @item = @doc.elements.to_a( '*/Item' )[0]
    end
  end
  
  def nodes(path)
    if @parser == :oga
      if @item
        @item.xpath(path)
      else
        @doc.xpath(path)
      end
    else
      if @item
        @item.elements.to_a(path)
      else
        @doc.elements.to_a(path)
      end
    end
  end
  
  def has_item?
    !@item.nil?
  end
  
  def title
    @label || nodes('*/Title')[0].text
  end
  def url
    nodes('DetailPageURL')[0].text
  end
  def to_rd
    %Q[((<"#{title}"|URL:#{url}>))]
  end
  def to_md
    "[#{title}](#{url})"
  end
end
CACHE_PATH = "/home/shugo/diary/cache"
def amazon_get(asin, label)
  country = "jp"
  cache = "#{CACHE_PATH}/amazon"
  s = asin.gsub(/-/, "")
  xml = File.read("#{cache}/#{country}#{s}.xml")
  AmazonItem.new(xml, :rexml, label)
end
ARGF.inplace_mode = ".bak"
ARGF.each_line do |line|
  line.gsub!(/\(\(% *(isbn|amazon)\w* *["'](?<asin>.*?)["'](, *["'](?<label>.*?)["'])?.*?%\)\)/) {
    item = amazon_get($~[:asin], $~[:label])
    item.to_rd
  }
  line.gsub!(/\{\{ *(isbn|amazon)\w* *["'](?<asin>.*?)["'](, *["'](?<label>.*?)["'])?.*?\}\}/) {
    item = amazon_get($~[:asin], $~[:label])
    item.to_md
  }
  print line
end
2019-12-04 (Wed) [長年日記]
_ Textbringerのインストール
※Textbringer Advent Calendar 2019の参加記事です。
READMEに書いてある通りだが、
$ gem install textbringer
でインストールできる。日本語を扱うためにncurseswが必要な環境があるので、その場合は例えば以下のように事前にインストールしておく。
$ sudo apt install libncursesw5-dev
インストールしたら
$ textbringer
で起動できるが、コマンド名が長いので自分は以下のようなスクリプトを~/bin/tbに置いている。
#!/bin/sh
/home/shugo/local/bin/ruby -I /home/shugo/src/textbringer/lib /home/shugo/src/textbringer/exe/textbringer "$@"
上の例は手元のソースコードを直接実行しているが、gemで入れたコマンドを使う場合は alias tb=textbringer とでもしておけばよい。
ただし、rbenvを使っていたりするとRubyのバージョンによってgemが入ってないことがありがちなので注意。
2019-12-08 (Sun) [長年日記]
_ Textbringerのredo_command
※Textbringer Advent Calendar 2019の参加記事です。
Textbringerの操作はほとんどEmacsと同じなのでEmacsチートシートを利用できるが、少しだけ違うところがある。
EmacsのC-/でundoを実行できるがちょっと癖があり、戻しすぎた時にredoするコマンドがなく、いったんundoをC-fなどの他のコマンドで中断すると、以降のundoはそれまでのundoを取り消す動作になる。
Textbringerはこの挙動は踏襲せず、redo_command(デフォルトではC-x C-/)というredo用コマンドを別途用意している。
ちなみにundoのコマンド名はundoなのに対し、redo_commandというコマンド名になっているのは、redoはRubyでは予約語であるため。 Rubyでは予約語と同名のメソッドも定義できるが、レシーバを省略した呼び出しができないのは不便なので、別の名前にしている。
2019-12-10 (Tue) [長年日記]
_ Textbringerのマーク
※Textbringer Advent Calendar 2019の参加記事です。
TextbringerのマークはEmacs同様C-SPCでセットできる。
最近のEmacsではマークとポイント(カーソル位置)の範囲がデフォルトでハイライト表示されるようだが、Textbringerではエコーエリア(最下行)に控えめに「Mark set」と表示されるだけの、昔のEmacsのような挙動にしている。
マークしたからといって必ずしもリージョン(マークとポイントの範囲)のコピーなどの操作を行うわけではなく、C-x C-x(exhange_point_and_mark)でマークとポイントの位置を入れ替えたりといった用途もあるので、これはこれで合理的だと思っている。やっぱりやめた、という時にC-gとかでハイライト表示を消さなくていいし。
2019-12-14 (Sat) [長年日記]
_ 平成Ruby会議01
平成Ruby会議01で発表してきた。
立派なビルだなあと思って写真を撮ってたけど、このビルとは違う棟だったらしくて道に迷った。
ライブコーディングの練習を事前にしておいたのが徒になって、練習の時のメソッド定義が残っていて微妙な感じになってしまった。 置いておいたステッカーはなくなっていたので平成生まれの人は優しい。
印象に残ったのは氏久さんのRubyKaigiのトークに触発されて右代入を実装した話で、Ruby Hack Challengeとかの成果もあって若い人たちがRubyをハックしてくれているのは心強い。
スタッフ・参加者のみなさん、ありがとうございました。いい会議でした。
2019-12-25 (Wed) [長年日記]
_ Textbringer 1.0.2リリース
※Textbringer Advent Calendar 2019の参加記事です。
Ruby 2.7.0がリリースされたので、Textbringer 1.0.2をリリースした。
変更点:
- isearch_quoted_insertの追加(インクリメントサーチ中にC-qで次に入力した特殊文字を検索できるようにするため)
 - define_keyやヘルプでESCの代わりにM-記法を使うようにした
 - indent_new_comment_line_commandの追加(平成Ruby会議01でライブコーディングした機能)
 - find_alternate_fileの追加(ただしEmacsと違って現在のバッファはkillせず残す)
 - Rubyモードのインデントのバグの修正
 
最後のが一番つらいやつで、平成Ruby会議の時も当日の朝にライブコーディングの練習をしていたらバグに気づいて修正していた。
デモの練習しようと思ったらTextbringerバグってるんだけど……
— Shugo Maeda (@shugomaeda) December 13, 2019
TextbringerのRubyモードでは、インデントの計算をする時にまず最初にカーソル位置から一番近いclassやdef、ifなどを探してそこからカーソル位置までのトークンをRipperで切り出す。例えば、
module Foo
  class Bar
    def foo
      if bar
      end
    end
end
の最後のendの行でC-iすると
      if bar
      end
    end
end
の部分のトークンを切り出し、ifの行のインデント位置からendの対応関係を見てインデントを計算する。 ところが、Ripper.lexは2番目のendで文法エラーを検出して最後のendはトークンとして返してくれない。
そこでTextbringer 1.0.2では途中で文法エラーになったら残りの部分を繰り返しRipperに食わせて最後までトークンを切り出すようにした。
また、
      if bar
      end
    }
のように { との対応が取れていない } があると、 :on_rbrace ではなく :on_embexpr_end として返ってくるのでそのあたりも修正した。
Ripperは正しいRubyプログラムを先頭から食わせるような使い方では便利だが、テキストエディタのように正しくないかもしれないRubyプログラムを一部分だけ処理するようなケースではちょっと工夫が必要になる。

_ kurod1492 [なるほど、そういう理由なんですね。ハイライトしてくれるEmacsに慣れてしまった身としては、ハイライトしてくれると嬉..]
_ shugo [プルリクエストお待ちしております。]
_ kurod1492 [がんばります]