Skip to content

Commit 8c7f36a

Browse files
author
Caspar Gruijthuijsen
committed
matlab open+end statement pairing implemented
1 parent d9ab031 commit 8c7f36a

File tree

5 files changed

+264
-2
lines changed

5 files changed

+264
-2
lines changed

AutoMatlab.sublime-commands

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
"caption": "AutoMatlab: Toggle AutoHotkey return focus",
1616
"command": "toggle_auto_hotkey_focus",
1717
},
18+
{
19+
"caption": "AutoMatlab: Pair open+end statement",
20+
"command": "pair_matlab_statements",
21+
"args": {"action": "popup"},
22+
},
1823
{
1924
"caption": "AutoMatlab: Open file",
2025
"command": "run_matlab_command",

auto_matlab_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,4 +348,4 @@ def run(self):
348348
msg = '[INFO] AutoMatlab - AutoHotkey return focus deactiveted ' \
349349
'(non-persistent)'
350350
# print(msg)
351-
self.window.status_message(msg)
351+
self.window.status_message(msg)

auto_matlab_statement_pairing.py

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import re
2+
3+
import sublime
4+
import sublime_plugin
5+
6+
class PairMatlabStatementsCommand(sublime_plugin.TextCommand):
7+
8+
"""Find opening statement that is paired with the current 'end'
9+
"""
10+
11+
general_keywords = 'if|for|while|switch|try|function|classdef'
12+
class_keywords = 'properties|methods|events|enumeration'
13+
14+
def iskeyword(self, point):
15+
"""Does the code contain a Matlab keyword at the point?
16+
"""
17+
all_scopes = self.view.scope_name(point)
18+
return any([scope.startswith('keyword')
19+
for scope in all_scopes.split()])
20+
21+
def run(self, edit, action='popup'):
22+
"""Find opening statement that is paired with the current 'end'
23+
"""
24+
# determine valid keywords
25+
keywords = self.general_keywords
26+
all_code = self.view.substr(sublime.Region(0, self.view.size()))
27+
if all_code.lstrip().startswith('classdef'):
28+
keywords += '|' + self.class_keywords
29+
30+
# get current selection
31+
sel = self.view.sel()
32+
if not len(sel):
33+
return
34+
reg_key = self.view.word(sel[0])
35+
text_key = self.view.substr(reg_key).strip()
36+
37+
# check if the current selection equals a valid keyword
38+
if not self.iskeyword(reg_key.begin()) \
39+
or not text_key in keywords + '|end':
40+
if not(action == 'jump' or action == 'select'):
41+
msg = '[WARNING] AutoMatlab - Cursor not in open/end keyword.'
42+
self.view.window().status_message(msg)
43+
return
44+
45+
if text_key == 'end':
46+
reg_paired = self.pair_with_open_statement(reg_key, keywords)
47+
# the below disregards edge-cases with partial one-line statements
48+
# or with , or ; in strings or comments
49+
if self.view.line(reg_key) == self.view.line(reg_paired):
50+
sel_lines = [reg_key.begin(), reg_paired.end()]
51+
else:
52+
sel_lines = [self.view.full_line(reg_key.begin()).begin(),
53+
self.view.full_line(reg_paired.end()).end()]
54+
else:
55+
reg_paired = self.pair_with_end_statement(reg_key, keywords)
56+
if self.view.line(reg_key) == self.view.line(reg_paired):
57+
sel_lines = [reg_key.end(), reg_paired.begin()]
58+
else:
59+
sel_lines = [self.view.full_line(reg_key.end()).end(),
60+
self.view.full_line(reg_paired.begin()).begin()]
61+
if reg_paired == None:
62+
if not(action == 'jump' or action == 'select'):
63+
msg = '[WARNING] AutoMatlab - Cannot pair statement: ' \
64+
'invalid syntax.'
65+
self.view.window().status_message(msg)
66+
return
67+
68+
if action == 'jump':
69+
self.view.show(reg_paired)
70+
self.view.sel().clear()
71+
self.view.sel().add(sublime.Region(reg_paired.end()))
72+
elif action == 'select':
73+
self.view.sel().clear()
74+
self.view.sel().add(sublime.Region(min(sel_lines), max(sel_lines)))
75+
else:
76+
# read the text surrounding the paired statement
77+
reg_line = self.view.full_line(reg_paired)
78+
reg_surround = self.view.lines(
79+
sublime.Region(reg_line.begin() - 1, reg_line.end() + 1))
80+
text_lines = [self.view.substr(reg).rstrip()
81+
for reg in reg_surround]
82+
nr_lines = [self.view.rowcol(reg.a)[0] + 1 for reg in reg_surround]
83+
84+
# make paired statement bold
85+
nr_paired = self.view.rowcol(reg_line.a)[0] + 1
86+
borders_paired = [reg_paired.begin() - reg_line.begin(),
87+
reg_paired.end() - reg_line.begin()]
88+
text_paired = text_lines[nr_lines.index(nr_paired)]
89+
text_lines[nr_lines.index(nr_paired)] = \
90+
text_paired[:borders_paired[0]] + '<i><b>' \
91+
+ text_paired[borders_paired[0]:borders_paired[1]] \
92+
+ '</b></i>'+ text_paired[borders_paired[1]:]
93+
94+
# remove the minimum indentation
95+
lstrip_lines = [len(text) - len(text.lstrip())
96+
for text in text_lines]
97+
text_lines = \
98+
['&nbsp;'*(lstrip_lines[ii]-min(lstrip_lines))
99+
+ text_lines[ii].lstrip()
100+
for ii in range(len(text_lines))]
101+
102+
# add line numbers and concatenate text lines
103+
text = ''
104+
for ii in range(len(nr_lines)):
105+
text += \
106+
'&nbsp;'*(len(str(max(nr_lines)))-len(str(nr_lines[ii]))) \
107+
+ '{}: {}<br>'.format(nr_lines[ii], text_lines[ii])
108+
109+
# add hyperlink link
110+
text += '<a href="{}">goto</a> <a href="{}">select</a>'.format(
111+
reg_paired, sel_lines)
112+
self.view.show_popup(text,
113+
max_width=80*self.view.em_width(),
114+
max_height=5*self.view.line_height(),
115+
on_navigate=self.select)
116+
117+
118+
def pair_with_open_statement(self, reg, keywords):
119+
"""Find the open statement paired with the end statement
120+
in region reg.
121+
"""
122+
# read all code until current end statement
123+
code = self.view.substr(sublime.Region(0, reg.end()))
124+
125+
# look for end statements
126+
pattern = r'(?:\W|^)(end)(?:\W|$)'
127+
mo_end = re.finditer(pattern, code, re.M)
128+
129+
# look for opening statements to pair
130+
pattern = r'(?:\W|^)(' + keywords + r')(?:\W|$)'
131+
mo_open = re.finditer(pattern, code, re.M)
132+
133+
# strip non-keywords
134+
end_statements = sorted([mo.start(1)
135+
for mo in mo_end
136+
if self.iskeyword(mo.start(1))], reverse=True)
137+
open_statements = sorted([mo.start(1)
138+
for mo in mo_open
139+
if self.iskeyword(mo.start(1))], reverse=True)
140+
141+
# check validity of open/end combinations
142+
no = len(open_statements)
143+
ne = len(end_statements)
144+
if ne > no:
145+
return None
146+
147+
for ii in range(no):
148+
# check how many end statements come after each open statement,
149+
# starting from the last open statement
150+
end_after = sum([open_statements[ii] < estat
151+
for estat in end_statements])
152+
# the paired open statement is found when end_after matches
153+
# the current counter
154+
if end_after == ii+1:
155+
reg_paired = self.view.word(open_statements[ii])
156+
break
157+
return reg_paired
158+
159+
def pair_with_end_statement(self, reg, keywords):
160+
"""Find the end statement paired with the open statement
161+
in region reg.
162+
"""
163+
# read all code from current open statement
164+
code = self.view.substr(sublime.Region(reg.begin(), self.view.size()))
165+
166+
# look for end statements
167+
pattern = r'(?:\W|^)(end)(?:\W|$)'
168+
mo_end = re.finditer(pattern, code, re.M)
169+
170+
# look for opening statements to pair
171+
pattern = r'(?:\W|^)(' + keywords + r')(?:\W|$)'
172+
mo_open = re.finditer(pattern, code, re.M)
173+
174+
# strip non-keywords
175+
end_statements = sorted([reg.begin() + mo.start(1)
176+
for mo in mo_end
177+
if self.iskeyword(reg.begin() + mo.start(1))])
178+
open_statements = sorted([reg.begin() + mo.start(1)
179+
for mo in mo_open
180+
if self.iskeyword(reg.begin() + mo.start(1))])
181+
182+
# check validity of open/end combinations
183+
no = len(open_statements)
184+
ne = len(end_statements)
185+
if no > ne:
186+
print(code, no, ne)
187+
return None
188+
189+
for ii in range(ne):
190+
# check how many open statements come before each end statement,
191+
# starting from the first end statement
192+
open_before = sum([end_statements[ii] > ostat
193+
for ostat in open_statements])
194+
# the paired end statement is found when open_before matches
195+
# the current counter
196+
if open_before == ii+1:
197+
reg_paired = self.view.word(end_statements[ii])
198+
break
199+
return reg_paired
200+
201+
def select(self, reg):
202+
"""Select the lines between the paired open/end statements
203+
"""
204+
reg = eval(reg)
205+
206+
###
207+
# these lines are necessary to force the view to update (Sublime bug?)
208+
self.view.sel().clear()
209+
self.view.sel().add(sublime.Region(reg[0]))
210+
self.view.run_command('insert', {'characters':' '})
211+
self.view.run_command('left_delete')
212+
###
213+
214+
self.view.show(reg[1])
215+
self.view.sel().clear()
216+
self.view.sel().add(sublime.Region(reg[0], reg[1]))

messages.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@
44
"0.2.0": "messages/0.2.0.txt",
55
"1.0.0": "messages/1.0.0.txt",
66
"1.0.1": "messages/1.0.1.txt",
7-
"1.0.2": "messages/1.0.2.txt"
7+
"1.0.2": "messages/1.0.2.txt",
8+
"1.1.0": "messages/1.1.0.txt"
89
}

