TSE PRO v4.4 Undocumented Features

DocumentTSE_PRO_v4.4_Undocumented_Features.html
AuthorCarlo Hogeveen
OriginWebsite
Version0.50 - 6 Feb 2022

This text file describes some of the features of TSE Pro v4.4 which are not documented in TSE's built-in Help system.

A topic title here usually references a same-named topic title in TSE's built-in Help system. A topic only occurs here if there is additional or corrected information. In this document only a topic's additional or corrected information is described.

Note that after you select a topic in the index, then you can copy its URL from the browser's address bar to use it elsewhere as a link.

Index

#if, #ifdef, #ifndef, #ifexist, #ifnexist, #else
Abandoneditor
Appendix A: Technical Specifications
AskFilename
BackSpace
Binary Mode
case
ChangeCurrFilename
ChrSet
CmpiStr
Command-Line Options
config ... endconfig
constant
CopyBlock
CopyFile
CopyToWinClip
DateFormat
Debugging Macros
DelAnyChar
DelChar
DelLeftWord
DelLine
DelRightWord
DisplayMode
Down
Dos
DupLine
EditBuffer
EditThisFile
Endline
EndProcess
EquateEnhancedKbd
Exit
FFDateStr
FFTimeStr
File System
FileExists
FindThisFile
Flip
fOpen
for
Format
fRead
GetBufferStr
GetClockTicks
GetNextProfileItem
GetProfileStr
GetTime
GetToken
GotoLine
HiLiteFoundText
Hook
HookDisplay
isDigit
isFullScreen
in
#Include
InsertData
InsertFile
KeyDef
KeyPressed
KillLine
lDos
Left
LoadBuffer
LoadDir
Lower
lRepeatFind
LTrim
MacroStackAvail
MarkAll
MakeTempName
MAXLINELEN
menu
Message
MkDir
MoveFile
MsgBox
MsgBoxBuff
NextChar
NoOp
PageDown
PageUp
PopUndoMark
PressKey
PrevChar
PrevPosition
Process
PushKey
PushKeyStr
PushUndoMark
PutStrAttrXY
_quiet_
QuitFile
QuotePath
RemoveTrailingSlash
RepeatCmd
Right
RTrim
RollDown
RollLeft
RollRight
RollUp
SaveAllAndExit
SaveAs
SaveBlock
ScrollDown
ScrollLeft
ScrollRight
ScrollUp
Searching With Regular Expressions
SetBufferStr
SetUndoOn
Sort
SplitLine
StrCount
StrFind
StrReplace
String Slices
SyntaxHilite Mode
TabLeft
TabRight
TimeFormat
Transparency
Trim
Undo
UndoMode
UnhookDisplay
UnloadAllBuffers
UnloadBuffer
UnLockCurrentFile
Up
Upper
UserHiliteFoundText
Val
Warn
WhenPurged
WordLeft
WordRight
WordSet

#if, #ifdef, #ifndef, #ifexist, #ifnexist, #else

It is not possible to conditionally compile only part of a condition.

For example, compiling the following macro reports a syntax error.

  proc Main()
    if FALSE
  #ifdef WIN32
    or TRUE
  #endif
      Warn('Yes!')
    else
      Warn('No!')
    endif
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

Bug: It is not possible to use a non-existing predefined constant in an #else clause.

For example, this fails to compile:

  #if FALSE
  #else
    #define FUTURE_CONSTANT _FUTURE_VALUE_
  #endif

A work-around is to use two #if statements, one for each conditional branch.

Abandoneditor

This function does not call the _on_exit_called_ hook, but otherwise on the last file implicitly does an Exit() too.

Therefore also see: Process().

Appendix A: Technical Specifications

  "A maximum of 60 external Compiled Macro files may be loaded at any one time."
        
My tests say the actual number is 58.


  "Each external Compiled Macro file may have up to 63k of Compiled Macros."
        
But not always:

Each procedure ("proc") can in its compiled form not be larger than 32 kB. For a larger compiled procedure the compiler will give the error message:

  "Range of loop or goto > 32k, please reduce size"
        
For example, if a macro only consists of one "proc", then compiling the macro will report an error if the procedure is so large that the resulting .mac file would be larger than 32 kB, even though normally .mac files may be 63kB in size.

AskFilename

White space means a sequence of space and horizontal tab characters.

BackSpace

This function has an optional integer parameter that indicates how many times to perform the action.

Binary Mode

Some not explicitly documented behaviour of binary mode.

When loading a file in normal mode, TSE strips line ending characters like the carriage return (CR) and line feed (LF) characters from a text before showing it to us.

When loading a file in binary mode it does not do that stripping and treats the carriage return and line feed characters as any other character.

When you load a file in binary mode, for example by opening it with "-b<N> <filename>" where <N> is a positive number, then editor's shown lines for that file will have a fixed length <N> that will have have nothing to do with the text's actual lines: Line ending characters like a carriage return and line feed will explicitly be shown as characters in the text. (But see Note 2.)

Undocumented feature:
If you load a file with binary mode <N> is -3, then the file is loaded like with a positive <N>, but with each line's actual length, including its actual line ending characters like carriage returns and line feeds. Practical!

Note 1:
So binary mode -3 is like the binary modes <positive number>, but handier, and unlike binary modes -1 and -2, which do something completely different.

Note 2:
In the GUI version of TSE with an ANSI compatible font any control characters, like the line ending characters carriage return and line feed, are shown as spaces, which is not practical in binary mode. The TSE extension "ControlChars" from my website solves that by explicitly showing each control character as a different character, and by making control characters stand out for any TSE version and font.

Once you are editing a file in binary mode, you can lengthen and shorten an editor line by inserting characters up to TSE's maximum line length and by deleting characters down to an empty line. When you do not add or delete line ending characters this has no impact on the number of lines in the actual text.

You can even delete and insert empty editor lines without any impact on the file.

When saving a file in binary mode the editor's visual lines are ignored.

The saved file wil only have line endings where explicit carriage return and/or line feed characters occurred in the editor text.

TSE has a problem with searching in binary mode:
While TSE no longer implicitly sees text lines in binary mode, TSE's search commands are still limited to finding search results in buffer lines, so they will not find search results which are (arbitrarily!) split between two buffer lines.

case

The "when" clause of a "case" statement can not only have a list of values separated by commas and a range of values separated by "..", but also both at the same time.

For Example:

  case CurrChar()
    when 66..68, 70..72, 74..78, 80..84, 86..90
      Warn('This is an upper case consonant')
  endcase

The range of a "when" clause can also be a range of characters.

For example:

  case GetText(CurrPos(),1)
    when 'B'..'D', 'F'..'H', 'J'..'N', 'P'..'T', 'V'..'Z'
      Warn('This is an upper case consonant')
  endcase

ChangeCurrFilename

If ChangeCurrFilename() successfully changes only the case of a file name, then the file is not marked as modified.

For example, given an unchanged file "D:\helloworld.txt", changing its name to "D:\HelloWorld.txt" will not mark the file as modified.

ChrSet

The WordSet and ChrSet() documentation misses a reference to the ClearBit(), GetBit() and SetBit() functions.

CmpiStr

Its documentation states not wrongly that it returns a value < 0, == 0 or > 0, but actually it only returns the values -1, 0 or 1.

This enables us to execute the "expensive" CmpiStr() only once using a "case" statement.

For example:

  case CmpiStr(string1, string2)
    when -1     // Usually most common
      ...       // Handle ascending order
    when  1
      ...       // Hanlde descending order
    otherwise
      ...       // Handle same order
  endcase

Command-Line Options

The 2nd paragraph of this topic's documentation still states:

  "Command-line options are identified by either the "-" or "/" character,
  immediately followed by the letter that designates the desired option."
        
As of TSE Pro 4.4, only the "-" character is supported.

Tip for macro programmers:
Default you can start a macro from the command line, but not pass it any parameters other than files to be loaded simultaneously. By installing the TSE extension CmdLineParameter you can pass parameters too.

config ... endconfig

You cannot debug a macro that contains a config ... endconfig clause.

constant

A constant declaration may be made globally and in a procedure.

There is an undocumented way to initialize multiple constants in one statement. The actual syntax for "constant" is:

  constant identifier [= constant_expression]
        [, identifier [= constant_expression]] ...
The difference is, that the constant_expressions may be omitted.

If the constant_expression of the first identifier is omitted, then its value is 1.

