-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathopen_under_cursor.py
More file actions
202 lines (162 loc) · 7.64 KB
/
Copy pathopen_under_cursor.py
File metadata and controls
202 lines (162 loc) · 7.64 KB
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
"""
Created on Jun 06, 2023
@author: Éric Piel
"""
# gedit plugin to automatically open in a new tab the file based on the name under the cursor.
# This is triggered with Ctrl+Shift+O
# This is a similar feature as in vim the "gf" function.
# If there is a text selection, it's used as-is for the filename.
# Otherwise, the file name is guessed as the word around the cursor.
import os
import re
from typing import Optional
import gi
import webbrowser
gi.require_version('Gedit', '3.0')
from gi.repository import GObject, Gedit, Gio
# Any separator for the filenames, encoded as a regex.
# These are not the "forbidden" characters
# from a filename according to the OS. These are just the characters that are
# more likely to be before or after a filename than in the filename.
#FILENAME_SEPARATORS = r'\s\",:()[\]{}'
FILENAME_SEPARATORS = r'\s\",()[\]{}' # no :, to support URLs (ideally, we would consider :// as a special case)
# TODO: change to take the regex of a *word* instead of a separator? This could allow
# to search for words that contain a ., but which don't end with a .
def get_word_around_index(line: str, idx: int, separators: str=r"\s") -> Optional[str]:
"""
Get the word around the specified index in a given line.
It return the word which has a character on the given idx, as well as for which
the last character is just before idx. For instance, " The", with indices 1,2,3,4
will return "The".
Args:
line: The line of text to extract the word from.
idx (0 .. len(line)): The index to locate the word around.
separators: a regex pattern defining the set of characters that separate words.
Defaults to whitespaces.
Returns:
str: The word found around the specified index, or None if no word is found.
Raises:
ValueError: If the specified index is out of range.
"""
if idx > len(line):
raise ValueError(f"idx {idx} > than line length of {len(line)}.")
re_word = f"[^{separators}]+"
# Go through every every word, until it finds the one spanning over the index.
for m in re.finditer(re_word, line):
if m.start() <= idx <= m.end():
# we are just on the right word
return m.group()
elif m.start() > idx:
# Too far, not need to search more
break
return None
def is_web_url(url: str) -> bool:
"""
returns: True if the given URL (or path) looks like a web URL
"""
web_protocols = ['http://', 'https://', 'ftp://']
return any(url.startswith(protocol) for protocol in web_protocols)
class OpenSelectedFilePluginApp(GObject.Object, Gedit.AppActivatable):
app = GObject.property(type=Gedit.App)
__gtype_name__ = "OpenSelectedFilePluginApp"
def __init__(self):
GObject.Object.__init__(self)
self.menu_ext = None
self.menu_item = None
def do_activate(self):
self.app.set_accels_for_action("win.open-under-cursor", ["<Ctrl><Shift>O"])
def do_deactivate(self):
self.app.set_accels_for_action("win.open-under-cursor", ())
class OpenSelectedFilePlugin(GObject.Object, Gedit.WindowActivatable):
__gtype_name__ = "OpenSelectedFilePlugin"
window = GObject.property(type=Gedit.Window)
def __init__(self):
GObject.Object.__init__(self)
def do_activate(self):
# Create an action
action = Gio.SimpleAction(name="open-under-cursor") # , label="Open File Under Cursor", tooltip="Display the path of the current document")
action.connect("activate", self.open_file)
# Add the action to the Gedit window
self.window.add_action(action)
def do_deactivate(self):
# Remove the action from the Gedit window
self.window.remove_action("open-under-cursor")
def open_file(self, action, data):
# Get the current text selection
selection = self.get_selected_text()
if not selection:
# If selection is empty, get the word around the cursor
selection = self.get_word_around_cursor()
# print(f"selection: {selection}")
if selection:
# if the selection looks like a webpage, (ex, http://google.com) => open in a browser
if is_web_url(selection):
webbrowser.open(selection)
else:
# Check if the file exists
selection_path = self.get_abs_path_from_current_doc(selection)
# print(f"full path: {selection_path}")
# Check if the file exists
if os.path.exists(selection_path):
# Open the file in a new tab
location = Gio.file_new_for_path(selection_path)
# TODO: if URI is already opened, jump to the tab, instead of opening another one
# See https://github.com/addiks/gedit-window-management/blob/0dd12c462af53ad4153f35f8c774c9dcfccf8ab9/AddiksWindowManagementWindow.py#L98
self.window.create_tab_from_location(location, None, 0, 0, False, True) # location, encoding, line, column, create, jump_to
else:
# A better, but still subtle way to indicate the shortcut was received, but the filename doesn't match anything?
print(f"Couldn't find file '{selection_path}', not opening it")
def get_abs_path_from_current_doc(self, path: str) -> str:
"""
return an absolute path corresponding to the current path.
Similar to os.path.abspath(), but instead of use the current working directory (of the process),
use the directory of the current document.
"""
if os.path.isabs(path):
# Just take it as is
return path
else:
doc = self.window.get_active_document()
if not doc: # Shouldn't happen
return path
# file_uri = doc.get_uri_for_display() # removed in v40
file_uri = doc.get_file().get_location().get_uri() # Typically, just the path, but could start with "file://" ??
# print(f"current uri = {file_uri}")
file_path = file_uri.replace('file://', '') # Remove the file:// in case it's there
base = os.path.dirname(file_path)
return os.path.join(base, path)
def get_selected_text(self):
# Get the current active document
doc = self.window.get_active_document()
# Get the selection bounds
bounds = doc.get_selection_bounds()
if bounds:
# Get the selected text
start_iter, end_iter = bounds
return doc.get_slice(start_iter, end_iter, True)
return None
def get_word_around_cursor(self) -> str:
"""
return the word (filename) that is just after or around the cursor
return None if no word
"""
# Get the current active document
doc = self.window.get_active_document()
# Get the current cursor position
cursor = doc.get_iter_at_mark(doc.get_insert())
cursor_pos = cursor.get_line_offset()
# Get the current line
start_iter = cursor.copy()
start_iter.set_line_offset(0)
end_iter = start_iter.copy()
if not end_iter.ends_line():
end_iter.forward_to_line_end()
line_str = doc.get_text(start_iter, end_iter, True)
# print(f"cursor at {cursor_pos}, line: {line_str}")
word = get_word_around_index(line_str, cursor_pos, FILENAME_SEPARATORS)
# print(f"found word: {word}")
# Trick: usually a filename doesn't end with a . (but they can contain some)
# so drop the last ".".
if len(word) > 1 and word[-1] == ".":
word = word[:-1]
return word