Skip to content

Commit 4a862d2

Browse files
committed
Documentation changes for replace and callbacks
1 parent 3b9bf15 commit 4a862d2

2 files changed

Lines changed: 184 additions & 71 deletions

File tree

docs/source/intro.rst

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ as both sides are talking the same language. However, there are a few things to
7070
what you want to do. One important point is to make sure your script is saved in the same encoding as your target file(s) - this helps unicode strings
7171
be interpreted the same way.
7272

73-
If you need to work with the string (for instance chance the case), you need to convert the string to a Python Unicode string. To convert the string
73+
If you need to work with the string (for instance change the case), you need to convert the string to a Python Unicode string. To convert the string
7474
append ``.decode('utf8')`` to the string. Obviously if your string is in a different format, use the name of the correct encoding.
7575

7676
To put text back to Scintilla (so editor.something()), use .encode('utf8') from a unicode string.
@@ -201,16 +201,22 @@ To unregister a callback for a particular function, for particular events (perha
201201
The Callback smallprint
202202
-----------------------
203203

204-
Due to the nature of Scintilla events, they are processed internally slightly differently to Notepad++ events.
204+
Due to the nature of Scintilla events, they are by default processed internally slightly differently to Notepad++ events.
205205
Notepad++ events are always processed *sychronously*, i.e. your event handler finishes before Python Script lets
206-
Notepad++ continue. Scintilla events are placed in a queue, and your event handlers process the queue (this happens
207-
automatically, you don't need to do anything different) - the only difference is that if you have a lot of callbacks registered,
208-
you might receive the event some time after it has actually occurred. In normal circumstances the time delay is so small it
209-
doesn't matter, but you may need to be aware of it if you're doing something time-sensitive.
210-
211-
This does mean that cancelling the SCN_AUTOCSELECTION notification is not possible (as the SCI_AUTOCCANCEL message must be sent
212-
before the notification returns). I'm open to suggestions for this, but processing the notifications sychronously would mean
213-
(slightly) reduced performance of "normal" code, added complication and more risk of obscure threading bugs.
206+
Notepad++ continue. Scintilla events are placed in a queue, and your event handlers are called as the queue is *asynchronously* processed
207+
- i.e. the event completes before your event handler is complete (or potentially before your event handler is even called).
208+
209+
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
210+
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
211+
need to be aware of it if you're doing something time-sensitive.
212+
213+
As of version 1.0, you can use :method:`Editor.callbackSync` to add a synchronous callback. This allows you to perform time-sensitive
214+
operations in an event handler. It also allows for calling :method:`Editor.autoCCancel` in a ``SCINTILLANOTIFICATION.AUTOCSELECTION``
215+
notification to cancel the auto-complete. Note that there are certain calls which cannot be made in a synchronous callback -
216+
:method:`Editor.findText`, :method:`Editor.searchInTarget` and :method:`Editor.setDocPointer` are notable examples. :method:`Notepad.createScintilla`
217+
and :method:`Notepad.destroyScintilla` are other examples in the ``Notepad`` object - note that this only applies to Scintilla (``Editor``) callbacks,
218+
``Notepad`` callbacks can perform any operation.
219+
214220

215221

216222
.. _Python: http://www.python.org/

docs/source/scintilla.rst

Lines changed: 168 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4226,95 +4226,202 @@ Helper Methods
42264226

42274227
Clears the callback for the given callback function for the list of events
42284228

4229+
.. method:: Editor.callback(function, eventsList)
42294230

4230-
.. method:: Editor.replace(search, replace)
4231+
Adds a handler for an ``Editor`` (Scintilla) event. The events list is a list of events to respond to, from the :class:`SCINTILLANOTIFICATION` enum.
4232+
Documentation on notifications from Scintilla can be found here: http://www.scintilla.org/ScintillaDoc.html#Notifications
42314233

4232-
Simple search and replace.
4234+
For a simple example, here's a script that automatically saves the document as soon as a change is made::
42334235

4236+
def saveCurrentDoc(args):
4237+
notepad.save()
4238+
4239+
editor.callback(saveCurrentDoc, [SCINTILLANOTIFICATION.SAVEPOINTLEFT])
42344240

4235-
.. method:: Editor.rereplace(regex, replace)
4241+
This script is not really sensible in real life, as for large files, it will take some time to save the file, and it will be saved after every key press.
42364242

4237-
Simple regular expression search and replace (using Notepad++/Scintilla regular expressions).
4238-
4243+
Note that ``Editor`` callbacks are processed *asynchronously* by default. What this means in practice is that your event handler function
4244+
(saveCurrentDoc in the previous example) is called just after the event has fired. If the callback handler is slow, and the callbacks occur quickly, you
4245+
could get "behind". Callbacks are placed in a queue and processed in the order they arrived. If you need to do something before letting the user continue, you
4246+
can use :method:`Editor.callbackSync`, which adds a synchronous callback.
42394247

4240-
.. method:: Editor.pyreplace(search, replace[, count[, flags[, startLine[, endLine]]]])
42414248

4242-
Python regular expression search and replace. Full support for Python regular expressions.
4243-
Works line-by-line, so does not require significant memory overhead, however multiline regular expressions won't work (see pymlreplace_).
4249+
.. method:: Editor.callbackSync(function, eventsList)
4250+
4251+
Adds a *synchronous* handler for an ``Editor`` (Scintilla) event. The events list is a list of events to respond to, from the :class:`SCINTILLANOTIFICATION` enum.
4252+
4253+
What this means is that the handler function is called, and must complete, before control is returned to the user. If you perform a slow operation in your handler
4254+
function, this will have an effect on the speed of Notepad++ for the user (i.e. Notepad++ may appear to have "locked up", whilst your event processes).
4255+
4256+
Synchronous callbacks are mostly used for calling :method:`Editor.autoCCancel` in response to :class:`SCINTILLANOTIFICATION.AUTOCSELECTION`, but could be used for
4257+
anything where the timing of the handler function is critical.
4258+
4259+
4260+
.. method:: Editor.replace(search, replace[, flags[, startPosition[, endPosition[, maxCount]]]])
4261+
4262+
See :method:`Editor.rereplace`, as this method is identical, with the exception that the search string is treated literally,
4263+
and not as a regular expression.
4264+
4265+
If you use a function as the replace argument, the function will still receive a ``re.MatchObject`` like object as the parameter,
4266+
``group(0)`` will therefore always contain the string searched for (possibly in a different case if ``re.IGNORECASE`` was passed in the flags)
4267+
4268+
For example::
4269+
42444270
4271+
counter = 0
4272+
4273+
def get_counter(m):
4274+
global counter
4275+
counter += 1
4276+
return 'C' + str(counter)
4277+
4278+
editor.replace('(x)', get_counter, re.IGNORECASE)
4279+
4280+
# Replacing:
4281+
#
4282+
# This (x) is some (X) text. The bracketed X's will (x) get numerical replacements
4283+
#
4284+
# results in
4285+
#
4286+
# This C1 is some C2 text. The bracketed X's will C3 get numerical replacements
4287+
4288+
4289+
4290+
4291+
.. method:: Editor.rereplace(search, replace[, flags[, startPosition[, endPosition[, maxCount]]]])
4292+
4293+
The main search and replace method, using regular expressions. The regular expression syntax in use is
4294+
that from Notepad++, which is actually the `Boost::Regex <http://www.boost.org/doc/libs/1_55_0/libs/regex/doc/html/index.html>`
4295+
implementation (specifically the Perl regular expression syntax).
4296+
4297+
42454298
``flags`` are from the re module (e.g. ``re.IGNORECASE``), so ``import re`` if you use the flags.
4246-
A maximum of ``count`` replacements are made, if zero or not given, then all replacements are made.
4247-
4248-
``startLine`` and ``endLine`` are optional, and if provided refer the first and last (zero indexed)
4249-
lines to perform the replace on.
42504299

4251-
*Note that as ``$`` only matches ``\n``, for Windows line ending files (i.e. ``\r\n``) if you want to match
4252-
the end of the line, you need to use ``\r$`` - see note about ``Editor.INCLUDELINEENDINGS``*
4253-
4254-
As each line is searched individually, if you want to remove the line breaks, for instance to join lines together,
4255-
you need to add a flag ``Editor.INCLUDELINEENDINGS``. This includes the line endings in the search string, and also
4256-
in the replacement.
4257-
4258-
To just add a star (*) to the end of every line, you'd just use::
4300+
The ``re.MULTILINE`` flag is automatically set, so ``^`` matches the start of each line of the document, and
4301+
``$`` the end of each line. If you want to ``^`` and ``$`` to match the start and end of the whole document,
4302+
you can override the behaviour by adding in the ``editor.WHOLEDOC`` flag.
4303+
4304+
Note that line endings are now handled automatically.
4305+
4306+
``search`` can be a string, a unicode string, or an object. An object will be converted to a string using it's __str__ method.
4307+
For a unicode string, the current document encoding is checked, and an attempt is made at a conversion. If the conversion cannot be
4308+
successfully performed, an error occurs. When a standard Python string is used, no conversion takes place. If you need to replace
4309+
strings in documents in both UTF-8 and ANSI (or other single byte encodings), then it's best to pass unicode strings.
4310+
4311+
``replace`` follows the same conversion rules as ``search``. However, you can also pass a function or lambda expression
4312+
as the ``replace`` parameter. This function receives a single parameter, which is an object resembling a re.MatchObject instance.
4313+
It only resembles an re.MatchObject because it doesn't support all the methods. Specifically, ``groupdict()``, ``pos``, ``endpos``, ``re`` and ``string``
4314+
methods and properties are not supported. ``expand()``, ``group()`` and ``groups()`` (for example) all work identically. The function should
4315+
return the string to use as the replacement.
4316+
4317+
A simple function replacement::
4318+
4319+
def add_1(m):
4320+
return 'Y' + str(number(m.group(1)) + 1)
4321+
4322+
# replace X followed by numbers by an incremented number
4323+
# e.g. X56 X39 X999
4324+
# becomes
4325+
# Y57 Y40 Y1000
4326+
4327+
editor.rereplace('X([0-9]+)', add_1);
4328+
4329+
``startPosition`` is the binary position to start the search. Use :method:`Editor.positionFromLine`
4330+
to get the binary position from the (zero indexed) line number.
4331+
4332+
``endPosition`` is the binary position to end the search. Use :method:`Editor.positionFromLine`
4333+
to get the binary position from the (zero indexed) line number.
4334+
4335+
A maximum of ``count`` replacements are made, if zero or None, then all replacements are made.
42594336

4260-
editor.pyreplace("$", "*")
42614337

4262-
However, to join lines together, and put a pipe character (|) in between each one::
4338+
An small point to note, is that the replacements are first searched, and then all replacements are made.
4339+
This is done for performance and reliability reasons. Generally this will have no side effects, however there
4340+
may be cases where it makes a difference. (Author's note: If you have such a case, please post a note on the forums
4341+
such that it can be added to the documentation, or corrected).
4342+
4343+
.. method:: Editor.research(search, matchFunction[, flags[, startPosition[, endPosition[, maxCount]]]])
4344+
4345+
The main search method, using regular expressions. The regular expression syntax in use is
4346+
that from Notepad++, which is actually the `Boost::Regex <http://www.boost.org/doc/libs/1_55_0/libs/regex/doc/html/index.html>`
4347+
implementation (specifically the Perl regular expression syntax).
4348+
4349+
``flags`` are from the re module (e.g. ``re.IGNORECASE``), so ``import re`` if you use the flags.
42634350

4264-
editor.pyreplace(r"\r$\n", "|", 0, Editor.INCLUDELINEENDINGS)
4265-
4266-
Would result in::
4351+
The ``re.MULTILINE`` flag is automatically set, so ``^`` matches the start of each line of the document, and
4352+
``$`` the end of each line. If you want to ``^`` and ``$`` to match the start and end of the whole document,
4353+
you can override the behaviour by adding in the ``editor.WHOLEDOC`` flag.
4354+
4355+
Note that line endings are now handled automatically.
4356+
4357+
``search`` can be a string, a unicode string, or an object. An object will be converted to a string using it's __str__ method.
4358+
For a unicode string, the current document encoding is checked, and an attempt is made at a conversion. If the conversion cannot be
4359+
successfully performed, an error occurs. When a standard Python string is used, no conversion takes place. If you need to replace
4360+
strings in documents in both UTF-8 and ANSI (or other single byte encodings), then it's best to pass unicode strings.
4361+
4362+
``matchFunction`` is a function that gets callled with each match. This function receives a single parameter, which is an object resembling a re.MatchObject instance.
4363+
It only resembles an re.MatchObject because it doesn't support all the methods. Specifically, ``groupdict()``, ``pos``, ``endpos``, ``re`` and ``string``
4364+
methods and properties are not supported. ``expand()``, ``group()`` and ``groups()`` (for example) all work identically. The function should
4365+
return the string to use as the replacement.
4366+
4367+
A simple function replacement::
4368+
4369+
matches = []
4370+
def match_found(m):
4371+
# append the match (start, end) positions to the matches array
4372+
matches.append(m.span(0))
4373+
4374+
# find X followed by numbers
4375+
# e.g. X56 X39 X999
4376+
4377+
editor.research('X([0-9]+)', match_found)
4378+
4379+
You can do the same thing with a lambda expression::
4380+
4381+
matches = []
4382+
4383+
editor.research('X([0-9]+)', lambda m: matches.append(m.span(0)))
4384+
4385+
4386+
4387+
4388+
``startPosition`` is the binary position to start the search. Use :method:`Editor.positionFromLine`
4389+
to get the binary position from the (zero indexed) line number.
4390+
4391+
``endPosition`` is the binary position to end the search. Use :method:`Editor.positionFromLine`
4392+
to get the binary position from the (zero indexed) line number.
4393+
4394+
If ``maxCount`` is not zero or None, then the search stops as soon as ``maxCount`` matches have been found.
4395+
42674396

4268-
this is line 1
4269-
this is line 2
4270-
this is line 3
4271-
4272-
Becoming::
4397+
.. method:: Editor.pyreplace(search, replace[, count[, flags[, startLine[, endLine]]]])
4398+
4399+
This method has been removed from version 1.0. It was last present in version 0.9.2.0
4400+
4401+
You should use :method:`Editor.rereplace`.
4402+
42734403

4274-
this is line 1|this is line 2|this is line 3
42754404

42764405

42774406
.. _pymlreplace:
42784407
.. method:: Editor.pymlreplace(search, replace[, count[, flags[, startPosition[, endPosition]]]])
42794408

4280-
Python Multiline regular expression search and replace - works for multiline regular expressions,
4281-
but makes at least 2 copies of the entire document, so is unsuitable for large documents.
4282-
Note that re.MULTILINE is specified in the flags automatically.
4283-
4284-
``flags`` are from the re module (e.g. ``re.IGNORECASE``), so ``import re`` if you use the flags.
4285-
A maximum of ``count`` replacements are made, if zero or not given, then all replacements are made.
4286-
4287-
``startPosition`` and ``endPosition`` are optional, if provided they refer to the section of text,
4288-
in offset bytes, to perform the search and replace in.
4289-
4290-
*Note that as ``$`` only matches ``\n``, for Windows line ending files (i.e. ``\r\n``) if you want to match
4291-
the end of the line, you need to use ``\r$``*
4409+
This method has been removed from version 1.0. It was last present in version 0.9.2.0
42924410

4411+
You should use :method:`Editor.rereplace`.
42934412

4413+
42944414
.. method:: Editor.pysearch(expression, function[, flags[, startLine[, endLine]]])
42954415

4296-
Python regular expression search, calling a function for each match found.
4297-
The function gets called with the (zero indexed) line number, and the match object.
4298-
4299-
``startLine`` and ``endLine`` are optional, and if provided refer the first and last (zero indexed)
4300-
lines to perform the replace on.
4301-
4302-
*Note that the match object start and end refer to the positions **within the line** and not the entire document.
4303-
Use :ref:`editor.positionFromLine` to find the location in the document*
4416+
This method has been removed from version 1.0. It was last present in version 0.9.2.0
43044417

4418+
You should use :method:`Editor.research`.
43054419

4306-
.. method:: Editor.pymlsearch(expression, function[, flags[, startPosition[, endPosition]]])
43074420

4308-
Python multiline regular expression search, calling a function for each match found.
4309-
The function gets called with the (zero indexed) line number, and the match object.
4310-
4311-
Note that this runs the search on the entire text, and therefore makes at least 2 copies of the entire document,
4312-
therefore it may not be suitable for large documents.
4421+
.. method:: Editor.pymlsearch(expression, function[, flags[, startPosition[, endPosition]]])
43134422

4314-
``startPosition`` and ``endPosition`` are optional, if provided they refer to the section of text,
4315-
in offset bytes, to perform the search in.
4316-
4317-
*Note that the match object start and end refer to the positions within the entire document, unlike :ref:`editor.pysearch`*
4423+
This method has been removed from version 1.0. It was last present in version 0.9.2.0
43184424

4425+
You should use :method:`Editor.research`.
43194426

43204427

0 commit comments

Comments
 (0)