If the constant_expression of any next identifier is omitted, then its value is that of its predecessor plus 1.

Examples:

  constant a = 1, b, c    // a ==   1, b ==  2, c ==  3.
  constant d, e = 10, f   // d ==   1, e == 10, f == 11.
  constant g = -10, h, i  // g == -10, h == -9, i == -8.
The purpose is to shortly give significant names to consecutive values.

Examples:

  constant January, February, March, April, May, June, July,
           August, September, October, November, December
  constant smaller_than = -1, equal_to, greater_than

CopyBlock

A test showed that CopyBlock() used 33% less memory than Copy() + Paste().

CopyFile

To overwrite the destination file the third parameter must be truthy (any value that a conditional statement would interpret as true).

For example, instead of TRUE we can also use the more readable constant _OVERWRITE_.

CopyToWinClip

The content of the Windows clipboard stops at a NULL character. In TSE that is the character represented by the single byte value 0. In the Windows clipboad text is default character encoded as UTF16-LE and then ended with two bytes with value 0.

This is not a TSE bug. It is a property of the Windows clipboard when it contains text.

This might become relevant when copying the contents of a binary file or a file with non-printable characters.

TSE's internal copy commands do not have this problem.

DateFormat

This setting determines the format of FFDateStr() too.

Debugging Macros

In these cases the macro debugger will not work:

DelAnyChar

This function has an optional integer parameter that indicates how many times to perform the action.

DelChar

This function has an optional integer parameter that indicates how many times to perform the action.

Despite what its name might suggest, DelChar() does not add the deleted character to the Deletion Buffer, so it cannot be undeleted, and so there is overhead involved.

DelLeftWord

This function has an optional integer parameter that indicates how many times to perform the action.

DelLine

This function has an optional integer parameter that indicates how many times to perform the action.

DelRightWord

This function has an optional integer parameter that indicates how many times to perform the action.

DisplayMode

Besides the documented parameters

  _DISPLAY_TEXT_
  _DISPLAY_HEX_
  _DISPLAY_PICKFILE_
it has these undocumented ones:
  _DISPLAY_FINDS_
  _DISPLAY_HELP_
  _DISPLAY_USER_

_DISPLAY_FINDS_

This parameter will colour the current file the same as TSE colours the results of a Find() with the "v" option: The current line will be coloured with MenuSelectLtrAttr if the line starts with "File: ", and MenuSelectAttr otherwise. Other lines will be coloured with MenuTextLtrAttr if the line starts with "File: ", and MenuTextAttr otherwise.

But: Browsing to a new line will not change colouring accordingly. Modifying a line will change that line's colouring to that of a current line but will not recolour other lines. It seems necessary to do a new DisplayMode(_DISPLAY_FINDS_) after each user action that therefore might require new colouring of the editing window.

_DISPLAY_HELP_

A valuable source for this section was Chris Antos' GetHelp macro from 2002. Any mistakes in the document you are now reading are mine.

Again, a Unicode warning: This document uses UTF-8 character encoding, which matters to the below text: Read it with a web browser, or install the Unicode macro from my website to read this text in TSE.

DisplayMode(_DISPLAY_HELP_) changes the display of the current file immensely:

The file is displayed with OEM characters, mainly to use their line drawing capability (ANSI does not support this). For example see the Help2txt macro's "to_utf8" proc for Unicode codes and descriptions which are the equivalents of typically used OEM character codes. For users of a GUI version of TSE: Temporarily switch to an OEM font like Terminal, start drawing lines or (partioned) boxes, and switch back to an ANSI font like Courier New to see what you have wrought character-wise.

Default the text is coloured with HelpTextAttr.

Text between the following tags is coloured accordingly.

Text between the tagsIs coloured with
®I¯ and ®/I¯ HelpItalicsAttr
®B¯ and ®/B¯ HelpBoldAttr (aka "Highlighted")
®L¯ and ®/L¯ HelpLinkAttr
®LI¯ and ®/L¯ HelpInfoAttr
®T¯ and ®/T¯ HelpTextAttr
®S¯ and ®/S¯ HelpTextAttr

The tags are not shown: Shown text is left-shifted to the actual text.

Tag colouring exception 1:
If the cursor is on the first character of the opening tag ®L¯ or ®LI¯, then the text after the tag is coloured with HelpSelectAttr.

Tag colouring exception 2:
Some tag's colouring can overrule another's. Text between the tags ®B¯®T¯ and ®/T¯®/B¯, and text between the tags ®B¯®S¯ and ®/S¯®/B¯ will in both cases colour the text with HelpBoldAttr.

Note that DisplayMode(_DISPLAY_HELP_) only changes how the text of the current file is coloured, nothing more. It has no notion of what the text between tags means, and it performs no actions of any kind. For example, in TSE's Help the tags are used to colour links, but it is TSE's internal Help that acts when you select a link: DisplayMode(_DISPLAY_HELP_) and its tags do not do that.

_DISPLAY_USER_

Intended for use with HookDisplay(). See HookDisplay().

Outside HookDisplay() _DISPLAY_USER_ seems to behave like _DISPLAY_TEXT_.

Down

This function has an optional integer parameter that indicates how many times to perform the action.

Dos

This function's flag parameter also has the flag _START_HIDDEN_, which will cause the command to invisibly start and run in a hidden Windows process.

In newer versions of TSE the _DONT_PROMPT_ and _DONT_CLEAR_ flags can be omitted when using the _START_HIDDEN_ flag, but that makes the macro not backwards compatible, so I advise against it.

This function also has a flag _DONT_WAIT_, which when combined with the _RUN_DETACHED_ flag will make the started command a parallel process.

For example,

  #define DOS_SYNC_CALL_FLAGS  _DONT_PROMPT_|_DONT_CLEAR_|_START_HIDDEN_|_RETURN_CODE_
  #define DOS_ASYNC_CALL_FLAGS _DONT_PROMPT_|_DONT_CLEAR_|_START_HIDDEN_|_RETURN_CODE_|_RUN_DETACHED_|_DONT_WAIT_
  if FALSE
    lDos(LoadDir(TRUE), '', DOS_ASYNC_CALL_FLAGS)
  else
    Dos(LoadDir(TRUE), DOS_ASYNC_CALL_FLAGS)
  endif
starts a second instance of the editor, and you can work in both instances of the editor simultaneously. Here lDos() is better, because Dos() also starts a useless blank window.

Without _RUN_DETACHED_ the Dos() command "eats" any unprocessed keys, making TSE ignore them. In tools that is usually not a problem, in extensions it ususally is.

DupLine

This function has an optional integer parameter that indicates how many times to perform the action.

EditBuffer

Unlike its documentation implies, its return value will only indicate whether it was capable of creating a new buffer, not whether it succeeded in loading the supplied file into it.

So if it could not load the file, then it still returns the id of a newly created empty buffer.

Also unlike its documentation implies, a successfully loaded file will not be marked as a character block like InsertFile() does.

EditThisFile

Bug: EditThisFile() can open more than one file.

  /*
    Example of EditThisFile() opening TWO files.
    Since quotes are illegal in file names,
    the appropriate response would be returning FALSE,
    and giving a warning if _DONT_PROMPT_ is not passed.
  */
  proc Main()
    string my_file [MAXSTRINGLEN] = '"file1.txt" "file2.txt"'
    EditThisFile(my_file)
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

Endline

White space means a sequence of space and horizontal tab characters.

The below line from the documentation needed two additional qualifications:
If the RemoveTrailingWhite variable is set ON and the buffer is not a _SYSTEM_ buffer and the buffer is not in binary mode , then this command will remove any trailing white space found on the current line.

EndProcess

Definitely see: Process().

EquateEnhancedKbd

Keyboards can have multiple places where you can type the same key. Typically this is the case for the cursor keys and the Home, End, Insert, Delete, PageUp, PageDown, /, *, +, - and Enter keys.

If the EquateEnhancedKbd setting is ON, which is the default, then it becomes irrelevant at which place on the keyboard you type a key.

If the EquateEnhancedKbd setting is OFF, then TSE sees different keys depending on at which place on the keyboard you type a key.

By using the ShowKey macro from TSE's Potpourri menu we can see, that TSE does or does not prefix the key name with "Grey " depending on where on the keyboard a key was typed.

Here is the main undocumented fact:

If EquateEnhancedKbd is OFF and no action is assigned to a "grey" key, then TSE executes the action assigned to the "non-grey" key.

