You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
So I looked in BracketHighlighter as an example and I don't see a "controlling" function? I assume that's because it uses callsbacks to do it's work?
So in this case, would I effectively need to move the callback registration to startup.py?
So I looked in BracketHighlighter as an example and I don't see a "controlling" function?
I looked at this sample, and you're right -- it isn't set up in the best way for getting it running automatically at startup.
I'll try to work with it and make it better in that regard -- check back in this issue later...
This is merely a "classed" example, very similar to how I showed it without classes EARLIER.
These techniques do the job; they nicely allow another script to call them.
But they are "run and done" techniques -- the called script runs, does its work, and then ends.
If we need a technique where a script runs, and parts of it "hang around" (e.g., the bracket-highlighter script), then we have to tweak the technique a bit:
In foo3.py:
classFoo3(object):
def__init__(self):
print('fubar')
# e.g. set up a Notepad++ callback hereFOO3=Foo3()
and then in the calling script:
fromfoo3importFOO3
The FOO3 variable will remain in the Python namespace, and can be accessed by other code, if needed. More importantly, any callbacks created by Foo3 will remain active in memory.
The new bracket-highlighter that I will post here will use the technique shown here for "foo3".
Here's a new version of BracketHighlighter.py.
I modified as little as possible while "modernizing" the script to use the class-based approach shown in the previous posting.
The modernization allows easier execution at Notepad++ startup via a line in user startup.py: from BracketHighlighter import BRACKET_HIGHLIGHTER
The script listing:
# -*- coding: utf-8 -*-########################################### BracketHighlighter (BH)########################################### references:# https://github.com/bruderstein/PythonScript/issues/356# https://community.notepad-plus-plus.org/topic/14501/has-a-plugin-like-sublime-plugin-brackethighlighter# for newbie info on PythonScripts, see https://community.notepad-plus-plus.org/topic/23039/faq-desk-how-to-install-and-run-a-script-in-pythonscript#-------------------------------------------------------------------------------# to execute, use this in (e.g.) user startup.py:# from BracketHighlighter import BRACKET_HIGHLIGHTER# another execution option would be to copy and then paste that line into the PythonScript console >>> box# note: if running via startup.py, need to make sure that "Initialisation" for "Python Script Configuration" is set to "ATSTARTUP" and not "LAZY".#-------------------------------------------------------------------------------fromNppimport*#-------------------------------------------------------------------------------classBH(object):
def__init__(self):
self.settings_dict=dict()
self.settings_dict['indic_for_box_at_caret'] =notepad.allocateIndicator(1)
assertself.settings_dict['indic_for_box_at_caret'] isnotNoneforeditorXin (editor1, editor2):
self.indicatorOptionsSet(self.settings_dict['indic_for_box_at_caret'], INDICATORSTYLE.STRAIGHTBOX, (238,121,159), 0, 255, True, editorX) # white box rimmed in "pale violet red 2"self.settings_dict['last_modificationType_for_hack'] =Noneeditor.callbackSync(self.updateui_callback, [SCINTILLANOTIFICATION.UPDATEUI]) # install callbackeditor.callbackSync(self.modified_callback, [SCINTILLANOTIFICATION.MODIFIED]) # may not need to be "Sync", but for now we'll make it that waydefupdateui_callback(self, args):
# hack, see https://community.notepad-plus-plus.org/topic/12360/vi-simulator-how-to-highlight-a-word/27, look for "16400" in code:ifargs['updated'] ==UPDATE.CONTENTandself.settings_dict['last_modificationType_for_hack'] == (MODIFICATIONFLAGS.CHANGEINDICATOR|MODIFICATIONFLAGS.USER): returnfor (editorX, pos_range_tuple_list) inself.getViewableEditorAndRangeTupleListList(True):
# clear out any existing highlighting in areas the user can currently seefor (start_pos, end_pos) inpos_range_tuple_list:
editorX.setIndicatorCurrent(self.settings_dict['indic_for_box_at_caret'])
editorX.indicatorClearRange(start_pos, end_pos-start_pos)
for (start_pos, end_pos) inpos_range_tuple_list:
ifstart_pos<=editorX.getCurrentPos() <=end_pos:
(box_start_offset, box_end_offset) =self.containing_box_indices_into_string(
editorX.getTextRange(start_pos, end_pos),
editorX.getCurrentPos() -start_pos
)
ifbox_start_offset!=None:
size_of_box_in_chars=box_end_offset-box_start_offsetifsize_of_box_in_chars<=2:
pass# rather pointless to box in if the opening and closing delims are right next to each otherelse:
editorX.setIndicatorCurrent(self.settings_dict['indic_for_box_at_caret'])
editorX.indicatorFillRange(start_pos+box_start_offset, size_of_box_in_chars)
defmodified_callback(self, args):
self.settings_dict['last_modificationType_for_hack'] =args['modificationType']
defindicatorOptionsSet(self, indicator_number, indicator_style, rgb_color_tup, alpha, outline_alpha, draw_under_text, which_editor=editor):
which_editor.indicSetStyle(indicator_number, indicator_style) # e.g. INDICATORSTYLE.ROUNDBOXwhich_editor.indicSetFore(indicator_number, rgb_color_tup)
which_editor.indicSetAlpha(indicator_number, alpha) # integerwhich_editor.indicSetOutlineAlpha(indicator_number, outline_alpha) # integerwhich_editor.indicSetUnder(indicator_number, draw_under_text) # booleandefcontaining_box_indices_into_string(self, str_containing_caret, caret_index_into_str):
classStack:
def__init__(self): self.clear()
defisEmpty(self): returnself.size() ==0defpush(self, item): self.items.append(item)
defpop(self): returnNoneifself.size() ==0elseself.items.pop()
defpeek(self): returnNoneifself.size() ==0elseself.items[self.size() -1]
defsize(self): returnlen(self.items)
defclear(self): self.items= []
retval= (None, None) # default to no valid boxget_opening_char_via_closing_char_dict= {
')' : '(',
']' : '[',
'}' : '{',
}
get_closing_char_via_opening_char_dict=dict((v, k) for (k, v) inget_opening_char_via_closing_char_dict.items())
closing_chars=get_opening_char_via_closing_char_dict.keys()
opening_chars=get_opening_char_via_closing_char_dict.values()
box_ending_index=-1box_starting_index=-1stack=Stack()
forjinrange(caret_index_into_str, len(str_containing_caret)):
c=str_containing_caret[j]
ifcinclosing_chars:
ifstack.isEmpty():
box_ending_index=jbreakelse:
ifstack.peek() ==get_opening_char_via_closing_char_dict[c]:
stack.pop()
else:
break# unbalancedelifcinopening_chars:
stack.push(c)
ifbox_ending_index!=-1:
stack.clear()
box_starting_index=-1forjinrange(caret_index_into_str-1, -1, -1):
c=str_containing_caret[j]
ifcinopening_chars:
ifstack.isEmpty():
box_starting_index=jbreakelse:
ifstack.peek() ==get_closing_char_via_opening_char_dict[c]:
stack.pop()
else:
break# unbalancedelifcinclosing_chars:
stack.push(c)
ifbox_ending_index!=-1:
ifbox_starting_index!=-1:
ifstr_containing_caret[box_ending_index] ==get_closing_char_via_opening_char_dict[str_containing_caret[box_starting_index]]:
retval= (box_starting_index, box_ending_index+1)
returnretvaldeffileIsCloned(self, file_name_to_test):
retval=Falseclone_detect_dict= {}
file_tup_list=notepad.getFiles()
fortupinfile_tup_list:
(filename, _, _, _) =tupiffilenamenotinclone_detect_dict:
clone_detect_dict[filename] =0else:
clone_detect_dict[filename] +=1iffilename==file_name_to_test: breakiffile_name_to_testinclone_detect_dict:
ifclone_detect_dict[file_name_to_test] >=1: retval=TruereturnretvaldeffileIsClonedAndIsActiveInBothViews(self, file_name_to_test):
retval=Falseifeditor1andeditor2:
# both views are in useifself.fileIsCloned(file_name_to_test):
curr_doc_index_main_view=notepad.getCurrentDocIndex(0)
curr_doc_index_2nd_view=notepad.getCurrentDocIndex(1)
main_view_active_doc_bool=Falsesecondary_view_active_doc_bool=Falsefile_tup_list=notepad.getFiles()
fortupinfile_tup_list:
(filename, _, index_in_view, view_number) =tupiffilename==file_name_to_test:
ifview_number==0:
ifindex_in_view==curr_doc_index_main_view:
main_view_active_doc_bool=Trueelifview_number==1:
ifindex_in_view==curr_doc_index_2nd_view:
secondary_view_active_doc_bool=Trueifmain_view_active_doc_boolandsecondary_view_active_doc_bool:
retval=Truebreakreturnretvaldefconsolidate_range_tuple_list(self, range_tup_list):
sorted_range_tup_list=sorted(range_tup_list) # sort criteria is first element of tuple in listsaved_2element_list=list(sorted_range_tup_list[0])
for (start, end) insorted_range_tup_list:
ifstart<=saved_2element_list[1]:
saved_2element_list[1] =max(saved_2element_list[1], end)
else:
yieldtuple(saved_2element_list)
saved_2element_list[0] =startsaved_2element_list[1] =endyieldtuple(saved_2element_list)
defgetViewableEditorAndRangeTupleListList(self, work_across_both_views):
retval= []
# retval looks like these examples:# [ ( editor, [ (0, 1000), (2020, 3000) ] ) ]# [ ( editor1, [ (0, 1000), (2020, 3000) ] ), ( editor2, [ (4000, 5000), (6020, 7000) ] ) ]both_views_open=Trueifeditor1andeditor2elseFalsecurr_file_active_in_both_views=self.fileIsClonedAndIsActiveInBothViews(notepad.getCurrentFilename()) ifboth_views_openelseFalseifboth_views_open:
ed1_range_tup_list=self.get_onscreen_pos_tup_list(editor1)
ed2_range_tup_list=self.get_onscreen_pos_tup_list(editor2)
ifcurr_file_active_in_both_views:
range_tup_list=list(self.consolidate_range_tuple_list(ed1_range_tup_list+ed2_range_tup_list))
retval.append((editor, range_tup_list))
elifboth_views_openandwork_across_both_views:
retval.append((editor1, ed1_range_tup_list))
retval.append((editor2, ed2_range_tup_list))
else:
range_tup_list=self.get_onscreen_pos_tup_list(editor)
retval.append((editor, range_tup_list))
returnretvaldefget_onscreen_pos_tup_list(self, which_editor): # which_editor is editor1 or editor2 (or maybe even just plain editor)# loosely based upon the N++ source for SmartHighlighter::highlightViewWithWord()retval_tup_list=list()
temp_tup_list= []
MAXLINEHIGHLIGHT=400firstLine=which_editor.getFirstVisibleLine()
currentLine=firstLinenbLineOnScreen=which_editor.linesOnScreen()
nrLines=min(nbLineOnScreen, MAXLINEHIGHLIGHT) +1lastLine=firstLine+nrLinesprevDocLineChecked=-1break_out=FalsewhilecurrentLine<lastLine:
docLine=which_editor.docLineFromVisible(currentLine)
ifdocLine!=prevDocLineChecked:
prevDocLineChecked=docLinestartPos=which_editor.positionFromLine(docLine)
endPos=which_editor.positionFromLine(docLine+1)
ifendPos==-1:
endPos=which_editor.getTextLength() -1break_out=TrueifendPos>startPos: temp_tup_list.append((startPos, endPos))
ifbreak_out: breakcurrentLine+=1iflen(temp_tup_list) >0:
retval_tup_list=list(self.consolidate_range_tuple_list(temp_tup_list))
returnretval_tup_list#-------------------------------------------------------------------------------BRACKET_HIGHLIGHTER=BH()
Which helped but then I got an error in the new Brackethighlighter itself:
Traceback (most recent call last):
File "C:\Users\vbonaventura\Programs\Notepad++\plugins\PythonScript\scripts\startup.py", line 51, in <module>
from BracketHighlighter import BRACKET_HIGHLIGHTER
File ".\Samples\BracketHighlighter.py", line 254, in <module>
BRACKET_HIGHLIGHTER = BH()
File ".\Samples\BracketHighlighter.py", line 34, in __init__
self.settings_dict['indic_for_box_at_caret'] = notepad.allocateIndicator(1)
AttributeError: 'Notepad' object has no attribute 'allocateIndicator'
Do I need to so something to include the samples directory itself so it can find BracketHighlighter?
@chcg Maybe Pythonscript could be modified so that this wouldn't happen?
sys.path.insert(0,'.\Samples')
Yes, this is the right idea.
then I got an error in the new Brackethighlighter itself
AttributeError: 'Notepad' object has no attribute 'allocateIndicator'
Your PythonScript is too old; doesn't have support for allocateIndicator.
You can go back to using the hardcoded indicator 10 if that works for you (if you don't want to get a newer PythonScript).
Regarding the import of examples:
At the moment I see two ways to solve this, either by overfilling the sys.path with another directory or by adding a __init__.py to the examples directory.
But all these possibilities mean that the examples are written in such a way that an import is possible.
Personally, I would tend towards the second option.
or by adding a __init__.py to the examples directory.
You mean the Samples folder.
I tried creating an empty file there: ...\plugins\PythonScript\scripts\Samples\__init__.py
and then trying this in the PS console: from BracketHighlighter import BRACKET_HIGHLIGHTER
resulted in:
Traceback (most recent call last):
File "<console>", line 1, in <module>
ModuleNotFoundError: No module named 'BracketHighlighter'
@mpheath 's technique can be used to solve the problem, but I was just curious about the __init__.py technique.
Personally, I'm currently using the "modify sys.path" technique for my own scripts, see HERE, but I'm always looking to improve the way I do things, if there's a better way.
Yes, but from Samples ..., it's always the directory that contains the init.py that defines the module/package.
But as @mpheath posted, it's already possible to import it, so there's no need to have an additional __init__.py there.
So from my point of view this issue can be closed too.
The import would be from Samples import BracketHighlighter and not from BracketHighlighter import BRACKET_HIGHLIGHTER
because Python would look for a directory named 'BracketHighlighter' for the latter.
As you can see, this still rises questions. I'd propose the following enhancements to improve UX:
In either Configuration UI or in the plugin menu, add the "open startup.py folder" item, so that one can find it without going to docs, learning about startup, then looking for the right path and opening it;
Well, when I've looked at the sources regarding the menu, I've noticed that clicking a script while holding ctrl opens the script, this is nice, but not easily explorable. What about adding a disabled menu item that tells "ctrl + click a scirpt to open it" below all the scripts?
There's also a problem that editing startup.py requires admin rights, this bit can also be improved, but not in the first place, I think.
In startup.py itself, give this example, like (for now, even docs don't mention that such import will work):
# to run your script on startup, normally you add something like this:
# import my_script # your script is named my_script.py
# my_script.init() # the init function should be defined in your script (def init():)
Also, it would be helpful to add some instructions how to test this quickly and reliably.
And maybe a link to description of advanced techniques like this thread (not the first priority).
Activity
alankilborn commentedon Nov 15, 2024
Yes!
your script, called
foo.py, has a "controlling" function (the function that will do the work for the script) called "bar":in user
startup.py:This is a minimal way to do it. I'll post some more advanced examples later.
Skrell commentedon Nov 15, 2024
So I looked in BracketHighlighter as an example and I don't see a "controlling" function? I assume that's because it uses callsbacks to do it's work?
So in this case, would I effectively need to move the callback registration to startup.py?
Skrell commentedon Nov 15, 2024
Easiest solution I found:
Put this at bottom of startup.py
alankilborn commentedon Nov 15, 2024
This is more of a brute force way, not a smart way. :-(
alankilborn commentedon Nov 16, 2024
I looked at this sample, and you're right -- it isn't set up in the best way for getting it running automatically at startup.
I'll try to work with it and make it better in that regard -- check back in this issue later...
alankilborn commentedon Nov 17, 2024
Here's another way, using Python classes, to architect a script so that it is callable by another script:
In
foo2.py:and then in the calling script:
This is merely a "classed" example, very similar to how I showed it without classes EARLIER.
These techniques do the job; they nicely allow another script to call them.
But they are "run and done" techniques -- the called script runs, does its work, and then ends.
If we need a technique where a script runs, and parts of it "hang around" (e.g., the bracket-highlighter script), then we have to tweak the technique a bit:
In
foo3.py:and then in the calling script:
The
FOO3variable will remain in the Python namespace, and can be accessed by other code, if needed. More importantly, any callbacks created by Foo3 will remain active in memory.The new bracket-highlighter that I will post here will use the technique shown here for "foo3".
alankilborn commentedon Nov 17, 2024
Here's a new version of
BracketHighlighter.py.I modified as little as possible while "modernizing" the script to use the class-based approach shown in the previous posting.
The modernization allows easier execution at Notepad++ startup via a line in user
startup.py:from BracketHighlighter import BRACKET_HIGHLIGHTERThe script listing:
Skrell commentedon Nov 19, 2024
Do I need to so something to include the samples directory itself so it can find BracketHighlighter?
Skrell commentedon Nov 19, 2024
I added
Which helped but then I got an error in the new Brackethighlighter itself:
alankilborn commentedon Nov 19, 2024
@chcg Maybe Pythonscript could be modified so that this wouldn't happen?
Yes, this is the right idea.
Your PythonScript is too old; doesn't have support for
allocateIndicator.You can go back to using the hardcoded indicator 10 if that works for you (if you don't want to get a newer PythonScript).
Ekopalypse commentedon Dec 20, 2024
@Skrell - Is there anything else to do about this, if not, can you please close this issue?
Ekopalypse commentedon Dec 27, 2024
Regarding the import of examples:
At the moment I see two ways to solve this, either by overfilling the sys.path with another directory or by adding a
__init__.pyto the examples directory.But all these possibilities mean that the examples are written in such a way that an import is possible.
Personally, I would tend towards the second option.
mpheath commentedon Jan 1, 2025
Test OK in PS3.
alankilborn commentedon Jan 1, 2025
You mean the
Samplesfolder.I tried creating an empty file there:
...\plugins\PythonScript\scripts\Samples\__init__.pyand then trying this in the PS console:
from BracketHighlighter import BRACKET_HIGHLIGHTERresulted in:
@mpheath 's technique can be used to solve the problem, but I was just curious about the
__init__.pytechnique.Personally, I'm currently using the "modify sys.path" technique for my own scripts, see HERE, but I'm always looking to improve the way I do things, if there's a better way.
Ekopalypse commentedon Jan 1, 2025
Yes, but
from Samples ..., it's always the directory that contains the init.py that defines the module/package.But as @mpheath posted, it's already possible to import it, so there's no need to have an additional
__init__.pythere.So from my point of view this issue can be closed too.
alankilborn commentedon Jan 1, 2025
I don't understand how that answers my question, but no worries, I'll just keep going with my
sys.pathapproach.Ekopalypse commentedon Jan 1, 2025
The import would be
from Samples import BracketHighlighterand notfrom BracketHighlighter import BRACKET_HIGHLIGHTERbecause Python would look for a directory named 'BracketHighlighter' for the latter.
YakovL commentedon Apr 7, 2025
As you can see, this still rises questions. I'd propose the following enhancements to improve UX:
In either Configuration UI or in the plugin menu, add the "open startup.py folder" item, so that one can find it without going to docs, learning about startup, then looking for the right path and opening it;
Well, when I've looked at the sources regarding the menu, I've noticed that clicking a script while holding ctrl opens the script, this is nice, but not easily explorable. What about adding a disabled menu item that tells "ctrl + click a scirpt to open it" below all the scripts?
There's also a problem that editing
startup.pyrequires admin rights, this bit can also be improved, but not in the first place, I think.In startup.py itself, give this example, like (for now, even docs don't mention that such import will work):
Also, it would be helpful to add some instructions how to test this quickly and reliably.
And maybe a link to description of advanced techniques like this thread (not the first priority).
alankilborn commentedon Apr 7, 2025
@YakovL Documentation changes are fine, but I don't think any of your suggestions needs to result in a code change to the plugin.