Skip to content

Instantly share code, notes, and snippets.

@penguin2716
Created February 29, 2012 11:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save penguin2716/1940257 to your computer and use it in GitHub Desktop.
Save penguin2716/1940257 to your computer and use it in GitHub Desktop.
mikutterの投稿ボックスにEmacsっぽいキーバインドを付けるやつ(スタックがStringになるバグを除去)
#-*- coding: utf-8 -*-
=begin
* これは何?
mikutterの投稿ボックスにEmacsっぽいキーバインドを付けます.
だいたい次みたいなことができます.
・C-[fbnpae] カーソルの移動
・C-[dh] 文字の削除
・C-SPC 選択のトグル
・C-[/z] 戻る
・C-w 選択領域のカット
・C-k 行末までカット
・C-y カーソル位置に貼り付け
・C-g 選択のトグルをOFF
・C-A C-aの全選択がなくなったのでC-Aで全選択にしてみた
* 使い方とか
ファイルをコピーして,ソースを2行書き換えます.
1. gtk_emacslike_textview.rb を mikutter/core/mui/ にコピー
2. 下のパッチをあてます
3. mikutter を再起動しましょう
--- core/mui/gtk_postbox.rb.org 2012-02-29 11:33:51.421695468 +0900
+++ core/mui/gtk_postbox.rb 2012-02-29 20:08:21.966950690 +0900
@@ -8,6 +8,7 @@
require 'thread'
miquire :mui, 'miracle_painter'
miquire :mui, 'intelligent_textview'
+miquire :mui, 'emacslike_textview'
module Gtk
class PostBox < Gtk::EventBox
@@ -62,7 +63,7 @@
def widget_post
return @post if defined?(@post)
- @post = Gtk::TextView.new
+ @post = Gtk::EmacsLikeTextView.new
post_set_default_text(@post)
@post.wrap_mode = Gtk::TextTag::WRAP_CHAR
@post.border_width = 2
* 投稿が完了してもundoスタックが残ってるんだけど
仕様です.
* バグを見つけました!
@penguin2716 までリプライとか飛ばして頂けるとうれしいです.
* コードが汚い
ごめんなさい.
=end
require 'gtk2'
module Gtk
class EmacsLikeTextView < Gtk::TextView
# @@hist_limit : 履歴スタックの最大保存数
# @@targetkey : Ctrlで装飾してEmacsっぽいキーバインドにするキー.
# 元から割り当てられていた機能は呼ばない.
# @@unselectkey : 選択トグルを自動的にOFFにするキー.
# @select : 選択トグルのON/OFFを格納
# @history_stack : 履歴スタック
@@hist_limit = 100
@@targetkey = ['A', 'space', 'g', 'f', 'b', 'n', 'p', 'a',
'e', 'd', 'h', 'w', 'k', 'y', 'slash', 'z']
@@unselectkey = ['g', 'd', 'h', 'w', 'k', 'y', 'slash', 'z']
def initialize
super
@select = false
@history_stack = []
@history_stack.push(self.buffer.text)
# バッファが変更されたら自動的に履歴スタックに積む
self.buffer.signal_connect('changed') {
self.push_buffer
}
# キーバインドの追加
self.signal_connect('key_press_event') { |w, e|
if Gdk::Window::ModifierType::CONTROL_MASK ==
e.state & Gdk::Window::CONTROL_MASK then
key = Gdk::Keyval.to_name(e.keyval)
# 選択トグルの解除
if @@unselectkey.select{|k| k == key}.length > 0 then
@select = false
end
case key
when 'A' # 全選択
self.select_all(true)
when 'space' # 選択トグルのON/OFF
if @select then
@select = false
else
@select = true
end
when 'g' # 選択解除
self.select_all(false)
when 'f' # 右に移動
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, 1, @select)
when 'b' # 左に移動
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, -1, @select)
when 'n' # 次の行に移動
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, 1, @select)
when 'p' # 前の行に移動
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, -1, @select)
when 'a' # 行頭へ移動
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, -1, @select)
when 'e' # 行末へ移動
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, 1, @select)
when 'd' # Deleteの挙動
self.delete_from_cursor(Gtk::DELETE_CHARS, 1)
when 'h' # BackSpaceの挙動
self.delete_from_cursor(Gtk::DELETE_CHARS, -1)
when 'w' # カット
self.cut_clipboard
when 'k' # 現在位置から行末までカット.行末の場合はDeleteの挙動になる
before = self.buffer.text
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, 1, true)
self.cut_clipboard
if before == self.buffer.text then
self.delete_from_cursor(Gtk::DELETE_CHARS, 1)
end
when 'y' # 現在位置に貼り付け
self.paste_clipboard
when 'slash', 'z' # undoの挙動
self.undo
end
end
# Emacsっぽいキーバインドとして実行したら,もとから割り当てられていた機能は呼ばない
if @@targetkey.select{|k| k == key}.length > 0 then
true
else
false
end
}
end
# 現在のバッファと最新の履歴が異なっていればスタックに現在の状態を追加
def push_buffer
if @history_stack == nil then
@history_stack = ['']
end
if self.buffer.text != '' then
if @history_stack[-1] != self.buffer.text then
@history_stack.push(self.buffer.text)
end
if @history_stack.size > @@hist_limit then
@history_stack.delete(@history_stack[0])
end
end
end
# undoの実装.バッファの内容を変更すると自動的に履歴スタックに追加されるので,
# 履歴スタックに追加したら最新の履歴を捨てる
def undo
top = @history_stack[-1]
if top != nil then
if top == self.buffer.text then
# 最新履歴が現在の状態と同じなら,2番目の履歴を参照
@history_stack.pop
second = @history_stack.pop
if second != nil then
self.buffer.set_text(second)
@history_stack.pop
else # 上から2番目が空
self.buffer.set_text('')
@history_stack.pop
end
else
self.buffer.set_text(top)
@history_stack.pop
end
else # 履歴スタックが空
self.buffer.set_text('')
@history_stack.pop
end
end
# 初期状態にリセットする.現在は使っていない
def reset
@history_stack = []
@select = false
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment