Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
622 lines (526 sloc) 19.3 KB
require 'edit_view_swt/document'
require 'edit_view_swt/edit_tab'
require 'edit_view_swt/word_movement'
$:.push(Redcar.root + "/javamateview/lib")
require "javamateview"
module Redcar
class EditViewSWT
NAVIGATION_COMMANDS = {
:LINE_UP => 16777217,
:LINE_DOWN => 16777218,
:LINE_START => 16777223,
:LINE_END => 16777224,
:COLUMN_PREVIOUS => 16777219,
:COLUMN_NEXT => 16777220,
:PAGE_UP => 16777221,
:PAGE_DOWN => 16777222,
:WORD_PREVIOUS => 17039363,
:WORD_NEXT => 17039364,
:TEXT_START => 17039367,
:TEXT_END => 17039368,
:WINDOW_START => 17039365,
:WINDOW_END => 17039366
}
SELECTION_COMMANDS = {
:SELECT_ALL => 262209,
:SELECT_LINE_UP => 16908289,
:SELECT_LINE_DOWN => 16908290,
:SELECT_LINE_START => 16908295,
:SELECT_LINE_END => 16908296,
:SELECT_COLUMN_PREVIOUS => 16908291,
:SELECT_COLUMN_NEXT => 16908292,
:SELECT_PAGE_UP => 16908293,
:SELECT_PAGE_DOWN => 16908294,
:SELECT_WORD_PREVIOUS => 17170435,
:SELECT_WORD_NEXT => 17170436,
:SELECT_TEXT_START => 17170439,
:SELECT_TEXT_END => 17170440,
:SELECT_WINDOW_START => 17170437,
:SELECT_WINDOW_END => 17170438
}
MODIFICATION_COMMANDS = {
:CUT => 131199,
:COPY => 17039369,
:PASTE => 16908297,
:DELETE_PREVIOUS => 8,
:DELETE_NEXT => 0x7F,
:DELETE_WORD_PREVIOUS => 262152,
:DELETE_WORD_NEXT => 262271
}
ALL_ACTIONS = NAVIGATION_COMMANDS.merge(SELECTION_COMMANDS).merge(MODIFICATION_COMMANDS)
include Redcar::Observable
include Redcar::Controller
def self.start_with_app
if gui = Redcar.gui
gui.register_controllers(Redcar::EditTab => EditViewSWT::Tab)
end
load_textmate_assets
end
def self.load_textmate_assets
Redcar.log.info("loaded JavaMateView #{JavaMateView::MateText.VERSION}")
Redcar.log.benchmark("load textmate assets") do
load_textmate_assets_from_dir(RedcarBundles.dir)
Redcar.plugin_manager.loaded_plugins.each do |plugin|
load_textmate_assets_from_dir(File.dirname(plugin.definition_file) + "/")
end
load_textmate_assets_from_dir(Redcar.user_dir + "/")
end
EditView.themes.unshift(*JavaMateView::ThemeManager.themes.to_a.map {|th| th.name })
end
def self.load_textmate_assets_from_dir(dir)
JavaMateView::Bundle.load_bundles(dir)
JavaMateView::ThemeManager.load_themes(dir)
end
attr_reader :mate_text, :widget, :model, :key_listener, :verify_key_listener
def initialize(model, parent, options={})
@options = options
@model = model
@parent = parent
@handlers = []
create_mate_text
create_document
Redcar.plugin_manager.objects_implementing(:edit_view_gui_update).each do |object|
object.edit_view_gui_update(@mate_text)
end
attach_listeners
@mate_text.set_grammar_by_name("Plain Text")
@model.set_grammar("Plain Text")
@mate_text.set_theme_by_name(EditView.theme)
create_undo_manager
@document.attach_modification_listeners # comes after undo manager
remove_control_keybindings
mate_text.add_grammar_listener do |new_grammar|
@model.set_grammar(new_grammar)
end
create_model_listeners
end
def create_mate_text
@mate_text = JavaMateView::MateText.new(@parent, !!@options[:single_line])
@mate_text.set_font(EditView.font, EditView.font_size)
@model.controller = self
add_styled_text_command_key_listeners
end
class CommandKeyListener
attr_reader :st
def initialize(styled_text, edit_view)
@edit_view = edit_view
@st = styled_text
end
def key_pressed(event)
end
def key_released(event)
record_action(event)
end
# This pile of crap is copied from StyledText#handleKey. Wouldn't
# it be great if this logic was accessible on StyledText somehow?
def record_action(event)
if (event.keyCode != 0)
# special key pressed (e.g., F1)
action = st.getKeyBinding(event.keyCode | event.stateMask)
else
# character key pressed
action = st.getKeyBinding(event.character | event.stateMask)
if (action == Swt::SWT::NULL)
# see if we have a control character
if ((event.stateMask & Swt::SWT::CTRL) != 0 && event.character <= 31)
# get the character from the CTRL+char sequence, the control
# key subtracts 64 from the value of the key that it modifies
c = event.character + 64
action = st.getKeyBinding(c | event.stateMask)
end
end
end
if (action == Swt::SWT::NULL)
ignore = false
if (Redcar.platform == :osx)
# Ignore accelerator key combinations (we do not want to
# insert a character in the text in this instance). Do not
# ignore COMMAND+ALT combinations since that key sequence
# produces characters on the mac.
ignore = (event.stateMask & Swt::SWT::COMMAND) != 0 ||
(event.stateMask & Swt::SWT::CTRL) != 0
else
# Ignore accelerator key combinations (we do not want to
# insert a character in the text in this instance). Don't
# ignore CTRL+ALT combinations since that is the Alt Gr
# key on some keyboards. See bug 20953.
ignore = (event.stateMask ^ Swt::SWT::ALT) == 0 ||
(event.stateMask ^ Swt::SWT::CTRL) == 0 ||
(event.stateMask ^ (Swt::SWT::ALT | Swt::SWT::SHIFT)) == 0 ||
(event.stateMask ^ (Swt::SWT::CTRL | Swt::SWT::SHIFT)) == 0
end
# -ignore anything below SPACE except for line delimiter keys and tab.
# -ignore DEL
if (!ignore && event.character > 31 && event.character != Swt::SWT::DEL ||
event.character == Swt::SWT::CR || event.character == Swt::SWT::LF ||
event.character == Swt::SWT::TAB)
@edit_view.history.record(event.character)
end
else
@edit_view.history.record(EditViewSWT::ALL_ACTIONS.invert[action])
end
end
end
def add_styled_text_command_key_listeners
st = @mate_text.get_text_widget
st.add_key_listener(CommandKeyListener.new(st, @model))
end
def create_undo_manager
@undo_manager = JFace::Text::TextViewerUndoManager.new(5000)
@undo_manager.connect(@mate_text.viewer)
end
def undo
@undo_manager.undo
unless @undo_manager.undoable?
@undoable_override = false
end
EditView.undo_sensitivity.recompute
EditView.redo_sensitivity.recompute
end
def undoable?
# The override malarky is because the JFace::TextViewerUndoManager doesn't
# recognize that typing while in Block Selection mode makes the edit view
# undoable. (Even though it faithfully records the actions, and responds
# correctly to "undo".) So we override the undo manager for this case.
@undoable_override || @undo_manager.undoable
end
def redo
@undo_manager.redo
EditView.undo_sensitivity.recompute
EditView.redo_sensitivity.recompute
end
def redoable?
@undo_manager.redoable
end
def reset_undo
@undo_manager.reset
@undoable_override = false
end
def compound
begin_compound
yield
end_compound
end
def begin_compound
@undo_manager.begin_compound_change
end
def end_compound
@undo_manager.end_compound_change
end
def annotations(options = nil)
return @mate_text.annotations unless options
if options[:line]
@mate_text.annotationsOnLine(options[:line] - 1)
elsif options[:type]
annotations_of_type(options[:type])
end
end
def annotations_of_type(type)
annotations.select {|a| a.type == type }
end
def add_annotation_type(name, image_path, rgb)
rgb = Swt::Graphics::RGB.new(rgb[0], rgb[1], rgb[2])
@mate_text.addAnnotationType(name, image_path, rgb)
end
def add_annotation(annotation_name, line, text, start, length)
@mate_text.addAnnotation(annotation_name, line, text, start, length)
end
def remove_annotation(mate_annotation)
@mate_text.removeAnnotation(mate_annotation)
end
def remove_all_annotations(options = nil)
return @mate_text.remove_all_annotations unless options
annotations(options).each do |ann|
remove_annotation(ann)
end
end
def reset_right_margin
return if @options[:single_line]
if @model.word_wrap?
size = @mate_text.get_text_widget.get_size
width = size.x
@mate_text.get_text_widget.right_margin = width - char_width*(@model.margin_column||0) - 3*char_width
else
@mate_text.get_text_widget.right_margin = 0
end
end
def char_width
return 0 if !!@options[:single_line]
@char_width ||= begin
gc = Swt::Graphics::GC.new(@mate_text.get_text_widget)
fm = gc.getFontMetrics
width = fm.getAverageCharWidth
gc.dispose
width
end
end
def clear_char_width
@char_width = nil
end
def create_document
@document = EditViewSWT::Document.new(@model.document, @mate_text.mate_document)
@model.document.controller = @document
h1 = @model.document.add_listener(:before => :new_mirror,
&method(:update_grammar))
h2 = @model.add_listener(:grammar_changed, &method(:model_grammar_changed))
h3 = @model.add_listener(:focussed, &method(:focus))
h4 = @model.add_listener(:tab_width_changed) do |new_tab_width|
@mate_text.get_control.set_tabs(new_tab_width)
end
h5 = @model.add_listener(:invisibles_changed) do |new_bool|
@mate_text.showInvisibles(new_bool)
end
h6 = @model.add_listener(:word_wrap_changed) do |should_word_wrap|
@mate_text.set_word_wrap(should_word_wrap)
reset_right_margin
end
h11 = @model.add_listener(:margin_column_changed) do |new_column|
@mate_text.set_margin_column(new_column)
reset_right_margin
end
h12 = @model.add_listener(:show_margin_changed) do |new_bool|
if new_bool
@mate_text.set_margin_column(@model.margin_column)
else
@mate_text.set_margin_column(-1)
end
end
h7 = @model.add_listener(:font_changed) do
@mate_text.set_font(EditView.font, EditView.font_size)
clear_char_width
reset_right_margin
end
h8 = @model.add_listener(:theme_changed) do
@mate_text.set_theme_by_name(EditView.theme)
end
h9 = @model.add_listener(:line_number_visibility_changed) do |new_bool|
@mate_text.set_line_numbers_visible(new_bool)
end
h13 = @model.document.add_listener(:changed) do
@undoable_override = true
end
@mate_text.getTextWidget.addMouseListener(MouseListener.new(self))
@mate_text.getTextWidget.addFocusListener(FocusListener.new(self))
@mate_text.getTextWidget.addVerifyListener(VerifyListener.new(@model.document, self))
@mate_text.getTextWidget.addModifyListener(ModifyListener.new(@model.document, self))
@verify_key_listener = VerifyKeyListener.new(self)
@key_listener = KeyListener.new(self)
@mate_text.get_control.add_verify_key_listener(@verify_key_listener)
@mate_text.get_control.add_key_listener(@key_listener)
@handlers << [@model.document, h1] << [@model, h2] << [@model, h3] << [@model, h4] <<
[@model, h5] << [@model, h6] << [@model, h7] << [@model, h8] <<
[@model, h9] << [@model, h11] << [@model.document, h13]
end
def right_click(mouse_event)
if @model.document.controller and @model.document.controller.respond_to?(:right_click)
location = ApplicationSWT.display.get_cursor_location
#offset = @mate_text.parser.styledText.get_offset_at_location(location)
Redcar.safely("right click on edit view") do
@model.document.controller.right_click(@model)
end
end
end
def type_character(character)
mate_text.get_text_widget.doContent(character)
mate_text.get_text_widget.update
end
model_listener :type_character
def invoke_action(action_symbol)
const = EditViewSWT::ALL_ACTIONS[action_symbol]
mate_text.get_text_widget.invokeAction(const)
end
model_listener :invoke_action
class VerifyKeyListener
def initialize(edit_view_swt)
@edit_view_swt = edit_view_swt
end
def verify_key(key_event)
#uncomment this line for key debugging
#puts "got keyevent: #{key_event.character} #{key_event.stateMask}"
if @edit_view_swt.model.document.block_selection_mode?
@edit_view_swt.begin_compound
end
if key_event.character == Swt::SWT::TAB
key_event.doit = !@edit_view_swt.model.tab_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
elsif key_event.character == Swt::SWT::ESC
key_event.doit = !@edit_view_swt.model.esc_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
elsif key_event.keyCode == Swt::SWT::ARROW_LEFT
key_event.doit = !@edit_view_swt.model.left_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
elsif key_event.keyCode == Swt::SWT::ARROW_RIGHT
key_event.doit = !@edit_view_swt.model.right_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
elsif key_event.character == Swt::SWT::DEL
key_event.doit = !@edit_view_swt.model.delete_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
elsif key_event.character == Swt::SWT::BS
key_event.doit = !@edit_view_swt.model.backspace_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
elsif key_event.character == 13 and (key_event.stateMask == Swt::SWT::COMMAND or key_event.stateMask == Swt::SWT::CTRL)
key_event.doit = !@edit_view_swt.model.cmd_enter_pressed(ApplicationSWT::Menu::BindingTranslator.modifiers(key_event))
end
end
end
class KeyListener
def initialize(edit_view_swt)
@edit_view_swt = edit_view_swt
end
def key_pressed(_)
@was_in_block_selection = @edit_view_swt.model.document.block_selection_mode?
end
def key_released(_)
if @was_in_block_selection
@edit_view_swt.end_compound
end
end
end
def delay_parsing
mate_text.delay_parsing { yield }
end
def swt_focus_gained
EditView.focussed_edit_view = @model
@model.focus
end
def swt_focus_lost
EditView.focussed_edit_view = nil
end
def focus
@mate_text.get_control.set_focus
end
def is_current?
EditView.current == @mate_text.get_control
end
def has_focus?
focus_control = ApplicationSWT.display.get_focus_control
focus_control == @mate_text.get_control
end
def exists?
@mate_text.get_control and !@mate_text.get_control.disposed
end
def attach_listeners
# h = @document.add_listener(:set_text, &method(:reparse))
# @handlers << [@document, h]
@mate_text.get_text_widget.add_word_movement_listener(WordMoveListener.new(self))
@mate_text.get_text_widget.add_control_listener(ControlListener.new(self))
end
class ControlListener
def initialize(edit_view_swt)
@edit_view_swt = edit_view_swt
end
def controlMoved(*_)
end
def controlResized(e)
@edit_view_swt.reset_right_margin
end
end
def reparse
@mate_text.parser.parse_range(0, @mate_text.parser.styledText.get_line_count-1)
end
def cursor_offset=(offset)
@mate_text.get_text_widget.set_caret_offset(offset)
end
def cursor_offset
@mate_text.get_text_widget.get_caret_offset
end
def scroll_to_line(line_index)
@mate_text.parser.parserScheduler.last_visible_line_changed(line_index + 100)
@mate_text.viewer.set_top_index(line_index)
end
def scroll_to_horizontal_offset(offset)
@mate_text.get_text_widget.set_horizontal_index(offset)
end
def smallest_visible_horizontal_index
@mate_text.get_text_widget.get_horizontal_index
end
def largest_visible_horizontal_index
wpix = @mate_text.get_text_widget.get_client_area.width
gc = org.eclipse.swt.graphics.GC.new(@mate_text.get_text_widget)
inc = gc.getFontMetrics().getAverageCharWidth()
gc.dispose
@mate_text.get_text_widget.get_horizontal_index + (wpix/inc)
end
def model_grammar_changed(name)
@mate_text.set_grammar_by_name(name)
end
def smallest_visible_line
@mate_text.viewer.get_top_index
end
def biggest_visible_line
@mate_text.viewer.get_bottom_index
end
def ensure_visible(offset)
line = @document.line_at_offset(offset)
line_start_offset = @document.offset_at_line(line)
if offset == line_start_offset
# This doesn't work. Bug in JFace.SourceViewer?
@mate_text.viewer.reveal_range(offset, 1)
# so we do this too:
@mate_text.get_control.set_horizontal_pixel(0)
else
@mate_text.viewer.reveal_range(offset, 1)
end
end
def update_grammar(new_mirror)
title = new_mirror.title
contents = new_mirror.read
first_line = contents.to_s.split("\n").first
grammar_name = @mate_text.set_grammar_by_first_line(first_line) if first_line
unless grammar_name
grammar_name = @mate_text.set_grammar_by_filename(title)
end
grammar_name ||= "Plain Text"
@model.set_grammar(grammar_name)
end
STRIP_KEYS = {
:cut => 120|Swt::SWT::MOD1,
:copy => 99|Swt::SWT::MOD1,
:paste => 118|Swt::SWT::MOD1
}
def remove_control_keybindings
styled_text = @mate_text.get_text_widget
STRIP_KEYS.each do |_, key|
styled_text.set_key_binding(key, Swt::SWT::NULL)
end
end
def dispose
@handlers.each {|obj, h| obj.remove_listener(h) }
@handlers.clear
end
class MouseListener
def initialize(obj)
@obj = obj
end
def mouse_double_click(_); end
def mouse_up(_)
end
def mouse_down(e)
if e.button == 3
@obj.right_click(e)
end
end
end
class FocusListener
def initialize(obj)
@obj = obj
end
def focusGained(e)
@obj.swt_focus_gained
end
def focusLost(_)
@obj.swt_focus_lost
end
end
class VerifyListener
def initialize(document, obj)
@document, @obj = document, obj
end
def verify_text(e)
@document.verify_text(e.start, e.end, e.text)
end
end
class ModifyListener
def initialize(document, obj)
@document, @obj = document, obj
end
def modify_text(e)
@document.modify_text
end
end
end
end