Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
vimlike_textview for mikutter (development preview)
#!/usr/bin/env ruby
#-*- coding: utf-8 -*-
require 'gtk2'
require 'gtksourceview2'
class VimlikeTextView < Gtk::SourceView
attr_accessor :current_mode, :default_bgcolor, :insert_bgcolor, :visual_bgcolor, :select
@@hook_keys_normal = ['a', 'A', 'i', 'I', 'o', 'O', 'v', 'h', 'j', 'k', 'l', 'u', 'p', 'P', 'x', 'X', 'BackSpace']
@@hook_keys_insert = ['Escape']
@@hook_keys_visual = ['Escape', 'h', 'j', 'k', 'l', 'y', 'x']
@@hist_limit = 8000
@@post_history = []
@@post_history_ptr = 0
module Mode
NORMAL = 0
INSERT = 1
VISUAL = 2
end
def initialize
super
self.show_line_numbers = true
@select = false
@current_mode = Mode::NORMAL
update_bgcolor
@default_bgcolor = Gdk::Color.new(0xffff, 0xffff, 0xffff)
@insert_bgcolor = Gdk::Color.new(0xffff, 0xffff, 0xaaaa)
@visual_bgcolor = Gdk::Color.new(0xffff, 0xaaaa, 0xffff)
@history_stack = []
@history_stack.push([self.buffer.text, self.buffer.cursor_position])
@stack_ptr = 0
@isundo = false
add_signal(self.buffer)
signal_connect('key_press_event') { |w, e|
key = Gdk::Keyval.to_name(e.keyval)
@isundo = (current_mode == Mode::NORMAL) and (key == 'u')
case current_mode
when Mode::NORMAL
case key
when 'a'
self.current_mode = Mode::INSERT
update_bgcolor
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, 1, @select)
when 'A'
self.current_mode = Mode::INSERT
update_bgcolor
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, 1, @select)
when 'i'
self.current_mode = Mode::INSERT
update_bgcolor
when 'I'
self.current_mode = Mode::INSERT
update_bgcolor
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, -1, @select)
when 'o'
self.current_mode = Mode::INSERT
update_bgcolor
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, 1, @select)
self.insert_at_cursor("\n")
when 'O'
self.current_mode = Mode::INSERT
update_bgcolor
self.move_cursor(Gtk::MOVEMENT_PARAGRAPH_ENDS, -1, @select)
self.insert_at_cursor("\n")
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, -1, @select)
when 'v'
self.current_mode = Mode::VISUAL
update_bgcolor
self.select = true
when 'h'
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, -1, @select)
when 'j'
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, 1, @select)
when 'k'
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, -1, @select)
when 'l'
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, 1, @select)
when 'u'
self.undo
when 'p'
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, 1, @select)
self.paste_clipboard
when 'P'
self.paste_clipboard
when 'x'
self.delete_from_cursor(Gtk::DELETE_CHARS, 1)
when 'X'
self.delete_from_cursor(Gtk::DELETE_CHARS, -1)
when 'BackSpace'
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, -1, @select)
end
true
when Mode::INSERT
case key
when 'Escape'
self.current_mode = Mode::NORMAL
update_bgcolor
end
@@hook_keys_insert.include? key
when Mode::VISUAL
case key
when 'Escape'
self.current_mode = Mode::NORMAL
update_bgcolor
self.select = false
self.select_all(false)
when 'h'
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, -1, @select)
when 'j'
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, 1, @select)
when 'k'
self.move_cursor(Gtk::MOVEMENT_DISPLAY_LINES, -1, @select)
when 'l'
self.move_cursor(Gtk::MOVEMENT_VISUAL_POSITIONS, 1, @select)
when 'y'
self.copy_clipboard
self.select = false
self.select_all(false)
self.current_mode = Mode::NORMAL
update_bgcolor
when 'x'
self.delete_from_cursor(Gtk::DELETE_CHARS, 1)
end
true
end
}
end
def update_bgcolor
case current_mode
when Mode::NORMAL
modify_base(Gtk::STATE_NORMAL, default_bgcolor)
when Mode::INSERT
modify_base(Gtk::STATE_NORMAL, insert_bgcolor)
when Mode::VISUAL
modify_base(Gtk::STATE_NORMAL, visual_bgcolor)
end
end
def self.pushGlobalStack(text)
@@post_history_ptr = @@post_history.length
@@post_history.push(text) end
def add_signal(buffer)
buffer.signal_connect('changed') {
if not @isundo then
@history_stack += @history_stack[@stack_ptr..-2].reverse
self.push_buffer
@stack_ptr = @history_stack.length - 1
end
}
buffer
end
# 現在のバッファと最新の履歴が異なっていればスタックに現在の状態を追加
def push_buffer
if @history_stack == nil then
@history_stack = [['', 0]]
end
if self.buffer.text != '' then
if @history_stack[-1][0] != self.buffer.text then
@history_stack.push([self.buffer.text, self.buffer.cursor_position])
end
if @history_stack.length > @@hist_limit then
@history_stack = @history_stack[(@history_stack.length - @@hist_limit)..-1]
end
end
end
# undoの実装.バッファの内容を変更すると自動的に履歴スタックに追加されるので,
# 履歴スタックに追加したら最新の履歴を捨てる
def undo
top = @history_stack[@stack_ptr]
if top != nil then
if top[0] == self.buffer.text then
# 最新履歴が現在の状態と同じなら,2番目の履歴を参照
decStackPtr
second = @history_stack[@stack_ptr]
if second != nil then
self.buffer.set_text(second[0])
self.buffer.place_cursor(self.buffer.get_iter_at_offset(second[1]))
else # 上から2番目が空
self.buffer.set_text('')
end
else
self.buffer.set_text(top[0])
self.buffer.place_cursor(self.buffer.get_iter_at_offset(top[1]))
end
else # 履歴スタックが空
self.buffer.set_text('')
end
end
def incStackPtr
if @history_stack.length > @stack_ptr + 1 then
@stack_ptr += 1
end
end
def decStackPtr
if @stack_ptr > 0 then
@stack_ptr -= 1
end
end
def undoGlobalStack
if @@post_history != []
if not defined? @is_global_undo
@is_global_undo = true
end
if @is_global_undo == false
@@post_history_ptr = (@@post_history_ptr - 1) % @@post_history.length
@@post_history_ptr = (@@post_history_ptr - 1) % @@post_history.length
end
self.buffer.set_text(@@post_history[@@post_history_ptr])
@@post_history_ptr = (@@post_history_ptr - 1) % @@post_history.length
@is_global_undo = true
end
end
def redoGlobalStack
if @@post_history != []
if not defined? @is_global_undo
@is_global_undo = false
end
if @is_global_undo == true
@@post_history_ptr = (@@post_history_ptr + 1) % @@post_history.length
@@post_history_ptr = (@@post_history_ptr + 1) % @@post_history.length
end
self.buffer.set_text(@@post_history[@@post_history_ptr])
@@post_history_ptr = (@@post_history_ptr + 1) % @@post_history.length
@is_global_undo = false
end
end
end
vtv = VimlikeTextView.new
w = Gtk::Window.new
w.add(vtv)
w.set_size_request(300,200)
w.signal_connect('key_press_event') { |w, e|
if Gdk::Keyval.to_name(e.keyval) == 'q' then
Gtk.main_quit
end
}
w.signal_connect('destroy') {
Gtk.main_quit
}
w.show_all
Gtk.main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment