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 ( 69e16c...fce0da )
by thatsIch
01:14
created

TryOpenThread.__find_back_nearest_whitespace()   A

Complexity

Conditions 4

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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