forked from oskarols/foldcomments
-
Notifications
You must be signed in to change notification settings - Fork 0
/
foldcomments.py
203 lines (147 loc) · 5.75 KB
/
foldcomments.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
from sublime import Region, Settings, load_settings
import sublime_plugin
from itertools import tee, chain
try:
from itertools import izip as zip
except ImportError: # will be 3.x series
pass
def previous_and_current(iterable, *iterables):
"""
Includes the previous value of iterable in iteration
> previous_and_current([1,2,3])
=> (None, 1)
=> (1, 2)
=> (2, 3)
"""
prevs, items = tee(iterable, 2)
# Offset for the first element, since has no previous value
prevs = chain([None], prevs)
return zip(prevs, items, *iterables)
def is_comment_multi_line(view, region):
return len(view.lines(region)) > 1
def is_comment_doc_block(view, region):
region_str = view.substr(region)
return region_str.rfind('/**') != -1
def normalize_comment(view, region):
if is_comment_multi_line(view, region):
return normalize_multiline_comment(view, region)
else:
return normalize_singleline_comment(view, region)
def normalize_singleline_comment(view, region):
"""
Since single line comments include the newline
if we don't explicitly make sure newline is kept
out of the fold indicator, it will munge together
with code. Example:
// This is an example comment
function foo() {
Becomes:
(..) function foo() {
When what we really want is to keep the fold
on it's own line, like so:
(..)
function foo() {
"""
region_str = view.substr(region)
last_newline = region_str.rfind('\n')
if (last_newline == -1):
# Single-line block comments don't include
# their newline.
# /* foo bar baz */ <-- like this
return region
else:
return Region(region.begin(), region.begin() + last_newline)
def normalize_multiline_comment(view, region):
"""
This is needed since in some languages it seems
the boundaries for proper block-comments
and chained single-line comments differ. The
chaines single-line comments have the last point
( .end() .b etc) of their region set to the subsequent line,
while the block comments have it set to the last char
of their last line.
Example where the @ char signifies
the last endpoint:
BLOCK COMMENT
/**
* This is an example comment
*/@ <---
function foobar() {
MULTIPLE SINGLE COMMENTS
//
// This is an example comment
//
@function foobar() { <---
What we do to fix this is not to use the boundaries
for the regions, but instead use the last line
for the region - which seems to have the correct end
point set.
"""
lines = view.lines(region)
last_line = lines[-1]
last_point = last_line.b
return Region(region.a, last_point)
class CommentNodes:
def __init__(self, view):
self.comments = None # collection of Region objects
self.settings = load_settings("foldcomments.sublime-settings")
self.view = view
self.find_comments()
self.apply_settings()
def find_comments(self):
self.comments = [
normalize_comment(self.view, c) for c in self.view.find_by_selector('comment')
]
def apply_settings(self):
if not self.settings.get('fold_single_line_comments'):
self.remove_single_line_comments()
if not self.settings.get('fold_multi_line_comments'):
self.remove_multi_line_comments()
if not self.settings.get('fold_doc_block_comments'):
self.remove_doc_block_comments()
if self.settings.get('concatenate_adjacent_comments'):
self.concatenate_adjacent_comments()
def remove_single_line_comments(self):
self.comments = [c for c in self.comments if is_comment_multi_line(self.view, c) or is_comment_doc_block(self.view, c)]
def remove_multi_line_comments(self):
self.comments = [c for c in self.comments if not is_comment_multi_line(self.view, c) or is_comment_doc_block(self.view, c)]
def remove_doc_block_comments(self):
self.comments = [c for c in self.comments if not is_comment_doc_block(self.view, c)]
def concatenate_adjacent_comments(self):
"""
Merges any comments that are adjacent.
"""
def concatenate(region1, region2):
return region1.cover(region2)
def is_adjacent(region1, region2):
region_inbetween = Region(region1.end(), region2.begin())
return len(self.view.substr(region_inbetween).strip()) == 0
concatenated_comments = []
for prev_comment, comment in previous_and_current(self.comments):
concatenated_comment = None
# prev wont be set on first iteration
if prev_comment and is_adjacent(prev_comment, comment):
concatenated_comment = concatenate(concatenated_comments.pop(), comment)
concatenated_comments.append(concatenated_comment or comment)
self.comments = concatenated_comments
def fold(self):
self.view.fold(self.comments)
def unfold(self):
self.view.unfold(self.comments)
def toggle_folding(self):
def is_folded(comments):
return self.view.unfold(comments[0]) # False if /already folded/
self.unfold() if is_folded(self.comments) else self.fold()
# ================================= COMMANDS ==================================
class ToggleFoldCommentsCommand(sublime_plugin.TextCommand):
def run(self, edit):
comments = CommentNodes(self.view)
comments.toggle_folding()
class FoldCommentsCommand(sublime_plugin.TextCommand):
def run(self, edit):
comments = CommentNodes(self.view)
comments.fold()
class UnfoldCommentsCommand(sublime_plugin.TextCommand):
def run(self, edit):
comments = CommentNodes(self.view)
comments.unfold()