Linked text to open other files into Notepad++
-
Let’s pick up where THIS POSTING left off. While that posting showed how to obtain clickable text, it didn’t really show a complete example of a good usage for it. Hopefully this current posting will. So let’s get going…
Notepad++ by default makes it possible to embed links to other text files in your text by using a special syntax, e.g.:
file://w:\junk\test.txt
which will appear like this in a Notepad++ tab:
(double-click that text, and–maybe–the file specified after
file://
will open)This might be useful if I want to embed links to other files in a Notepad++ tab file, for easy opening of those files into Notepad++.
However, there are some limitations to the
file://
technique:-
I don’t “associate” .txt files with Notepad++ at the OS level (and I don’t want to), so double-clicking the
file:
link I show above opens the test.txt file in Notepad.exe -
If an extension on a
file:
linked file is “unknown” to Windows, it won’t know how to open it when the link is double-clicked -
File paths with spaces in them are problematic, e.g.
file://w:\junk\test but I have spaces.txt
would look for this file:w:\junk\test
instead of the desired file…and the link text just doesn’t give one any confidence:
- Even if the file is opened into Notepad++, there isn’t a way to move the caret to a specific line automatically
I’ll present a technique that addresses all of the identified issues.
Imagine writing such file links in any of the following ways (note that the “lead-in” is
edit:
, notfile:
):- edit:w:\junk\test.txt
- edit:test.txt
- edit:…\junk\test.txt
- edit:w:\junk\test.txt(L3,C2)
- edit:w:\junk\test.txt(L-1)
- edit:w:\junk\test%20but%20I%20have%20spaces.txt
- edit:w:/junk/test.txt
After implementing the total solution, the links appear like this in your Notepad++ tab and are “followed” (meaning that the specified file will be opened by Notepad++) by pressing Alt+LeftClick with the mouse (NOT double-clicking):
Here are some corresponding notes, point by point:
- typical, general use case
test.txt
to be located in the same folder as the currently active Notepad++ tab filetest.txt
to be located using a relative path from the currently active Notepad++ tab file- after loading
test.txt
into Notepad++, move the caret to line 3 column 2 - after loading
test.txt
into Notepad++, move the caret to the last line of the file - load a file with spaces in its path/name (note that spaces are replaced by
%20
in the link text) - same as point
1
but using forward slashes rather than backslashes as pathname component separators
To achieve the goal, we need a PythonScript, shown below, and a configuration change.
The configuration change is:
- add
edit
to the URI customized schemes: box on the Cloud & Link tab in the Preferences:
Somewhat obviously, you’ll also need Clickable Link Settings’s Enable box ticked, as is also shown above.
Some limitations of the approach:
- custom syntax, i.e.,
edit:
- Alt+Lclick link text instead of double-Lclick
- (obviously) requires some effort in the setup
Anyway, enough is enough, let’s present the code, I call the script
UriIndicatorAltClick.py
:# -*- coding: utf-8 -*- from Npp import * import os import re class UIAC(object): def __init__(self): self.URL_INDIC = 8 # URL_INDIC is used in N++ source code self.ALT_MODIFIER = 4 self.backslash = '\\' ; self.two_backslashes = self.backslash * 2 self.alt_held_at_click = False self.installed = False self.install() def install(self): if not self.installed: # https://www.scintilla.org/ScintillaDoc.html#SCN_INDICATORCLICK editor.callback(self.indicator_click_callback, [SCINTILLANOTIFICATION.INDICATORCLICK]) # https://www.scintilla.org/ScintillaDoc.html#SCN_INDICATORRELEASE editor.callback(self.indicator_release_callback, [SCINTILLANOTIFICATION.INDICATORRELEASE]) self.installed = True def uninstall(self): if self.installed: editor.clearCallbacks(self.indicator_click_callback) editor.clearCallbacks(self.indicator_release_callback) self.installed = False def is_installed(self): return self.installed def mb(self, msg, flags=0, title=''): return notepad.messageBox(msg, title, flags) def get_indicator_range(self, indic_number): # similar to ScintillaEditView::getIndicatorRange() in N++ source # https://github.com/notepad-plus-plus/notepad-plus-plus/blob/8f38707d33d869a5b8f5014dbb18619b166486a0/PowerEditor/src/ScitillaComponent/ScintillaEditView.h#L562 curr_pos = editor.getCurrentPos() indic_mask = editor.indicatorAllOnFor(curr_pos) if (indic_mask & (1 << indic_number)) != 0: start_pos = editor.indicatorStart(indic_number, curr_pos) end_pos = editor.indicatorEnd(indic_number, curr_pos) if curr_pos >= start_pos and curr_pos <= end_pos: return (start_pos, end_pos) return (0, 0) def indicator_click_callback(self, args): # example: INDICATORCLICK: {'position': 12294, 'idFrom': 0, 'modifiers': 4, 'code': 2023, 'hwndFrom': 1577146} #print('UriIndicatorAltClick indicator click callback') self.alt_held_at_click = (args['modifiers'] & self.ALT_MODIFIER) != 0 def indicator_release_callback(self, args): # example: INDICATORRELEASE: {'position': 12294, 'idFrom': 0, 'modifiers': 0, 'code': 2024, 'hwndFrom': 1577146} #print('UriIndicatorAltClick indicator release callback') if not self.alt_held_at_click: return self.alt_held_at_click = False (start_pos, end_pos) = self.get_indicator_range(self.URL_INDIC) if start_pos == end_pos: return # if click on indicator that is not URL_INDIC uri_text = editor.getTextRange(start_pos, end_pos) (uri_scheme, _, uri_path) = uri_text.partition(':') uri_path = uri_path.replace('%20', ' ').replace('%24', '$').replace('/', self.backslash) # check for optional syntax at end: edit:....txt(L127,C12) goto_line = goto_col = 0 m = re.search(r'\(L(-?\d+)(?:,C(\d+))?\)$', uri_path) if m: uri_path = uri_path[:-len(m.group())] goto_line = int(m.group(1)) if m.group(2): goto_col = int(m.group(2)) if not os.path.isfile(uri_path): # look for a relative path, relative to currently active document try: (valid_dir_of_active_doc, _) = notepad.getCurrentFilename().rsplit(os.sep, 1) except ValueError: # we started out in a "new 1" file, no path on that whatsoever self.mb('Cannot find file:\r\n\r\n{}'.format(uri_path)) return test_path_in_active_doc_dir = valid_dir_of_active_doc + os.sep + uri_path if os.path.isfile(test_path_in_active_doc_dir): uri_path = test_path_in_active_doc_dir else: (test_dir, test_filename) = test_path_in_active_doc_dir.rsplit(os.sep, 1) if os.path.isdir(test_dir): expanded_test_dir = os.path.abspath(test_dir) if expanded_test_dir != test_dir: self.mb('Cannot find file:\r\n\r\n{}\r\n\r\nLooked in this dir:\r\n\r\n{}'.format(test_filename, expanded_test_dir)) return self.mb('Cannot find file:\r\n\r\n{}'.format(uri_path)) return notepad.open(uri_path) if goto_line != 0: if goto_line == -1: goto_line = editor.getLineCount() goto_line -= 1 if goto_col != 0: goto_col_pos = editor.findColumn(goto_line, goto_col) editor.gotoPos(goto_col_pos) else: editor.gotoLine(goto_line) if __name__ == '__main__': if 'uiac' not in globals(): uiac = UIAC() # will automatically "install" it else: # each running the script toggles install/uninstall: uiac.uninstall() if uiac.is_installed() else uiac.install() print('uiac installed?:', uiac.is_installed())
It can be set up to run automatically from user
startup.py
by adding the following two lines there:import UriIndicatorAltClick uiac = UriIndicatorAltClick.UIAC()
-
-
Your screenshot shows
edit
in the config box, instead of theedit:
I expected. It works whether I do either, but if I haveedit
, then text likeediting:code
will show up as underlined,
whereas if I haveedit:
in the box,editing:code
won’t think it’s a link
But other than that, pretty cool implementation. I’ve added it, and I’ll see if it helps my workflow…
-
@peterjones said in Linked text to open other files into Notepad++:
edit in the config box, instead of the edit:
That’s a good point, not sure why I used the colonless version when I composed the post. My real implemenation uses the colon. :-P
-
As a side note, if you work a lot with pathnames that contain spaces and you want to use the above technique, it could be helpful to record a macro for the following Replace All operation:
The little blue rectangle in the Find what: box is a single space.
After recording and saving the macro, when you are dealing with a pathname with a space and it is problematic as a link because of the spaces, e.g.:
Just select it:
And run the macro, to obtain:
Kinda basic stuff, but maybe worth explicitly pointing out…
-
I should also mention that I didn’t show an example of network paths, but they work fine with this technique as well.
For example, the text for this cheesy network path:
edit:\\127.0.0.1\w$\junk\test.txt
becomes a nice alt+clickable link that works and appears as you’d expect in a N++ tab: