# -*- coding: utf-8 -*-
# modelines.py - Emacs, Kate and Vim-style modelines support for gedit.
#
# Copyright (C) 2005 - Steve Frécinaux
#
# 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import gedit
import re
import gtk
import gconf

class ModelinePlugin(gedit.Plugin):
	def __init__(self):
		gedit.Plugin.__init__(self)

	def connect_handlers(self, view):
		doc = view.get_buffer()
		loaded_id = doc.connect("loaded", apply_modeline, view)
		saved_id  = doc.connect("saved", apply_modeline, view)
		doc.set_data("ModelinePluginHandlerIds", (loaded_id, saved_id))
	
	def disconnect_handlers(self, view):
		doc = view.get_buffer()
		loaded_id, saved_id = doc.get_data("ModelinePluginHandlerIds")
		doc.disconnect(loaded_id)
		doc.disconnect(saved_id)
		doc.set_data("ModelinePluginHandlerIds", None)
	
	def activate(self, window):
		for view in window.get_views(): 
			self.connect_handlers(view)
			apply_modeline(view.get_buffer(), None, view)
			
		tab_added_id = window.connect("tab_added",
		                              lambda w, t: self.connect_handlers(t.get_view()))
		window.set_data("ModelinePluginHandlerId", tab_added_id)
	
	def deactivate(self, window):
		tab_added_id = window.get_data("ModelinePluginHandlerId")
		window.disconnect(tab_added_id)
		window.set_data("ModelinePluginHandlerId", None)
		
		for view in window.get_views():
			self.disconnect_handlers(view)

def apply_modeline(doc, error, view):
	if error is None:
		options = get_modeline_options(doc)
		apply_options(options, view)

def get_modeline_options(doc):
	options = dict()

	# Search the two first lines for emacs- or vim-style modelines
	start = doc.get_start_iter()
	for i in (1, 2):
		end = start.copy()
		if not end.forward_to_line_end():
			return options
		modeline = doc.get_text(start, end, False)
		options.update(parse_modeline_kate(modeline))
		options.update(parse_modeline_vim(modeline))
		options.update(parse_modeline_emacs(modeline))
		start = end

	# Vim can read modeline on the 3rd line
	end = start.copy()
	if not end.forward_to_line_end():
		return options
	modeline = doc.get_text(start, end, False)
	options.update(parse_modeline_kate(modeline))
	options.update(parse_modeline_vim(modeline))
	start = end

	# Kate reads modelines on the 10 first lines
	for i in (4, 5, 6, 7, 8, 9, 10):
		end = start.copy()
		if not end.forward_to_line_end():
			break
		modeline = doc.get_text(start, end, False)
		options.update(parse_modeline_kate(modeline))
		start = end

	# Search the three last lines for vim- and kate-style modelines
	start = doc.get_end_iter()
	start.backward_lines(2)	
	for i in (1, 2, 3):
		end = start.copy()
		if not end.forward_to_line_end():
			return options
		modeline = doc.get_text(start, end, False)
		options.update(parse_modeline_kate(modeline))
		options.update(parse_modeline_vim(modeline))
		start = end

	# Kate reads modelines on the 10 last lines
	start.backward_lines(9)	
	for i in (4, 5, 6, 7, 8, 9, 10):
		end = start.copy()
		if not end.forward_to_line_end():
			return options
		modeline = doc.get_text(start, end, False)
		options.update(parse_modeline_kate(modeline))
		start = end
	
	return options

def parse_modeline_vim(line):
	# First form modeline
	# [text]{white}{vi:|vim:|ex:}[white]{options}
	#
	# Second form
	# [text]{white}{vi:|vim:|ex:}[white]se[t] {options}:[text]

	# Find the modeline
	for i in ('ex:', 'vi:', 'vim:'):
		start = line.find(i)
		if start != -1:	
			start = start + len(i)
			break
	else:
		return {}
	
	if line[start : start + 2] == "se":
		end = line.find(":", start + 3)
		if end == -1:
			return {}
		elif line[start + 2] == 't':
			line = line[start + 4 : end]
		else:
			line = line[start + 3 : end]
	else:
		line = line[start :]
	
	# Get every individual directives
	directives = [x for x in re.split(r'[: ]', line) if x != '']
	options = dict();
	
	for directive in directives:
		# Reformat the directive
		if (directive.find("=") != -1):
			name, value = directive.split("=")
		elif directive[0:2] == "no":
			value = False
			name = directive[2:]
		else:
			value = True
			name = directive
		
		# Set the options according to the directive
		if name == "et" or name == "expandtab":
			options["use-tabs"] = not value
		elif name == "ts" or name == "tabstop":
			options["tabs-width"] = int(value)
		elif name == "wrap":
			options["wrapping"] = value
		elif name == "textwidth":
			options["margin"] = int(value)
	return options

