Style token not saved
-
Peter said:
I’m not good with the bookmarks / mark-styles… but I seem to remember there’s been some PythonScript about them before
Peter was thinking of this I think: https://notepad-plus-plus.org/community/topic/18052/bookmark-by-style
Sure, this type of thing could be adapted for a Load/Save type functionality. Let me see what I can do. But no promises… [Note, I only go off and do these types of things because there could potentially be value in it for my use as well.]
-
So as luck would have it I had some time immediately. :)
I would caution users to work with this with documents that are read-only, otherwise be very careful about not changing any data until you’ve reloaded the styles with it from a previous run of N++. Otherwise the styling that is loaded will be “skewed” and will be, well, basically lost and unrecoverable. You’ve been warned. No actual text data will be harmed in any event.
Here’s the Pythonscript that works for either saving or loading the styling. It works for the currently loaded Notepad++ file (name doesn’t matter) and saves its info to a fixed-name data file. All this behavior can be changed, but that’s left as an exercise for the reader; I’m just trying to get the core logic expressed here. Error-checking is minimal to non-existent. Niceties: missing-in-acton as well. Much of the core of the script copied from the link referenced earlier.
def main(): result = notepad.messageBox("SAVE current doc's styling to disk file?\r\n\r\nYES = Yes, please\r\nNO = LOAD styling info from file and apply to current doc\r\nCANCEL = I'm outta here", '', MESSAGEBOXFLAGS.YESNOCANCEL) if result == MESSAGEBOXFLAGS.RESULTCANCEL: return saving_not_loading = True if result == MESSAGEBOXFLAGS.RESULTYES else False # identifiers pulled from N++ source code: SCE_UNIVERSAL_FOUND_STYLE_EXT1 = 25 # N++ style 1 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT2 = 24 # N++ style 2 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT3 = 23 # N++ style 3 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT4 = 22 # N++ style 4 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT5 = 21 # N++ style 5 indicator number SCE_UNIVERSAL_FOUND_STYLE = 31 # N++ red-"mark" feature highlighting style indicator number indicator_number_list = [] indicator_number_list.append(SCE_UNIVERSAL_FOUND_STYLE_EXT1) indicator_number_list.append(SCE_UNIVERSAL_FOUND_STYLE_EXT2) indicator_number_list.append(SCE_UNIVERSAL_FOUND_STYLE_EXT3) indicator_number_list.append(SCE_UNIVERSAL_FOUND_STYLE_EXT4) indicator_number_list.append(SCE_UNIVERSAL_FOUND_STYLE_EXT5) indicator_number_list.append(SCE_UNIVERSAL_FOUND_STYLE) if saving_not_loading: def highlight_indicator_range_tups_generator(indicator_number): ''' the following logic depends upon behavior that isn't exactly documented; it was noticed that calling editor.indicatorEnd() will yield the "edge" (either leading or trailing) of the specified indicator greater than the position specified by the caller this is definitely different than the scintilla documentation: "Find the start or end of a range with one value from a position within the range" ''' if editor.indicatorEnd(indicator_number, 0) == 0: return indicator_end_pos = 0 # set special value to key a check the first time thru the while loop while True: if indicator_end_pos == 0 and editor.indicatorValueAt(indicator_number, 0) == 1: # we have an indicator starting at position 0! # when an indicator highlight starts at position 0, editor.indicatorEnd() # gives us the END of the marking rather than the beginning; # have to compensate for that: indicator_start_pos = 0 else: indicator_start_pos = editor.indicatorEnd(indicator_number, indicator_end_pos) indicator_end_pos = editor.indicatorEnd(indicator_number, indicator_start_pos) if indicator_start_pos == indicator_end_pos: break # no more matches yield (indicator_start_pos, indicator_end_pos) with open('styling.txt', 'w') as f: for indic_number in indicator_number_list: for (styled_start_pos, styled_end_pos) in highlight_indicator_range_tups_generator(indic_number): f.write('{i} {start} {stop}\n'.format(i=indic_number, start=styled_start_pos, stop=styled_end_pos)) else: with open('styling.txt') as f: for line in f: (indic, start, end) = line.rstrip().split() editor.setIndicatorCurrent(int(indic)) editor.indicatorFillRange(int(start), int(end) - int(start)) main()
-
@alan-kilborn Thank you, Alan!
I was looking for something like this, and have verified this Python script still works on Notepad++ v8.1.9.2.
For me it saved the styling.txt file in the same folder as the Python scripts, under [install path]\Notepad++\plugins\config\PythonScript\scripts.
When I tried to use the Python script to reload/apply the saved styles, at first I got a “No such file or directory” error.
That went away after I changed the script to save the styling file in the same folder as the file being edited.
I also changed the styling filename to match the name of the file being edited with “_nppStyling.txt” appended.
This lets you save multiple styling files, and you can easily see by browsing your folders, which files you’ve saved styles for.To do that, I added these lines to the beginning of the main() method:
currentFilePath = notepad.getCurrentFilename() stylingFileName = currentFilePath + '_nppStyling.txt'
Then I used the stylingFileName variable in place of ‘styling.txt’.
-
@Alan-Kilborn
Hi, I know that the topic is so old, but I try the same to explain my problem:I executed your code, but the script returns the following error:
SyntaxError: (unicode error) ‘unicodeescape’ codec can’t decode bytes in position 16-17: malformed \N character escape
Can you help me? I’m not a Python expert, so I don’t understand how to fix this issue. I’m using a v8.4.2 of Notepad++.
Thanks
-
@BallardiniFANS said in Style token not saved:
Can you help me?
I’ll certainly try.
SyntaxError: (unicode error) ‘unicodeescape’ codec can’t decode bytes in position 16-17: malformed \N character escape
A
SyntaxError
is a problem with the script, not with the data you are running it on.Perhaps as a first step to debugging this problem, try changing the script to put this as line 1 of it:
# -*- coding: utf-8 -*-
-
-
Hmm, at this point, I’m not sure.
Does the Notepad++ status bar indicate that your file’s encoding is
UTF-8
?I just tried the script as written above in a fresh setup of N++ v8.4.7 and PythonScript v3.0.12, as well as also trying it with PS v2.0, and the script ran fine, either with or without the new first line I asked you to add.
Aside from having you examine your copy of the script file in a hex editor, to see if somehow an oddball character has crept in (at the position indicated by the error message), I’m not sure what else to do here.
Maybe @PeterJones has an idea?
-
@Alan-Kilborn said in Style token not saved:
Does the Notepad++ status bar indicate that your file’s encoding is UTF-8?
yep, I checked it now, it is setted on: Format -> Coding in UTF-8.
However, I explain the step that i’m doing, probably I wrong something:- This is the path where the script is located : C:\Program Files\Notepad++\plugins\Config\PythonScript
In this path I putted also a file called styling.txt
- I’m using Python 3.10 to run the script.
-
@BallardiniFANS said in Style token not saved:
This is the path where the script is located
As long as you can see the script when you go here in the N++ menu (as you need to do to run it), the location of the script should be fine:
In this path I putted also a file called
styling.txt
Well the script puts that file there, not you. So I don’t think knowing this gets us anywhere with the debugging.
-
@Alan-Kilborn
It works now, I see the windows of the script.
Click on yes, the script should save the style token, but when I close and reopen the file .txt the style token of the file is disappear. -
@BallardiniFANS said in Style token not saved:
It works now
GREAT!
but when I close and reopen the file .txt the token style of the file is disappear
Are you running the script again, and choosing
No
this time? -
@Alan-Kilborn said in Style token not saved:
Are you running the script again, and choosing No this time?
I’m doing these steps:
- I put a style token on a word.
- Run the script and click on YES.
- Close and Reopen the file .txt
- I don’t see the style token, I Run the script again and click on NO.
After step 4 I see the text without style token.
-
@BallardiniFANS said in Style token not saved:
Click on yes, the script should save the style token, but when I close and reopen the file .txt the style token of the file is disappear.
The problem can be due to the Notepad++ version?
-
Update: I tried with the v8.1.9.2, but nothing changes.
@Alan-Kilborn Have you any idea? Is it work in your environment?
Let me know, this function is fundamental for me eheh. -
Yes, it works fine for me. I would have mentioned it if it didn’t.
I’m sort of out of ideas on why it isn’t working for you.
It might be nice to have some independent verification from others that the script works fine for them. @PeterJones maybe?
You could certainly try the script with a newer version of Notepad++ than 8.1.* and see if that makes a difference.
-
@Alan-Kilborn
There is an error when I choose YES or NO: -
Now it works fine!!!
Running Notepad as administrator the issue is fixed.Thank you very much @Alan-Kilborn
-
OK, with the more detailed error reporting I can see what failed with the script. While running as administrator solves the problem, it is rather like cutting off your arm because you have a hangnail – problem solved, but…
Note that I believe the intent of the script was just to demo the functionality, not really be useful in a general sense. Why do I say this? Because a single file is used for the data, it is only useful with a single source file. (There are some other logistical problems with saving and restoring styling, as well).
Let me rework the script a little, I’ll post a new one here; check back here in a few days…
-
It appears I didn’t give the original script a name in this thread, although I called it
StylingSaveOrLoad.py
on my setup. Thus, for a new script, I’ll call itStylingSaveOrLoad2.py
.The new script is similar to the old, but a major way it is different is that it saves a
.sty
file in the same folder as the original file, to hold the styling data. Thus, if you’re editingtest.txt
and then save the styling by running the script, you’ll seetest.sty
in the same folder.Basic instructions on PythonScripting may be found HERE.
Here’s the script listing:
# -*- coding: utf-8 -*- from __future__ import print_function # references: # https://community.notepad-plus-plus.org/topic/18134/style-token-not-saved/ from Npp import * import inspect import os #------------------------------------------------------------------------------- class SSOL2(object): def __init__(self): self.debug = True if 0 else False if self.debug: pass #console.show() #console.clear() self.this_script_name = inspect.getframeinfo(inspect.currentframe()).filename.split(os.sep)[-1].rsplit('.', 1)[0] # identifiers pulled from N++ source code: SCE_UNIVERSAL_FOUND_STYLE_EXT1 = 25 # N++ style 1 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT2 = 24 # N++ style 2 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT3 = 23 # N++ style 3 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT4 = 22 # N++ style 4 indicator number SCE_UNIVERSAL_FOUND_STYLE_EXT5 = 21 # N++ style 5 indicator number SCE_UNIVERSAL_FOUND_STYLE = 31 # N++ red-"mark" feature highlighting style indicator number self.indicator_number_list = [ SCE_UNIVERSAL_FOUND_STYLE_EXT1, SCE_UNIVERSAL_FOUND_STYLE_EXT2, SCE_UNIVERSAL_FOUND_STYLE_EXT3, SCE_UNIVERSAL_FOUND_STYLE_EXT4, SCE_UNIVERSAL_FOUND_STYLE_EXT5, SCE_UNIVERSAL_FOUND_STYLE, ] def get_data_file_path_for_active_tab(self): active_file_pathname = notepad.getCurrentFilename() if not os.path.isfile(active_file_pathname): return None if active_file_pathname.endswith('.sty'): return None data_file_for_active_tab = active_file_pathname.rsplit('.', 1)[0] + '.sty' return data_file_for_active_tab def save_data_file(self): data_file_for_active_tab = self.get_data_file_path_for_active_tab() if data_file_for_active_tab == None: self.mb('Problem saving styling data for active tab file') return with open(data_file_for_active_tab, 'w') as fo: for indic_number in self.indicator_number_list: for (styled_start_pos, styled_end_pos) in self.highlight_indicator_range_tups_generator(indic_number): fo.write('{i} {start} {stop}\n'.format(i=indic_number, start=styled_start_pos, stop=styled_end_pos)) def load_data_file(self): data_file_for_active_tab = self.get_data_file_path_for_active_tab() if data_file_for_active_tab == None or not os.path.isfile(data_file_for_active_tab): self.mb('Problem loading styling data for active tab file') return with open(data_file_for_active_tab) as fi: for line in fi: (indic, start, end) = line.rstrip().split() editor.setIndicatorCurrent(int(indic)) editor.indicatorFillRange(int(start), int(end) - int(start)) def run(self): saving_not_loading = self.yes_no_cancel('\r\n'.join([ "SAVE current doc's styling to disk file?\r\n", "YES = Yes, please SAVE it", "NO = LOAD styling info from file and apply to current doc", "CANCEL = I'm outta here"])) if saving_not_loading == None: return self.save_data_file() if saving_not_loading else self.load_data_file() def highlight_indicator_range_tups_generator(self, indicator_number): ''' the following logic depends upon behavior that isn't exactly documented; it was noticed that calling editor.indicatorEnd() will yield the "edge" (either leading or trailing) of the specified indicator greater than the position specified by the caller this is definitely different than the scintilla documentation: "Find the start or end of a range with one value from a position within the range" ''' if editor.indicatorEnd(indicator_number, 0) == 0: return indicator_end_pos = 0 # set special value to key a check the first time thru the while loop while True: if indicator_end_pos == 0 and editor.indicatorValueAt(indicator_number, 0) == 1: # we have an indicator starting at position 0! # when an indicator highlight starts at position 0, editor.indicatorEnd() # gives us the END of the marking rather than the beginning; # have to compensate for that: indicator_start_pos = 0 else: indicator_start_pos = editor.indicatorEnd(indicator_number, indicator_end_pos) indicator_end_pos = editor.indicatorEnd(indicator_number, indicator_start_pos) if indicator_start_pos == indicator_end_pos: break # no more matches yield (indicator_start_pos, indicator_end_pos) def print(self, *args): if self.debug: print(self.__class__.__name__ + ':', *args) def mb(self, msg, flags=0, title=''): # a message-box function return notepad.messageBox(msg, title if title else self.this_script_name, flags) def yes_no_cancel(self, question_text): retval = None answer = self.mb(question_text, MESSAGEBOXFLAGS.YESNOCANCEL, self.this_script_name) if answer == MESSAGEBOXFLAGS.RESULTYES: retval = True elif answer == MESSAGEBOXFLAGS.RESULTNO: retval = False return retval #------------------------------------------------------------------------------- if __name__ == '__main__': SSOL2().run()
You can directly invoke this script, to get a prompt box much like the original script:
Or, if you prefer, you can set two up new keycombo-invoked scripts, such that one directly saves the data file, and one applies the styling from the data file (i.e., loads it).
If you like the two-keycombo approach, here’s how to set up the additional scripts needed for that:
- Add these lines to (user)
startup.py
:
import StylingSaveOrLoad2 ssol2 = StylingSaveOrLoad2.SSOL2()
- Create a new script file called
StylingSaveForCurrentFile.py
with content:
# -*- coding: utf-8 -*- ssol2.save_data_file()
- Create a new script file called
StylingLoadForCurrentFile.py
with content:
# -*- coding: utf-8 -*- ssol2.load_data_file()
- Bind the running of these two scripts to keycombos of your choosing
- Add these lines to (user)
-
-
@Alan-Kilborn Works!!! I don’t use notepad++ to develop code, only to create content for my websites, and that helped me a lot, thank you very much.
-