Skip to content
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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

victorel-petrovich
Copy link

@victorel-petrovich victorel-petrovich commented Jul 31, 2023

Hey, first time making a PR on this repo.
The description of changes are in comments of individual commits, as they are long descriptions.

If it seems too much to merge or review, let me know and I could break so that each commit will be a separate PR.

…ode Text

#### in Introduction:
* Removed the line with `editor.pyreplace(...)`: it produces error `AttributeError: 'Editor' object has no attribute 'pyreplace' ` and is not present in PythonScript/doc/scintilla.html
* Corrected the `notepad.runMenuCommand(....)` to use an example of menu items that actually exist in current menu (N++ 8.5.4)
#### in Working with Unicode Text
* Corrected missing 1st argument for `editor.replaceWholeLine`
* For whole script, added a 1st line with encoding
`# -*- coding: utf-8 -*-`
 otherwise, get error :
```
SyntaxError: Non-ASCII character '\xc3' in file C:\Users\Vic\AppData\npp.8.5.4.portable\plugins\Config\PythonScript\scripts\test.py on line 5, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
```
So I followed that link and added that encoding comment line.

NOTE: needs verification, because when seen with Preview on github, the `*`-s in that line are interpreted as emphasis, instead as showing as-is in the code section
- typos: "and" to "a", "an" to "a"
- explain abreviation: CHM (context help manual)
@victorel-petrovich victorel-petrovich changed the title Update intro.rst : Introduction and Working with Unicode Text Update usage.rst and intro.rst Jul 31, 2023
- Explain what's "callback"
- minor typos/grammar things
- explain that for a script that defines a callback, run it only once. If run the script N times, then N registrations will occur: once the event occurs, then N times the callback function will be called
- minor typos/grammar things
- minor improvement in clarity
- mention callbacks stay active till you close Notepad++ or "cancel" them
- mention problem & solution when canceling callbacks that where registered several times, by running the defining script several times.

Explanation of the last point: run following script 2 times:
```
import datetime

def addSaveStamp(args):
    editor.appendText("File saved on %s\r\n" % datetime.date.today())

notepad.callback(addSaveStamp, [NOTIFICATION.FILEBEFORESAVE])
```
As a result, when save current file, you'll see 2 timestaps appended. 
Now run one of the next scripts 1 time: 
this 
```
notepad.clearCallbacks(addSaveStamp)
```
or this:
```
notepad.clearCallbacks(addSaveStamp, [NOTIFICATION.FILEBEFORESAVE])
```

You will see that when save current file, you'll still see 1 timespamp appended. (While if you command were to be just `notepad.clearCallbacks()`, then there would be no timestamps. )
1. reorder so that `clearCallbacks()` and `clearCallbacks(eventsList)` come before the other 2 variants, because these 2 don't have the issue of function-object being different after redefinition
2. for: `clearCallbacks(function)`, expand the description to say that it can also fail when the script doing the registration of the callback has been re-run.
3. for: `clearCallbacks(function)`, mention: "Same note as for the previous variant applies."

Explanation of 2): run following script 2 times:
```
import datetime

def addSaveStamp(args):
    editor.appendText("File saved on %s\r\n" % datetime.date.today())

notepad.callback(addSaveStamp, [NOTIFICATION.FILEBEFORESAVE])
```
As a result, when save current file, you'll see 2 timestaps appended. 
Now run one of the next scripts 1 time: 
this:
```
notepad.clearCallbacks(addSaveStamp)
```
or this:
```
notepad.clearCallbacks(addSaveStamp, [NOTIFICATION.FILEBEFORESAVE])
```

You will see that when save current file, you'll still see 1 timespamp appended. (While if you command were to be just `notepad.clearCallbacks()` or `clearCallbacks([NOTIFICATION.FILEBEFORESAVE])`, then there would be no timestamps. )
- replaced `console.write(x)`, where x - integer, `console.write(str(x))` 
  - as otherwise get error (TypeError: object of type 'int' has no len()), because console.write only works with strings, 
  - By the way, why not just use `print()` everywhere in this Python Primer? Easier to type and to the point of this primer. 
