├── LICENSE
├── Main.sublime-menu
├── README.md
├── common.py
├── messages.json
├── messages
├── 0.1.1.txt
├── 0.1.2.txt
├── 0.1.3.txt
├── 0.1.4.txt
├── 0.1.5.txt
├── 0.1.6.txt
└── install.txt
├── outline-Dark.hidden-tmTheme
├── outline.hidden-tmLanguage
├── outline.hidden-tmTheme
├── outline.py
├── outline.sublime-commands
├── outline.sublime-settings
├── screenshot.png
└── show.py
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2015 Michael Kleehammer and Allen Bargi
4 | Copyright (c) 2016 Xiongbing Jin
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences",
4 | "mnemonic": "n",
5 | "id": "preferences",
6 | "children":
7 | [
8 | {
9 | "caption": "Package Settings",
10 | "mnemonic": "P",
11 | "id": "package-settings",
12 | "children":
13 | [
14 | {
15 | "caption": "Outline",
16 | "children":
17 | [
18 | {
19 | "command": "edit_settings",
20 | "args": {
21 | "base_file": "${packages}/Outline/outline.sublime-settings",
22 | "default": "{\n\t$0\n}\n"
23 | },
24 | "caption": "Settings"
25 | },
26 | { "caption": "-" },
27 | {
28 | "command": "open_file",
29 | "args": {"file": "${packages}/Outline/outline.sublime-settings"},
30 | "caption": "Settings – Default"
31 | },
32 | {
33 | "command": "open_file",
34 | "args": {"file": "${packages}/User/outline.sublime-settings"},
35 | "caption": "Settings – User"
36 | }
37 | ]
38 | }
39 | ]
40 | }
41 | ]
42 | }
43 | ]
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Outline for Sublime Text 3
2 |
3 | ### Overview
4 |
5 | Inspired by [FileBrowser](https://github.com/aziz/SublimeFileBrowser), this package shows the outline of your file, class/function name list of your code, or table of content/toc of your markdown/LaTeX document in a sidebar-style tab.
6 |
7 | 
8 |
9 | ### Installation
10 |
11 | #### Install via Package Control
12 |
13 | This package is available on [Package Control](https://packagecontrol.io/). Search for `Outline`.
14 |
15 | #### Manual installation
16 |
17 | 1. Clone or download this repository to your hard drive using the green `Clone or download` button
18 | 2. Rename the cloned or extracted folder to `Outline`. Make sure `outline.py` is at the root of the `Outline` folder.
19 | 3. Move the `Outline` folder to your Sublime Text's `Packages` folder. To find the `Packages` folder, click menu `Preferences` > `Browse Packages`.
20 | 4. Restart Sublime Text, and press `Ctrl + Shift + P` to select your preferred layout (`Browse Mode`)
21 |
22 | ### Default layout
23 |
24 | The outline tab can be set as a sidebar on the left or right. Press `Ctrl + Shift + P` and select either `Browse Mode: Outline (Left)` or `Browse Mode: Outline (Right)` to set your preferred layout.
25 |
26 | If you also use [FileBrowser](https://github.com/aziz/SublimeFileBrowser), you can use both in three different layouts:
27 |
28 | * `FileBrowser` left, `Outline` right
29 | * `FileBrowser` top left, `Outline` bottom left
30 | * `FileBrowser` top right, `Outline` bottom right
31 |
32 | To use `FileBrowser` and `Outline` together, please close the `FileBrowser` sidebar first and then use the correponding `Browse Mode` command to set the layout, otherwise the `Outline` view may not work as intended.
33 |
34 | ### Color theme
35 |
36 | `Outline` can be configured to use the current editor color scheme. To do so, change the setting below:
37 |
38 | ```json
39 | "outline_inherit_color_scheme": true
40 | ```
41 |
42 | `Outline` has two built-in color themes: Bright (default theme) and Dark. To switch to the Dark theme, add the following to your user settings file. Open the user settings file by "Preferences > Package Settings > Outline > Settings" (Sublime Text version 3124 or later), or "Settings - User":
43 |
44 | ```json
45 | "outline_inherit_color_scheme": false
46 | "color_scheme": "Packages/Outline/outline-Dark.hidden-tmTheme"
47 | ```
48 |
49 | Remove `-Dark` or remove the entire line to return to the bright theme. To customize your own color theme, see [this issue](https://github.com/warmdev/SublimeOutline/issues/1).
50 |
51 | ### Outline content and indentation
52 |
53 | Outline is updated when you save a file or switch between files.
54 |
55 | Content and indentation in the `Outline` tab is controlled by the `Symbol List.tmPreferences` file (file name may differ) corresponding to the syntax of your file.
56 |
57 | ### Known issue
58 |
59 | * This package may not work if you use a multi-column/row layout.
60 |
61 | ### License
62 |
63 | This plugin is licensed under the MIT license.
64 |
--------------------------------------------------------------------------------
/common.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | '''Common stuff, used in other modules'''
4 |
5 | from __future__ import print_function
6 | import re, os, fnmatch, sys, itertools
7 | import sublime
8 | from sublime import Region
9 | from os.path import isdir, join, basename
10 |
11 | if sublime.platform() == 'windows':
12 | import ctypes
13 |
14 | ST3 = int(sublime.version()) >= 3000
15 |
16 | if ST3:
17 | MARK_OPTIONS = sublime.DRAW_NO_OUTLINE
18 | else:
19 | MARK_OPTIONS = 0
20 |
21 | OS = sublime.platform()
22 | NT = OS == 'windows'
23 | LIN = OS == 'linux'
24 | OSX = OS == 'osx'
25 | RE_FILE = re.compile(r'^(\s*)([^\\//].*)$')
26 | PARENT_SYM = u"⠤"
27 |
28 |
29 | def first(seq, pred):
30 | '''similar to built-in any() but return the object instead of boolean'''
31 | return next((item for item in seq if pred(item)), None)
32 |
33 |
34 | def sort_nicely(names):
35 | """ Sort the given list in the way that humans expect.
36 | Source: http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
37 | """
38 | convert = lambda text: int(text) if text.isdigit() else text.lower()
39 | alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
40 | names.sort(key=alphanum_key)
41 |
42 |
43 | def print(*args, **kwargs):
44 | """ Redefine print() function; the reason is the inconsistent treatment of
45 | unicode literals among Python versions used in ST2.
46 | Redefining/tweaking built-in things is relatively safe; of course, when
47 | ST2 will become irrelevant, this def might be removed undoubtedly.
48 | """
49 | if not (ST3 or NT):
50 | args = (s.encode('utf-8') if isinstance(s, unicode) else str(s) for s in args)
51 | else:
52 | args = (s if isinstance(s, str if ST3 else unicode) else str(s) for s in args)
53 | sep, end = kwargs.get('sep', ' '), kwargs.get('end', '\n')
54 | sys.stdout.write(sep.join(s for s in args) + end)
55 |
56 |
57 | def set_proper_scheme(view):
58 | '''
59 | this is callback, it is not meant to be called directly
60 | view.settings().add_on_change('color_scheme', lambda: set_proper_scheme(view))
61 | set once, right after view is created
62 | _note_, color_scheme must not be set directly, but in a setting file
63 | '''
64 | # Since we cannot create file with syntax, there is moment when view has no settings,
65 | # but it is activated, so some plugins (e.g. Color Highlighter) set wrong color scheme
66 | if view.settings().get('outline_rename_mode', False):
67 | outline_settings = sublime.load_settings('outline-rename-mode.sublime-settings')
68 | else:
69 | outline_settings = sublime.load_settings('outline.sublime-settings')
70 |
71 | if view.settings().get('color_scheme') == outline_settings.get('color_scheme'):
72 | return
73 |
74 | view.settings().set('color_scheme', outline_settings.get('color_scheme'))
75 |
76 |
77 | def calc_width(view):
78 | '''
79 | return float width, which must be
80 | 0.0 < width < 1.0 (other values acceptable, but cause unfriendly layout)
81 | used in show.show() and "outline_select" command with other_group=True
82 | '''
83 | width = view.settings().get('outline_width', 0.3)
84 | if isinstance(width, float):
85 | width -= width//1 # must be less than 1
86 | elif isinstance(width, int if ST3 else long): # assume it is pixels
87 | wport = view.viewport_extent()[0]
88 | width = 1 - round((wport - width) / wport, 2)
89 | if width >= 1:
90 | width = 0.9
91 | else:
92 | sublime.error_message(u'FileBrowser:\n\noutline_width set to '
93 | u'unacceptable type "%s", please change it.\n\n'
94 | u'Fallback to default 0.3 for now.' % type(width))
95 | width = 0.3
96 | return width or 0.1 # avoid 0.0
97 |
98 |
99 | def get_group(groups, nag):
100 | '''
101 | groups amount of groups in window
102 | nag number of active group
103 | return number of neighbour group
104 | '''
105 | if groups <= 4 and nag < 2:
106 | group = 1 if nag == 0 else 0
107 | elif groups == 4 and nag >= 2:
108 | group = 3 if nag == 2 else 2
109 | else:
110 | group = nag - 1
111 | return group
112 |
113 |
114 | def relative_path(rpath):
115 | u'''rpath is either list or empty string (if list, we need only first item);
116 | return either empty string or rpath[0] (or its parent), e.g.
117 | foo/bar/ → foo/bar/
118 | foo/bar → foo/
119 | '''
120 | if rpath:
121 | rpath = rpath[0]
122 | if rpath[~0] != os.sep:
123 | rpath = os.path.split(rpath)[0] + os.sep
124 | if rpath == os.sep:
125 | rpath = ''
126 | return rpath
127 |
128 |
129 | def hijack_window():
130 | '''Execute on loading plugin or on new window open;
131 | allow to open FB automatically in ST3
132 | '''
133 | settings = sublime.load_settings('outline.sublime-settings')
134 | command = settings.get("outline_hijack_new_window")
135 | if command:
136 | if command == "jump_list":
137 | sublime.set_timeout(lambda: sublime.windows()[-1].run_command("outline_jump_list"), 1)
138 | else:
139 | sublime.set_timeout(lambda: sublime.windows()[-1].run_command("outline", {"immediate": True}), 1)
140 |
141 |
142 | class outlineBaseCommand:
143 | """
144 | Convenience functions for outline TextCommands
145 | """
146 | @property
147 | def path(self):
148 | return self.view.settings().get('outline_path')
149 |
150 | def get_path(self):
151 | path = self.path
152 | if path == 'ThisPC\\':
153 | path = ''
154 | return path
155 |
156 | def filecount(self):
157 | """
158 | Returns the number of files and directories in the view.
159 | """
160 | return self.view.settings().get('outline_count', 0)
161 |
162 | def move_to_extreme(self, extreme="bof"):
163 | """
164 | Moves the cursor to the beginning or end of file list. Clears all sections.
165 | """
166 | files = self.fileregion(with_parent_link=True)
167 | self.view.sel().clear()
168 | if extreme == "bof":
169 | ext_region = Region(files.a, files.a)
170 | else:
171 | name_point = self.view.extract_scope(self.view.line(files.b).a + 2).a
172 | ext_region = Region(name_point, name_point)
173 | self.view.sel().add(ext_region)
174 | self.view.show_at_center(ext_region)
175 |
176 | def move(self, forward=None):
177 | """
178 | Moves the cursor one line forward or backwards. Clears all sections.
179 | """
180 | assert forward in (True, False), 'forward must be set to True or False'
181 |
182 | files = self.fileregion(with_parent_link=True)
183 | if not files:
184 | return
185 |
186 | new_sels = []
187 | for s in list(self.view.sel()):
188 | new_sels.append(self._get_name_point(self.next_line(forward, s.a, files)))
189 |
190 | self.view.sel().clear()
191 | for n in new_sels:
192 | self.view.sel().add(Region(n, n))
193 | name_point = new_sels[~0] if forward else new_sels[0]
194 | surroundings = True if self.view.rowcol(name_point)[0] < 3 else False
195 | self.view.show(name_point, surroundings)
196 |
197 | def next_line(self, forward, pt, filergn):
198 | '''Return Region of line for pt within filergn'''
199 | if filergn.contains(pt):
200 | # Try moving by one line.
201 | line = self.view.line(pt)
202 | pt = forward and (line.b + 1) or (line.a - 1)
203 | if not filergn.contains(pt):
204 | # Not (or no longer) in the list of files, so move to the closest edge.
205 | pt = (pt > filergn.b) and filergn.b or filergn.a
206 | return self.view.line(pt)
207 |
208 | def _get_name_point(self, line):
209 | '''Return point at which filename starts (i.e. after icon & whitspace)'''
210 | scope = self.view.scope_name(line.a)
211 | if 'indent' in scope:
212 | name_point = self.view.extract_scope(line.a).b
213 | else:
214 | name_point = line.a + (2 if not 'parent_dir' in scope else 0)
215 | return name_point
216 |
217 | def show_parent(self):
218 | return self.view.settings().get('outline_show_parent', False)
219 |
220 | def fileregion(self, with_parent_link=False):
221 | """
222 | Returns a region containing the lines containing filenames.
223 | If there are no filenames None is returned.
224 | """
225 | if with_parent_link:
226 | all_items = sorted(self.view.find_by_selector('outline.item'))
227 | else:
228 | all_items = sorted(self.view.find_by_selector('outline.item.directory') +
229 | self.view.find_by_selector('outline.item.file'))
230 | if not all_items:
231 | return None
232 | return Region(all_items[0].a, all_items[~0].b)
233 |
234 | def get_parent(self, line, path):
235 | u'''
236 | Returns relative path for line
237 | • line is a region
238 | • path is self.path
239 | • self.index is list stored in view settings as 'outline_index'
240 | '''
241 | return self.get_fullpath_for(line).replace(path, '', 1)
242 |
243 | def get_fullpath_for(self, line):
244 | return self.index[self.view.rowcol(line.a)[0]]
245 |
246 | def get_all(self):
247 | """
248 | Returns a list of all filenames in the view.
249 | outline_index is always supposed to represent current state of view,
250 | each item matches corresponding line, thus list will never be empty unless sth went wrong;
251 | if header is enabled then first two elements are empty strings
252 | """
253 | index = self.view.settings().get('outline_index', [])
254 | if not index:
255 | return sublime.error_message(u'FileBrowser:\n\n"outline_index" is empty,\n'
256 | u'that shouldn’t happen ever, there is some bug.')
257 | return index
258 |
259 | def get_all_relative(self, path):
260 | return [f.replace(path, '', 1) for f in self.get_all()]
261 |
262 | def get_selected(self, parent=True, full=False):
263 | """
264 | parent
265 | if False, returned list does not contain PARENT_SYM even if it is in view
266 | full
267 | if True, items in returned list are full paths, else relative
268 |
269 | Returns a list of selected filenames.
270 | self.index should be assigned before call it
271 | """
272 | fileregion = self.fileregion(with_parent_link=parent)
273 | if not fileregion:
274 | return None
275 | path = self.get_path()
276 | names = []
277 | for line in self._get_lines([s for s in self.view.sel()], fileregion):
278 | text = self.get_fullpath_for(line) if full else self.get_parent(line, path)
279 | if text and text not in names:
280 | names.append(text)
281 | return names
282 |
283 | def get_marked(self, full=False):
284 | '''self.index should be assigned before call it'''
285 | if not self.filecount():
286 | return []
287 | path = self.get_path()
288 | names = []
289 | for line in self.view.get_regions('marked'):
290 | text = self.get_fullpath_for(line) if full else self.get_parent(line, path)
291 | if text and text not in names:
292 | names.append(text)
293 | return names
294 |
295 | def _mark(self, mark, regions):
296 | """
297 | Marks the requested files.
298 |
299 | mark
300 | True, False, or a function with signature `func(oldmark, filename)`.
301 | The function should return True or False.
302 |
303 | regions
304 | List of region(s). Only files within the region will be modified.
305 | """
306 | filergn = self.fileregion()
307 | if not filergn:
308 | return
309 |
310 | self.index = self.get_all()
311 | # We can't update regions for a key, only replace, so we need to record the existing marks.
312 | marked = dict((self.get_fullpath_for(r), r) for r in self.view.get_regions('marked') if not r.empty())
313 |
314 | for line in self._get_lines(regions, filergn):
315 | filename = self.get_fullpath_for(line)
316 |
317 | if mark not in (True, False):
318 | newmark = mark(filename in marked, filename)
319 | assert newmark in (True, False), u'Invalid mark: {0}'.format(newmark)
320 | else:
321 | newmark = mark
322 |
323 | if newmark:
324 | name_point = self._get_name_point(line)
325 | marked[filename] = Region(name_point, line.b)
326 | else:
327 | marked.pop(filename, None)
328 |
329 | if marked:
330 | r = sorted(list(marked.values()), key=lambda region: region.a)
331 | self.view.add_regions('marked', r, 'outline.marked', '', MARK_OPTIONS)
332 | else:
333 | self.view.erase_regions('marked')
334 |
335 | def _get_lines(self, regions, within):
336 | '''
337 | regions is a list of non-overlapping region(s), each may have many lines
338 | within is a region which is supposed to contain each line
339 | '''
340 | return (line for line in itertools.chain(*(self.view.lines(r) for r in regions)) if within.contains(line))
341 |
342 | def set_ui_in_rename_mode(self, edit):
343 | header = self.view.settings().get('outline_header', False)
344 | if header:
345 | regions = self.view.find_by_selector('text.outline header.outline punctuation.definition.separator.outline')
346 | else:
347 | regions = self.view.find_by_selector('text.outline outline.item.parent_dir')
348 | if not regions:
349 | return
350 | region = regions[0]
351 | start = region.begin()
352 | self.view.erase(edit, region)
353 | if header:
354 | new_text = u"——[RENAME MODE]——" + u"—"*(region.size()-17)
355 | else:
356 | new_text = u"⠤ [RENAME MODE]"
357 | self.view.insert(edit, start, new_text)
358 |
359 | def set_status(self):
360 | '''Update status-bar;
361 | self.show_hidden must be assigned before call it'''
362 | # if view isnot focused, view.window() may be None
363 | window = self.view.window() or sublime.active_window()
364 | path_in_project = any(folder == self.path[:-1] for folder in window.folders())
365 | settings = self.view.settings()
366 | copied_items = settings.get('outline_to_copy', [])
367 | cut_items = settings.get('outline_to_move', [])
368 | status = u" 𝌆 [?: Help] {0}Hidden: {1}{2}{3}".format(
369 | 'Project root, ' if path_in_project else '',
370 | 'On' if self.show_hidden else 'Off',
371 | ', copied(%d)' % len(copied_items) if copied_items else '',
372 | ', cut(%d)' % len(cut_items) if cut_items else ''
373 | )
374 | self.view.set_status("__FileBrowser__", status)
375 |
376 | def prepare_filelist(self, names, path, goto, indent):
377 | '''About self.index see outlineRefreshCommand
378 | could be called from outlineExpand.expand_single_folder
379 | or from outlineRefresh.continue_refresh
380 | '''
381 | items = []
382 | tab = self.view.settings().get('tab_size')
383 | line = self.view.line(self.sel.a if self.sel is not None else self.view.sel()[0].a)
384 | content = self.view.substr(line).replace('\t', ' '*tab)
385 | ind = re.compile('^(\s*)').match(content).group(1)
386 | level = indent * int((len(ind) / tab) + 1) if ind else indent
387 | files = []
388 | index_dirs = []
389 | index_files = []
390 | for name in names:
391 | full_name = join(path, goto, name)
392 | if isdir(full_name):
393 | index_dirs.append(u'%s%s' % (full_name, os.sep))
394 | items.append(''.join([level, u"▸ ", name, os.sep]))
395 | else:
396 | index_files.append(full_name)
397 | files.append(''.join([level, u"≡ ", name]))
398 | index = index_dirs + index_files
399 | self.index = self.index[:self.number_line] + index + self.index[self.number_line:]
400 | items += files
401 | return items
402 |
403 | def is_hidden(self, filename, path, goto=''):
404 | if not (path or goto): # special case for ThisPC
405 | return False
406 | tests = self.view.settings().get('outline_hidden_files_patterns', ['.*'])
407 | if isinstance(tests, str):
408 | tests = [tests]
409 | if any(fnmatch.fnmatch(filename, pattern) for pattern in tests):
410 | return True
411 | if sublime.platform() != 'windows':
412 | return False
413 | # check for attribute on windows:
414 | try:
415 | attrs = ctypes.windll.kernel32.GetFileAttributesW(join(path, goto, filename))
416 | assert attrs != -1
417 | result = bool(attrs & 2)
418 | except (AttributeError, AssertionError):
419 | result = False
420 | return result
421 |
422 | def try_listing_directory(self, path):
423 | '''Return tuple of two element
424 | items sorted list of filenames in path, or empty list
425 | error exception message, or empty string
426 | '''
427 | items, error = [], ''
428 | try:
429 | if not self.show_hidden:
430 | items = [name for name in os.listdir(path) if not self.is_hidden(name, path)]
431 | else:
432 | items = os.listdir(path)
433 | except OSError as e:
434 | error = str(e)
435 | if NT:
436 | error = error.split(':')[0].replace('[Error 5] ', 'Access denied').replace('[Error 3] ', 'Not exists, press r to refresh')
437 | if not ST3 and LIN:
438 | error = error.decode('utf8')
439 | else:
440 | sort_nicely(items)
441 | finally:
442 | return items, error
443 |
444 | def try_listing_only_dirs(self, path):
445 | '''Same as self.try_listing_directory, but items contains only directories.
446 | Used for prompt completion'''
447 | items, error = self.try_listing_directory(path)
448 | if items:
449 | items = [n for n in items if isdir(join(path, n))]
450 | return (items, error)
451 |
452 | def restore_marks(self, marked=None):
453 | if marked:
454 | # Even if we have the same filenames, they may have moved so we have to manually
455 | # find them again.
456 | path = self.get_path()
457 | regions = []
458 | for mark in marked:
459 | matches = self._find_in_view(mark)
460 | for region in matches:
461 | filename = self.get_parent(region, path)
462 | if filename == mark:
463 | regions.append(region)
464 | # if it is found, no need to check other mathes, so break
465 | break
466 | self._mark(mark=True, regions=regions)
467 | else:
468 | self.view.erase_regions('marked')
469 |
470 | def restore_sels(self, sels=None):
471 | '''
472 | sels is tuple of two elements:
473 | 0 list of filenames
474 | relative paths to search in the view
475 | 1 list of Regions
476 | copy of view.sel(), used for fallback if filenames are not found
477 | in view (e.g. user deleted selected file)
478 | '''
479 | if sels:
480 | seled_fnames, seled_regions = sels
481 | path = self.get_path()
482 | regions = []
483 | for selection in seled_fnames:
484 | matches = self._find_in_view(selection)
485 | for region in matches:
486 | filename = self.get_parent(region, path)
487 | if filename == selection:
488 | name_point = self._get_name_point(region)
489 | regions.append(Region(name_point, name_point))
490 | break
491 | if regions:
492 | return self._add_sels(regions)
493 | else:
494 | # e.g. when user remove file(s), we just restore sel RegionSet
495 | # despite positions may be wrong sometimes
496 | return self._add_sels(seled_regions)
497 | # fallback:
498 | return self._add_sels()
499 |
500 | def _find_in_view(self, item):
501 | '''item is Unicode'''
502 | fname = re.escape(basename(os.path.abspath(item)) or item.rstrip(os.sep))
503 | if item[~0] == os.sep:
504 | pattern = u'^\s*[▸▾] '
505 | sep = re.escape(os.sep)
506 | else:
507 | pattern = u'^\s*≡ '
508 | sep = ''
509 | return self.view.find_all(u'%s%s%s' % (pattern, fname, sep))
510 |
511 | def _add_sels(self, sels=None):
512 | self.view.sel().clear()
513 |
514 | if sels:
515 | eof = self.view.size()
516 | for s in sels:
517 | if s.begin() <= eof:
518 | self.view.sel().add(s)
519 |
520 | if not sels or not list(self.view.sel()): # all sels more than eof
521 | item = (self.view.find_by_selector('text.outline outline.item.parent_dir ') or
522 | self.view.find_by_selector('text.outline outline.item.directory string.name.directory.outline ') or
523 | self.view.find_by_selector('text.outline outline.item.file string.name.file.outline '))
524 | s = Region(item[0].a, item[0].a) if item else Region(0, 0)
525 | self.view.sel().add(s)
526 |
527 | self.view.show_at_center(s)
528 |
529 | def display_path(self, folder):
530 | display = folder
531 | home = os.path.expanduser("~")
532 | if folder.startswith(home):
533 | display = folder.replace(home, "~", 1)
534 | return display
535 |
--------------------------------------------------------------------------------
/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "install": "messages/install.txt",
3 | "0.1.1": "messages/0.1.1.txt",
4 | "0.1.2": "messages/0.1.2.txt",
5 | "0.1.3": "messages/0.1.3.txt",
6 | "0.1.4": "messages/0.1.4.txt",
7 | "0.1.5": "messages/0.1.5.txt",
8 | "0.1.6": "messages/0.1.6.txt"
9 | }
10 |
--------------------------------------------------------------------------------
/messages/0.1.1.txt:
--------------------------------------------------------------------------------
1 | 0.1.1
2 |
3 | * Added package settings menu items (Preferences > Package Settings > Outline)
4 | * Added dark theme. To enable the dark theme, add the following line to your package user settings file. Open the user settings file by "Preferences > Package Settings > Outline > Settings" (Sublime Text version 3124 or later), or "Settings - User"
5 |
6 | ```json
7 | "color_scheme": "Packages/Outline/outline-Dark.hidden-tmTheme"
8 | ```
--------------------------------------------------------------------------------
/messages/0.1.2.txt:
--------------------------------------------------------------------------------
1 | 0.1.2
2 |
3 | * Added ability to sort outline alphabetically by setting or by command
4 | - By setting: add the following line in settings and change the value to `true`:
5 |
6 | ```json
7 | "outline_alphabetical": true,
8 | ```
9 |
10 | - By command: press `Ctrl + Shift + P` and select `Outline: Toggle Sort`
11 |
--------------------------------------------------------------------------------
/messages/0.1.3.txt:
--------------------------------------------------------------------------------
1 | 0.1.3
2 |
3 | * Added ability to keep outline in sync with current file position (outline view will scroll to current symbol and have it highlighted). Note this does not work with alphabetical sort.
4 |
5 | * Added setting to turn on/off the syncing feature (default is on)
6 |
7 | ```json
8 | "outline_sync": true,
9 | ```
10 |
--------------------------------------------------------------------------------
/messages/0.1.4.txt:
--------------------------------------------------------------------------------
1 | 0.1.4
2 |
3 | * Fixed issue where after closing outline sidebar, focus is on an empty tab
4 |
5 | * When clicking on a symbol item in the outline pane, highlight in the main view depends on the setting `outline_main_view_highlight_mode`:
6 |
7 | * `cursor`: no highlight, put cursor at the end of the symbol line
8 | * `symbol`: highlight the current symbol (same as previous implementation)
9 | * `block`: highlight the entire symbol block (i.e. between current and next symbol)
10 |
--------------------------------------------------------------------------------
/messages/0.1.5.txt:
--------------------------------------------------------------------------------
1 | 0.1.5
2 |
3 | * Added option to use editor color scheme for outline. Change setting `outline_inherit_color_scheme` to `true`. Close and reopen outline tab for the change to take effect. If you change the editor color scheme, close and reopen outline tab for the new color scheme to take effect.
4 |
--------------------------------------------------------------------------------
/messages/0.1.6.txt:
--------------------------------------------------------------------------------
1 | 0.1.6
2 |
3 | * Made the width of the outline sidebar user customizable. Use `outline_width` setting to change the width (default 0.2, i.e. 20% of the entire window width)
4 |
--------------------------------------------------------------------------------
/messages/install.txt:
--------------------------------------------------------------------------------
1 | Thank you for installing the Outline package.
2 |
3 | To start, bring up the Command Palette with `Ctrl + Shift + P`, and select `Browse Mode: Outline (Left)` or another preferred layout.
4 |
5 | Please visit https://github.com/warmdev/SublimeOutline for more details.
--------------------------------------------------------------------------------
/outline-Dark.hidden-tmTheme:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | Browse
7 | settings
8 |
9 |
10 | settings
11 |
12 | background
13 | #272822
14 | foreground
15 | #222222
16 | caret
17 | #367cc055
18 | lineHighlight
19 | #367cc055
20 | gutter
21 | #272822
22 | invisibles
23 | #88222233
24 | selection
25 | #22222233
26 | selectionBorder
27 | #22222233
28 | inactiveSelection
29 | #22222219
30 | shadow
31 | #272822
32 | shadowWidth
33 | 16
34 |
35 |
36 |
37 |
38 |
39 | name
40 | Project Symbols > Star
41 | scope
42 | punctuation.definition.projects.star.symbol.outline
43 | settings
44 |
45 | foreground
46 | #7c8692
47 |
48 |
49 |
50 | name
51 | Project Symbols > Chevron
52 | scope
53 | punctuation.definition.projects.chevron.symbol.outline
54 | settings
55 |
56 | foreground
57 | #7c869266
58 |
59 |
60 |
61 |
62 | name
63 | Project Path
64 | scope
65 | string.name.project_path.outline
66 | settings
67 |
68 | foreground
69 | #707e8b
70 |
71 |
72 |
73 |
74 | name
75 | File/Dir Symbols
76 | scope
77 | punctuation.definition.directory.symbol.outline, punctuation.definition.file.symbol.outline, outline.item.parent_dir
78 | settings
79 |
80 | foreground
81 | #7c8692
82 |
83 |
84 |
85 |
86 | name
87 | Dir Slash
88 | scope
89 | punctuation.definition.directory.slash.outline
90 | settings
91 |
92 | foreground
93 | #919aa388
94 |
95 |
96 |
97 |
98 | name
99 | File Extension
100 | scope
101 | string.name.file.extension.outline
102 | settings
103 |
104 | foreground
105 | #919aa3
106 |
107 |
108 |
109 |
110 | name
111 | Dir Name
112 | scope
113 | string.name.directory.outline, string.name.project.outline
114 | settings
115 |
116 | fontStyle
117 | bold
118 |
119 |
120 |
121 |
122 | name
123 | Header
124 | scope
125 | header.outline
126 | settings
127 |
128 | foreground
129 | #f8f8f2
130 |
131 |
132 |
133 |
134 | name
135 | Header punctuations
136 | scope
137 | header.outline punctuation.definition.separator.outline
138 | settings
139 |
140 | foreground
141 | #6f798733
142 |
143 |
144 |
145 |
146 | name
147 | Header Rename Mode Title
148 | scope
149 | punctuation.definition.rename_mode
150 | settings
151 |
152 | foreground
153 | #E99023
154 |
155 |
156 |
157 |
158 | name
159 | Marked
160 | scope
161 | outline.marked
162 | settings
163 |
164 | foreground
165 | #FFFFFF
166 | background
167 | #4b96d6
168 |
169 |
170 |
171 |
172 |
173 | name
174 | Help: Table Name
175 | scope
176 | outline.help.header
177 | settings
178 |
179 | foreground
180 | #707E8B
181 | fontStyle
182 | bold
183 |
184 |
185 |
186 |
187 | name
188 | Help: Table Header
189 | scope
190 | outline.table.th
191 | settings
192 |
193 | background
194 | #4B96D6
195 |
196 |
197 |
198 |
199 | name
200 | Help: Table Header > Title
201 | scope
202 | outline.table.th title
203 | settings
204 |
205 | foreground
206 | #FFFFFF
207 |
208 |
209 |
210 |
211 | name
212 | Help: Table Header
213 | scope
214 | outline.table.tr
215 | settings
216 |
217 | background
218 | #FFFFFF
219 |
220 |
221 |
222 |
223 | name
224 | Help: Table punctuations
225 | scope
226 | outline.table.punctuations
227 | settings
228 |
229 | foreground
230 | #d9dee5
231 | background
232 | #d9dee5
233 |
234 |
235 |
236 |
237 | name
238 | Help: Shortcut
239 | scope
240 | outline.table.tr command_shortcut
241 | settings
242 |
243 | foreground
244 | #D6774B
245 | fontStyle
246 | bold
247 |
248 |
249 |
250 |
251 | name
252 | Help: Command Description
253 | scope
254 | outline.table.tr command_desc
255 | settings
256 |
257 | foreground
258 | #000000
259 |
260 |
261 |
262 |
263 |
264 | name
265 | Error
266 | scope
267 | string.error.outline
268 | settings
269 |
270 | foreground
271 | #aa0000
272 |
273 |
274 |
275 |
276 | name
277 | VCS Modified
278 | scope
279 | item.modified.outline
280 | settings
281 |
282 | background
283 | #d9dee0
284 | foreground
285 | #F09609
286 |
287 |
288 |
289 |
290 | name
291 | VCS Untracked
292 | scope
293 | item.untracked.outline
294 | settings
295 |
296 | background
297 | #d9dee0
298 | foreground
299 | #00c406
300 |
301 |
302 |
303 |
304 | name
305 | VCS Colorblind
306 | scope
307 | item.colorblind.outline
308 | settings
309 |
310 | background
311 | #000000
312 |
313 |
314 |
315 |
316 | uuid
317 | 07379361-ce49-4e6c-a03f-26378a7a2131
318 |
319 |
320 |
--------------------------------------------------------------------------------
/outline.hidden-tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | outline
7 | scopeName
8 | text.outline
9 | uuid
10 | ed69e186-9ee2-11e5-8994-feff819cdc9f
11 | fileTypes
12 |
13 | outline
14 |
15 | patterns
16 |
17 |
18 |
19 | match
20 | ^(\s*)([▸▾] )([^\\/]*)(\\|/)?(.*)?$
21 | name
22 | outline.item.directory
23 | captures
24 |
25 | 1
26 |
27 | name
28 | indent
29 |
30 | 2
31 |
32 | name
33 | punctuation.definition.directory.symbol.outline
34 |
35 | 3
36 |
37 | name
38 | string.name.directory.outline
39 |
40 | 4
41 |
42 | name
43 | punctuation.definition.directory.slash.outline
44 |
45 | 5
46 |
47 | name
48 | string.error.outline
49 |
50 |
51 |
52 |
53 |
54 | match
55 | ^(\s*)(≡ )(\S.*?(\.[^\.\n]+)?)$
56 | name
57 | outline.item.file
58 | captures
59 |
60 | 1
61 |
62 | name
63 | indent
64 |
65 | 2
66 |
67 | name
68 | punctuation.definition.file.symbol.outline
69 |
70 | 3
71 |
72 | name
73 | string.name.file.outline
74 |
75 | 4
76 |
77 | name
78 | string.name.file.extension.outline
79 |
80 |
81 |
82 |
83 |
84 | match
85 | ^⠤(\s*\[.+\]){0,1}$
86 | name
87 | outline.item.parent_dir
88 | captures
89 |
90 | 1
91 |
92 | name
93 | punctuation.definition.rename_mode.outline
94 |
95 |
96 |
97 |
98 |
99 | begin
100 | (\S(.+)?$)
101 | end
102 | ^(—+)(\[RENAME MODE\]){0,1}(—*)\n
103 | name
104 | header.outline
105 | endCaptures
106 |
107 | 1
108 |
109 | name
110 | punctuation.definition.separator.outline
111 |
112 | 2
113 |
114 | name
115 | punctuation.definition.rename_mode.outline
116 |
117 | 3
118 |
119 | name
120 | punctuation.definition.separator.outline
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/outline.hidden-tmTheme:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | Browse
7 | settings
8 |
9 |
10 | settings
11 |
12 | background
13 | #d9dee5
14 | foreground
15 | #222222
16 | caret
17 | #367cc055
18 | lineHighlight
19 | #367cc055
20 | gutter
21 | #d9dee5
22 | invisibles
23 | #88222233
24 | selection
25 | #22222233
26 | selectionBorder
27 | #22222233
28 | inactiveSelection
29 | #22222219
30 | shadow
31 | #d9dee5
32 | shadowWidth
33 | 16
34 |
35 |
36 |
37 |
38 |
39 | name
40 | Project Symbols > Star
41 | scope
42 | punctuation.definition.projects.star.symbol.outline
43 | settings
44 |
45 | foreground
46 | #7c8692
47 |
48 |
49 |
50 | name
51 | Project Symbols > Chevron
52 | scope
53 | punctuation.definition.projects.chevron.symbol.outline
54 | settings
55 |
56 | foreground
57 | #7c869266
58 |
59 |
60 |
61 |
62 | name
63 | Project Path
64 | scope
65 | string.name.project_path.outline
66 | settings
67 |
68 | foreground
69 | #707e8b
70 |
71 |
72 |
73 |
74 | name
75 | File/Dir Symbols
76 | scope
77 | punctuation.definition.directory.symbol.outline, punctuation.definition.file.symbol.outline, outline.item.parent_dir
78 | settings
79 |
80 | foreground
81 | #7c8692
82 |
83 |
84 |
85 |
86 | name
87 | Dir Slash
88 | scope
89 | punctuation.definition.directory.slash.outline
90 | settings
91 |
92 | foreground
93 | #919aa388
94 |
95 |
96 |
97 |
98 | name
99 | File Extension
100 | scope
101 | string.name.file.extension.outline
102 | settings
103 |
104 | foreground
105 | #919aa3
106 |
107 |
108 |
109 |
110 | name
111 | Dir Name
112 | scope
113 | string.name.directory.outline, string.name.project.outline
114 | settings
115 |
116 | fontStyle
117 | bold
118 |
119 |
120 |
121 |
122 | name
123 | Header
124 | scope
125 | header.outline
126 | settings
127 |
128 | foreground
129 | #707e8b
130 |
131 |
132 |
133 |
134 | name
135 | Header punctuations
136 | scope
137 | header.outline punctuation.definition.separator.outline
138 | settings
139 |
140 | foreground
141 | #6f798733
142 |
143 |
144 |
145 |
146 | name
147 | Header Rename Mode Title
148 | scope
149 | punctuation.definition.rename_mode
150 | settings
151 |
152 | foreground
153 | #E99023
154 |
155 |
156 |
157 |
158 | name
159 | Marked
160 | scope
161 | outline.marked
162 | settings
163 |
164 | foreground
165 | #FFFFFF
166 | background
167 | #4b96d6
168 |
169 |
170 |
171 |
172 |
173 | name
174 | Help: Table Name
175 | scope
176 | outline.help.header
177 | settings
178 |
179 | foreground
180 | #707E8B
181 | fontStyle
182 | bold
183 |
184 |
185 |
186 |
187 | name
188 | Help: Table Header
189 | scope
190 | outline.table.th
191 | settings
192 |
193 | background
194 | #4B96D6
195 |
196 |
197 |
198 |
199 | name
200 | Help: Table Header > Title
201 | scope
202 | outline.table.th title
203 | settings
204 |
205 | foreground
206 | #FFFFFF
207 |
208 |
209 |
210 |
211 | name
212 | Help: Table Header
213 | scope
214 | outline.table.tr
215 | settings
216 |
217 | background
218 | #FFFFFF
219 |
220 |
221 |
222 |
223 | name
224 | Help: Table punctuations
225 | scope
226 | outline.table.punctuations
227 | settings
228 |
229 | foreground
230 | #d9dee5
231 | background
232 | #d9dee5
233 |
234 |
235 |
236 |
237 | name
238 | Help: Shortcut
239 | scope
240 | outline.table.tr command_shortcut
241 | settings
242 |
243 | foreground
244 | #D6774B
245 | fontStyle
246 | bold
247 |
248 |
249 |
250 |
251 | name
252 | Help: Command Description
253 | scope
254 | outline.table.tr command_desc
255 | settings
256 |
257 | foreground
258 | #000000
259 |
260 |
261 |
262 |
263 |
264 | name
265 | Error
266 | scope
267 | string.error.outline
268 | settings
269 |
270 | foreground
271 | #aa0000
272 |
273 |
274 |
275 |
276 | name
277 | VCS Modified
278 | scope
279 | item.modified.outline
280 | settings
281 |
282 | background
283 | #d9dee0
284 | foreground
285 | #F09609
286 |
287 |
288 |
289 |
290 | name
291 | VCS Untracked
292 | scope
293 | item.untracked.outline
294 | settings
295 |
296 | background
297 | #d9dee0
298 | foreground
299 | #00c406
300 |
301 |
302 |
303 |
304 | name
305 | VCS Colorblind
306 | scope
307 | item.colorblind.outline
308 | settings
309 |
310 | background
311 | #000000
312 |
313 |
314 |
315 |
316 | uuid
317 | 07379361-ce49-4e6c-a03f-26378a7a2131
318 |
319 |
320 |
--------------------------------------------------------------------------------
/outline.py:
--------------------------------------------------------------------------------
1 | import sublime
2 | from sublime import Region
3 | from sublime_plugin import WindowCommand, TextCommand, EventListener
4 | from .show import show, refresh_sym_view, get_sidebar_views_groups, get_sidebar_status, binary_search
5 |
6 | class OutlineCommand(WindowCommand):
7 | def run(self, immediate=False, single_pane=False, project=False, other_group=False, layout=0):
8 | show(self.window, single_pane=single_pane, other_group=other_group, layout=layout)
9 |
10 | class OutlineCloseSidebarCommand(WindowCommand):
11 | def run(self):
12 | active_view = self.window.active_view()
13 |
14 | for v in self.window.views():
15 | if u'𝌆' in v.name():
16 | self.window.focus_view(v)
17 | self.window.run_command('close_file')
18 |
19 | self.window.set_layout({"cols": [0.0, 1.0], "rows": [0.0, 1.0], "cells": [[0, 0, 1, 1]]})
20 | self.window.focus_view(active_view)
21 |
22 | class OutlineRefreshCommand(TextCommand):
23 | def run(self, edit, symlist=None, symkeys=None, path=None, to_expand=None, toggle=None):
24 | self.view.erase(edit, Region(0, self.view.size()))
25 | if symlist and self.view.settings().get('outline_alphabetical'):
26 | symlist, symkeys = (list(t) for t in zip(*sorted(zip(symlist, symkeys))))
27 | self.view.insert(edit, 0, "\n".join(symlist))
28 | self.view.settings().set('symlist', symlist)
29 | self.view.settings().set('symkeys', symkeys)
30 | self.view.settings().set('current_file', path)
31 | self.view.sel().clear()
32 |
33 | class OutlineToggleSortCommand(TextCommand):
34 | def run(self, edit):
35 | sym_view = None
36 | for v in self.view.window().views():
37 | if u'𝌆' in v.name():
38 | v.settings().set('outline_alphabetical', not v.settings().get('outline_alphabetical'))
39 | sym_view = v
40 |
41 | symlist = self.view.get_symbols()
42 | refresh_sym_view(sym_view, symlist, self.view.file_name())
43 |
44 | class OutlineEventHandler(EventListener):
45 | def on_selection_modified(self, view):
46 | if 'outline.hidden-tmLanguage' not in view.settings().get('syntax'):
47 | return
48 |
49 | sym_view, sym_group, fb_view, fb_group = get_sidebar_views_groups(view)
50 |
51 | sym_view = view
52 | window = view.window()
53 | sym_group, i = window.get_view_index(sym_view)
54 |
55 | if len(sym_view.sel()) == 0:
56 | return
57 |
58 | (row, col) = sym_view.rowcol(sym_view.sel()[0].begin())
59 |
60 | active_view = None
61 | for group in range(window.num_groups()):
62 | if group != sym_group and group != fb_group:
63 | active_view = window.active_view_in_group(group)
64 | if active_view != None:
65 | symkeys = None
66 | # get the symbol list
67 | symlist = active_view.get_symbols()
68 | # depending on setting, set different regions
69 | if sym_view.settings().get('outline_main_view_highlight_mode') == 'cursor':
70 | symbol_line_ends = [active_view.line(range.a).end() for range, symbol in symlist]
71 | symkeys = list(zip(symbol_line_ends, symbol_line_ends))
72 | if sym_view.settings().get('outline_main_view_highlight_mode') == 'symbol':
73 | symkeys = sym_view.settings().get('symkeys')
74 | if sym_view.settings().get('outline_main_view_highlight_mode') == 'block':
75 | symbol_block_begins = [active_view.line(range.a).begin() for range, symbol in symlist]
76 | symbol_blocks_ends = [x - 1 for x in symbol_block_begins[1:len(symbol_block_begins)]] + [active_view.size()]
77 | symkeys = list(zip(symbol_block_begins, symbol_blocks_ends))
78 | if not symkeys:
79 | return
80 | region_position = symkeys[row]
81 | r = Region(region_position[0], region_position[1])
82 | active_view.show_at_center(r)
83 | active_view.sel().clear()
84 | active_view.sel().add(r)
85 | window.focus_view(active_view)
86 |
87 | def on_activated(self, view):
88 | if u'𝌆' in view.name():
89 | return
90 | # Avoid error message when console opens, as console is also a view, albeit with a group index of -1
91 | if view.window().get_view_index(view)[0] == -1:
92 | return
93 |
94 | if not get_sidebar_status(view):
95 | return
96 |
97 | sym_view, sym_group, fb_view, fb_group = get_sidebar_views_groups(view)
98 |
99 | if sym_view != None:
100 | if sym_view.settings().get('current_file') == view.file_name() and view.file_name() != None:
101 | return
102 | else:
103 | sym_view.settings().set('current_file', view.file_name())
104 |
105 | symlist = view.get_symbols()
106 |
107 | refresh_sym_view(sym_view, symlist, view.file_name())
108 |
109 | def on_pre_save(self, view):
110 | if u'𝌆' in view.name():
111 | return
112 | # this is not absolutely necessary, and prevents views that do not have a file reference from displaying
113 | # the symbol list
114 | # but it solves a console error if the console is activiated, as console is also a view....
115 | if view.file_name() == None:
116 | return
117 |
118 | if not get_sidebar_status(view):
119 | return
120 |
121 | sym_view, sym_group, fb_view, fb_group = get_sidebar_views_groups(view)
122 |
123 | if sym_view != None:
124 | # Note here is the only place that differs from on_activate_view
125 | if sym_view.settings().get('current_file') != view.file_name():
126 | sym_view.settings().set('current_file', view.file_name())
127 |
128 | symlist = view.get_symbols()
129 | refresh_sym_view(sym_view, symlist, view.file_name())
130 |
131 | # sync the outline view with current file location
132 | if view.window() is None or not sym_view.settings().get('outline_sync'):
133 | return
134 | # get the current cursor location
135 | point = view.sel()[0].begin()
136 | # get the current symbol and its line in outline
137 | range_lows = [view.line(range.a).begin() for range, symbol in symlist]
138 | range_sorted = [0] + range_lows[1:len(range_lows)] + [view.size()]
139 | sym_line = binary_search(range_sorted, point) - 1
140 |
141 | if (sym_view is not None):
142 | sym_point_start = sym_view.text_point(sym_line, 0)
143 | # center symview at the point
144 | sym_view.show_at_center(sym_point_start)
145 | sym_view.sel().clear()
146 | sym_view.sel().add(sym_view.line(sym_point_start))
147 | view.window().focus_view(view)
148 |
--------------------------------------------------------------------------------
/outline.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Browse Mode: Outline (Right)",
4 | "command": "outline",
5 | "args": {
6 | "immediate": true,
7 | "other_group": "right",
8 | "single_pane": true,
9 | "project": true,
10 | "layout": 0
11 | }
12 | },
13 | {
14 | "caption": "Browse Mode: Outline (Left)",
15 | "command": "outline",
16 | "args": {
17 | "immediate": true,
18 | "other_group": "right",
19 | "single_pane": true,
20 | "project": true,
21 | "layout": 1
22 | }
23 | },
24 | {
25 | "caption": "Browse Mode: FileBrowser Left, Outline Right",
26 | "command": "outline",
27 | "args": {
28 | "immediate": true,
29 | "other_group": "right",
30 | "single_pane": true,
31 | "project": true,
32 | "layout": 2
33 | }
34 | },
35 | {
36 | "caption": "Browse Mode: FileBrowser Top Left, Outline Bottom Left",
37 | "command": "outline",
38 | "args": {
39 | "immediate": true,
40 | "other_group": "right",
41 | "single_pane": true,
42 | "project": true,
43 | "layout": 3
44 | }
45 | },
46 | {
47 | "caption": "Browse Mode: FileBrowser Top Right, Outline Bottom Right",
48 | "command": "outline",
49 | "args": {
50 | "immediate": true,
51 | "other_group": "right",
52 | "single_pane": true,
53 | "project": true,
54 | "layout": 4
55 | }
56 | },
57 | {
58 | "caption": "Browse Mode: Close sidebar(s)",
59 | "command": "outline_close_sidebar",
60 | "args": {
61 | }
62 | },
63 | {
64 | "caption": "Outline: Toggle Sort",
65 | "command": "outline_toggle_sort",
66 | "args": {
67 | }
68 | }
69 | ]
70 |
--------------------------------------------------------------------------------
/outline.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | // If you don't like `..` symbol on the top of each view that moves you to
3 | // the parent directory and want to hide it you can set this to false
4 | "outline_show_parent": true,
5 |
6 | // If you want to see header (underlined full path) on top of file list
7 | "outline_header": false,
8 |
9 | // If you want to see full path in tab title and thus in window title
10 | // if tab is focused
11 | "outline_show_full_path": false,
12 |
13 | // If true 'Send to Trash' command will confirm the action with a dialog box
14 | "outline_confirm_send2trash": true,
15 |
16 | // Showing hidden files by default
17 | "outline_show_hidden_files": true,
18 |
19 | // Customize the patterns used to determine if a file should be hidden
20 | // e.g. [".*", "__pycache__", "*.pyc"]
21 | "outline_hidden_files_patterns": [".*"],
22 |
23 | // (ST3 Only) shows a FileBrowser or a jump list view in new window by default
24 | // false: hijacking is disabled
25 | // "jump_list": shows the project jump list
26 | // "outline" : shows a normal FileBrower view defaults to user directory
27 | "outline_hijack_new_window": false,
28 |
29 | // Path to git executable.
30 | // Wildcards are supported, e.g. if you use GitHub for Windows it would be:
31 | // "git_path": "~/Appdata/Local/GitHub/PortableGit_*/cmd/git.exe"
32 | // To disable git integration set to empty string:
33 | // "git_path": ""
34 | "git_path": "git",
35 |
36 | // Path to hg executable.
37 | // Wildcards are supported, e.g. if you use TortoiseHg it would be:
38 | // "hg_path": "%ProgramFiles%/TortoiseHg/hg.exe"
39 | // To disable hg integration set to empty string:
40 | // "hg_path": ""
41 | "hg_path": "hg",
42 |
43 | // true: the default, pressing Enter on a directory uses the current view.
44 | // false: a new view is created.
45 | "outline_reuse_view": true,
46 |
47 | // true: Enable alphabetical sort in output.
48 | // false: default because of losing parent dependences.
49 | "outline_alphabetical": false,
50 |
51 | // true: default, outline is in sync with current file position
52 | // false: no syncing, may be useful if use alphabetical sort
53 | "outline_sync": true,
54 |
55 | // when clicking on a symbol in the outline pane, how to highlight in the main view
56 | // "cursor": put the cursor at the end of the symbol line
57 | // "symbol": highlight the symbol in the main view
58 | // "block": highlight the entire block between the current symbol and next symbol
59 | "outline_main_view_highlight_mode": "symbol",
60 |
61 | // true: outline view will use the same color scheme as current file
62 | // false: outline will use its own color schemes (default or dark)
63 | "outline_inherit_color_scheme": true,
64 |
65 | // How should be positioned the Browse Mode view opened when jumping
66 | // "left": the default, open the view on the left
67 | // "right": open the view on the right
68 | // true: take all space available
69 | // false: don’t open any view
70 | "outline_open_on_jump": "left",
71 |
72 | // true: keep the active window if empty when jumping
73 | // false: the default, always open a new window when jumping
74 | "outline_smart_jump": false,
75 |
76 | // Width for FileBrowser column (as sidebar) accepts float or int:
77 | // float 0.1-0.9 put 1 is a window width, so e.g. 0.5 is half of window
78 | // int e.g. 99 pixels, if value will be more than current window width
79 | // then value will be treated as 0.9
80 | "outline_width": 0.2,
81 |
82 | // Automatically disable Vintageous to avoid keybindings incompatibilities;
83 | // note it is Vintageous setting, if it does not work you should report into
84 | // appropriate repository https://github.com/guillermooo/Vintageous/issues
85 | "__vi_external_disable": true,
86 |
87 | // The rest of these settings are normal sublime settings to
88 | // make FileBrowser views look more like a file browser than an editor
89 | // This plugin does not change font and font size, but it's
90 | // highly recommended to customize them in your user files.
91 | "color_scheme": "Packages/Outline/outline.hidden-tmTheme",
92 | "line_numbers": false,
93 | "word_wrap": false,
94 | "spell_check": false,
95 | "scroll_past_end": false,
96 | "draw_indent_guides": false,
97 | "fold_buttons": false,
98 | "drag_text": false,
99 | "highlight_line": true,
100 | "margin": 0,
101 | "tab_size": 3,
102 | "rulers": [],
103 | "caret_extra_width": 0 // extra caret width causes partially hidden chars
104 | // "font_face": "comic sans",
105 | // "font_size": 13,
106 | }
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/warmdev/SublimeOutline/220e3b0e9f21aed3ed68517aab2dc2cf82c5066d/screenshot.png
--------------------------------------------------------------------------------
/show.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import sublime
6 | from os.path import basename
7 |
8 | ST3 = int(sublime.version()) >= 3000
9 |
10 | if ST3:
11 | from .common import first, set_proper_scheme, calc_width, get_group
12 | else:
13 | from common import first, set_proper_scheme, calc_width, get_group
14 |
15 |
16 | def set_active_group(window, view, other_group):
17 | nag = window.active_group()
18 | if other_group:
19 | group = 0 if other_group == 'left' else 1
20 | groups = window.num_groups()
21 | if groups == 1:
22 | width = calc_width(view)
23 | cols = [0.0, width, 1.0] if other_group == 'left' else [0.0, 1-width, 1.0]
24 | window.set_layout({"cols": cols, "rows": [0.0, 1.0], "cells": [[0, 0, 1, 1], [1, 0, 2, 1]]})
25 | elif view:
26 | group = get_group(groups, nag)
27 | window.set_view_index(view, group, 0)
28 | else:
29 | group = nag
30 |
31 | # when other_group is left, we need move all views to right except FB view
32 | if nag == 0 and other_group == 'left' and group == 0:
33 | for v in reversed(window.views_in_group(nag)[1:]):
34 | window.set_view_index(v, 1, 0)
35 |
36 | return (nag, group)
37 |
38 |
39 | def set_view(view_id, window, ignore_existing, single_pane):
40 | view = None
41 | active_view = None
42 | if view_id:
43 | # The Goto command was used so the view is already known and its contents should be
44 | # replaced with the new path.
45 | view = first(window.views(), lambda v: v.id() == view_id)
46 |
47 | if not view and not ignore_existing:
48 | # See if any reusable view exists in case of single_pane argument
49 | any_path = lambda v: v.score_selector(0, "text.outline") > 0
50 | view = first(window.views(), any_path if single_pane else same_path)
51 |
52 | if not view:
53 | active_view = window.active_view()
54 | view = window.new_file()
55 | view.set_syntax_file('Packages/Outline/outline.hidden-tmLanguage')
56 | view.set_scratch(True)
57 | if view.settings().get('outline_inherit_color_scheme'):
58 | view.settings().set('color_scheme', active_view.settings().get('color_scheme'))
59 | else:
60 | view.settings().add_on_change('color_scheme', lambda: set_proper_scheme(view))
61 |
62 | reset_sels = True
63 | else:
64 | reset_sels = path != view.settings().get('outline_path', '')
65 |
66 | return (view, reset_sels)
67 |
68 |
69 | def show(window, view_id=None, ignore_existing=False, single_pane=False, other_group='', layout=1):
70 | """
71 | Determines the correct view to use, creating one if necessary, and prepares it.
72 | """
73 | symlist = []
74 | file_path = None
75 | prev_focus = None
76 | if other_group:
77 | prev_focus = window.active_view()
78 | symlist = prev_focus.get_symbols()
79 | file_path = prev_focus.file_name()
80 | # simulate 'toggle sidebar':
81 | if prev_focus and 'outline' in prev_focus.scope_name(0):
82 | window.run_command('close_file')
83 | return
84 |
85 | view, reset_sels = set_view(view_id, window, ignore_existing, single_pane)
86 |
87 | nag, group = set_active_group(window, view, other_group)
88 |
89 | if other_group and prev_focus:
90 | window.focus_view(prev_focus)
91 |
92 | view_name = "Outline"
93 |
94 | if ST3:
95 | name = u"𝌆 {0}".format(view_name)
96 | else:
97 | name = u"■ {0}".format(view_name)
98 |
99 | view.set_name(name)
100 | view.settings().set('outline_rename_mode', False)
101 | window.focus_view(view)
102 |
103 | if layout >= 2:
104 | window.run_command('dired', {'immediate': True, 'other_group': 'right', 'single_pane': True, 'project': True})
105 |
106 | width = calc_width(view)
107 | if layout == 0:
108 | window.set_layout({"cols": [0.0, width, 1-width, 1.0], "rows": [0.0, 0.5, 1.0], "cells": [[2, 0, 3, 2], [0, 0, 2, 2]]})
109 | elif layout == 1:
110 | window.set_layout({"cols": [0.0, width, 1-width, 1.0], "rows": [0.0, 0.5, 1.0], "cells": [[0, 0, 1, 2], [1, 0, 3, 2]]})
111 | elif layout == 2:
112 | window.set_layout({"cols": [0.0, width, 1-width, 1.0], "rows": [0.0, 0.5, 1.0], "cells": [[2, 0, 3, 2], [1, 0, 2, 2], [0, 0, 1, 2]]})
113 | elif layout == 3:
114 | window.set_layout({"cols": [0.0, width, 1-width, 1.0], "rows": [0.0, 0.5, 1.0], "cells": [[0, 1, 1, 2], [1, 0, 3, 2], [0, 0, 1, 1]]})
115 | elif layout == 4:
116 | window.set_layout({"cols": [0.0, width, 1-width, 1.0], "rows": [0.0, 0.5, 1.0], "cells": [[2, 1, 3, 2], [0, 0, 2, 2], [2, 0, 3, 1]]})
117 |
118 | window.set_view_index(view, 0, 0)
119 |
120 | for v in reversed(window.views_in_group(0)[1:]):
121 | if layout >= 2 and u"𝌆" in v.name():
122 | window.set_view_index(v, 2, 0)
123 | else:
124 | window.set_view_index(v, 1, 0)
125 |
126 | window.focus_view(prev_focus)
127 |
128 | refresh_sym_view(view, symlist, file_path)
129 |
130 | def refresh_sym_view(sym_view, symlist, path):
131 | l = [symbol for range, symbol in symlist]
132 | k = [(range.a, range.b) for range, symbol in symlist]
133 | if sym_view != None:
134 | sym_view.settings().erase('symlist')
135 | sym_view.settings().erase('symkeys')
136 | sym_view.run_command('outline_refresh', {'symlist': l, 'symkeys': k, 'path': path})
137 |
138 | def get_sidebar_views_groups(view):
139 | window = view.window()
140 | views = window.views()
141 | sym_view = None
142 | sym_group = None
143 | fb_view = None
144 | fb_group = None
145 | for v in views:
146 | if 'outline.hidden-tmLanguage' in v.settings().get('syntax'):
147 | sym_view = v
148 | sym_group, i = window.get_view_index(sym_view)
149 | if u'𝌆' in v.name() and v.id() != sym_view.id():
150 | fb_view = v
151 | if fb_view != None:
152 | fb_group, j = window.get_view_index(fb_view)
153 |
154 | return (sym_view, sym_group, fb_view, fb_group)
155 |
156 | def get_sidebar_status(view):
157 | sidebar_on = False
158 | for v in view.window().views():
159 | if u'𝌆' in v.name():
160 | sidebar_on = True
161 |
162 | return sidebar_on
163 |
164 | # given a sorted array, returns the location of x if inserted into the array
165 | def binary_search(array, x):
166 | low = 0
167 | high = len(array) - 1
168 | mid = 0
169 |
170 | while low < high:
171 | # middle location
172 | mid = (high + low) // 2
173 | if array[mid] <= x:
174 | low = mid + 1
175 | else:
176 | high = mid
177 |
178 | return low
179 |
--------------------------------------------------------------------------------