An example of this can be found in the Potpourri's Calendar macro, that even if EquateEnhancedKbd is OFF will act on "grey" cursor keys despite having no definition for them.

Exit

Definitely see: Process().

FFDateStr

The returned date is formatted according to the DateFormat setting.

FFTimeStr

The returned time is formatted according to the TimeFormat setting.

File System

This is not a topic in TSE's built-in Help system.

Note:

To find out which specific folders and files on your computer TSE cannot access, you can execute the DirList macro, and supply it with "C:\ D:\" etcetera as a parameter.

In the result you can then search for "<inaccessible" with options "giv", and optionally use <Alt e> to edit that search result as a file.

Possible annotations to find are "<inaccessible folder>" and "<inaccessible file>".

Too long paths

TSE has several problems with too long paths. For example MkDir()'s specific problem starts after 247 characters. According to Semware the general limit is >= 255 characters.

Luckily, such deep paths are not the norm.

Unluckily, even one such path can make TSE's "-s" parameter return an aborted result or even hang TSE. The "-s" is a command line and EditFile() parameter to list files in all subfolders too.

Unicode folder and file names

Reader warning: If you read this file offline with TSE: This is a web browser optimized document that uses Unicode characters, especially in this topic. To read it in TSE you should have TSE's Unicode extension installed. Otherwise use a web browser to read this document.

The Windows file system stores its folder and file names in the UTF-16LE character encoding, which is a fully complient Unicode character encoding, that therefore implements practically all the world's characters, for example containing 137,994 characters as of Unicode 12.1, released in March 2019.

TSE only knows the ANSI (formally Windows-1252) character encoding for the file system. ANSI contains 218 characters.

TSE does no conversion of its own, but uses the standard Windows ANSI APIs to access the file system. For more info see the Microsoft documentation about Unicode in the Windows API and Code Pages .

These Windows ANSI APIs have the nice feature, that (when TSE reads a folder name or file name) they convert the file system's UTF-16LE to ANSI if these character encodings have an equivalent character, and that they convert a UTF-16LE character to a close approximation in ANSI if the character encodings do not have an equivalent character. The other way around (when TSE writes a folder name or file name) the Windows APIs always convert characters exactly, because all ANSI characters have a Unicode equivalent.

For example, this means that a "copyright sign", written as "©", is no problem in a folder or file name, because both UTF-16LE and ANSI have this character, so the Windows ANSI APIs never have a problem converting it.

For another example, this means that when we browse with TSE, we see a "greek small letter alpha" in a folder or file name as an "a", because the greek letter alpha exists in UTF-16LE but not in ANSI, so the Windows ANSI APIs show TSE an approximation. From TSE opening a folder name containing an alpha fails with an error, because the nearest ANSI equivalent of an alpha is an "a", and the folder does not exist with an "a" in its name. From TSE opening a file name containing an alpha fails by opening a new empty file: It does not open the file with an alpha in its name, but it opens a new empty file that has an "a" in the alpha's place.

If you do not use any Apple products in Windows, then the following does not apply to you. But if, for instance, you use Apple's iCloud drive in Windows, or you otherwise have or get access to files created on a Mac or iProduct, then do read on for some dirty details.

In Unicode (and therefore in UTF-16LE) characters with a diacrital mark can be written in two ways:

- As one character. For example, the character "é" can be written as the one Unicode character "latin small letter e with acute".

- As two characters. For example, the character "é" can be written as the two Unicode characters "latin small letter e" and "combining acute accent".

Windows, and thus the Windows ANSI APIs, and thus TSE, all default to the first format. So if you use TSE just with Windows products then life is good.

Enter Apple, who uses the second format when it creates new folders and files.

Both Apple's operating systems and Windows know both formats, and for both formats show the same single character. So far life is still OK.

( Though not perfect: This creates the weird possibility, that in one folder you can have two files with what appears to be the same name without the ability in Apple and Windows to see why they differ. )

The Windows ANSI APIs choose to convert the second format to two ANSI characters: The base character followed by a normal accent character. (Because ANSI does not have "combining" accent characters, the APIs again convert to the nearest equivalent; a "normal" accent character.)

To continue the previous example, when browsing, TSE sees an Apple folder or file with an "é" character as "e'". Opening a folder with an Apple "é" in its name fails with an error, because TSE tries to open that name with "e'" instead. Opening a file with an Apple "é" in its name opens a new empty file with "e'" in its name instead.

Unlike with the alpha character you can work around the diacritical problem:

- The first work-around is to not use diacritical marks for folder and file names shared between Apple products and Windows.

- The second work-around is to create or once rename such folders and files in Windows: In my experience Apple products understand and do not change back the "non-combining" Windows convention for existing folder and file names with diacritical marks. Note, that you typically only need to do this for folder and file names that need to be accessible from TSE.

FileExists

This function contains an obscure bug, that I ran into when looking for a solution to be absolute sure about opening only one specific given file.

Here is a macro that demonstrates the problem:

  /*
    If the following file exists in the current folder
      "file1.txt file2.txt"
    then when accessing the not-existing file '"file1.txt" "file2.txt"'
    FileExists() and fOpen() erroneously do find it,
    while FirstFirstFile() correctly does not.
    It does not matter if "file1.txt" and "file2.txt" exist.
  */

  integer proc FileExists_with_FindFirstFile(string p_file)
    integer result = FALSE
    integer handle = -1
    handle = FindFirstFile(p_file, -1)
    if handle &lt;&gt; -1
      result = FFAttribute()
      FindFileClose(handle)
    endif
    return(result)
  end FileExists_with_FindFirstFile

  integer proc FileExists_with_fOpen(string p_file)
    integer result = FALSE
    integer handle = -1
    handle = fOpen(p_file)
    if handle &lt;&gt; -1
      fClose(handle)
      result = TRUE
    endif
    return(result)
  end FileExists_with_fOpen

  proc Main()
    string my_file [MAXSTRINGLEN] = '"file1.txt" "file2.txt"'
    while Ask('Test existence of which folder or file:', my_file)
      Message('File is ===', my_file, '===')
      Warn('File';
           iif(FileExists(my_file), 'DOES', 'does NOT');
           'exist with FileExists().')
      Warn('File';
           iif(FileExists_with_fOpen(my_file), 'DOES', 'does NOT');
           'exist with fOpen().')
      Warn('File';
           iif(FileExists_with_FindFirstFile(my_file), 'DOES', 'does NOT');
           'exist with FindFirstFile().')
    endwhile
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

FindThisFile

Unlike its documentation states, this command will NOT find a folder unless a second parameter _DIRECTORY_ is supplied.

Flip

As of TSE Pro v4.2 this function also works for letters with diacritics if the editor does not use an OEM font.

fOpen

This function contains an obscure bug, that I ran into when looking for a solution to be absolute sure about opening only one specific given file:

If the following file exists in the current folder "file1.txt file2.txt", then when accessing the non-existing file '"file1.txt" "file2.txt"' FileExists() and fOpen() erroneously do find it, while FirstFirstFile() correctly does not. It does not matter if "file1.txt" and "file2.txt" exist. For an example macro see FileExists().

for

Given that the "for" statement syntax is:

  for integer_variable =         numeric_expression
                       to/downto numeric_expression2
                      [by        numeric_expression3]
        statements
  endfor
The "statements" can influence the for-loop by changing the value of "integer_variable", but not by changing "numeric_expression", "numeric_expression2" or "numeric_expression3".

In other words, "numeric_expression", "numeric_expression2" and "numeric_expression3" are evaluated only once; at the start of the for-loop.

In some cases this can make a "for" statement (possibly in combination with a "break" statement) more efficient than a "while" statement.

If "by numeric_expression3" comes after "to numeric_expression2", then a positive value for "numeric_expression3" will increase "integer_variable" and a negative value for "numeric_expression3" will decrease "integer_variable".

If "by numeric_expression3" comes after "downto numeric_expression2", then a positive value for "numeric_expression3" will decrease "integer_variable" and a negative value for "numeric_expression3" will increase "integer_variable".

That said, "numeric_expression3" should never be negative, because with it only for-loops can be written that loop zero or infinite times.

It could be argued that this is a bug, since its value is allowed to be negative but is then handled inconsistently.

