forked from jiggak/gedit-autotab
-
Notifications
You must be signed in to change notification settings - Fork 1
/
autotab.py
173 lines (139 loc) · 5.39 KB
/
autotab.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# -*- coding: utf-8 -*-
#
# Auto Tab for gedit, automatically detect tab preferences for source files.
# Can be used together with the Modelines plugin without ill effect, modelines
# will take precedence.
#
# Copyright (C) 2007-2010 Kristoffer Lundén (kristoffer.lunden@gmail.com)
# Copyright (C) 2007 Lars Uebernickel (larsuebernickel@gmx.de)
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
from gi.repository import GObject, Gio, Gedit
import operator
# Main class
class AutoTab(GObject.Object, Gedit.ViewActivatable):
__gtype_name__ = "AutoTab"
view = GObject.property(type=Gedit.View)
def do_activate(self):
self.spaces_instead_of_tabs = False
self.tabs_width = 2
settings = Gio.Settings("org.gnome.gedit.preferences.editor")
self.new_tabs_size(settings)
self.new_insert_spaces(settings)
settings.connect("changed::tabs-size", self.new_tabs_size)
settings.connect("changed::insert-spaces", self.new_insert_spaces)
doc = self.view.get_buffer()
# Using connect_after() because we want other plugins to do their
# thing first.
loaded_id = doc.connect_after("loaded", self.auto_tab, self.view)
saved_id = doc.connect_after("saved", self.auto_tab, self.view)
doc.AutoTabPluginHandlerIds = (loaded_id, saved_id)
self.auto_tab(self.view.get_buffer(), self.view)
def do_deactivate(self):
doc = self.view.get_buffer()
loaded_id, saved_id = doc.AutoTabPluginHandlerIds
doc.disconnect(loaded_id)
doc.disconnect(saved_id)
doc.AutoTabPluginHandlerIds = None
# If default tab size changes
def new_tabs_size(self, settings, key=None):
self.tabs_width = settings.get_value("tabs-size").get_uint32()
# If default space/tabs changes
def new_insert_spaces(self, settings, key=None):
self.spaces_instead_of_tabs = settings.get_boolean("insert-spaces")
# Update the values and set a new statusbar message
def update_tabs(self, view, size, space):
view.set_tab_width(size)
view.set_insert_spaces_instead_of_tabs(space)
# Main workhorse, identify what tabs we should use and use them.
def auto_tab(self, doc, view):
# Other plugins compatibility, other plugins can do
# view.AutoTabSkip = True
# and Auto Tab will skip that document as long as this value is true.
if hasattr(view, 'AutoTabSkip') and view.AutoTabSkip:
return
# Special case for makefiles, so the plugin uses tabs even for the empty file:
if doc.get_mime_type() == "text/x-makefile":
self.update_tabs(view, self.tabs_width, False)
return
start, end = doc.get_bounds()
if not end:
return
text = doc.get_text(start, end, True)
# Special marker so all keys are ints
TABS = 666
# Needs to be ordered from largest to smallest
indent_levels = (TABS, 8, 4, 3, 2)
indent_count = {}
for spaces in indent_levels:
indent_count[spaces] = 0
seen_tabs = 0
seen_spaces = 0
prev_indent = 0
for line in text.splitlines():
if len(line) == 0 or not line[0].isspace():
prev_indent = 0
continue
if line[0] == '\t':
indent_count[TABS] += 1
prev_indent = 0
seen_tabs += 1
continue
elif line[0] == ' ':
seen_spaces += 1
indent = 0
for indent in range(0, len(line)):
if line[indent] != ' ':
break
# First pass: indented exactly one step from the previous line?
# larger steps are favoured over smaller ones as
# they might be multiples of a smaller one
for spaces in indent_levels:
if spaces == TABS:
continue
if (indent % spaces) != 0:
continue
if abs(indent - prev_indent) != spaces:
continue
indent_count[spaces] += 1
break
else:
# Second pass: indentation ambigious; add to all candidates
for spaces in indent_levels:
if spaces == TABS:
continue
if (indent % spaces) != 0:
continue
indent_count[spaces] += 1
prev_indent = indent
# no indentations detected
if sum(indent_count.values()) == 0:
# if we've seen tabs or spaces, default to those
# can't guess at size, so using default
if seen_tabs or seen_spaces:
if seen_tabs > seen_spaces:
self.update_tabs(view, self.tabs_width, False)
else:
self.update_tabs(view, self.tabs_width, True)
return
# Since some indentation steps may be multiples of others, we
# need to prioritise larger indentations when there is a tie.
winner = None
for key in indent_levels:
if (winner is None) or (indent_count[key] > indent_count[winner]):
winner = key
if winner == TABS:
self.update_tabs(view, self.tabs_width, False)
else:
self.update_tabs(view, winner, True)