-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update Doc: usage.rst , intro.rst , pythonprimer.rst #296
base: master
Are you sure you want to change the base?
Changes from all commits
862831e
448a7de
0d6a5ae
8a425df
e905591
4e90e1e
d7ed25c
41f739c
a6a8bc5
a8fb28d
78c3c29
13d4315
34a9726
ccdc56b
ab6bfd7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
Introduction | ||
Introduction | ||
============ | ||
|
||
.. highlight:: python | ||
|
@@ -11,20 +11,20 @@ Don't worry if you've not seen, or never even heard of Python_, it's incredibly | |
To whet your appetite, here's a quick (complete) sample script that shows some of the power of Python Script.:: | ||
|
||
editor.replace("old", "new") | ||
editor.pyreplace(r"^Code: ([A-Z]{4,8})", r"The code is \1") | ||
|
||
notepad.runMenuCommand("TextFX Tools", "Delete Blank Lines") | ||
|
||
notepad.runMenuCommand("Line Operations", "Remove Empty Lines") | ||
|
||
notepad.save() | ||
|
||
|
||
Line 1 performs a normal search and replace on the current document, replacing the word "old" with the word "new". | ||
|
||
Line 2 also performs a search and replace, but uses Python_ regular expressions, which is a much more complete implementation of regular expressions than is natively supported in Notepad++. | ||
Line 1 performs a normal search and replace on the current document, replacing the word "old" with the word "new". | ||
|
||
Line 4 runs the menu command called "Delete Blank Lines" from the TextFX Tools menu. | ||
Line 3 runs the menu command called "Remove Empty Lines" from menu: Edit -> Line Operations. | ||
|
||
Line 5 saves the current document (equivalent to clicking File, Save). | ||
|
||
WARNING: While for the most part, a PythonScript script will behave like a Python script run by a Python interpreter, one important difference is that ``exit()``, ``quit()`` and related functions like ``sys.exit()`` and ``os._exit(0)``, that normally terminate the script and exit the Python interpreter, in PythonScript they will also abruptly terminate Notepad++ (crash it without saving your unsaved files and other data). So, you best avoid them, and use other techniques for early exiting from your code (like ``return`` inside a function, for example). | ||
|
||
|
||
Objects | ||
======== | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example needs to be modified to make it python2/3 compatible. E.g. in Python3 a str has no decode method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ekopalypse , which exmple exactly? Please comment directly under the changes that you refer to. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you mean in intro.rst#working-with-unicode-text . Please correct me if I'm wrong: after merging this doc PR, people will get to see it in their installed PS only after PS project releases a new version. If yes, then why need to make it compatible with both py 2 and 3, instead of only py 3? And, also other parts of doc will need adjustments to py 3. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, the changes only apply to a new version of PS(2/3). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Aside from what you say, I noticed a few mentionings of py 2 or 2.7. If you're not sure that next PS version will be with python3, then let's just push this PR with current changes, then later can make another one specifically to ensure all is for python 3. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or let @chcg weigh in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the master just python3 is relevant. There is a dedicated branch for the old python2 version, see https://github.com/bruderstein/PythonScript/tree/stable_python27. |
||
|
@@ -76,11 +76,13 @@ append ``.decode('utf8')`` to the string. Obviously if your string is in a diffe | |
To put text back to Scintilla (so editor.something()), use .encode('utf8') from a unicode string. | ||
|
||
For example:: | ||
|
||
|
||
# -*- coding: utf-8 -*- | ||
|
||
# define a unicode variable | ||
someUnicodeString = u'This häs fünny ünicode chäractêrs in it' | ||
|
||
# append the text to the current buffer - assuming the current buffer is set to utf8 | ||
# add the string to the current buffer at current position - assuming the current buffer is set to utf8 | ||
editor.addText(someUnicodeString.encode('utf8')) | ||
|
||
# grab the first line | ||
|
@@ -93,7 +95,7 @@ For example:: | |
firstLineUnicode = firstLineUnicode.upper() | ||
|
||
# and put the line back | ||
editor.replaceWholeLine(firstLineUnicode.encode('utf8') | ||
editor.replaceWholeLine(0, firstLineUnicode.encode('utf8') ) | ||
|
||
|
||
.. _Notifications: | ||
|
@@ -106,14 +108,14 @@ Overview | |
|
||
You can call a Python function when events occur in Notepad++ or Scintilla_. Events in Notepad++ are things like the active document changing, a file being opened or saved etc. Events in Scintilla are things like a character being added, a *save point* being reached, the document being made *dirty* and so on. | ||
|
||
Basically, you register in a script a Python_ function to call when an event occurs, and thereafter the function always runs whenever that event occurs. One function can be registered to handle more than one event. | ||
Basically, you register in a script a Python_ function to call when an event occurs, and thereafter the function always runs whenever that event occurs. A function in such a role will be called an event handler or a "callback". One function can be registered to handle more than one event. | ||
|
||
You can unregister the callback later, either by using the name of the function, or the event names, or a combination. | ||
|
||
A simple example | ||
---------------- | ||
|
||
Let's register a callback for the FILEBEFORESAVE event - the occurs just before the file is saved, | ||
Let's register a callback for the FILEBEFORESAVE event - which occurs just before the file is saved, | ||
and we'll add a "saved on" log entry to the end of the file, if the filename ends in '.log'.:: | ||
|
||
import datetime | ||
|
@@ -124,19 +126,21 @@ and we'll add a "saved on" log entry to the end of the file, if the filename end | |
|
||
notepad.callback(addSaveStamp, [NOTIFICATION.FILEBEFORESAVE]) | ||
|
||
Note: the actual registration happens when you run the script. If run this script N times, then N registrations will occur: once the event occurs, N times the callback function will be called. Callbacks will be active until you close Notepad++, or disable them in a script as explained later in this section. | ||
|
||
Line 1 imports the datetime module so we can get today's date. | ||
|
||
Line 3 defines a function called ``addSaveStamp``. | ||
|
||
Line 4 checks that the extension of the file is '.log'. | ||
Line 4 checks that the extension of the currently-active file is '.log'. | ||
|
||
Line 5 appends text like ``"File saved on 2009-07-15"`` to the file. | ||
|
||
Line 7 registers the callback function for the FILEBEFORESAVE event. Notice the square brackets around the ``NOTIFICATION.FILEBEFORESAVE``. This is a list, and can contain more than one item (so that the function is called when any of the events are triggered). | ||
|
||
Really, we should improve this function a little. Currently, it assumes the file being saved is the active document - in the case of using "Save All", it isn't necessarily. However, it's easy to fix... | ||
Really, we should improve this function a little. Currently, it assumes the file being saved is the active document - but in the case of using "Save All", it isn't necessarily. However, it's easy to fix... | ||
|
||
The ``args`` parameter to the function is a map (similar a dictionary in C# or a hashmap in Java), that contains the arguments for the event - many events are signalled for a ``BufferID``, which is the Notepad++ internal number for a particular file or tab. We can do things with the bufferID like get the filename, switch to it to make it active and so on. | ||
The ``args`` parameter to the function is a map (similar to a dictionary in C# or a hashmap in Java), that when the callback is registered, will contain the arguments from (details of) the event. Many events are signalled for a specific ``BufferID``, which is the Notepad++ internal number for a particular file or tab. We can do things with the bufferID like get the filename, switch to it to make it active and so on. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll do that. (together with all modifications from your suggestions). |
||
|
||
So, first we'll change it so that we check the filename of the bufferID being saved, rather than the active document. | ||
Then, if the filename has a '.log' extension, we'll change to it and add our "File saved on ....." line.:: | ||
|
@@ -152,7 +156,7 @@ Then, if the filename has a '.log' extension, we'll change to it and add our "Fi | |
|
||
|
||
|
||
Great, now it works properly. There's a side effect though, if we do use save-all, we might change the active document, | ||
Great, now it works properly. There's a side effect though, if we do use save-all when the current document is other than a ".log" file, the callback will make that ".log" file the active document, | ||
which might seem a bit strange when we use it. Again, very easy to fix.:: | ||
|
||
|
||
|
@@ -169,7 +173,7 @@ which might seem a bit strange when we use it. Again, very easy to fix.:: | |
|
||
Now everything works as should, and it's nice and easy to see what's going on, and we leave the user with the same document they had open if they use Save-All. | ||
|
||
See the :class:`NOTIFICATION` enum for more details on what arguments are provided for each notification, and the different events that are available. | ||
See the :class:`NOTIFICATION` enum for more details on what arguments are provided from each notification, and the different events that are available. | ||
|
||
Cancelling Callbacks | ||
-------------------- | ||
|
@@ -180,42 +184,87 @@ The simplest form is:: | |
|
||
notepad.clearCallbacks() | ||
|
||
This unregisters all callbacks for all events. If you want to just clear one or more events, just pass the list of :class:`NOTIFICATION` events you wish to clear.:: | ||
This unregisters all callbacks for all new events. If you want to just clear one or more events, just pass the list of :class:`NOTIFICATION` events you wish to clear.:: | ||
|
||
notepad.clearCallbacks([NOTIFICATION.FILEBEFORESAVE, NOTIFICATION.FILESAVED]) | ||
|
||
*Note that if you want to clear the callback for just one event, you still need to pass a list (i.e. surrounded with square brackets)* | ||
|
||
To unregister a callback for a particular function, just pass the function.:: | ||
To unregister all callback for a particular function, just pass the function:: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A callback seems to be more accurate because Notepad has no asynchronous callbacks, which means that there is always one callback to this function at any time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only one callback will be at work/active at a particular time, yes. notepad.callback(myfun, [NOTIFICATION.EVENTTYPE1])
notepad.callback(myfun, [NOTIFICATION.EVENTTYPE2])
# notepad.clearCallbacks(myfun, [NOTIFICATION.EVENTTYPE1] ) # would only clear the callback for this event type ,1
notepad.callback(myfun) # will clear both callbacks There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or do we say instead that in lines 1 & 2 above only 1 callback has been registered ? (It's a matter of what's usual terminlogoly / language usage here; you know better than me ) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be honest I never thought about it but you are right, is that two callbacks now or still one? @chcg what do you think? And if we differentiate it between "all callbacks" for the editor object and just "a callback" for the notepad object, that confuses people too. |
||
|
||
notepad.clearCallbacks(addSaveStamp) | ||
|
||
|
||
To unregister a callback for a particular function, for particular events (perhaps you want to keep the function registered for FILEBEFORESAVE, but don't want FILESAVED anymore) | ||
To unregister a callback for a particular function, for particular events (perhaps you want to keep the function registered for FILEBEFORESAVE, but not for FILESAVED anymore):: | ||
|
||
notepad.clearCallbacks(addSaveStamp, [NOTIFICATION.FILESAVED]) | ||
|
||
*Note that redefining the function (in this case ``addSaveStamp``) will mean that this method no longer works, as the function name is now a new object.* | ||
*Note that redefining the function (in this case ``addSaveStamp``) will mean that this method, or the one before it, no longer works, as the function name is now a new object. Same problem if you re-run the script registering the callback several times: calling ``notepad.clearCallbacks(addSaveStamp)`` or ``notepad.clearCallbacks(addSaveStamp, [NOTIFICATION.FILESAVED])`` will only clear the most recently added callback. If these situations occur, you can use one of the other 2 forms of the ``clearCallbacks`` function * | ||
|
||
|
||
The Callback smallprint | ||
Synchronous and Asynchronous Callbacks | ||
----------------------- | ||
|
||
Due to the nature of Scintilla events, they are by default processed internally slightly differently to Notepad++ events. | ||
Notepad++ events are always processed *synchronously*, i.e. your event handler finishes before Python Script lets | ||
Notepad++ continue. Scintilla events are placed in a queue, and your event handlers are called as the queue is *asynchronously* processed | ||
- i.e. the event completes before your event handler is complete (or potentially before your event handler is even called). | ||
By default, Notepad++ events and Scintilla events are, by default, processed internally slightly differently. | ||
Notepad++ events are always processed *synchronously* ("in sync", in step) relative to Notepad++ : your event handler finishes before Python Script lets Notepad++ continue with creating and processing other events. Thus, Notepad++ will appear unresponsive to a new user action for the (usually very short) period until the handler has finished processing current event. | ||
The following script demostrates this:: | ||
|
||
The only visible difference is that if you have a lot of callbacks registered, or your callbacks perform a lot of work, you might receive | ||
the event some time after it has actually occurred. In normal circumstances the time delay is so small it doesn't matter, but you may | ||
console.clear() | ||
import time | ||
|
||
starttime=time.time() | ||
|
||
def on_buffer_activated(args): | ||
print("on_buffer_activated") | ||
print((time.time()-starttime)//1) , | ||
print(" ") , | ||
time.sleep(4) | ||
print((time.time()-starttime)//1) | ||
|
||
notepad.callback(on_buffer_activated, [NOTIFICATION.BUFFERACTIVATED]) | ||
|
||
time.sleep(20) | ||
|
||
notepad.clearCallbacks() | ||
|
||
print("\nExperiment is over.") | ||
|
||
|
||
In case of Scintilla events, when you use ``editor.callback(..)`` to register callbacks for them, their notifications are placed in a queue that is processed *asynchronously* relative to Notepad++ app. This means that while your event handler on one particular notification in the queue, Notepad++ does not wait for the handler to finish before accepting and responding to other user events. As a result, a particular event may happen a long time before your event handler finishes processing that event (notification) (or potentially before your event handler is even called). | ||
|
||
In normal circumstances the time delay is so small it doesn't matter, but you may | ||
need to be aware of it if you're doing something time-sensitive. | ||
The script below demonstrates asynchronous processing where the delay is deliberately exaggerated:: | ||
|
||
console.clear() | ||
import time | ||
|
||
starttime=time.time() | ||
|
||
def on_update_ui(args): | ||
print("on_update_ui") | ||
print((time.time()-starttime)//1) , | ||
print(" ") , | ||
time.sleep(4) | ||
print((time.time()-starttime)//1) | ||
|
||
editor.callback(on_update_ui, [SCINTILLANOTIFICATION.UPDATEUI]) | ||
|
||
time.sleep(20) | ||
|
||
editor.clearCallbacks() | ||
|
||
print("\nExperiment is over.") | ||
|
||
If you tried sufficiently many actions during its run (clicks in text or menu, selections etc), then you would notice that after the script finished, thus the callback unregistered, the console is still outputing print-out messages from the handler. That is because the event handler was STILL processing some past events left on the queue. The ``clearCallbacks(...)`` functions only disable the handler for NEW events (not yet on the queue). | ||
|
||
One other reason to be aware of the asynchronous nature of default Scintilla callbacks (besides potential lag in time relative to actual events) is that both your event handler in PythonScript and Notepad++ can access the same variable/state (from different threads), which could lead to unexpected behavior if you are not careful. | ||
|
||
However, as of version 1.0, you can use :meth:`Editor.callbackSync` to add a synchronous callback for Scintilla events. This allows you to perform time-sensitive operations in an event handler. In particular, it allows for calling :meth:`Editor.autoCCancel` in a ``SCINTILLANOTIFICATION.AUTOCSELECTION`` notification to cancel the auto-complete. | ||
|
||
As of version 1.0, you can use :meth:`Editor.callbackSync` to add a synchronous callback. This allows you to perform time-sensitive | ||
operations in an event handler. It also allows for calling :meth:`Editor.autoCCancel` in a ``SCINTILLANOTIFICATION.AUTOCSELECTION`` | ||
notification to cancel the auto-complete. Note that there are certain calls which cannot be made in a synchronous callback - | ||
:meth:`Editor.findText`, :meth:`Editor.searchInTarget` and :meth:`Editor.setDocPointer` are notable examples. :meth:`Notepad.createScintilla` | ||
and :meth:`Notepad.destroyScintilla` are other examples in the ``Notepad`` object - note that this only applies to Scintilla (``Editor``) callbacks, | ||
``Notepad`` callbacks can perform any operation. | ||
Note that there are certain calls which cannot be made in a *synchronous* Scintilla (``Editor``) callback - :meth:`Editor.findText`, :meth:`Editor.searchInTarget` and :meth:`Editor.setDocPointer` are notable examples. | ||
:meth:`Notepad.createScintilla` and :meth:`Notepad.destroyScintilla` are other examples in the ``Notepad`` object. | ||
``Notepad`` callbacks do not have such restrictions. | ||
|
||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,13 +78,6 @@ Notepad++ Object | |
|
||
Unregisters all callbacks | ||
|
||
.. method:: notepad.clearCallbacks(function) | ||
|
||
Unregisters all callbacks for the given function. Note that this uses the actual function object, so if the function has | ||
been redefined since it was registered, this will fail. If this has happened, use one of the other ``clearCallbacks()`` | ||
functions. | ||
|
||
|
||
|
||
.. method:: notepad.clearCallbacks(eventsList) | ||
|
||
|
@@ -94,9 +87,14 @@ Notepad++ Object | |
|
||
See :class:`NOTIFICATION` | ||
|
||
|
||
.. method:: notepad.clearCallbacks(function) | ||
|
||
Unregisters all callbacks for the given function. Note that this uses the actual function object, so if the function has been redefined since it was registered, or if the script doing the registration of the callback has been re-run, then ``clearCallbacks(function)`` will fail. In such cases, use one of the other ``clearCallbacks`` functions. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above, |
||
|
||
.. method:: notepad.clearCallbacks(function, eventsList) | ||
|
||
Unregisters the callback for the given callback function for the list of events. | ||
Unregisters all callbacks for the given callback function for the list of events. Same note as for the previous variant applies. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above, ... |
||
|
||
|
||
.. method:: notepad.close() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assumes that there is a
Line Operations
menu item that has aRemove Empty Lines
submenu item, which is not the case. Insteadnotepad.menuCommand(MENUCOMMAND.EDIT_REMOVEEMPTYLINES)
should be used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ekopalypse , I double-checked that that menu item I used IS present in Npp 8.5.6 (same as in 8.5.4) by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, it may be absent with other languages than English. So then I can add there note to use your command if user's menu language is not English.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The runMenuCommand method expects a main menu item like "File, Edit, Search, View ..." as first parameter and a subitem that is directly a child of this main menu item.
Does the call you mentioned actually work for you? With PS3 it doesn't and to be honest, I'd be surprised if it worked with PS2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it does! (PS2). The old example there, with ("TextFX Tools", "Delete Blank Lines"), didn't work because THAT was not present in the menu at all.
I have verified each of the examples in the doc.
How else do you think I would have dared to make doc changes ? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it again after changing the localization to a different language (ex, Romanian) and the command strings in that language, respectively, . At first it didn't work, then fixed it with putting
# coding=utf-8
at the top of the script.
So maybe that was the issue in your case?
Or, maybe you can try setting the 3rd parameter,
refreshCache
, toTrue
, as explained in the doc.I suspect why it works (with PS2, at least) is that 1st argument is searched not only in the main-menu entries (file, edit etc), but also sub-entries, as listed in the language.xml files.
In any case...
The doc says:
because of efficiency reasons, I understand.
So I'll replace with
notepad.menuCommand(MENUCOMMAND.EDIT_REMOVEEMPTYLINES)
, as you suggested.