For example:

  for i = 1 to      2 by -1  ->  infinite times
  for i = 1 to      1 by -1  ->  infinite times
  for i = 1 to      0 by -1  ->  zero     times
  for i = 1 to     -1 by -1  ->  zero     times
  for i = 1 to     -2 by -1  ->  zero     times

  for i = 1 downto  2 by -1  ->  zero     times
  for i = 1 downto  1 by -1  ->  infinite times
  for i = 1 downto  0 by -1  ->  infinite times
  for i = 1 downto -1 by -1  ->  infinite times
  for i = 1 downto -2 by -1  ->  infinite times

Format

If you use semicolons (";") instead of commas (",") to separate parameters, then the concatanated parameters will be separated by the equal amount of spaces.

For example, after

  s1 = Format('a', 'b'; 'c';; 'd';;; 'e';;;; 'f';;;;; 'g')
s1 will contain the string
  'ab c  d   e    f     g'

fRead()

If you keep reading when the end of the file has been reached, then fRead() keeps returning 0 for the bytes read, not -1 for an error.

GetBufferStr

Chr(0) bug: SetBufferStr() and GetBufferStr() strip characters with ASCII code 0 from the end of a value, and Warn() strips it and all other characters after one such a character.

Demo macro:

  proc Main()
    integer hash_id          = 0
    integer org_id           = GetBufferId()
    string  s [MAXSTRINGLEN] = 'a' + Chr(0)
    hash_id = CreateTempBuffer()
    GotoBufferId(org_id)
    SetBufferStr('key', s, hash_id)
    Warn('Bug: "2 2 a ." <> "',
         Length(s);
         Length(GetBufferStr('key', hash_id));
         s,
         '."')
    AbandonFile(hash_id)
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

GetClockTicks

Beware of this risk!   :-)   GetClockTicks() returns the number of "clockticks" (the number of 1/18ths of a second) since the start of the TSE session, and TSE's integer limit is 2,147,483,647, so your TSE session might destabilize if left open for more than 3.78 years!

GetNextProfileItem

As documented this function has two var parameters, that return the next variable name and variable value from a section of an initialization file (typically with extension ".ini"), the contents of which for example look like:

  [section name 1]
  var name 1=value 1
  var name 2=value 2
  var name 3=value 3
  ...
However, sometimes you need to store just a list of values, for example:
  [section name 2]
  value 1
  value 2
  value 3
  ...
Undocumented is, that GetNextProfileItem() can handle that as long as the .ini file's data line contains no "=" character. In that case it returns an empty string for the variable name and it returns the whole line for the variable value.

For completeness sake: If a data line contains two or more "=" characters then GetNextProfileItem() returns everything before the first "=" character as the variable name and everything after the first "=" character as the variable value. For example for this data line:

  var name 1=value 1=the best value=a great deal=the greatest
        
the function GetNextProfileItem() returns the two parameter values "var name 1" and "value 1=the best value=a great deal=the greatest".

GetNextProfileItem() will trim spaces from variable names and values. For instance, for a line " variable 1 name = variable 1 value " GetNextProfileItem() will return the parameter values "variable 1 name" and "variable 1 value".

GetNextProfileItem() only ignores empty lines, so there is no way to comment lines in a section that GetNextProfileItem() might be used on. The only safe ways to comment a TSE initialization file is before the first section or to put comments in their own separate section(s). For example:

  This is a comment.
  [section 1]
  var1=value1
  [section 2 comments]
  The value "value1" may only be the string "true" or "false".
  [section 2]
  var1=value1

GetProfileStr

The parameter "item" stands for the item's name.

Spaces around the item name are irrelevant, both in the parameter and in the initialization file.

If a section in the initialization file contains the same item name twice, then GetProfileStr() will return the value of the first occurrence.

The parameter item name may be an empty string. For example, if the initialization file looks like this:

  [file extension descriptions]
  .s=This is a SAL (Semware Application Language) macro.
  .txt=This is a text file.
  =This is an extensionless file.
  .pl=This is a Perl program.
then GetProfileStr('file extension descriptions', '', 'Not Found') will return "This is an extensionless file.".

See GetNextProfileItem() for more info on the initialization file.

GetTime

To define it more specifically, the second format, GetTime() without parameters, returns the number of hundredths of seconds since the last midnight.

GetToken

GetToken(string s, string delimiter, integer token_no) works differently if the delimiter is a space character:

- Multiple spaces are treated as one space.

- Leading spaces are ignored.

For example, Gettoken('          a        b', ' ', 1) returns "a" and Gettoken('          a        b', ' ', 2) returns "b" instead of empty strings.

GotoLine

This command does not change the column position.

HiLiteFoundText

This function has an optional integer parameter.

If UserHiliteFoundText() is FALSE, then HiLiteFoundText(N) will hilite a string N positions to the right of where it otherwise would. N can be negative to hilite to the left.

According to Semware this parameter was once upon a time created for one user for one macro.

Hook

The editor can handle 168 hooks total overall.

Undocumented properties for these specific hooks:

_IDLE_ and _NONEDIT_IDLE_

It was not possible to update the screen from an idle event, but as of the GUI version of TSE Pro v4.4 it is: See the Transparency topic.

_LOSING_FOCUS_

This hook exists from the GUI version of TSE Pro v4.2 onwards.

It is also called when TSE closes. A Warn() statement from a _LOSING_FOCUS_ hook, that is called when TSE closes, might leave a hidden TSE session. I am still investigating the details.

_ON_ABANDON_EDITOR_

Bug: This hook is not called when the GUI version of TSE is closed with its close button.

_ON_FIRST_EDIT_

Contrary to what its documentation states, the _ON_FIRST_EDIT_ hook is also called when changing an already loaded file's name.

HookDisplay

I received the following information from Semware:

QUOTE

  HookDisplay(currline_offset, before_offset, after_offset, foundtext)

  currline_offset, before_offset, after_offset, foundtext_offset are macro procs
  that are called at certain times:

    currline_offset : called for each line (redrawline)
    before_offset   : called before redraw
    after_offset    : called after redraw
    foundtext_offset: called when hilited text is found

  See debug.si, potpourr.s, and whtspc.s for examples.
UNQUOTE

The following information was deduced and tested.

HookDisplay([proc1], [proc2], [proc3], [proc4]) optionally hooks each of four specific events to a proc name without parameters. The commas are mandatory.

If you do not want to use all four hooks then you just leave out a proc name. For example, HookDisplay(,,,MyProc) is legal syntax.

For the first proc you can define it to have no or one integer parameter. An integer parameter would receive a boolean value that is TRUE if the proc is called for the current line and FALSE otherwise

HookDisplay() also sets DisplayMode(_DISPLAY_USER_) to enable these hooks.

Temporarily setting DisplayMode() to another value disables the hooks without unhooking them.

For example: The Potpourri menu uses DisplayHook() to colour the first 12 characters of each line differently, but with DisplayMode(_DISPLAY_TEXT_) it temporarily disables that while viewing the help for an item (F1) and while editing the help text for an item (Alt E), and it sets DisplayMode(_DISPLAY_USER_) back when these excursions from the Potpourri menu are done.

UnhookDisplay() unhooks the four possible events, and restores DisplayMode() to the value it had when HookDisplay() was called.

isDigit

The function name and the first line of the official documentation are misleading as to its broader functionality, which is correctly explained in the rest of the topic as recognizing a string that consists of one or more digits.

Another way to define isDigit(s) is, that for every value of string s:

  isDigit(s) == StrFind('^[0-9]#$', s, 'x'))
Note that the value of isDigit('') is FALSE, and that the value of isDigit() depends on the character under the cursor.

isFullScreen

This function is obsolete and always returns 0.

in

The "in" operator has non-intuitive behaviour.

The "in" operator is correctly implemented as documented, but it deserves an explicit mention because of

"and" and "or" are higher precedence operators than "in" and ",".

For instance, the condition

  if  (2 in 1, 2, 3)
  and FALSE
evaluates to
  FALSE

But the condition

  if  2 in 1, 2, 3
  and FALSE
evaluates as
  if 2 in 1, 2, (3 and FALSE)
which evaluates as
 if 2 in 1, 2, 0
which evaluates to
  TRUE

"not" is a higher precedence operator than "in" and ",".

For instance, the condition

  if not (2 in 4, 5, 6)
evaluates to
  TRUE

But the condition

  if not 2 in 4, 5, 6
evaluates as
  if (not 2) in 4, 5, 6
which evaluates as
  if 0 in 4, 5, 6
which evaluates to
  FALSE

Advice:
Whether (you are in doubt) or (you are not in doubt), always use parentheses with the "in" operator.

#Include

