#    Pluma snippets plugin
#    Copyright (C) 2005-2006  Jesse van den Kieboom <jesse@icecrew.nl>
#
#    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

import gtk
from gtk import gdk
import gio
import pluma
import gtksourceview2 as gsv
import gobject

from Library import Library
from Snippet import Snippet
from Placeholder import *
import Completion

class DynamicSnippet(dict):
        def __init__(self, text):
                self['text'] = text
                self.valid = True

class Document:
        TAB_KEY_VAL = (gtk.keysyms.Tab, \
                        gtk.keysyms.ISO_Left_Tab)
        SPACE_KEY_VAL = (gtk.keysyms.space,)
        
        def __init__(self, instance, view):
                self.view = None
                self.instance = instance
                
                self.placeholders = []
                self.active_snippets = []
                self.active_placeholder = None
                self.signal_ids = {}
                
                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)
                self.defaults_provider = Completion.Defaults(self.on_default_activated)
                
                # Always have a reference to the global snippets
                Library().ref(None)
                self.set_view(view)
        
        # Stop controlling the view. Remove all active snippets, remove references
        # to the view and the plugin instance, disconnect all signal handlers
        def stop(self):
                if self.timeout_update_id != 0:
                        gobject.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.set_view(None)
                self.instance = None
                self.active_placeholder = None

        def disconnect_signal(self, obj, signal):
                if (obj, signal) in self.signal_ids:
                        obj.disconnect(self.signal_ids[(obj, signal)])
                        del self.signal_ids[(obj, signal)]
                
        def connect_signal(self, obj, signal, cb):
                self.disconnect_signal(obj, signal)     
                self.signal_ids[(obj, signal)] = obj.connect(signal, cb)

        def connect_signal_after(self, obj, signal, cb):
                self.disconnect_signal(obj, signal)
                self.signal_ids[(obj, signal)] = obj.connect_after(signal, cb)
                
        # Set the view to be controlled. Installs signal handlers and sets current
        # language. If there is already a view set this function will first remove
        # all currently active snippets and disconnect all current signals. So
        # self.set_view(None) will effectively remove all the control from the
        # current view
        def _set_view(self, view):
                if self.view:
                        buf = self.view.get_buffer()

                        # Remove signals
                        signals = {self.view: ('key-press-event', 'destroy', 
                                               'notify::editable', 'drag-data-received', 'expose-event'),
                                   buf:       ('notify::language', 'changed', 'cursor-moved', 'insert-text'),
                                   self.view.get_completion(): ('hide',)}

                        for obj, sig in signals.items():
                                for s in sig:
                                        self.disconnect_signal(obj, s)
                        
                        # Remove all active snippets
                        for snippet in list(self.active_snippets):
                                self.deactivate_snippet(snippet, True)
                        
                        completion = self.view.get_completion()
                        completion.remove_provider(self.provider)
                        completion.remove_provider(self.defaults_provider)                        

                self.view = view
                
                if view != None:
                        buf = view.get_buffer()
                       
                        self.connect_signal(view, 'destroy', self.on_view_destroy)

                        if view.get_editable():
                                self.connect_signal(view, 'key-press-event', self.on_view_key_press)

                        self.connect_signal(buf, 'notify::language', self.on_notify_language)
                        self.connect_signal(view, 'notify::editable', self.on_notify_editable)
                        self.connect_signal(view, 'drag-data-received', self.on_drag_data_received)
                        self.connect_signal_after(view, 'expose-event', self.on_expose_event)

                        self.update_language()

                        completion = view.get_completion()
                        completion.add_provider(self.provider)
                        
                        completion.add_provider(self.defaults_provider)
                        
                        self.connect_signal(completion, 'hide', self.on_completion_hide)
                elif self.language_id != 0:
                        langid = self.language_id
                        
                        self.language_id = None;
                        self.provider.language_id = self.language_id
                        
                        if self.instance:
                                self.instance.language_changed(self)

                        Library().unref(langid)

        def set_view(self, view):
                if view == self.view:
                        return
                
                self._set_view(view)

        # 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 == None and self.language_id == 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 self.instance:
                        self.instance.language_changed(self)

                if langid != 0:
                        Library().unref(langid)

                Library().ref(self.language_id)
                self.provider.language_id = self.language_id

        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)

                snippets_debug('Accel!')

                if len(snippets) == 0:
                        return False
                elif len(snippets) == 1:
                        self.apply_snippet(snippets[0])
                else:
                        # Do the fancy completion dialog
                        self.provider.set_proposals(snippets)
                        self.view.show_completion((self,))

                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, '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, '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):
                                foundIndex = index
                                found = placeholder
                        
                        # Find the current placeholder
                        if piter.compare(begin) >= 0 and \
                                        piter.compare(end) <= 0 and \
                                        current == 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:
                                self.defaults_provider.set_defaults(next.defaults)
                                
                                cm = self.view.get_completion()
                                cm.show([self.defaults_provider], cm.create_context())

                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 env_get_selected_text(self, buf):
                bounds = buf.get_selection_bounds()

                if bounds:
                        return buf.get_text(bounds[0], bounds[1])
                else:
                        return ''

        def env_get_current_word(self, buf):
                start, end = buffer_word_boundary(buf)
                
                return buf.get_text(start, end)

        def env_get_current_line(self, buf):
                start, end = buffer_line_boundary(buf)
                
                return buf.get_text(start, end)

        def env_get_current_line_number(self, buf):
                start, end = buffer_line_boundary(buf)
                
                return str(start.get_line() + 1)
                
        def env_get_document_uri(self, buf):
                location = buf.get_location()
                
                if location:
                        return location.get_uri()
                else:
                        return ''
        
        def env_get_document_name(self, buf):
                location = buf.get_location()
                
                if location:
                        return location.get_basename()
                else:
                        return ''

        def env_get_document_scheme(self, buf):
                location = buf.get_location()
                
                if location:
                        return location.get_uri_scheme()
                else:
                        return ''

        def env_get_document_path(self, buf):
                location = buf.get_location()
                
                if location:
                        return location.get_path()
                else:
                        return ''

        def env_get_document_dir(self, buf):
                location = buf.get_location()

                if location:
                        return location.get_parent().get_path() or ''
                else:
                        return ''

        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()
                
                if isinstance(toplevel, pluma.Window):
                        documents_uri = [doc.get_location().get_uri()
                                         for doc in toplevel.get_documents()
                                         if doc.get_location() is not None]
                else:
                        documents_uri = []
                
                return ' '.join(documents_uri)

        def env_get_documents_path(self, buf):
                toplevel = self.view.get_toplevel()
                
                if isinstance(toplevel, pluma.Window):
                        documents_location = [doc.get_location()
                                              for doc in toplevel.get_documents()
                                              if doc.get_location() is not None]

                        documents_path = [location.get_path()
                                          for location in documents_location
                                          if pluma.utils.uri_has_file_scheme(location.get_uri())]
                else:
                        documents_path = []
                
                return ' '.join(documents_path)

        def update_environment(self):
                buf = self.view.get_buffer()
                
                variables = {'PLUMA_SELECTED_TEXT': self.env_get_selected_text, 
                             'PLUMA_CURRENT_WORD': self.env_get_current_word, 
                             'PLUMA_CURRENT_LINE': self.env_get_current_line,
                             'PLUMA_CURRENT_LINE_NUMBER': self.env_get_current_line_number,
                             'PLUMA_CURRENT_DOCUMENT_URI': self.env_get_document_uri, 
                             'PLUMA_CURRENT_DOCUMENT_NAME': self.env_get_document_name,
                             'PLUMA_CURRENT_DOCUMENT_SCHEME': self.env_get_document_scheme,
                             'PLUMA_CURRENT_DOCUMENT_PATH': self.env_get_document_path,
                             'PLUMA_CURRENT_DOCUMENT_DIR': self.env_get_document_dir,
                             'PLUMA_CURRENT_DOCUMENT_TYPE': self.env_get_document_type,
                             'PLUMA_DOCUMENTS_URI': self.env_get_documents_uri,
                             'PLUMA_DOCUMENTS_PATH': self.env_get_documents_path,
                             }
                
                for var in variables:
                        os.environ[var] = variables[var](buf)
        
        def uses_current_word(self, snippet):
                matches = re.findall('(\\\\*)\\$PLUMA_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('(\\\\*)\\$PLUMA_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):
                if not snippet.valid:
                        return False

                buf = self.view.get_buffer()
                s = Snippet(snippet)
                
                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 = 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 = buffer_line_boundary(buf)

                # Set environmental variables
                self.update_environment()
                
                # 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)
                
                buf.begin_user_action()

                # Remove the tag, selection or current word
                buf.delete(start, end)
                
                # Insert the snippet
                holders = len(self.placeholders)
                
                if len(self.active_snippets) == 0:
                        self.first_snippet_inserted()

                sn = s.insert_into(self, start)
                self.active_snippets.append(sn)

                # Put cursor at first tab placeholder
                keys = filter(lambda x: x > 0, sn.placeholders.keys())

                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]])

                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
                
                if start.backward_word_start():
                        # Check if we were at a word start ourselves
                        tmp = start.copy()
                        tmp.forward_word_end()
                        
                        if tmp.equal(end):
                                word = buf.get_text(start, end)
                        else:
                                start = end.copy()
                else:
                        start = end.copy()
                
                if not word or word == '':
                        if start.backward_char():
                                word = start.get_char()

                                if word.isalnum() or word.isspace():
                                        return (None, None, None)
                        else:
                                return (None, None, None)

                return (word, start, end)

        def parse_and_run_snippet(self, data, iter):
                self.apply_snippet(DynamicSnippet(data), iter, iter)

        def run_snippet_trigger(self, trigger, bounds):
                if not self.view:
                        return False

                snippets = Library().from_tag(trigger, self.language_id)
                buf = self.view.get_buffer()

                if snippets:
                        if len(snippets) == 1:
                                return self.apply_snippet(snippets[0], bounds[0], bounds[1])
                        else:
                                # Do the fancy completion dialog
                                self.provider.set_proposals(snippets)
                                cm = self.view.get_completion()
                                
                                cm.show([self.provider], cm.create_context())
                                return True

                return False
                
        def run_snippet(self):
                if not self.view:
                        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):
                buf = self.view.get_buffer()
                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

        # Callbacks
        def on_view_destroy(self, view):
                self.stop()
                return

        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 = gobject.timeout_add(0, 
                                                self.update_snippet_contents)

        def on_buffer_changed(self, buf):
                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 = gobject.timeout_add(0, \
                                                self.update_snippet_contents)
        
        def on_buffer_insert_text(self, buf, piter, text, length):
                ctx = 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_notify_editable(self, view, spec):
                self._set_view(view)
        
        def on_view_key_press(self, view, event):
                library = Library()

                if not (event.state & gdk.CONTROL_MASK) and \
                                not (event.state & gdk.MOD1_MASK) and \
                                event.keyval in self.TAB_KEY_VAL:
                        if not event.state & gdk.SHIFT_MASK:
                                return self.run_snippet()
                        else:
                                return self.skip_to_previous_placeholder()
                elif not library.loaded and \
                                library.valid_accelerator(event.keyval, event.state):
                        library.ensure_files()
                        library.ensure(self.language_id)
                        self.accelerator_activate(event.keyval, \
                                        event.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 relative_path(self, first, second, mime):
                prot1 = re.match('(^[a-z]+:\/\/|\/)(.*)', first)
                prot2 = re.match('(^[a-z]+:\/\/|\/)(.*)', second)

                if not prot1 or not prot2:
                        return second

                # Different protocols
                if prot1.group(1) != prot2.group(1):
                        return second

                # Split on backslash
                path1 = self.path_split(prot1.group(2))
                path2 = self.path_split(prot2.group(2))

                # Remove as long as common
                while path1 and path2 and path1[0] == path2[0]:
                        path1.pop(0)
                        path2.pop(0)

                # If we need to ../ more than 3 times, then just return
                # the absolute path
                if len(path1) - 1 > 3:
                        return second

                if mime.startswith('x-directory'):
                        # directory, special case
                        if not path2:
                                result = './'
                        else:
                                result = '../' * (len(path1) - 1)
                else:
                        # Insert ../
                        result = '../' * (len(path1) - 1)

                        if not path2:
                                result = os.path.basename(second)
                
                if path2:
                        result += os.path.join(*path2)

                return result

        def apply_uri_snippet(self, snippet, mime, uri):
                # Remove file scheme
                gfile = gio.File(uri)
                pathname = ''
                dirname = ''
                ruri = ''

                if pluma.utils.uri_has_file_scheme(uri):
                        pathname = gfile.get_path()
                        dirname = gfile.get_parent().get_path()

                name = os.path.basename(uri)
                scheme = gfile.get_uri_scheme()

                os.environ['PLUMA_DROP_DOCUMENT_URI'] = uri
                os.environ['PLUMA_DROP_DOCUMENT_NAME'] = name
                os.environ['PLUMA_DROP_DOCUMENT_SCHEME'] = scheme
                os.environ['PLUMA_DROP_DOCUMENT_PATH'] = pathname
                os.environ['PLUMA_DROP_DOCUMENT_DIR'] = dirname
                os.environ['PLUMA_DROP_DOCUMENT_TYPE'] = mime

                buf = self.view.get_buffer()
                location = buf.get_location()
                if location:
                        ruri = location.get_uri()

                relpath = self.relative_path(ruri, uri, mime)

                os.environ['PLUMA_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)

        def in_bounds(self, x, y):
                rect = self.view.get_visible_rect()
                rect.x, rect.y = self.view.buffer_to_window_coords(gtk.TEXT_WINDOW_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 (gtk.targets_include_uri(context.targets) and data.data and self.in_bounds(x, y)):
                        return

                uris = drop_get_uris(data)
                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_completion_hide(self, completion):
                self.provider.set_proposals(None)

        def on_proposal_activated(self, proposal, piter):
                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.TEXT_WINDOW_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, col):
                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.TEXT_WINDOW_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.TEXT_WINDOW_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, col):
                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 from_color(self, col):
                return [col.red / 0x10000, col.green / 0x10000, col.blue / 0x10000]

        def draw_placeholder(self, ctx, placeholder):
                if isinstance(placeholder, PlaceholderEnd):
                        return

                buf = self.view.get_buffer()

                col = self.from_color(self.view.get_style().text[gtk.STATE_INSENSITIVE])
                ctx.set_source_rgba(col[0], col[1], col[2], 0.5)
                
                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, col)
                else:
                        self.draw_placeholder_rect(ctx, placeholder, col)

        def on_expose_event(self, view, event):
                if event.window != view.get_window(gtk.TEXT_WINDOW_TEXT):
                        return False

                # Draw something
                ctx = event.window.cairo_create()
                ctx.rectangle(event.area)
                ctx.clip()

                ctx.set_line_width(1.0)

                for placeholder in self.ordered_placeholders:
                        if not self.placeholder_in_area(placeholder, event.area):
                                continue

                        ctx.save()
                        self.draw_placeholder(ctx, placeholder)
                        ctx.restore()

                return False

# ex:ts=8:et:
