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.
Completed
Push — master ( fce0da...d0b34a )
by thatsIch
01:06
created

TryOpenThread.__open_enclosed_string()   B

Complexity

Conditions 6

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
c 2
b 0
f 0
dl 0
loc 15
rs 8
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 __open_whitespaced_region(self):
156
        # 3. Region from last whitespace to next whitespace
157
158
        lastspace = self.__find_front_nearest_whitespace()
159
160
        if lastspace == 0 \
161
                or self.line[lastspace] == " " \
162
                or self.line[lastspace] == "\t":
163
164
            nextspace = self.__find_back_nearest_whitespace()
165
166
            if nextspace >= len(self.line) \
167
                    or self.line[nextspace] == " " \
168
                    or self.line[nextspace] == "\t":
169
                string = self.line[lastspace: nextspace].strip()
170
                if self.opn(string):
171
                    logger.info("Open string enclosed in whitespace: " + string)
172
                    return True
173
174
    def __open_after_equals_sign(self):
175
        # 4. Everything after the first \"=\" until the end
176
        # of the line (strip quotes)
177
        mtch = re.search(r"=\s*(.*)\s*$", self.line)
178
        if mtch and self.opn(mtch.group(1).strip("\"")):
179
            logger.info("Open text after \"=\": " + mtch.group(1).strip("\""))
180
            return True
181
182
    def __open_whole_line(self):
183
        # 5. Whole line (strip comment character at start)
184
        stripmatch = re.search(r"^[ \t;]*?([^ \t;].*)\s*$", self.line)
185
        if self.opn(stripmatch.group(1)):
186
            logger.info("Open whole line: " + stripmatch.group(1))
187
            return
188
189
    def run(self):
190
        """Run the thread."""
191
        return self.__open_selected_text() or \
192
            self.__open_enclosed_string() or \
193
            self.__open_whitespaced_region() or \
194
            self.__open_after_equals_sign() or \
195
            self.__open_whole_line()
196
197
198
class RainmeterOpenPathsCommand(sublime_plugin.TextCommand):
199
    """Try to open paths on lines in the current selection.
200
201
    Will try to open paths to files, folders or URLs on each line in the
202
    current selection. To achieve this, the following substrings of each line
203
    intersecting the selection are tested:
204
205
    1. The string inside the selection
206
    2. The string between possible quotes preceding and following the
207
       selection, if any
208
    3. The string between the preceding and following whitespace
209
    4. Everything after the first "=" on the line until the end of the line
210
    5. The whole line, stripped of preceding semicolons
211
    """
212
213
    def __split_selection_by_new_lines(self, selection):
214
        # Split all regions into individual segments on lines (using nicely
215
        # confusing python syntax).
216
        return [
217
            j for i in [
218
                self.view.split_by_newlines(region)
219
                for region in selection
220
            ]
221
            for j in i
222
        ]
223
224
    def __open_each_line_by_thread(self, lines):
225
        """Identify segments in selected lines and tries to open them each in a new thread.
226
227
        This can be resource intensive.
228
        """
229
        fnm = self.view.file_name()
230
231
        def opn(string):
232
            """Simple callback method to apply multiple opening operations.
233
234
            An URL can be either external or internal and thus is opened differently.
235
            """
236
            opened = open_path(rainmeter.make_path(string, fnm)) or open_url(string)
237
            if opened:
238
                logger.info("found file or url '" + string + "' to open")
239
240
        for linereg in lines:
241
            wholeline = self.view.line(linereg)
242
            thread = TryOpenThread(self.view.substr(wholeline),
243
                                   sublime.Region(linereg.a - wholeline.a,
244
                                                  linereg.b - wholeline.a),
245
                                   opn)
246
            thread.start()
247
248
    def run(self, _):
249
        """Detect various scenarios of file paths and try to open them one after the other.
250
251
        :param _: _ edit unused
252
        """
253
        selection = self.view.sel()
254
        lines = self.__split_selection_by_new_lines(selection)
255
256
        loaded_settings = sublime.load_settings("Rainmeter.sublime-settings")
257
        max_open_lines = loaded_settings.get("rainmeter_max_open_lines", 40)
258
259
        # Refuse if too many lines selected to avoid freezing
260
261
        if len(lines) > max_open_lines:
262
            accept = sublime.ok_cancel_dialog(
263
                "You are trying to open " +
264
                str(len(lines)) + " lines.\n" +
265
                "That's a lot, and could take some time. Try anyway?")
266
            if not accept:
267
                return
268
269
        self.__open_each_line_by_thread(lines)
270
271
    def is_enabled(self):
272
        """Check if current syntax is rainmeter."""
273
        israinmeter = self.view.score_selector(self.view.sel()[0].a,
274
                                               "source.rainmeter")
275
276
        return israinmeter > 0
277
278
    def is_visible(self):
279
        """It is visible if it is in Rainmeter scope."""
280
        return self.is_enabled()
281