GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

TryOpenThread   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 115
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
dl 0
loc 115
rs 9
c 4
b 0
f 0
wmc 35

13 Methods

Rating   Name   Duplication   Size   Complexity  
A __open_selected_text() 0 6 2
A __find_front_nearest_whitespace() 0 12 4
A __find_back_nearest_whitespace() 0 9 4
B __open_enclosed_string() 0 15 6
A __find_next_quotation_mark() 0 6 3
A __is_back_space() 0 4 1
A __open_after_equals_sign() 0 7 3
A __open_whitespaced_region() 0 12 4
A __is_front_space() 0 4 1
A __open_whole_line() 0 6 2
A __init__() 0 6 1
A __find_prior_quotation_mark() 0 6 3
A run() 0 7 1
1
"""Module for opening paths containing Rainmeter-specific or Windows environment variables."""
2
3
import os
4
import re
5
import threading
6
7
import sublime
8
import sublime_plugin
9
10
from . import rainmeter
11
from . import logger
12
13
14
# Files to open with sublime instead of the system default
15
SETTINGS = sublime.load_settings("Rainmeter.sublime-settings")
16
DEF_EXTS = SETTINGS.get("rainmeter_default_open_sublime_extensions", "")
17
18
if DEF_EXTS is not None:
19
    DEF_EXTS = DEF_EXTS.strip().strip(r"|").strip()
20
else:
21
    DEF_EXTS = ""
22
23
ADD_EXTS = SETTINGS.get("rainmeter_open_sublime_extensions", "")
24
25
if ADD_EXTS is not None:
26
    ADD_EXTS = ADD_EXTS.strip().strip(r"|").strip()
27
else:
28
    ADD_EXTS = ""
29
30
SUBLIME_FILES = re.compile("(?i).*\\.(" + ADD_EXTS + "|" + DEF_EXTS + ")\\b")
31
32
33
def open_path(path, transient=False):
34
    """Try to open a path.
35
36
    A path could be opened either as:
37
38
    * a file or folder path or
39
    * URL in the system default application, or
40
    * in Sublime if it's a text file.
41
42
    Use transient=True to open a file in Sublime without assigning it a tab.
43
    A tab will be created once the buffer is modified.
44
45
    Will return False if the path doesn't exist in the file system, and
46
    True otherwise.
47
    """
48
    if not path:
49
        return False
50
51
    if not os.path.exists(path):
52
        return False
53
54
    sublime.set_timeout(lambda: sublime.status_message("Opening " + path), 10)
55
    if SUBLIME_FILES.search(path):
56
        if transient:
57
            sublime.set_timeout(
58
                lambda: sublime.active_window().open_file(path,
59
                                                          sublime.TRANSIENT),
60
                10)
61
        else:
62
            sublime.set_timeout(
63
                lambda: sublime.active_window().open_file(path),
64
                10)
65
    else:
66
        os.startfile(path)
67
68
    return True
69
70
71
def open_url(url):
72
    """Try opening a url with the system default for urls.
73
74
    Will return False if it's not a url, and True otherwise.
75
    """
76
    if re.match(r"(?i)(https?|ftp)://", url.strip()):
77
        os.startfile(url)
78
        sublime.set_timeout(lambda: sublime.status_message("Opening " + url),
79
                            10)
80
        return True
81
    else:
82
        return False
83
84
85
class TryOpenThread(threading.Thread):
86
    """A wrapper method to handle threading for opening line embedded URLs."""
87
88
    def __init__(self, line, region, opn):
89
        """Construct a thread for opening URL embedded in a line."""
90
        self.line = line
91
        self.region = region
92
        self.opn = opn
93
        threading.Thread.__init__(self)
94
95
    def __find_prior_quotation_mark(self):
96
        lastquote = self.region.a - 1
97
        while lastquote >= 0 and self.line[lastquote] != "\"":
98
            lastquote -= 1
99
100
        return lastquote
101
102
    def __open_selected_text(self):
103
        # 1. Selected text
104
        selected = self.line[self.region.a:self.region.b]
105
        if self.opn(selected):
106
            logger.info("Open selected text")
107
            return True
108
109
    def __find_next_quotation_mark(self):
110
        nextquote = self.region.b
111
        while nextquote == len(self.line) or self.line[nextquote] != "\"":
112
            nextquote += 1
113
114
        return nextquote
115
116
    def __open_enclosed_string(self):
117
        # 2. String enclosed in double quotes
118
        # Find the quotes before the current point (if any)
119
        lastquote = self.__find_prior_quotation_mark()
120
121
        if not lastquote < 0 and self.line[lastquote] == "\"":
122
            # Find the quote after the current point (if any)
123
            nextquote = self.__find_next_quotation_mark()
124
125
            if not nextquote == len(self.line) \
126
                    and self.line[nextquote] == "\"":
127
                string = self.line[lastquote: nextquote].strip("\"")
128
                if self.opn(string):
129
                    logger.info("Open string enclosed in quotes: " + string)
130
                    return True
131
132
    def __find_front_nearest_whitespace(self):
133
        # Find the space before the current point (if any)
134
        lastspace = self.region.a - 1
135
        while lastspace >= 0 \
136
                and self.line[lastspace] != " " \
137
                and self.line[lastspace] != "\t":
138
            lastspace -= 1