def parse_modeline_emacs(line):
	# cf. http://www.delorie.com/gnu/docs/emacs/emacs_486.html
	# Extract the modeline from the given line
	start = line.find("-*-")
	if start == -1:
		return {}
	else:
		start = start + 3
	end = line.find("-*-", start)
	if end == -1:
		return {}
	
	# Get every individual directives
	directives = [x for x in [i.strip() for i in line[start : end].split(";")] if x != '']
	options = dict()
	
	for directive in directives:
		name, value = ([i.strip() for i in directive.split(":", 1)] + [None])[0:2]
		if name == "tab-width":
			options["tabs-width"] = int(value)
		elif name == "indent-tabs-mode":
			options["use-tabs"] = value != "nil"
		elif name == "autowrap":
			options["wrapping"] = value != "nil"
	return options

def parse_modeline_kate(line):
	# cf. http://wiki.kate-editor.org/index.php/Modelines

	# Extract the modeline from the given line
	start = line.find("kate:")
	if start == -1:
		return {}
	else:
		start = start + 5
	end = line.rfind(";")
	if end == -1:
		return {}
	
	# Get every individual directives
	directives = [x for x in [i.strip() for i in line[start : end].split(";")] if x != '']
	options = dict()
	
	for directive in directives:
		name, value = ([i.strip() for i in directive.split(None, 1)] + [None])[0:2]
		if name == "tab-width" or name == "indent-width":
			options["tabs-width"] = int(value)
		elif name == "space-indent":
			options["use-tabs"] = value not in ("on", "true", "1")
		elif name == "word-wrap":
			options["wrapping"] = value in ("on", "true", "1")
		elif name == "word-wrap-column":
			options["margin"] = int(value)
	return options

def apply_options(options, view):
	old_options = view.get_data("ModelineOptions")

	if not old_options:
		old_options = {}
	if not options:
		options = {}

	if options.has_key("tabs-width"):
		view.set_tabs_width(options["tabs-width"])
	elif old_options.has_key("tabs-width"):
		view.set_tabs_width(gconf_get_int("/apps/gedit-2/preferences/editor/tabs/tabs_size", 8))
	
	if options.has_key("use-tabs"):
		view.set_insert_spaces_instead_of_tabs(not options["use-tabs"])
	elif old_options.has_key("use-tabs"):
		view.set_insert_spaces_instead_of_tabs(gconf_get_bool("/apps/gedit-2/preferences/editor/tabs/insert_spaces", False))

	if options.has_key("wrapping"):
		if options["wrapping"]:
			view.set_wrap_mode(gtk.WRAP_WORD)
		else:
			view.set_wrap_mode(gtk.WRAP_NONE)
	elif old_options.has_key("wrapping"):
		wrap_mode = gconf_get_str("/apps/gedit-2/preferences/editor/wrap_mode/wrap_mode", "GTK_WRAP_WORD")
		if wrap_mode == "GTK_WRAP_WORD":
			view.set_wrap_mode(gtk.WRAP_WORD)
		else:
			view.set_wrap_mode(gtk.WRAP_NONE)		

	if options.has_key("margin"):
		if options["margin"] > 0:
			view.set_margin(long(options["margin"]))
			view.set_show_margin(True)
		else:
			view.set_show_margin(False)
	elif old_options.has_key("margin"):
		view.set_margin(long(gconf_get_int("/apps/gedit-2/preferences/editor/right_margin/right_margin_position", 80)))
		view.set_show_margin(gconf_get_bool("/apps/gedit-2/preferences/editor/right_margin/display_right_margin", False))

	view.set_data("ModelineOptions", options)

gconf_client = gconf.client_get_default()
def gconf_get_bool(key, default = False):
	val = gconf_client.get(key)
	if val is not None and val.type == gconf.VALUE_BOOL:
		return val.get_bool()
	else:
		return default

def gconf_get_str(key, default = ""):
	val = gconf_client.get(key)
	if val is not None and val.type == gconf.VALUE_STRING:
		return val.get_string()
	else:
		return default

def gconf_get_int(key, default = 0):
	val = gconf_client.get(key)
	if val is not None and val.type == gconf.VALUE_INT:
		return val.get_int()
	else:
		return default

# ex:ts=8:noet:
# kate: word-wrap-column 120;