messages/1.1.0.txt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Changes in 1.0.2
2+
================
3+
4+
- Enhancement: Sending commands to Matlab through AutoHotkey has
5+
become faster with the new (default) option to paste commands
6+
into Matlab.
7+
8+
- New feature: AutoMatlab can now trigger keyboard shortcuts in
9+
Matlab, via AutoHotkey.
10+
11+
For instance, you can make AutoMatlab trigger Ctrl+C in Matlab
12+
to stop the execution of a currently running Matlab function.
13+
This command would look like:
14+
15+
{
16+
"caption": "AutoMatlab: Stop execution",
17+
"command": "run_matlab_command",
18+
"args": {"command": "^c", "type": "key"}
19+
}
20+
21+
22+
- New feature: AutoMatlab can now recognized matched opening
23+
statements (e.g., if, for, function) and ending statements.
24+
Play around with the following commands:
25+
26+
{
27+
"caption": "AutoMatlab: Pair open+end statement",
28+
"command": "pair_matlab_statements",
29+
"args": {"action": "popup"},
30+
},
31+
{
32+
"caption": "AutoMatlab: Jump open+end statement",
33+
"command": "pair_matlab_statements",
34+
"args": {"action": "jump"},
35+
},
36+
{
37+
"caption": "AutoMatlab: Select open+end statement",
38+
"command": "pair_matlab_statements",
39+
"args": {"action": "select"},
40+
},

0 commit comments

Comments
 (0)