You cannot include a file from an included file. That is, nested includes do not work.

InsertData

The inserted data is marked as a character block.

InsertFile

Additial note: This command also fails if the supplied file does not exist.

KeyDef

If a macro uses KeyDef to limit user keys while editing text, then CUAMark and "friends" (other macros that work likewise) do not honour those limits.

That is because CUAMark does not use key definitions for most of its editing keys, but it checks what keys were pressed and then adds it own functionality, whether a key is currently defined or not. Luckily CUAMark and friends are an exception, but on occasion they have to be dealt with.

A work-around for TSE Pro v4.4 upwards is for the "KeyDef-macro" to be lower in TSE's Macro AutoLoad List than CUAMark and friends or to be otherwise loaded later than CUAMark and friends, and for the "KeyDef-macro" to implement this _AFTER_GETKEY_ hook:

  proc after_getkey()
    if not isKeyAssigned(Query(Key))
      BreakHookChain() // Nice solution: leaves Key's value unchanged.
      // Set(Key, -1)  // Hard solution: changes Key to dummy value.
    endif
  end after_getkey

  proc WhenLoaded()
    Hook(_AFTER_GETKEY_, after_getkey)
  end WhenLoaded

KeyPressed

Known bug:

In TSE's GUI version after a key is pressed the first call to KeyPressed() always returns FALSE.

The work-around is to first do another KeyPressed() and ignore its result.

KeyPressed() has a useful side-effect for macros that run a long time.

Probably since an earlier Windows version but definitely since Windows 10, when a macro runs for some time without user interaction, Windows stops updating the screen and shows an hourglass until the macro is finished.

This is not a TSE-specific issue but a general Windows issue.

This typically is a problem if the macro is showing a progress indicator. Any other visual progress on the screen is halted too.

KeyPressed() to the rescue!

By adding a KeyPressed() to the macro inside a loop where it will be called regularly, Windows sees an attempt at user interaction and will not disable screen updates.

Since KeyPressed() has no other side-effects, it will not effect the macro's functionality.

KillLine

This function has an optional integer parameter that indicates how many times to perform the action.

lDos

lDos() also has a flag _START_HIDDEN_, which will cause the command to invisibly start and run in a hidden Windows process.

In newer versions of TSE the _DONT_PROMPT_ and _DONT_CLEAR_ flags can be omitted when using the _START_HIDDEN_ flag, but that makes the macro not backwards compatible, so I advise against it.

lDos() also has a flag _DONT_WAIT_, which when combined with the _RUN_DETACHED_ flag will make the started command a parallel process.

For example,

  #define DOS_SYNC_CALL_FLAGS  _DONT_PROMPT_|_DONT_CLEAR_|_START_HIDDEN_|_RETURN_CODE_
  #define DOS_ASYNC_CALL_FLAGS _DONT_PROMPT_|_DONT_CLEAR_|_START_HIDDEN_|_RETURN_CODE_|_RUN_DETACHED_|_DONT_WAIT_
  if TRUE
    lDos(LoadDir(TRUE), '', DOS_ASYNC_CALL_FLAGS)
  else
    Dos(LoadDir(TRUE), DOS_ASYNC_CALL_FLAGS)
  endif
starts a second instance of the editor, and you can work in both instances of the editor simultaneously.

Here lDos() is better because Dos() also starts a useless blank window.

Without _RUN_DETACHED_ the lDos() command "eats" any unprocessed keys, making TSE ignore them. In tools that is usually not a problem, in extensions it ususally is.

lDos() also has a flag _DONT_CHANGE_TITLE_.

Left

This function has an optional integer parameter that indicates how many times to perform the action.

LoadBuffer

The current buffer is emptied when the supplied file does not exist.

LoadDir

This function has an optional integer parameter, which if not zero (not FALSE) will make LoadDir() return the fully qualified name of TSE's executable.

For example for the GUI version of TSE:

  exe_string = LoadDir(TRUE)  // exe_string == 'C:\Users\Carlo\Tse\g32.exe'

Lower

As of TSE Pro v4.2 this function also works for letters with diacritics if the editor does not use an OEM font.

lRepeatFind()

If used to execute and terminate a loop within a file (the original Find() or lFind() it was based on did not use the "a" option to search across files), then this command automatically stops.

Bug: If used to execute and terminate a loop across files (the original Find() or lFind() it was based on did use the "a" option to search across files), and the search string occurs in more than one open file, then the loop becomes infinite, i.e. never stops.

LTrim

White space means a sequence of space and horizontal tab characters.

MacroStackAvail

The built-in documentation states that the available stack space is "approximately 16k". A test says it is approximately 61k.

MarkAll

This function has an optional integer parameter.

The parameter is the block_type, as in: _INCLUSIVE_, _NON_INCLUSIVE_, _LINE_, _COLUMN_.

MakeTempName

To be more specific, MakeTempName() only looks at the disk to create a new unique filename, not at TSE's unsaved buffers nor at its own previous call results.

You can supply the optional extension parameter with or without a leading period.

Aside, the numbers MakeTempName() generates are not as random as they seem, because in tests without saved disk files they consistently turned repetitive within 200 tries.

MAXLINELEN

The editors maximum line length is documented as being 16000, while it actually is 32000.

Message

If you use semicolons (";") instead of commas (",") to separate parameters, then the concatanated parameters will be separated by the equal amount of spaces.

For example

  Message('a', 'b'; 'c';; 'd';;; 'e';;;; 'f';;;;; 'g')
will show the string
  'ab c  d   e    f     g'

MkDir

TSE strings can have a maximum length of 255 characters, but based on experiments MkDir's parameter is limited to a maximum length of 247 characters.

MoveFile

To overwrite the destination file the third parameter can be any truthy value, i.e. any value not equal to FALSE.

For example, instead of TRUE we can also use the more readable constant _OVERWRITE_.

There is a very minor bug in MoveFile():

  MoveFile(<file1>, <file2> [, <overwrite>])
deletes <file2> if <file1> does not exist and <overwrite> is truthy.

MsgBox

When you pass MsgBox() two parameters (title and message), then MsgBox() presents an OK button, but always returns zero, even when you press or click the OK button.

MsgBoxBuff

Too long lines are not wrapped to the next line.

Its documentation states that it does.

MsgBox() and MsgBoxEx() do not have this bug.

Here is a test macro that demonstrates the bug:

  proc Main()
    integer i                = 0
    integer org_id           = GetBufferId()
    string  s [MAXSTRINGLEN] = ''
    integer tmp_id           = CreateTempBuffer()
    for i = 10 to 200 by 10
      s = Format(s, i:10:'.')
    endfor
    AddLine('Hello world!', tmp_id)
    AddLine(s, tmp_id)
    Warn('Hello world!', Chr(13), s)
    MsgBox('MsgBox', Format('Hello world!', Chr(13), s))
    MsgBoxEx('MsgBoxEx', Format('Hello world!', Chr(13), s), '[&OK]')
    MsgBoxBuff('MsgBoxBuff')
    GotoBufferId(org_id)
    AbandonFile(tmp_id)
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

NextChar

This function has an optional integer parameter that indicates how many times to perform the action.

NextChar(variable) returns FALSE if the variable has value 0.

NextChar(0) returns an unpredictable integer value that varies with the context in which the command is used. The return value can be 0 too. On the bright side, in practice it makes no sense to use a hardcoded 0 as NextChar()'s parameter, let alone question its return value.

NoOp

NoOp() has the erroneous side effect, that when it is used in an _AFTER_GETKEY_ hook, then it disables the CUAmark macro's ability to delete a shift-marked block when we start typing.

The logical work-around is to use nothing or a comment instead of NoOp().

PageDown

This function has an optional integer parameter that indicates how many times to perform the action.

PageUp

This function has an optional integer parameter that indicates how many times to perform the action.

PopUndoMark

PushUndoMark() and PopUndoMark() are undocumented functions with a serious bug.

Their intended function seems to be, that you can do PushUndoMark() in a normal buffer, do any number of temporary changes, and then restore the buffer with PopUndoMark() Undo().

However, if there were no changes between PushUndoMark() and PopUndoMark(), then PopUndoMark() Undo() restores the buffer to an extra Undo() earlier than that.

Demo of the bug:

