From ba429d344132c088177e853cce8ff7181570b221 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 19:42:51 +0200 Subject: Adding upstream version 44.2. Signed-off-by: Daniel Baumann --- plugins/snippets/snippets/document.py | 1097 +++++++++++++++++++++++++++++++++ 1 file changed, 1097 insertions(+) create mode 100644 plugins/snippets/snippets/document.py (limited to 'plugins/snippets/snippets/document.py') diff --git a/plugins/snippets/snippets/document.py b/plugins/snippets/snippets/document.py new file mode 100644 index 0000000..003237d --- /dev/null +++ b/plugins/snippets/snippets/document.py @@ -0,0 +1,1097 @@ +# Gedit snippets plugin +# Copyright (C) 2005-2006 Jesse van den Kieboom +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import re + +from gi.repository import Gtk, Gdk, Gio, GLib, Gedit, GObject + +from .library import Library +from .snippet import Snippet +from .placeholder import PlaceholderEnd +from . import completion +from .signals import Signals +from .shareddata import SharedData +from . import helper + +try: + import gettext + gettext.bindtextdomain('gedit') + gettext.textdomain('gedit') + _ = gettext.gettext +except: + _ = lambda s: s + +class DynamicSnippet(dict): + def __init__(self, text): + self['text'] = text + self.valid = True + +class Document(GObject.Object, Gedit.ViewActivatable, Signals): + TAB_KEY_VAL = (Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab) + SPACE_KEY_VAL = (Gdk.KEY_space,) + + view = GObject.Property(type=Gedit.View) + + def __init__(self): + GObject.Object.__init__(self) + Signals.__init__(self) + + self.placeholders = [] + self.active_snippets = [] + self.active_placeholder = None + + self.ordered_placeholders = [] + self.update_placeholders = [] + self.jump_placeholders = [] + self.language_id = 0 + self.timeout_update_id = 0 + + self.provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + + def do_activate(self): + # Always have a reference to the global snippets + Library().ref(None) + + buf = self.view.get_buffer() + + self.connect_signal(self.view, 'key-press-event', self.on_view_key_press) + self.connect_signal(buf, 'notify::language', self.on_notify_language) + self.connect_signal(self.view, 'drag-data-received', self.on_drag_data_received) + + self.connect_signal_after(self.view, 'draw', self.on_draw) + + self.update_language() + + completion = self.view.get_completion() + completion.add_provider(self.provider) + + SharedData().register_controller(self.view, self) + + def do_deactivate(self): + if self.timeout_update_id != 0: + GLib.source_remove(self.timeout_update_id) + self.timeout_update_id = 0 + + del self.update_placeholders[:] + del self.jump_placeholders[:] + + # Always release the reference to the global snippets + Library().unref(None) + self.active_placeholder = None + + self.disconnect_signals(self.view) + self.disconnect_signals(self.view.get_buffer()) + + # Remove all active snippets + for snippet in list(self.active_snippets): + self.deactivate_snippet(snippet, True) + + completion = self.view.get_completion() + + if completion: + completion.remove_provider(self.provider) + + if self.language_id != 0: + Library().unref(self.language_id) + + SharedData().unregister_controller(self.view, self) + + # Call this whenever the language in the view changes. This makes sure that + # the correct language is used when finding snippets + def update_language(self): + lang = self.view.get_buffer().get_language() + + if lang is None and self.language_id is None: + return + elif lang and lang.get_id() == self.language_id: + return + + langid = self.language_id + + if lang: + self.language_id = lang.get_id() + else: + self.language_id = None + + if langid != 0: + Library().unref(langid) + + Library().ref(self.language_id) + self.provider.language_id = self.language_id + + SharedData().update_state(self.view.get_toplevel()) + + def accelerator_activate(self, keyval, mod): + if not self.view or not self.view.get_editable(): + return False + + accelerator = Gtk.accelerator_name(keyval, mod) + snippets = Library().from_accelerator(accelerator, \ + self.language_id) + + if len(snippets) == 0: + return False + elif len(snippets) == 1: + self.apply_snippet(snippets[0]) + else: + # Do the fancy completion dialog + provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + provider.set_proposals(snippets) + + cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + return True + + def first_snippet_inserted(self): + buf = self.view.get_buffer() + + self.connect_signal(buf, 'changed', self.on_buffer_changed) + self.connect_signal(buf, 'tepl-cursor-moved', self.on_buffer_cursor_moved) + self.connect_signal_after(buf, 'insert-text', self.on_buffer_insert_text) + + def last_snippet_removed(self): + buf = self.view.get_buffer() + self.disconnect_signal(buf, 'changed') + self.disconnect_signal(buf, 'tepl-cursor-moved') + self.disconnect_signal(buf, 'insert-text') + + def current_placeholder(self): + buf = self.view.get_buffer() + + piter = buf.get_iter_at_mark(buf.get_insert()) + found = [] + + for placeholder in self.placeholders: + begin = placeholder.begin_iter() + end = placeholder.end_iter() + + if piter.compare(begin) >= 0 and piter.compare(end) <= 0: + found.append(placeholder) + + if self.active_placeholder in found: + return self.active_placeholder + elif len(found) > 0: + return found[0] + else: + return None + + def advance_placeholder(self, direction): + # Returns (CurrentPlaceholder, NextPlaceholder), depending on direction + buf = self.view.get_buffer() + + piter = buf.get_iter_at_mark(buf.get_insert()) + found = current = next = None + length = len(self.placeholders) + + placeholders = list(self.placeholders) + + if self.active_placeholder: + begin = self.active_placeholder.begin_iter() + end = self.active_placeholder.end_iter() + + if piter.compare(begin) >= 0 and piter.compare(end) <= 0: + current = self.active_placeholder + currentIndex = placeholders.index(self.active_placeholder) + + if direction == 1: + # w = piter, x = begin, y = end, z = found + nearest = lambda w, x, y, z: (w.compare(x) <= 0 and (not z or \ + x.compare(z.begin_iter()) < 0)) + indexer = lambda x: x < length - 1 + else: + # w = piter, x = begin, y = end, z = prev + nearest = lambda w, x, y, z: (w.compare(x) >= 0 and (not z or \ + x.compare(z.begin_iter()) >= 0)) + indexer = lambda x: x > 0 + + for index in range(0, length): + placeholder = placeholders[index] + begin = placeholder.begin_iter() + end = placeholder.end_iter() + + # Find the nearest placeholder + if nearest(piter, begin, end, found): + found = placeholder + + # Find the current placeholder + if piter.compare(begin) >= 0 and \ + piter.compare(end) <= 0 and \ + current is None: + currentIndex = index + current = placeholder + + if current and current != found and \ + (current.begin_iter().compare(found.begin_iter()) == 0 or \ + current.end_iter().compare(found.begin_iter()) == 0) and \ + self.active_placeholder and \ + current.begin_iter().compare(self.active_placeholder.begin_iter()) == 0: + # if current and found are at the same place, then + # resolve the 'hugging' problem + current = self.active_placeholder + currentIndex = placeholders.index(current) + + if current: + if indexer(currentIndex): + next = placeholders[currentIndex + direction] + elif found: + next = found + elif length > 0: + next = self.placeholders[0] + + return current, next + + def next_placeholder(self): + return self.advance_placeholder(1) + + def previous_placeholder(self): + return self.advance_placeholder(-1) + + def cursor_on_screen(self): + buf = self.view.get_buffer() + self.view.scroll_mark_onscreen(buf.get_insert()) + + def set_active_placeholder(self, placeholder): + self.active_placeholder = placeholder + + def goto_placeholder(self, current, next): + last = None + + if current: + # Signal this placeholder to end action + self.view.get_completion().hide() + current.leave() + + if current.__class__ == PlaceholderEnd: + last = current + + self.set_active_placeholder(next) + + if next: + next.enter() + + if next.__class__ == PlaceholderEnd: + last = next + elif len(next.defaults) > 1 and next.get_text() == next.default: + provider = completion.Defaults(self.on_default_activated) + provider.set_defaults(next.defaults) + + cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + if last: + # This is the end of the placeholder, remove the snippet etc + for snippet in list(self.active_snippets): + if snippet.placeholders[0] == last: + self.deactivate_snippet(snippet) + break + + self.cursor_on_screen() + + return next != None + + def skip_to_next_placeholder(self): + (current, next) = self.next_placeholder() + return self.goto_placeholder(current, next) + + def skip_to_previous_placeholder(self): + (current, prev) = self.previous_placeholder() + return self.goto_placeholder(current, prev) + + def string_in_native_doc_encoding(self, buf, s): + enc = buf.get_file().get_encoding() + + if not enc or enc.get_charset() == 'UTF-8': + return s + + try: + cv = GLib.convert(s, -1, enc.get_charset(), 'UTF-8') + return cv[0] + except GLib.GError: + pass + + return s + + def env_get_selected_text(self, buf): + bounds = buf.get_selection_bounds() + + if bounds: + u8 = buf.get_text(bounds[0], bounds[1], False) + + return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)} + else: + return '' + + def env_get_current_word(self, buf): + start, end = helper.buffer_word_boundary(buf) + + u8 = buf.get_text(start, end, False) + + return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)} + + def env_get_current_line(self, buf): + start, end = helper.buffer_line_boundary(buf) + + u8 = buf.get_text(start, end, False) + + return {'utf8': u8, 'noenc': self.string_in_native_doc_encoding(buf, u8)} + + def env_get_current_line_number(self, buf): + start, end = helper.buffer_line_boundary(buf) + + return str(start.get_line() + 1) + + def location_uri_for_env(self, location): + if not location: + return {'utf8': '', 'noenc': ''} + + u8 = location.get_parse_name() + + if location.has_uri_scheme('file'): + u8 = "file://" + u8 + + return {'utf8': u8, 'noenc': location.get_uri()} + + def location_name_for_env(self, location): + if location: + try: + info = location.query_info("standard::display-name", 0, None) + display_name = info.get_display_name() + except: + display_name = '' + + return {'utf8': display_name, + 'noenc': location.get_basename()} + else: + return '' + + def location_scheme_for_env(self, location): + if location: + return location.get_uri_scheme() + else: + return '' + + def location_path_for_env(self, location): + if location and location.has_uri_scheme('file'): + return {'utf8': location.get_parse_name(), + 'noenc': location.get_path()} + else: + return '' + + def location_dir_for_env(self, location): + if location: + parent = location.get_parent() + + if parent and parent.has_uri_scheme('file'): + return {'utf8': parent.get_parse_name(), + 'noenc': parent.get_path()} + + return '' + + def env_add_for_location(self, environ, location, prefix): + parts = {'URI': self.location_uri_for_env, + 'NAME': self.location_name_for_env, + 'SCHEME': self.location_scheme_for_env, + 'PATH': self.location_path_for_env, + 'DIR': self.location_dir_for_env} + + for k in parts: + v = parts[k](location) + key = prefix + '_' + k + + if isinstance(v, dict): + environ['utf8'][key] = v['utf8'] + environ['noenc'][key] = v['noenc'] + else: + environ['utf8'][key] = v + environ['noenc'][key] = str(v) + + return environ + + def env_get_document_type(self, buf): + typ = buf.get_mime_type() + + if typ: + return typ + else: + return '' + + def env_get_documents_uri(self, buf): + toplevel = self.view.get_toplevel() + + documents_uri = {'utf8': [], 'noenc': []} + + if isinstance(toplevel, Gedit.Window): + for doc in toplevel.get_documents(): + r = self.location_uri_for_env(doc.get_file().get_location()) + + if isinstance(r, dict): + documents_uri['utf8'].append(r['utf8']) + documents_uri['noenc'].append(r['noenc']) + else: + documents_uri['utf8'].append(r) + documents_uri['noenc'].append(str(r)) + + return {'utf8': ' '.join(documents_uri['utf8']), + 'noenc': ' '.join(documents_uri['noenc'])} + + def env_get_documents_path(self, buf): + toplevel = self.view.get_toplevel() + + documents_path = {'utf8': [], 'noenc': []} + + if isinstance(toplevel, Gedit.Window): + for doc in toplevel.get_documents(): + r = self.location_path_for_env(doc.get_file().get_location()) + + if isinstance(r, dict): + documents_path['utf8'].append(r['utf8']) + documents_path['noenc'].append(r['noenc']) + else: + documents_path['utf8'].append(r) + documents_path['noenc'].append(str(r)) + + return {'utf8': ' '.join(documents_path['utf8']), + 'noenc': ' '.join(documents_path['noenc'])} + + def get_environment(self): + buf = self.view.get_buffer() + environ = {'utf8': {}, 'noenc': {}} + + for k in os.environ: + # Get the original environment, as utf-8 + v = os.environ[k] + environ['noenc'][k] = v + environ['utf8'][k] = os.environ[k].encode('utf-8') + + variables = {'GEDIT_SELECTED_TEXT': self.env_get_selected_text, + 'GEDIT_CURRENT_WORD': self.env_get_current_word, + 'GEDIT_CURRENT_LINE': self.env_get_current_line, + 'GEDIT_CURRENT_LINE_NUMBER': self.env_get_current_line_number, + 'GEDIT_CURRENT_DOCUMENT_TYPE': self.env_get_document_type, + 'GEDIT_DOCUMENTS_URI': self.env_get_documents_uri, + 'GEDIT_DOCUMENTS_PATH': self.env_get_documents_path} + + for var in variables: + v = variables[var](buf) + + if isinstance(v, dict): + environ['utf8'][var] = v['utf8'] + environ['noenc'][var] = v['noenc'] + else: + environ['utf8'][var] = v + environ['noenc'][var] = str(v) + + self.env_add_for_location(environ, buf.get_file().get_location(), 'GEDIT_CURRENT_DOCUMENT') + + return environ + + def uses_current_word(self, snippet): + matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_WORD', snippet['text']) + + for match in matches: + if len(match) % 2 == 0: + return True + + return False + + def uses_current_line(self, snippet): + matches = re.findall('(\\\\*)\\$GEDIT_CURRENT_LINE', snippet['text']) + + for match in matches: + if len(match) % 2 == 0: + return True + + return False + + def apply_snippet(self, snippet, start = None, end = None, environ = {}): + if not snippet.valid: + return False + + # Set environmental variables + env = self.get_environment() + + if environ: + for k in environ['utf8']: + env['utf8'][k] = environ['utf8'][k] + + for k in environ['noenc']: + env['noenc'][k] = environ['noenc'][k] + + buf = self.view.get_buffer() + s = Snippet(snippet, env) + + if not start: + start = buf.get_iter_at_mark(buf.get_insert()) + + if not end: + end = buf.get_iter_at_mark(buf.get_selection_bound()) + + if start.equal(end) and self.uses_current_word(s): + # There is no tab trigger and no selection and the snippet uses + # the current word. Set start and end to the word boundary so that + # it will be removed + start, end = helper.buffer_word_boundary(buf) + elif start.equal(end) and self.uses_current_line(s): + # There is no tab trigger and no selection and the snippet uses + # the current line. Set start and end to the line boundary so that + # it will be removed + start, end = helper.buffer_line_boundary(buf) + + # You know, we could be in an end placeholder + (current, next) = self.next_placeholder() + if current and current.__class__ == PlaceholderEnd: + self.goto_placeholder(current, None) + + if len(self.active_snippets) > 0: + self.block_signal(buf, 'tepl-cursor-moved') + + buf.begin_user_action() + + # Remove the tag, selection or current word + buf.delete(start, end) + + # Insert the snippet + if len(self.active_snippets) == 0: + self.first_snippet_inserted() + self.block_signal(buf, 'tepl-cursor-moved') + + sn = s.insert_into(self, start) + self.active_snippets.append(sn) + + # Put cursor at first tab placeholder + keys = [x for x in sn.placeholders.keys() if x > 0] + + if len(keys) == 0: + if 0 in sn.placeholders: + self.goto_placeholder(self.active_placeholder, sn.placeholders[0]) + else: + buf.place_cursor(sn.begin_iter()) + else: + self.goto_placeholder(self.active_placeholder, sn.placeholders[keys[0]]) + + self.unblock_signal(buf, 'tepl-cursor-moved') + + if sn in self.active_snippets: + # Check if we can get end_iter in view without moving the + # current cursor position out of view + cur = buf.get_iter_at_mark(buf.get_insert()) + last = sn.end_iter() + + curloc = self.view.get_iter_location(cur) + lastloc = self.view.get_iter_location(last) + + if (lastloc.y + lastloc.height) - curloc.y <= \ + self.view.get_visible_rect().height: + self.view.scroll_mark_onscreen(sn.end_mark) + + buf.end_user_action() + self.view.grab_focus() + + return True + + def get_tab_tag(self, buf, end = None): + if not end: + end = buf.get_iter_at_mark(buf.get_insert()) + + start = end.copy() + word = None + first = True + + # Move start backward as long as there is a valid character + while start.backward_char(): + c = start.get_char() + + if not helper.is_tab_trigger_character(c): + # Check this for a single special char + if first and helper.is_tab_trigger(c): + break + + # Make sure first char is valid + while not start.equal(end) and \ + not helper.is_first_tab_trigger_character(start.get_char()): + start.forward_char() + + break + + first = False + + if not start.equal(end): + word = buf.get_text(start, end, False) + + if word and word != '': + return (word, start, end) + + return (None, None, None) + + def parse_and_run_snippet(self, data, iter): + if not self.view.get_editable(): + return + + self.apply_snippet(DynamicSnippet(data), iter, iter) + + def run_snippet_trigger(self, trigger, bounds): + if not self.view: + return False + + if not self.view.get_editable(): + return False + + buf = self.view.get_buffer() + + if buf.get_has_selection(): + return False + + snippets = Library().from_tag(trigger, self.language_id) + + if snippets: + if len(snippets) == 1: + return self.apply_snippet(snippets[0], bounds[0], bounds[1]) + else: + # Do the fancy completion dialog + provider = completion.Provider(_('Snippets'), self.language_id, self.on_proposal_activated) + provider.set_proposals(snippets) + + cm = self.view.get_completion() + cm.show([provider], cm.create_context(None)) + + return True + + return False + + def run_snippet(self): + if not self.view: + return False + + if not self.view.get_editable(): + return False + + buf = self.view.get_buffer() + + # get the word preceding the current insertion position + (word, start, end) = self.get_tab_tag(buf) + + if not word: + return self.skip_to_next_placeholder() + + if not self.run_snippet_trigger(word, (start, end)): + return self.skip_to_next_placeholder() + else: + return True + + def deactivate_snippet(self, snippet, force = False): + remove = [] + ordered_remove = [] + + for tabstop in snippet.placeholders: + if tabstop == -1: + placeholders = snippet.placeholders[-1] + else: + placeholders = [snippet.placeholders[tabstop]] + + for placeholder in placeholders: + if placeholder in self.placeholders: + if placeholder in self.update_placeholders: + placeholder.update_contents() + + self.update_placeholders.remove(placeholder) + elif placeholder in self.jump_placeholders: + placeholder[0].leave() + + remove.append(placeholder) + elif placeholder in self.ordered_placeholders: + ordered_remove.append(placeholder) + + for placeholder in remove: + if placeholder == self.active_placeholder: + self.active_placeholder = None + + self.placeholders.remove(placeholder) + self.ordered_placeholders.remove(placeholder) + + placeholder.remove(force) + + for placeholder in ordered_remove: + self.ordered_placeholders.remove(placeholder) + placeholder.remove(force) + + snippet.deactivate() + self.active_snippets.remove(snippet) + + if len(self.active_snippets) == 0: + self.last_snippet_removed() + + self.view.queue_draw() + + def update_snippet_contents(self): + self.timeout_update_id = 0 + + for placeholder in self.update_placeholders: + placeholder.update_contents() + + for placeholder in self.jump_placeholders: + self.goto_placeholder(placeholder[0], placeholder[1]) + + del self.update_placeholders[:] + del self.jump_placeholders[:] + + return False + + def on_buffer_cursor_moved(self, buf): + piter = buf.get_iter_at_mark(buf.get_insert()) + + # Check for all snippets if the cursor is outside its scope + for snippet in list(self.active_snippets): + if snippet.begin_mark.get_deleted() or snippet.end_mark.get_deleted(): + self.deactivate(snippet) + else: + begin = snippet.begin_iter() + end = snippet.end_iter() + + if piter.compare(begin) < 0 or piter.compare(end) > 0: + # Oh no! Remove the snippet this instant!! + self.deactivate_snippet(snippet) + + current = self.current_placeholder() + + if current != self.active_placeholder: + self.jump_placeholders.append((self.active_placeholder, current)) + + if self.timeout_update_id == 0: + self.timeout_update_id = GLib.timeout_add(0, + self.update_snippet_contents) + + def on_buffer_changed(self, buf): + for snippet in list(self.active_snippets): + begin = snippet.begin_iter() + end = snippet.end_iter() + + if begin.compare(end) >= 0: + # Begin collapsed on end, just remove it + self.deactivate_snippet(snippet) + + current = self.current_placeholder() + + if current: + if not current in self.update_placeholders: + self.update_placeholders.append(current) + + if self.timeout_update_id == 0: + self.timeout_update_id = GLib.timeout_add(0, \ + self.update_snippet_contents) + + def on_buffer_insert_text(self, buf, piter, text, length): + ctx = helper.get_buffer_context(buf) + + # do nothing special if there is no context and no active + # placeholder + if (not ctx) and (not self.active_placeholder): + return + + if not ctx: + ctx = self.active_placeholder + + if not ctx in self.ordered_placeholders: + return + + # move any marks that were incorrectly moved by this insertion + # back to where they belong + begin = ctx.begin_iter() + end = ctx.end_iter() + idx = self.ordered_placeholders.index(ctx) + + for placeholder in self.ordered_placeholders: + if placeholder == ctx: + continue + + ob = placeholder.begin_iter() + oe = placeholder.end_iter() + + if ob.compare(begin) == 0 and ((not oe) or oe.compare(end) == 0): + oidx = self.ordered_placeholders.index(placeholder) + + if oidx > idx and ob: + buf.move_mark(placeholder.begin, end) + elif oidx < idx and oe: + buf.move_mark(placeholder.end, begin) + elif ob.compare(begin) >= 0 and ob.compare(end) < 0 and (oe and oe.compare(end) >= 0): + buf.move_mark(placeholder.begin, end) + elif (oe and oe.compare(begin) > 0) and ob.compare(begin) <= 0: + buf.move_mark(placeholder.end, begin) + + def on_notify_language(self, buf, spec): + self.update_language() + + def on_view_key_press(self, view, event): + library = Library() + + state = event.get_state() + + if not self.view.get_editable(): + return False + + if not (state & Gdk.ModifierType.CONTROL_MASK) and \ + not (state & Gdk.ModifierType.MOD1_MASK) and \ + event.keyval in self.TAB_KEY_VAL: + if not state & Gdk.ModifierType.SHIFT_MASK: + return self.run_snippet() + else: + return self.skip_to_previous_placeholder() + elif not library.loaded and \ + library.valid_accelerator(event.keyval, state): + library.ensure_files() + library.ensure(self.language_id) + self.accelerator_activate(event.keyval, \ + state & Gtk.accelerator_get_default_mod_mask()) + + return False + + def path_split(self, path, components=[]): + head, tail = os.path.split(path) + + if not tail and head: + return [head] + components + elif tail: + return self.path_split(head, [tail] + components) + else: + return components + + def apply_uri_snippet(self, snippet, mime, uri): + # Remove file scheme + gfile = Gio.file_new_for_uri(uri) + + environ = {'utf8': {'GEDIT_DROP_DOCUMENT_TYPE': mime.encode('utf-8')}, + 'noenc': {'GEDIT_DROP_DOCUMENT_TYPE': mime}} + + self.env_add_for_location(environ, gfile, 'GEDIT_DROP_DOCUMENT') + + buf = self.view.get_buffer() + location = buf.get_file().get_location() + + relpath = location.get_relative_path(gfile) + + # CHECK: what is the encoding of relpath? + environ['utf8']['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath.encode('utf-8') + environ['noenc']['GEDIT_DROP_DOCUMENT_RELATIVE_PATH'] = relpath + + mark = buf.get_mark('gtk_drag_target') + + if not mark: + mark = buf.get_insert() + + piter = buf.get_iter_at_mark(mark) + self.apply_snippet(snippet, piter, piter, environ) + + def in_bounds(self, x, y): + rect = self.view.get_visible_rect() + rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.WIDGET, rect.x, rect.y) + + return not (x < rect.x or x > rect.x + rect.width or y < rect.y or y > rect.y + rect.height) + + def on_drag_data_received(self, view, context, x, y, data, info, timestamp): + if not self.view.get_editable(): + return + + uris = helper.drop_get_uris(data) + if not uris: + return + + if not self.in_bounds(x, y): + return + + uris.reverse() + stop = False + + for uri in uris: + try: + mime = Gio.content_type_guess(uri) + except: + mime = None + + if not mime: + continue + + snippets = Library().from_drop_target(mime, self.language_id) + + if snippets: + stop = True + self.apply_uri_snippet(snippets[0], mime, uri) + + if stop: + context.finish(True, False, timestamp) + view.stop_emission('drag-data-received') + view.get_toplevel().present() + view.grab_focus() + + def find_uri_target(self, context): + lst = Gtk.target_list_add_uri_targets((), 0) + + return self.view.drag_dest_find_target(context, lst) + + def on_proposal_activated(self, proposal, piter): + if not self.view.get_editable(): + return False + + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + + if bounds: + self.apply_snippet(proposal.snippet(), None, None) + else: + (word, start, end) = self.get_tab_tag(buf, piter) + self.apply_snippet(proposal.snippet(), start, end) + + return True + + def on_default_activated(self, proposal, piter): + buf = self.view.get_buffer() + bounds = buf.get_selection_bounds() + + if bounds: + buf.begin_user_action() + buf.delete(bounds[0], bounds[1]) + buf.insert(bounds[0], proposal.props.label) + buf.end_user_action() + + return True + else: + return False + + def iter_coords(self, piter): + rect = self.view.get_iter_location(piter) + rect.x, rect.y = self.view.buffer_to_window_coords(Gtk.TextWindowType.TEXT, rect.x, rect.y) + + return rect + + def placeholder_in_area(self, placeholder, area): + start = placeholder.begin_iter() + end = placeholder.end_iter() + + if not start or not end: + return False + + # Test if start is before bottom, and end is after top + start_rect = self.iter_coords(start) + end_rect = self.iter_coords(end) + + return start_rect.y <= area.y + area.height and \ + end_rect.y + end_rect.height >= area.y + + def draw_placeholder_rect(self, ctx, placeholder): + start = placeholder.begin_iter() + start_rect = self.iter_coords(start) + start_line = start.get_line() + + end = placeholder.end_iter() + end_rect = self.iter_coords(end) + end_line = end.get_line() + + line = start.copy() + line.set_line_offset(0) + geom = self.view.get_window(Gtk.TextWindowType.TEXT).get_geometry() + + ctx.translate(0.5, 0.5) + + while line.get_line() <= end_line: + ypos, height = self.view.get_line_yrange(line) + x_, ypos = self.view.window_to_buffer_coords(Gtk.TextWindowType.TEXT, 0, ypos) + + if line.get_line() == start_line and line.get_line() == end_line: + # Simply draw a box, both are on the same line + ctx.rectangle(start_rect.x, start_rect.y, end_rect.x - start_rect.x, start_rect.height - 1) + ctx.stroke() + elif line.get_line() == start_line or line.get_line() == end_line: + if line.get_line() == start_line: + rect = start_rect + else: + rect = end_rect + + ctx.move_to(0, rect.y + rect.height - 1) + ctx.rel_line_to(rect.x, 0) + ctx.rel_line_to(0, -rect.height + 1) + ctx.rel_line_to(geom[2], 0) + ctx.stroke() + + if not line.forward_line(): + break + + def draw_placeholder_bar(self, ctx, placeholder): + start = placeholder.begin_iter() + start_rect = self.iter_coords(start) + + ctx.translate(0.5, 0.5) + extend_width = 2.5 + + ctx.move_to(start_rect.x - extend_width, start_rect.y) + ctx.rel_line_to(extend_width * 2, 0) + + ctx.move_to(start_rect.x, start_rect.y) + ctx.rel_line_to(0, start_rect.height - 1) + + ctx.rel_move_to(-extend_width, 0) + ctx.rel_line_to(extend_width * 2, 0) + ctx.stroke() + + def draw_placeholder(self, ctx, placeholder): + if isinstance(placeholder, PlaceholderEnd): + return + + col = self.view.get_style_context().get_color(Gtk.StateFlags.INSENSITIVE) + col.alpha = 0.5 + Gdk.cairo_set_source_rgba(ctx, col) + + if placeholder.tabstop > 0: + ctx.set_dash([], 0) + else: + ctx.set_dash([2], 0) + + start = placeholder.begin_iter() + end = placeholder.end_iter() + + if start.equal(end): + self.draw_placeholder_bar(ctx, placeholder) + else: + self.draw_placeholder_rect(ctx, placeholder) + + def on_draw(self, view, ctx): + window = view.get_window(Gtk.TextWindowType.TEXT) + + if not Gtk.cairo_should_draw_window(ctx, window): + return False + + # Draw something + ctx.set_line_width(1.0) + + Gtk.cairo_transform_to_window(ctx, view, window) + + clipped, clip = Gdk.cairo_get_clip_rectangle(ctx) + + if not clipped: + return False + + for placeholder in self.ordered_placeholders: + if not self.placeholder_in_area(placeholder, clip): + continue + + ctx.save() + self.draw_placeholder(ctx, placeholder) + ctx.restore() + + return False + +# ex:ts=4:et: -- cgit v1.2.3