139
140
        # Set to zero if nothing was found until the start of the line
141
        lastspace = max(lastspace, 0)
142
143
        return lastspace
144
145
    def __find_back_nearest_whitespace(self):
146
        # Find the space after the current point (if any)
147
        nextspace = self.region.b
148
        while nextspace < len(self.line) \
149
                and self.line[nextspace] != " " \
150
                and self.line[nextspace] != "\t":
151
            nextspace += 1
152
153
        return nextspace
154
155
    def __is_front_space(self, lastspace):
156
        return lastspace == 0 \
157
            or self.line[lastspace] == " " \
158
            or self.line[lastspace] == "\t"
159
160
    def __is_back_space(self, nextspace):
161
        return nextspace >= len(self.line) \
162
            or self.line[nextspace] == " " \
163
            or self.line[nextspace] == "\t"
164
165
    def __open_whitespaced_region(self):
166
        # 3. Region from last whitespace to next whitespace
167
168
        lastspace = self.__find_front_nearest_whitespace()
169
        if self.__is_front_space(lastspace):
170
171
            nextspace = self.__find_back_nearest_whitespace()
172
            if self.__is_back_space(nextspace):
173
                string = self.line[lastspace: nextspace].strip()
174
                if self.opn(string):
175
                    logger.info("Open string enclosed in whitespace: " + string)
176
                    return True
177
178
    def __open_after_equals_sign(self):
179
        # 4. Everything after the first \"=\" until the end
180
        # of the line (strip quotes)
181
        mtch = re.search(r"=\s*(.*)\s*$", self.line)
182
        if mtch and self.opn(mtch.group(1).strip("\"")):
183
            logger.info("Open text after \"=\": " + mtch.group(1).strip("\""))
184
            return True
185
186
    def __open_whole_line(self):
187
        # 5. Whole line (strip comment character at start)
188
        stripmatch = re.search(r"^[ \t;]*?([^ \t;].*)\s*$", self.line)
189
        if self.opn(stripmatch.group(1)):
190
            logger.info("Open whole line: " + stripmatch.group(1))
191
            return
192
193
    def run(self):
194
        """Run the thread."""
195
        return self.__open_selected_text() or \
196
            self.__open_enclosed_string() or \
197
            self.__open_whitespaced_region() or \
198
            self.__open_after_equals_sign() or \
199
            self.__open_whole_line()
200
201
202
class RainmeterOpenPathsCommand(sublime_plugin.TextCommand):
203
    """Try to open paths on lines in the current selection.
204
205
    Will try to open paths to files, folders or URLs on each line in the
206
    current selection. To achieve this, the following substrings of each line
207
    intersecting the selection are tested:
208
209
    1. The string inside the selection
210
    2. The string between possible quotes preceding and following the
211
       selection, if any
212
    3. The string between the preceding and following whitespace
213
    4. Everything after the first "=" on the line until the end of the line
214
    5. The whole line, stripped of preceding semicolons
215
    """
216
217
    def __split_selection_by_new_lines(self, selection):
218
        # Split all regions into individual segments on lines (using nicely
219
        # confusing python syntax).
220
        return [
221
            j for i in [
222
                self.view.split_by_newlines(region)
223
                for region in selection
224
            ]
225
            for j in i
226
        ]
227
228
    def __open_each_line_by_thread(self, lines):
229
        """Identify segments in selected lines and tries to open them each in a new thread.
230
231
        This can be resource intensive.
232
        """
233
        fnm = self.view.file_name()
234
235
        def opn(string):
236
            """Simple callback method to apply multiple opening operations.
237
238
            An URL can be either external or internal and thus is opened differently.
239
            """
240
            opened = open_path(rainmeter.make_path(string, fnm)) or open_url(string)
241
            if opened:
242
                logger.info("found file or url '" + string + "' to open")
243
244
        for linereg in lines:
245
            wholeline = self.view.line(linereg)
246
            thread = TryOpenThread(self.view.substr(wholeline),
247
                                   sublime.Region(linereg.a - wholeline.a,
248
                                                  linereg.b - wholeline.a),
249
                                   opn)
250
            thread.start()
251
252
    def run(self, _):
253
        """Detect various scenarios of file paths and try to open them one after the other.
254
255
        :param _: _ edit unused
256
        """
257
        selection = self.view.sel()
258
        lines = self.__split_selection_by_new_lines(selection)
259
260
        loaded_settings = sublime.load_settings("Rainmeter.sublime-settings")
261
        max_open_lines = loaded_settings.get("rainmeter_max_open_lines", 40)
262
263
        # Refuse if too many lines selected to avoid freezing
264
265
        if len(lines) > max_open_lines:
266
            accept = sublime.ok_cancel_dialog(
267
                "You are trying to open " +
268
                str(len(lines)) + " lines.\n" +
269
                "That's a lot, and could take some time. Try anyway?")
270
            if not accept:
271
                return
272
273
        self.__open_each_line_by_thread(lines)
274
275
    def is_enabled(self):
276
        """Check if current syntax is rainmeter."""
277
        israinmeter = self.view.score_selector(self.view.sel()[0].a,
278
                                               "source.rainmeter")
279
280
        return israinmeter > 0
281
282
    def is_visible(self):
283
        """It is visible if it is in Rainmeter scope."""
284
        return self.is_enabled()
285