/*
  This macro demonstrates a bug in PushUndoMark() and PopUndoMark().
  For UNDO_MARK = 50  it   correctly creates a new buffer with numbers 1 to 50.
  for UNDO_MARK = 100 it incorrectly creates a new buffer with numbers 1 to 91.

  The bug can be phrased this way:
  If there were no changes between PushUndoMark() and PopUndoMark(),
  then PopUndoMark() and Undo() restore the buffer not to its state at
  PushUndoMark(), but to one extra Undo() before that.
*/
proc Main()
  integer i         = 0
  integer UNDO_MARK = 50
  NewFile()
  for i = 1 to 100
    if (i - 1) mod 10 == 0
      AddLine(' ' + Str(i))
    else
      EndLine()
      InsertText(' ' + Str(i), _INSERT_)
    endif
    if i == UNDO_MARK
      PushUndoMark()
    endif
  endfor
  PopUndoMark()
  Undo()
  PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
end Main

Example of how PushUndoMark() and PopUndoMark() can be used safely:

proc Main()
  integer old_UndoCount = 0

  // ... Get to the point where a current buffer's state needs preserving ...

  // Save the buffer's current state.
  old_UndoCount = UndoCount()
  PushUndoMark()

  // ... Do stuff to it that needs reversing later ...

  // Restore the buffer's saved state.
  PopUndoMark()
  Undo()
  if UndoCount() < old_UndoCount
    Redo()  // Correct for the PopUndoMark() bug's extra Undo().
  endif

  // ... Go on with the restored buffer ...

  PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
end Main

PressKey

Unlike its name suggests and the documentation states, the keyboard is completely bypassed, and no key appears pressed anywhere in any way.

No TSE command is capable of noticing a key was "pressed" by PressKey(), and the passed key is not stored in TSE's "Key" variable.

Instead PressKey() directly executes the commands that are assigned to the key, and any statements after PressKey() are not executed until PressKey()'s assigned commands are finished.

By itself PressKey() is not capable of executing commands that are assigned to a key combination. You can get around that by doing a PushKey() of the second key followed by a PressKey() of the first key.

PrevChar

This function has an optional integer parameter that indicates how many times to perform the action.

PrevPosition

This function has an optional integer parameter.

PrevPosition() only tracks file and line position changes.
PrevPosition(1) also tracks column position changes.

Process

This function has an optional integer parameter.

The intended functionality is this:

In a .ui file's WhenLoaded() procedure: Anywhere else:

The actual functionality is different and much more elaborate, but in its current usage the differences are irrelevant:

Terminology is very important here. Let's follow and build on Semware's terminology as much as possible.

The Process() topic states that it starts a new "editor process".

It would be more specific to state that Process() starts the "editing loop" of a new "editor process".

An "editing loop" is an optional part of an "editor process". For instance at the start of a .ui file's WhenLoaded() procedure there already is an "editor process" that is running the WhenLoaded() procedure but there is no "editing loop" yet. When Process() starts an "editor process" it also starts its "editing loop".

Semware speaks of "the main editing loop" in the "Event", "Hook", and "QueryEditState()" topics.

In TSE's context an "editing loop" is the part of the "editor process" that for the current editing buffer waits for and acts on your key strokes.

QueryEditState() will return zero for an editing loop started by Process() too.

Process() starts a new editor process like a subroutine: The editor process that calls Process() waits until the editing loop of the called editor process is finished before continuing with any next statement.

A called editor process inherits the complete state of the calling editor process.

When the called editor process finishes, the calling editor process inherits any state back that the called editor added with the specific exception of key definitions.

Any called Process() can initiate its own Process(), thus creating a stack of called editors processes.

A called editor process can mainly be exited in two ways:

Note that an Exit() is explicitly done by SaveAllAndExit(), and implicitly an Exit() is done by AbandonEditor() and if QuitFile() closes the last open file. The latter two do not call the _on_exit_called_ hook.

EndProcess() ends the editor process at the top of the stack of those editors processes that were created with Process(). So it does nothing when executed in the base editor process, because that was not started by a Process().

Exit() possibly ends the whole stack of those editor processes that were created with Process(), and sometimes the base editor process.

For Exit() it matters if the base editor process has an editing loop or not. If the WhenLoaded() procedure in the editor's .ui file starts a Process() and thus the first editing loop, then the base editor process has no editing loop.

At the time Exit() is called

  if all the editor processes on the stack
     were called with Process() or Process(0),
  then
    Exit() immediately terminates all editor processes
    without executing any statements after any Process(),
  otherwise
    Exit() will will wait for editing loops to be
    (re)turned to and terminate them, until no more
    editing loops exist or a new file is opened.

Note that not all of this is necessarily logical behaviour. For instance the above "if" condition reveals that a non-zero child Process() parameter can negate a zero parent Process() parameter. The described behaviour was revealed by extensively testing commands beyond their current usage.

PushKey

Unlike the documentation states, the key stack is not limited to 64 keys. That was true for TSE v2.8 and below. As of TSE v3.0 the key stack is limited to 512 keys.

The documentation uses the term "stack" to describe PushKey()'s behaviour.

Stack explanation:
A stack implies, that when you put things on a stack in a certain order, then you have to take them off off the stack in the reverse order.

For PushKey() this stack-like behaviour means for example that

  PushKey(<a>)
  PushKey(<b>)
  PushKey(<c>)
results in TSE's next keyboard reading commands seeing the text "cba".

Terminology:
TSE's documentation contains a pleonasm: It states that its keyboard stack is a LIFO (Last In First Out) stack. But all stacks are LIFO. That is what the term "stack" means. The common counterpart of a stack is a queue. A queue is processed FIFO (First In First Out).

PushKeyStr

Unlike the documentation states, the key stack is not limited to 64 keys. That was true for TSE v2.8 and below. As of TSE v3.0 the key stack is limited to 512 keys.

For example, the sequence of commands

  PushKeyStr('blue')
  PushKeyStr('pink')
results in TSE's next keyboard reading commands seeing the text "pinkblue".

For example, to enter two lines of text, the stack-principle applies too, so push the Enter key before pushing each line of text, and push the lines in reverse order:

  PushKey(<Enter>)
  PushKeyStr('This is the second line.')
  PushKey(<Enter>)
  PushKeyStr('This is the first line.')

PushUndoMark

See PopUndoMark().

_quiet_

This is a synonym for _dont_prompt_, and can be used everywhere where _dont_prompt_ can be used.

For an example: Semware uses it in their AutoSave macro.

PutStrAttrXY

Bug: PutStrAttrXY(..., ..., ...., '', Color(Black on Black)) uses Query(Attr) instead of Color(Black on Black).

This bug is specific to the Color(Black on Black) value.

For example, this will display a yellow on blue text:

  Set(Attr, Color(bright yellow ON blue))
  PutStrAttrXY(5, 5, 'Hello world!', '', Color(black ON black))

A work-around is to Set(Attr, Color(Black on Black)) before using PutStrAttrXY().

QuitFile

If QuitFile() closes the last open file, then (without calling the _onexit_called_ hook) implicitly an Exit() is done.

Therefore also see: Process().

QuotePath

TSE's maximum string length is 255 characters. When quoting an unquoted string of 254 or 255 characters, QuotePath() deletes the last 1 or 2 characters of the string respectively, so that it always can add the quotes.

RemoveTrailingSlash

This function takes a string parameter and returns the string without a trailing slash if it had one.

Examples:

  s = RemoveTrailingSlash('a')   // s == 'a'
  t = RemoveTrailingSlash('a\')  // t == 'a'

RepeatCmd

This function has an optional integer parameter that indicates how many times to perform the action.

RTrim

White space means a sequence of space and horizontal tab characters.

RollDown

This function has an optional integer parameter that indicates how many times to perform the action.

RollLeft

This function has an optional integer parameter that indicates how many times to perform the action.

RollRight

This function has an optional integer parameter that indicates how many times to perform the action.

RollUp

This function has an optional integer parameter that indicates how many times to perform the action.

SaveAllAndExit

Obviously and implicitly an Exit() is done too.

Therefore also see: Process().

SaveAs

As documented in Hook(), this function does not call the _ON_FILE_SAVE_ and _AFTER_FILE_SAVE_ hooks.

SaveBlock

As documented in Hook(), this function does not call the _ON_FILE_SAVE_ and _AFTER_FILE_SAVE_ hooks.

ScrollLeft

This function has an optional integer parameter that indicates how many times to perform the action.

ScrollRight

This function has an optional integer parameter that indicates how many times to perform the action.

Searching With Regular Expressions

Here is the most insignificant TSE bug I have ever found, but it is somewhat relevant, because it might confuse users who try to delve into the rest:

If you use TSE's regular Find() command to search without the "+" option for a regular expression that matches an empty string, and you do that twice without changing the cursor position, then TSE hilites the current cursor position.

This is a hiliting bug: in this case a character is hilited when an empty string is found.

Examples:

  Find('.*', 'x')  // Anywhere in or at the end of a line.
  Find('.@', 'x')  // At the end of a line.

The regular expression character "?" is non-greedy. In TSE's terminology: it uses minimal closure. Both mean to say that it prefers to match an empty string. A scary example: if a text contains "book" and you search for "book?" then you will find "boo" (highlighted).

Here is a cool pattern matching hack:

If you create a regular expression with a sequence of tagged patterns AND group them with a tag AND follow that group with "|<something that always matches>", then as long as from left to right the tagged subpatterns match, each match still gets a tag number (that is 1 higher than its occurrence).

For example, if a text contains a line with 3 items like

  "(a,b,c)"
and you search that line for 5 subpatterns like with
  "{({a},{b},{c},{d},{e})}|{,}"
with the "cgx" option, then for n = 1 to 5 the statement GetFoundText(n + 1) will return the subpatterns that matched until a non-matching one is found and an empty string otherwise; here "a", "b", "c", "", "".

Note 1: The patterns before the "|" must not be able to overlap for this to work. So for example instead of a list of patterns like '"{.*}","{.*}"' use a list of patterns like '"{[~"]*}","{[~"]*}"'. In other words, the list of patterns must be allowed to fail as a whole, and while failing the regular expression will try to match longer strings for a ".*" pattern, which much fail for our purpose.

Note 2: The <something that always matches> part is tricky: not all logical values work; you might have to experiment a bit.

The following is not an undocument feature, but a caveat about implied behaviour that macro programmers tend to stumble upon.

SetBufferStr

Chr(0) bug: SetBufferStr() and GetBufferStr() strip characters with ASCII code 0 from the end of a value, and Warn() strips it and all other characters after one such a character.

Demo macro:

  proc Main()
    integer hash_id          = 0
    integer org_id           = GetBufferId()
    string  s [MAXSTRINGLEN] = 'a' + Chr(0)
    hash_id = CreateTempBuffer()
    GotoBufferId(org_id)
    SetBufferStr('key', s, hash_id)
    Warn('Bug: "2 2 a ." <> "',
         Length(s);
         Length(GetBufferStr('key', hash_id));
         s,
         '."')
    AbandonFile(hash_id)
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

SetUndoOn

As the CreateBuffer() help states:
"Undo/Redo information is NOT recorded for _SYSTEM_ buffers."

Sort

TSE has "three" sort programs:

The internal Sort() function.

The TSE Help documentation states, that it needs a marked block to sort on, that it only uses the first 80 characters of that block as its sort key, and that it can sort a maximum of 65535 lines.

Tests show the maximum sort key size is actually 282 characters.

The external tsort.com executable.

Start it from the command line with "tsort /?" to see its possible arguments. Where it says "99" you can actually use more than two digits.

Tests show its maximum sort key size is 4095 characters.

Horribly, tests also show that it splits lines that are longer than 8191 characters!

The Sort.s macro, used by TSE's menu.

Based on convoluted rules this tool either calls the internal Sort() function or the external tsort.com executable.

The Sort macro calls the internal Sort() function if one of these conditions applies:

How When
Silently If no block is defined.
Silently If the block has less than 2000 lines.
After asking you If it cannot find the external tsort.com program.
After asking you If the current file is in binary mode.
After asking you If the current block contains a vertical tab, a carriage return or a line feed character. Note that a normal file normally contains no carriage returns nor line feeds when loaded in TSE.

If you are asked " ... Use slow sort?", then this refers to the internal Sort function, and if you reply No or Cancel or Escape, then no sort is done at all.

Sad conclusion:
There are files that TSE cannot sort. The menu's Sort a.k.a. the Sort macro, hides which set of limits applies and thereby why a sort fails. Moreover, when any TSE sort fails, it often does so silently, and possibly even horribly.

SplitLine

This function has an optional integer parameter that indicates how many times to perform the action.

StrCount

The function StrCount(needle, haystack) counts overlapping needles too.

Examples:

  StrCount('aa', 'aaa' )  // Returns 2.
  StrCount('aa', 'aaaa')  // Returns 3.

StrFind

This function is imperfectly documented, and the 4th parameter is incorrectly implemented.

Abbreviated syntax:

  result = StrFind(needle, haystack, options, start, var len)
StrFind's return value (further on referred to as "Result") is documented as:
"The position that needle occurs in haystack, if successful; zero (FALSE) on failure."
That is not always implemented that way: Details further on. The fifth parameter "len" is defined as:
"len is optional. Len is passed back to the caller as the length of the found text."
Undocumented is, that "len" is undefined when result is 0.

The fourth parameter "start" is weirdly documented as:

"start is optional. If specified, start denotes the occurrence of needle at which to start."

The fourth parameter was better named "occurrence", and was better defined as:

"occurrence is optional. If specified, occurrence denotes which occurrence of needle to find."
After all, that is how the parameter does work most of the time.

Especially for backward searches the "start" parameter is incorrectly implemented; its results are inconsistent.

Below is the output of a test macro that demonstrates the problems. When two separate assesments are given, then the first pertains to the returned result and the second to the returned length. It is acceptable that start 0 is treated as default for start 1. It is acceptable that for result 0 length has no defined value.

  StrFind('b', 'abcabc',  'i',-1, len) // Result = 1, Length = 0  WRONG
  StrFind('b', 'abcabc',  'i', 0, len) // Result = 2, Length = 1  ACCEPTABLE
  StrFind('b', 'abcabc',  'i', 1, len) // Result = 2, Length = 1  OK
  StrFind('b', 'abcabc',  'i', 2, len) // Result = 5, Length = 1  OK
  StrFind('b', 'abcabc',  'i', 3, len) // Result = 0, Length = 1  OK, ACCEPTABLE

  StrFind('b', 'abcabc', 'bi',-1, len) // Result = 7, Length = 0  WRONG
  StrFind('b', 'abcabc', 'bi', 0, len) // Result = 5, Length = 1  ACCEPTABLE
  StrFind('b', 'abcabc', 'bi', 1, len) // Result = 5, Length = 1  OK
  StrFind('b', 'abcabc', 'bi', 2, len) // Result = 5, Length = 1  WRONG
  StrFind('b', 'abcabc', 'bi', 3, len) // Result = 2, Length = 1  WRONG
  StrFind('b', 'abcabc', 'bi', 4, len) // Result = 0, Length = 1  OK, ACCEPTABLE

StrFind() is "slow". This is because it is implemented as pasting the haystack into a helper buffer and then doing an lFind(). Anything that updates a TSE buffer is "slow". This "slowness" is only human noticeable when a macro does massive amounts of StrFind()s. In such cases it pays where possible to use Pos() or FastStrFind() in its stead.

StrReplace

This function does not have the errors of StrFind.

  StrReplace('b', 'abcabc', 'B',  'i',-1)  // Result = "abcabc"  ACCEPTABLE
  StrReplace('b', 'abcabc', 'B',  'i', 0)  // Result = "aBcaBc"  ACCEPTABLE
  StrReplace('b', 'abcabc', 'B',  'i', 1)  // Result = "aBcaBc"  OK
  StrReplace('b', 'abcabc', 'B',  'i', 2)  // Result = "abcaBc"  OK
  StrReplace('b', 'abcabc', 'B',  'i', 3)  // Result = "abcabc"  OK

  StrReplace('b', 'abcabc', 'B', 'bi',-1)  // Result = "abcabc"  ACCEPTABLE
  StrReplace('b', 'abcabc', 'B', 'bi', 0)  // Result = "aBcaBc"  ACCEPTABLE
  StrReplace('b', 'abcabc', 'B', 'bi', 1)  // Result = "aBcaBc"  OK
  StrReplace('b', 'abcabc', 'B', 'bi', 2)  // Result = "aBcabc"  OK
  StrReplace('b', 'abcabc', 'B', 'bi', 3)  // Result = "abcabc"  OK
  StrReplace('b', 'abcabc', 'B', 'bi', 4)  // Result = "abcabc"  OK

String Slices

A string slice with only one parameter has length 1.

For example, the string slices s[3] and s[3:1] work exactly the same, both before and after the assigment operator.

Examples:

  string s[9] = 'abcde'
  string t[9] = ''
  t    = s[3]   // t == 'c'
  s[4] = 'x'    // s == 'abcxe'

SyntaxHilite Mode

Info:
Delimiters can be strings with letters: Such delimiters are case sensitive.

Bug:
In the TSE submenu "SyntaxHilite Mapping Configuration" -> "Delimiter Tokens" we can define three Quotes and an Escape character for each Quote. Testing shows, that each Escape character applies to its matching Quote only, so the position of the Escape character in the configuration menu matters. The bug is, that if a previous Quote has no Escape character in the configuration menu and an Escape character is applied to a later Quote in the menu, then initially and visually the menu seems to do so, but after (!) leaving the menu when the changes are automatically saved to the .syn file, then the Escape character is moved to the first Quote that had no Escape character. So you did not escape the Quote that you thought you did. Work-around: define a Quote that has an Escape character before a Quote that has none.

Info:

TSE's Keyword WordSet documentation states:
"All characters not in this set are used as keyword delimiters."
This is a huge oversimplification, and therefore not usable in practice.
In order to resolve this, the following extended scope was deduced by testing TSE's syntax hiliting.
Parsing a line from left to right the syntax hiliter repeatedly tries to match the beginning of the unmatched part of the line to these types of strings in this order:
Delimited - Hilited according to their delimiter type:
A string as defined by its configered delimiter(s).
Number - Hilited as a number:
The largest possible string that matches a number according to the configured syntax hiliting number type.
Keyword - Hilited according to the its Keywords group:
The largest matching predefined keyword if it either not ends with a hiliting-WordSet character or is not followed by a hiliting-WordSet character.
Note that if such a "largest" keyword meets neither of its conditions, then no match for a shorter keyword is searched for.
Bug - Wrongly highlited as text:
A predefined keyword that is a single hiliting-WordSet character AND not a letter or digit AND occurs before a hiliting-WordSet character.
Text - Hilited as text:
Anything else.

Examples of what strings are hilited as, assuming default SAL syntax hiliting and "-" being part of the hiliting Keyword WordSet:

String(s)Hilited as
1b A (binary) number.
1a One text.
+1 A keyword and a number.
-1 A bug and a number.
1a+b Text, a keyword and text.
1a-b One text.
1-ab A number, a bug and text.
and or A keyword, text and a keyword.
andor One text.
Grey+ One keyword.
Grey- One keyword.
Grey+procTwo keywords.
Grey-procOne text.

Note, that it is quite common that predefined keywords are not hilited as such.

TabLeft

This function has an optional integer parameter that indicates how many times to perform the action.

TabRight

This function has an optional integer parameter that indicates how many times to perform the action.

TimeFormat

This setting determines the format of FFTimeStr() too.

Transparency

Changing this setting only works in the GUI version of TSE.

Trick:
Changing the Transparancy setting refreshes the screen. This can be a work-around in cases where UpdateDisplay() does not work, for example from an _IDLE_ or _NONEDIT_IDLE_ hook. For these hooks this needs to be followed by an UpdateDisplay(), in other cases the screen updates without it.

Example:

  Set(Transparency, Query(Transparency))
  UpdateDisplay()  // Mandatory for _IDLE_ hook, optional in some other cases

Trim

White space means a sequence of space and horizontal tab characters.

Undo

As elsewhere the CreateBuffer() help states:
"Undo/Redo information is NOT recorded for _SYSTEM_ buffers."

UndoMode

As the CreateBuffer() help states:
"Undo/Redo information is NOT recorded for _SYSTEM_ buffers."

UnhookDisplay

Unhooks the procs hooked with HookDisplay() and restores DisplayMode() to the value it had when HookDisplay() was called.

UnloadAllBuffers

The exact specification is untested, so the following is just an educated guess.

Syntax: UnloadAllBuffers()

Returns: Unknown.

Does an UnloadBuffer() for all open files.

I do not know a practical use case for this command. If you actually do use this command please let me know what for.

UnloadBuffer

Syntax: UnloadBuffer(integer id)

Returns: TRUE if it did unload the buffer, FALSE if it did not.

Unloads the content of the buffer for which the id is passed, but only if that buffer:
AND has a _NORMAL_ BufferType(),
AND has no changes,
AND exists as an identically named file on disk.

Note, that it is a TSE feature, that files can be opened and not yet loaded. This typically happens when you open multiple files at once using wildcards; then only the first file will be loaded and the rest will not be until they are accessed.

One possible use of UnloadBuffer() is to simply reduce TSE's memory usage by unloading a file that for now is no longer used.

An extreme(ly useful) example of UnloadBuffer()'s usage is the following. Using wildcards you can open more files than TSE can load into memory. A macro can still process all those files without running out of memory by opening them one at a time and doing an UnloadBuffer() for each file after processing it.

UnLockCurrentFile

This function has two optional integer parameters:

  UnLockCurrentFile([integer keep[, integer file_attribute]])

There are two kinds of file locking - handle and attribute.

Handle locking

In handle locking, the file is sometimes created.

In some cases, when one is unlocking the file, one wants to remove this file, in other cases, not.

This can be indicated by setting "keep" to FALSE or TRUE.

Attribute locking

The file_attribute is used in attribute locking (e.g., read-only).

Up

This function has an optional integer parameter that indicates how many times to perform the action.

UpdateDisplay

In some cases UpdateDisplay() cannot refresh the screen, for example from an _IDLE_ or _NONEDIT_IDLE_ hook.

See the Transparency setting for a work-around trick.

Upper

As of TSE Pro v4.2 this function also works for letters with diacritics if the editor does not use an OEM font.

UserHiliteFoundText

This function is undocumented.

So far its observed behaviour is, that it seems to return TRUE immediately after a Find() and FALSE immediately after an lFind().

Val

Trailing spaces are ignored too. For example, Val("123 ") will return the number 123.

Warn

If you use semicolons (";") instead of commas (",") to separate parameters, then the concatanated parameters will be separated by the equal amount of spaces.

For example

  Warn('a', 'b'; 'c';; 'd';;; 'e';;;; 'f';;;;; 'g')
will show the string
  'ab c  d   e    f     g'

In the console version of TSE Warn() behaves as described.

In the GUI version of TSE this function displays the formatted result string differently than described:

Example:
A GUI version of TSE with a ridiculously small screen width of 40 would display

  Warn('He sissed:', Chr(13), '':40:'s')
as
  He sissed:
  ssssssssssssssssssssssssssssssssssss
  ssss

A Console version of TSE would display the single status line:

  He sissed:Xssssssssssssss Press <Escape>
where X is whatever your console version of TSE displays for a carriage-return character (possibly a music symbol) and where 26 "s" characters are cut off.

Tip:
In the file "Compatibility_downto_tse40.inc" v1.5 or higher a procedure

integer wurn(string s)
is provided, that just does a Warn(s) in the GUI version of TSE, and that emulates the GUI's Warn() in the console version of TSE.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Chr(0) bug: SetBufferStr() and GetBufferStr() strip characters with ASCII code 0 from the end of a value, and Warn() strips it and all other characters after one such a character.

Demo macro:

  proc Main()
    integer hash_id          = 0
    integer org_id           = GetBufferId()
    string  s [MAXSTRINGLEN] = 'a' + Chr(0)
    hash_id = CreateTempBuffer()
    GotoBufferId(org_id)
    SetBufferStr('key', s, hash_id)
    Warn('Bug: "2 2 a ." <> "',
         Length(s);
         Length(GetBufferStr('key', hash_id));
         s,
         '."')
    AbandonFile(hash_id)
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  end Main

WhenPurged

A WhenPurged() procedure is implicitly forward referenced. This means, that in a macro you can call a WhenPurged() proc that is defined further on in the source without defining a forward reference.

A WhenPurged() procere is not called when the editor is closed. Often this is irrelevant, because WhenPurged() usually just implements cleaning up data in TSE memory. But if a WhenPurged() procedure also cleans up data outside TSE, like for example a temporary disk file, then an easy solution is to add a Hook(_ON_ABANDON_EDITOR_, WhenPurged) to the WhenLoaded() procedure.

WordLeft

This function has an optional integer parameter that indicates how many times to perform the action.

WordRight

This function has an optional integer parameter that indicates how many times to perform the action.

WordSet

The WordSet and ChrSet() documentation misses a reference to the ClearBit(), GetBit() and SetBit() functions.