- append an "\n" in every string for `console.write`, for clarity of output
- a few typos
- added exception to using `\` in at the end of raw string
@victorel-petrovich victorel-petrovich changed the title Update usage.rst and intro.rst Update usage.rst , intro.rst , pythonprimer.rst Jul 31, 2023
A re-write of the sub-section "The callback smallprint". 
The original was very terse and hard to undertand for people who don't have a computer science degree (or  experience with events processing). See bruderstein#298

It improves the explanations and also adds 2 example scripts. 
Since it got a bit bigger, I changed the title as well to match the content: "Synchronous and asynchronous callbacks".
@victorel-petrovich victorel-petrovich changed the title Update usage.rst , intro.rst , pythonprimer.rst Update Doc: usage.rst , intro.rst , pythonprimer.rst Aug 1, 2023
In the subsection synch/async, added the note:
> 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. 

And in the subsection Introduction, added a warning on exit() and related crashing Notepad++ and suggesting alternatives. 
I considered 2 other places to put this note :
doc/usage.html#usage - not suitable as here don't yet talk about content of scripts
doc/pythonprimer.html - not good b/c advanced Python users will probably skip it and thus miss the warning.
Added a new subsection, "Scope", explaining local vs global scope in simple terms, and recommending a simple local-scope structure (def main()... main) to avoid unwanted inter-script interaction.

notepad.runMenuCommand("TextFX Tools", "Delete Blank Lines")

notepad.runMenuCommand("Line Operations", "Remove Empty Lines")
Copy link
Contributor

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 a Remove Empty Lines submenu item, which is not the case. Instead notepad.menuCommand(MENUCOMMAND.EDIT_REMOVEEMPTYLINES) should be used.

Copy link
Author

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.

Copy link
Author

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.

Copy link
Contributor

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the call you mentioned actually work for you?

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 ? :)

Copy link
Author

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, to True, 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:

For built-in menus use notepad.menuCommand(), for non built-in menus (e.g. TextFX and macros you’ve defined), use notepad.runMenuCommand(menuName, menuOption).

because of efficiency reasons, I understand.

So I'll replace with notepad.menuCommand(MENUCOMMAND.EDIT_REMOVEEMPTYLINES), as you suggested.


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
========
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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 so, will the new PS version be already with Python 3?

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.

Copy link
Contributor

Choose a reason for hiding this comment

The 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).
As far as I know, there is currently only one issue that prevents the project from deprecating the PS2 version, and that is exactly the discussion point of the example. Strings.
In Python2, a string is an array of bytes, while in Python3, a string is now a Unicode object. In Python3, a string has only one encode method to convert it to a byte array, and the bytes object knows only one decode method to create a Unicode object.
I haven't checked if other parts of the document need to be updated as well, but right now I can only think of the "print" function as a possible stumbling block.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked if other parts of the document need to be updated as well, but right now I can only think of the "print" function as a possible stumbling block.

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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or let @chcg weigh in


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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe using current BufferID instead of specific is even more accurate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do that. (together with all modifications from your suggestions).


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::
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Author

Choose a reason for hiding this comment

The 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.
But there may be several callbacks registered: for same function, but for different event types.
Like in:

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 

Copy link
Author

Choose a reason for hiding this comment

The 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 )

Copy link
Contributor

Choose a reason for hiding this comment

The 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.


.. 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above, Unregisters a callback seems more accurate to me.

.. 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above, ...

@@ -101,7 +104,37 @@ To pass the function to another function (as in callbacks in Python Script), jus
Line 11 calls the ``callFunc`` function, passing the printNameAge function to it. The ``callFunc`` function then calls printNameAge on line 8.


Scope
-----

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also a possibility to create scripts that are used by other scripts by importing them.
In such a case, a module scope has now been introduced.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean that with module scope was not possible so far (with older versions) in PS?

I would still choose to only talk about local and global scopes there : as you can tell, this python primer was meant to be appealing even to non-python programmers, and give just the minimum needed to be able to create working scripts.

Additionally, I'm not familiar with module scope...

So if you insist, you can add that yourself later, in other PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No this has been possible since the start of PS.
If you create a script and run it directly, it is exactly as you described,
but if you create a.py script

x = 5

and a second script b.y, which is then executed

import a
# x is not globally known, instead it must be used as
print(a.x) # here is now the scope on module level
print(x) # throws an exception

But I agree, it might already go too deep for a primer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. I get that it's simply a script-level (file-level) scope, for all items defined in a.py, available via a.name in b.py .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants