TSE PRO Undocumented Features

DocumentTSE_PRO_Undocumented_Features.html
AuthorCarlo Hogeveen
OriginWebsite
Date7 Jul 2023

This document describe bugs and undocumented features for the latest TSE Pro version.

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

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

 

Recently updated

(sorted by reverse recency)

tsehist.dat
OEM font bug
PasteFromWinClip
LinkSynFile
BuildPickBufferEx
ViewFindsId
isMacroLoaded
ExecMacro
QueryEditState
All functions with a single "perform how many times" parameter
FFTimeStr
FFDate
FFTime
MsgBoxEx
HookDisplay
SearchPath
Random
Help
Transparency
GetCharWidthHeight
_USE_3D_BUTTONS_
_USE_3D_CHARS_
SetFont
FileExists
 

Index

(sorted by name)
┌──────────────────────────────────────────────────────────────────────────────┐
│ A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z │
└──────────────────────────────────────────────────────────────────────────────┘
        
#if, #ifdef, #ifndef, #ifexist, #ifnexist, #else
_QUIET_
_USE_3D_BUTTONS_
_USE_3D_CHARS_
--- A ---
Abandoneditor
Appendix A: Technical Specifications
AskFilename
--- B ---
BackSpace
Binary Mode
BuildPickBufferEx
--- C ---
case
ChangeCurrFilename
ChrSet
CmpiStr
Command-Line Options
config ... endconfig
constant
CopyBlock
CopyFile
CopyToWinClip
CreateBuffer
--- D ---
DateFormat
Debugging Macros
DelAnyChar
DelChar
DelLeftWord
DelLine
DelRightWord
DisplayMode
Down
Dos
DupLine
--- E ---
EditBuffer
Endline
EndProcess
EquateEnhancedKbd
ExecMacro
Exit
--- F ---
FFDate
FFDateStr
FFTime
FFTimeStr
File System
FileExists
FindFirstFile
FindThisFile
Flip
fOpen
for
Format
fRead
--- G ---
GetBufferStr
GetClockTicks
GetCharWidthHeight
GetKeyFlags
GetNextProfileItem
GetProfileStr
GetTime
GetToken
GotoLine
--- H ---
Help
HiLiteFoundText
Hook
HookDisplay
--- I ---
in
#Include
InsertData
InsertFile
isDigit
isFullScreen
isMacroLoaded
--- J ---
--- K ---
KeyDef
KeyPressed
KillLine
--- L ---
lDos
Left
LinkSynFile
LoadBuffer
LoadDir
Lower
lRepeatFind
LTrim
--- M ---
MacroStackAvail
MarkAll
MakeTempName
MAXLINELEN
menu
Message
MkDir
MoveFile
MsgBox
MsgBoxBuff
--- N ---
NextChar
NoOp
--- O ---
OEM font bug
--- P ---
PageDown
PageUp
PasteFromWinClip
PopUndoMark
PressKey
PrevChar
PrevPosition
Process
PurgeMacro
PushKey
PushKeyStr
PushUndoMark
PutOemStrXY
PutStrAttrXY
--- Q ---
QueryEditState
QuitFile
QuotePath
--- R ---
Random
RemoveTrailingSlash
RepeatCmd
Right
RTrim
RollDown
RollLeft
RollRight
RollUp
--- S ---
SaveAllAndExit
SaveAs
SaveBlock
ScrollDown
ScrollLeft
ScrollRight
ScrollUp
Searching With Regular Expressions
SearchPath
SetBufferStr
SetFont
SetUndoOn
Sort
SplitLine
StrCount
StrFind
StrReplace
String Slices
SyntaxHilite Mode
--- T ---
TabLeft
TabRight
TimeFormat
Trim
tsehist.dat
--- U ---
Undo
UndoMode
UnhookDisplay
UnloadAllBuffers
UnloadBuffer
UnLockCurrentFile
Up
Upper
UserHiliteFoundText
--- V ---
Val
ViewFindsId
--- W ---
WaitForKeyPressed
Warn
WhenPurged
WIN32
WordLeft
WordRight
WordSet
--- X ---
--- Y ---
--- Z ---
 

Topics

(sorted by name)

#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: In an older TSE version it is not possible to use a not-yet-existing predefined TSE constant from a newer TSE version in an #else clause that should have skipped it.

For example, the TSE constant _STATE_EDITOR_PAUSED_ was introduced in TSE Pro versions after 4.4, but the following fails to compile in TSE Pro 4.4 and lower versions:

  #if EDITOR_VERSION < 4500h
    #define STATE_EDITOR_PAUSED 0x0800
  #else
    #define STATE_EDITOR_PAUSED _STATE_EDITOR_PAUSED_
  #endif

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

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

_USE_3D_BUTTONS_

Advanced macro programming side effect when using Windows' TextOutW API:

If TSE's _USE_3D_CHARS_ and _USE_3D_BUTTONS_ display configuration settings are both OFF, then TextOutW also displays characters the current font does not support, and it displays Unicode's "Combining" characters as combined with their preceding character.

If one of the 3D options is ON and the current font does not support a character, then TextOutW displays the font's "default" character, which for Courier New I see as an empty square.

Note, that the combining/not combining difference impacts the number of characters that are displayed, which might need compensating for.

For an example see the source code of the Uniview extension.

_USE_3D_CHARS_

See _USE_3D_BUTTONS_.

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:

A control statement and a conditional statement cannot refer to statements that are more than 32 kB away. If violated the compiler will give the error message:

  "Range of loop or goto > 32k, please reduce size"
        
This typically occurs in macros that were generated or edited from data files.
Examples:
A "case" statement where the first "when" is too far away from the "endcase".
An "if" statement where the "else" or "endif" is too far away.
An long series of "if ... [elseif ...]* endif" statements, where if the first "if" is TRUE, the final "endif" is too far away.
A "for" statement where the "endfor" is too far away.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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.

BuildPickBufferEx

This is what I found so far. It might not be a complete picture.

Bug: If the TSE variable PickFileFlags <> _NONE_ and BuildPickBufferEx() finds nothing, then BuildPickBufferEx() builds a list of directories instead.

The solution is to either temporarily set PickFileFlags to _NONE_ around the call to BuildPickBufferEx(), or to use the code below to clean up such directories.

  // Remove directory entries and empty lines after BuildPickBufferEx()
  // with PickFileFlags <> _NONE_.
  integer i = 0
  for i = 1 to NumLines()
    GotoLine(i)
    if (PBAttribute() & _DIRECTORY_)
      BegLine()
      KillToEol()
    endif
  endfor
  while lFind('^[ \d009]@$', 'gx')
    KillLine()
  endwhile

  if NumLines()
    Warn('BuildPickBufferEx() found matches.')
  else
    Warn('BuildPickBufferEx() found no matches.')
  endif

Note, 9 Dec 2022: Do not use syncfg.si as an example to clean up such directories, because that piece of code has many flaws, which go undetected because syncfg.si uses PickFileFlags == _NONE_.

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

CreateBuffer

CreateBuffer() has three optional parameters instead of two. Its full syntax is therefore:

CreateBuffer(STRING fn, [INTEGER type [, VAR INTEGER id [, INTEGER flag]]])

fn is the desired buffer name.

flag is _NORMAL_ (the default), _HIDDEN_ or _SYSTEM_.

If id is 0 or not the id of an existing buffer, then a new buffer is created and id returns the id of the newly created buffer, which is TSE's next new id, not the input one.

If id is the id of an existing buffer, then that buffer is reused and returned. The content of the existing buffer is not initialized.

In all of the above cases the function will fail and do nothing if the buffer name already exists as another buffer, unless _FORCE_NAME_ is supplied as the flag parameter, in which case an another buffer with the same same is created.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

DelChar

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

DelLine

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

DelRightWord

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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.

And unlike the documentation implies, a succesfully loaded file will not create a buffer with a name, like CreateBuffer() does.
EditBuffer() creates a buffer with an empty name, like CreateTempBuffer() does.

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.

ExecMacro

This function can also execute a public procedure without its macro filename, if the macro the public procedure occurs in is loaded.


public proc non_existing_macro()
  Warn('But I do exist!')
end non_existing_macro

proc Main()
  ExecMacro('non_existing_macro')
  Warn('IsMacroLoaded =';
        iif(isMacroLoaded('non_existing_macro'), 'TRUE', 'FALSE'))
  PurgeMacro(CurrMacroFilename())
end Main

Exit

Definitely see: Process().

FFDate

It returns the date in the same encoded format as the Windows API DosDateTimeToFileTime() uses as input:

Bits Description
0-4 Day of month (1-31)
5-8 Month (1 = January, 2 = February, and so on)
9-15 Year offset from 1980 (add 1980 to get actual year)

Bits are counted right to left, starting at 0.

FFDateStr

The returned date is formatted according to the DateFormat setting.

FFTime

It returns the time in the same encoded format as the Windows API DosDateTimeToFileTime() uses as input:

Bits Description
0-4 Second divided by 2
5-10 Minute (0–59)
11-15 Hour (0–23 on a 24-hour clock)

Bits are counted right to left, starting at 0.

Seconds are always even.
Windows does know a more detailed time, which FFTime() rounds up to the first time that has an even amount of seconds.
From the Windows command prompt, this Windows command lists files in the current folder with a time that can have an odd number of seconds:
forfiles /c "cmd /c echo @file @ftime"

FFTimeStr

The returned time is formatted according to the TimeFormat setting.

Seconds are always even.
Windows does know a more detailed time, which FFTime() rounds up to the first time that has an even amount of seconds.
From the Windows command prompt, this Windows command lists files in the current folder with a time that can have an odd number of seconds:
forfiles /c "cmd /c echo @file @ftime"

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. This problem is not caused by Apple or Windows, but by Unicode. )

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. Further on I speculate on why Windows made this choice.)

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.

Here is my speculation about why Windows' Unicode to ANSI conversion chooses to convert a character with a combining accent to a character following by a separate accent character. A problem arises when such characters occur in a filename. Suppose an ANSI program reads a file with a name that contains a combining accent, and later writes the file back. If Windows had chosen to convert the character with the combining accent to one character, then the ANSI program will write the file back with a single accented character in its name, creating a second, seemingly and indistinguishibly same-named file! Windows' actual choice makes this Unicode problem explicit, and gives the ANSI program the capability to make its own choice on how to deal with it.

FileExists

Bit 3 is a bit of a mystery. It is documented as the _VOLUME_ bit, but whatever I try FileExists() on, bit 3 is always set to 0.

Bit 4 is not just 1 if the parameter refers to a directory, but also if it contains a wildcard character and matches anything. For example, if a wildcard is used, then bit 4 will also be 1 if the file specification only matches a file.

Bit 7 is 1 if the file specification matches anything. Because it has not been documented, it is formally wrong to use _NORMAL_ for bit 7, but circumstantial documentation and extensive testing imply to me that this is a valid use of _NORMAL_.

It is surprisingly difficult to check if a file specification matches one specific file and is not a directory. Here is an example of how to achieve this. If the file specification contains a wildcard character, then the condition will return FALSE regardless. All the parentheses are necessary!

if (FileExists(file specification) & (_NORMAL_|_DIRECTORY_)) == _NORMAL_

FindFirstFile

Bug: Unlike its documentation states, the attribute _DIRECTORY_ cannot be used to specifically search for folders. Supplying _DIRECTORY_ has no effect whatsoever, because FindFirstFile() will still find everything when used with a wildcard in the filename.

FindThisFile

Bug: 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 for non-OEM fonts.

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!

GetCharWidthHeight

In TSE v4.44 a bug was solved, so that TSE better displays the monospace version of the Unifont font , and makes GetCharWidthHeight() return a more useful width and height for the Unifont font.

Advanced usage: As of TSE v4.44, TSE's GetCharWidthHeight() can also be used for the Unifont font for Windows' TextOut API, but still not for Windows's CreateFont API. The latter might not be a TSE problem, but could be a Unifont, API, or macro problem. See the Uniview extension for an example of how to possibly work around this.

GetKeyFlags

Both the documentation and implementation of the getKeyFlags() function are full of errors.

Here is what I discoverd with the test macro further on:

  CONSTANT                DOCUMENTED    DEFINED   WHEN KEY
                               VALUE      VALUE    PRESSED
  _RIGHT_SHIFT_KEY_                1         16         16
  _LEFT_SHIFT_KEY_                 2         16         16
  _CTRL_KEY_                       4         12          8
  _ALT_KEY_                        8          3          2
  _SCROLL_LOCK_DEPRESSED_         16         64         64
  _NUM_LOCK_DEPRESSED_            32         32         32
  _CAPS_LOCK_DEPRESSED_           64        128        128
  _INSERT_DEPRESSED_             128          0          0

Pressing the Insert key or the NumLock key either on or off will temporarily set bit 9 (value 256), but it does not stay set.

GetKeyFlags() always returns 0 in Linux TSE.

Test macro:


integer idle_counter = 0

proc show_key_flags()
  integer i                              = 1
  integer key_flag_bits                  = 0
  string  key_flag_string [MAXSTRINGLEN] = ''
  string  bits                      [32] = ''
  key_flag_bits = GetKeyFlags()
  if key_flag_bits >= 0
    key_flag_string = 'KeyFlags: ' + Str(key_flag_bits) + ' (dec), '
    i = key_flag_bits
    while i > 0
      bits = Str(i & 1) + bits
      i    = i / 2
    endwhile
    bits            = iif(bits == '', '0', bits)
    key_flag_string = key_flag_string + bits + ' (bin)   '
    if key_flag_bits & _RIGHT_SHIFT_KEY_
        key_flag_string = key_flag_string + ' RightShift'
    endif
    if key_flag_bits & _LEFT_SHIFT_KEY_
        key_flag_string = key_flag_string + ' LeftShift'
    endif
    if key_flag_bits & _CTRL_KEY_
        key_flag_string = key_flag_string + ' Ctrl'
    endif
    if key_flag_bits & _ALT_KEY_
        key_flag_string = key_flag_string + ' Alt'
    endif
    if key_flag_bits & _SCROLL_LOCK_DEPRESSED_
        key_flag_string = key_flag_string + ' ScrollLock'
    endif
    if key_flag_bits & _NUM_LOCK_DEPRESSED_
        key_flag_string = key_flag_string + ' NumLock'
    endif
    if key_flag_bits & _CAPS_LOCK_DEPRESSED_
        key_flag_string = key_flag_string + ' CapsLock'
    endif
    if key_flag_bits & _INSERT_DEPRESSED_
        key_flag_string = key_flag_string + ' Insert'
    endif
    Message(key_flag_string)
  else
    Warn('ERROR: GetKeyFlags < 0')
    PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
  endif
end show_key_flags

proc idle()
  if idle_counter
    idle_counter  = idle_counter - 1
  else
    idle_counter  = 9
    show_key_flags()
  endif
end idle

proc WhenLoaded()
  Hook(_AFTER_GETKEY_ , show_key_flags)
  Hook(_BEFORE_GETKEY_, show_key_flags)
  Hook(_IDLE_         , idle)
  Hook(_NONEDIT_IDLE_ , idle)
end WhenLoaded

proc Main()
  NewFile()
  AddLine(Format('CONSTANT'               :-23; 'DOCUMENTED':10; 'DEFINED'              :10; 'WHEN KEY':10))
  AddLine(Format(''                       :-23; 'VALUE'     :10; 'VALUE'                :10;  'PRESSED':10))
  AddLine(Format('_RIGHT_SHIFT_KEY_'      :-23;            1:10; _RIGHT_SHIFT_KEY_      :10;         16:10))
  AddLine(Format('_LEFT_SHIFT_KEY_'       :-23;            2:10; _LEFT_SHIFT_KEY_       :10;         16:10))
  AddLine(Format('_CTRL_KEY_'             :-23;            4:10; _CTRL_KEY_             :10;          8:10))
  AddLine(Format('_ALT_KEY_'              :-23;            8:10; _ALT_KEY_              :10;          2:10))
  AddLine(Format('_SCROLL_LOCK_DEPRESSED_':-23;           16:10; _SCROLL_LOCK_DEPRESSED_:10;         64:10))
  AddLine(Format('_NUM_LOCK_DEPRESSED_'   :-23;           32:10; _NUM_LOCK_DEPRESSED_   :10;         32:10))
  AddLine(Format('_CAPS_LOCK_DEPRESSED_'  :-23;           64:10; _CAPS_LOCK_DEPRESSED_  :10;        128:10))
  AddLine(Format('_INSERT_DEPRESSED_'     :-23;          128:10; _INSERT_DEPRESSED_     :10;          0:10))
  BegFile()
end Main

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.

Help

Bug: Unlike what its documentation states, Help() always returns FALSE.

TSE has two help files: tsehelp.hlp and compile.hlp.

tsehelp.hlp contains TSE's default Help. Once in the default Help there is no way to access compile.hlp. From a compile.hlp topic you can switch to the default Help using the Contents or Index function. The Search function searches the current Help.

The compile.hlp file contains descriptions for 40 advanced compilation topics related to TSE's Macro -> "Compile Menu" menu and submenus.

The Help() command can access a known topic like "Add Compiler->Command" in the compile.hlp file with this command:

Help('compile|Add Compiler->Command')

compile.hlp does not have an Index, and its topics are not indexed in the default Help.

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

According to Semware, 24 Aug 2021, using the Hook() command, the editor's maximum number of hooks is 168.

According to a test I did on 16 Dec 2022 in Windows GUI TSE v4.48, the editor's maximum number of hooks is 158.

The tool Iused was Test_hooks_free .

For what it is worth, I am a heavy user of macros with hooks, and on 16 Dec 2022 my Windows GUI TSE installation used 76 hooks.

As the syntax implies, you can attach multiple procedures to the same hook. The hook will call these procedures in the reverse order of their hooking.

Here are 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_

_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 this information from Semware:

  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.

The following information was deduced and tested.

HookDisplay([proc1], [proc2], [proc3], [proc4]) sets DisplayMode(_DISPLAY_USER_) and optionally hooks each of four types of events to the combination of their corresponding proc name, the current buffer at the time of HookDisplay(), and DisplayMode() being _DISPLAY_USER_.

If afterwards another buffer is current or another DisplayMode() is set, the procs are still hooked but not called. As soon as both the buffer at the time of HookDisplay() is current again, and DisplayMode() is _DISPLAY_USER_ again, the hooks are called again.

Multiple buffers can have their own different HookDisplay() hook(s) simultaneously. Only memory limits the potential number of HookDisplay() hooks.

All three 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 if proc MyProc() exists.

Only the first proc can optionally be defined with an integer parameter. The integer parameter receives a boolean value that is TRUE when the proc is called for the current line and FALSE otherwise.

For the first proc UpdateDisplay() notes what the current line is, and then for each screen line temporarily sets the text cursor and the video output position to their first positions on that screen line and calls the first proc.
In other words, each time proc1 is called, its optional parameter is TRUE or FALSE to indicate if it is called for the current line, CurrLine() temporarily returns the buffer line proc1 is called for, Currxoffset() + 1 temporarily returns the buffer's first screen column, VWhereY() temporarily returns the video output line proc1 is called for, and VWhereX() returns 1.
proc1 can do without VWhereX() and VWhereX() by just writing to the screen with PutChar() statements, closing them off with a ClrEol() statement.
Before proc1 no line is drawn: proc1 does not overwrite the line, it writes it, and as far as TSE is concerned is its only writer.

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 events, and restores DisplayMode() to the value it had when HookDisplay() was called.

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.

InsertFile() no longer works for an alternate data stream (ADS).
This is not a bug report, but just a write-up of a discovered and researched TSE change. Up to and including TSE Beta 4.40.97 (2016 Oct 22) the command InsertFile("my_text.txt:my_ads") loaded that ADS if it existed. Versions from and after TSE Beta v4.40.99 (2018 Apr 28) return "Open error: <filename>" for the same command. It was handy that InsertFile() worked for ADSes, but it is not a documented or required capability.

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.

isMacroLoaded

The documentation states it a bit wobbly.

isMacroLoaded() also accepts a public procedure's name as its single argument. See the example below.


public proc non_existing_macro()
  Warn('But I do exist!')
end non_existing_macro

proc Main()
  ExecMacro('non_existing_macro')
  Warn('IsMacroLoaded =';
        iif(isMacroLoaded('non_existing_macro'), 'TRUE', 'FALSE'))
  PurgeMacro(CurrMacroFilename())
end Main

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

Windows

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.

Linux

In Linux KeyPressed() waits 0.05 seconds for a key, which is annoyingly slow when used inside a loop.

This value was chosen to give keystrokes through Putty the ability to be processed.

For now the solution is to make our macros compensate for this behavior.

KillLine

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

LinkSynFile

Bug: The documented parameter _ALWAYS_LOAD_ does not exist, use -1 instead.

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 uses an ANSI compatible font like Courier New.

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

MsgBox

When you pass two parameters to MsgBox(), then MsgBox() presents an OK button, but always returns FALSE, even when you select the OK button.

This is a bug, because the documentation states, that FALSE is returned for pressing <Escape> and that for a selection its number is returned.

MsgBoxEx

The "buttons" parameter is limited to 80 characters, including the square brackets and any ampersands.

NextChar

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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().

OEM font bug

The OEM font that TSE internally (*) uses in Windows GUI TSE versions has been broken since TSE v4.40.98 (13 Sep 2017) or v4.40.99 (26 Feb 2018 and 28 Apr 2018).

(*) Here, "internally" mainly means in GUI TSE's help, menus, and borders, and in the macro command PutOemStrXY().

The bug is, that control characters (having character codes below 32) and non-ASCII characters (having character codes above 127) are displayed as spaces or drawing-characters.

The most noticeable effect in GUI TSE itself is, that its built-in help now displays the often occurring bullet character as a space, leading to weirdly formatted text.
My HelpRepair extension fixes displaying the bullet character.

Otherwise this bug mostly affects non-English users that use the GUI version of TSE with non-Semware/private tools and extensions that use an OEM font, because diacritics are no longer supported.

Semware has reported having reproduced the bug and still having the TSE sources from when the bug started occurring, but has not been able to fix it.

This demo macro displays all internal OEM characters in an 16 x 16 rectangle:

proc Main()
  integer row    = 10
  integer c      = 0
  string  s [32] = ''
  ClrScr()
  for c = 0 to 255
    s = s + Chr(c)
    if (c + 1) mod 16 == 0
      PutOemStrXY(10, row, s, Color(Bright Yellow ON Blue))
      s   = ''
      row = row + 1
    endif
  endfor
  GetKey()
  UpdateDisplay(_ALL_WINDOWS_REFRESH_)
  PurgeMacro(SplitPath(CurrMacroFilename(), _NAME_))
end Main

PageDown

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

PageUp

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

PasteFromWinClip

Bug: The _OVERWRITE_ parameter does not do anything.

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.

Multiple PressKey() commands are not capable of executing commands that are assigned to a combination of two keys. You can halfway 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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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.

PurgeMacro

Additionally to what its documentation states:

Without a parameter the user is presented with a list of loaded macros to purge.

As a parameter you can also supply the macro's filename, with or without its path. Any file extension is ignored.

E.g. these commands makes a macro purge itself when it finishes running:
PurgeMacro(CurrMacroFilename())
PurgeMacro(Splitpath(CurrMacroFilename(), _NAME_) + '.DoesNotExist')

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().

PutOemStrXY

In Windows GUI TSE the OEM font bug applies to this command.

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().

QueryEditState

QueryEditState() returns a combination of the following flags:

Name Value Description
_STATE_TWOKEY_ 0x0004 Waiting for 2nd key
_STATE_WARN_ 0x0008 At a Warn prompt
_STATE_EDITOR_PAUSED_ 0x0010 Dos or ShowEntryScreen
_STATE_POPWINDOW_ 0x0020 A popwin is active
_STATE_PROCESS_IN_WINDOW_ 0x0040 Editor has entered a recursive level
_STATE_MENU_ 0x0080 Menus are active
_STATE_PROMPTED_ 0x0100 Read in use
_STATE_BUSY_ 0x0200 File is being loaded. (The name "_STATE_BUSY_" was added in TSE 4.48.)

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.

Random

Random()'s bugs for input > 32767 and for lo == hi have been fixed in TSE v4.46.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

RollLeft

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

RollRight

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

RollUp

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

ScrollRight

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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.

SearchPath

Improved syntax: SearchPath(fn, paths [, subdir])

paths is a list of 1 to N paths separated by ";", and its total length is limited to MAXSTRINGLEN.

When no subdir is supplied, only paths are searched.

What happens when a subdir is supplied, can best be explained by an example.

  SearchPath("myfile.txt", "c:\path1;c:\path2", "subdir")

  searches myfile.txt in these 8 directories in this order:
    .      (the current directory)
    path1
    path1\subdir
    path2
    path2\subdir
    LoadDir()
    LoadDir()\subdir
    SplitPath(LoadDir(TRUE), _DRIVE_|_PATH_)
    SplitPath(LoadDir(TRUE), _DRIVE_|_PATH_)\subdir

Note that myfile.txt is not searched in subdir in the current directory.

SplitPath(LoadDir(TRUE), _DRIVE_|_PATH_) is the path where TSE's executable resides.
LoadDir() is default the same, but not if the editor was started with the "-i" parameter or if an environment variable TSELOADDIR was defined.
See Augmenting the Editor Load Directory for a further explanation.

SearchPath with a subdir parameter is dangerous!
In situations where the current directory can be anything, making it the first search result can lead to unintended outcomes.

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

SetFont

One:

SetFont() does not always return FALSE when it fails.

My advice is to also follow it up with a GetFont() to determine if it did what you wanted.

Two:

Since TSE v4.40.34 (14 Nov 2007)
the command SetFont('Terminal', <a pointsize>, 0) no longer changes the font to Terminal, though it does still set the current font to the new pontsize.

I posed a question to Semware on whether this change was a bug or a repair.

SetFont('Terminal', <a pointsize>, _FONT_OEM_) does still work.

Interactively changing the font to Terminal silently sets it to _FONT_OEM_.

No other pre-installed TSE font has Terminal's now limited capability.

Three:

See OEM font bug.

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 TSE buffer normally contains no carriage returns and line feeds when the buffer is not in binary mode.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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 hilited 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. For example, in a default TSE installation in a TSE macro, unlike the "+" in "+a", the "-" in "-a" is not highlighted as a KeyWord2 token, because it is in the WordSet and in a longer Wordset-string. It is not really a bug: it does what the syntax hiliting grammer proscribes. It is more a design flaw/limitation of the syntax hiliting grammer, in that it has no way to to define what we want here.
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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

TabRight

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

TimeFormat

This setting determines the format of FFTimeStr() too.

Transparency

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

TSE made the unfortunate implementation choice of making the whole TSE window equally transparent, which makes TSE's menus and text vague. I have seen another tool only making the editing text's background color(s) transparent, which looks awesome and retains the text's readability.

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.

tsehist.dat

I tried to reverse engineer the meaning of TSE's history lists data structure, and I think I have got it. This in itself will not solve the GetFreeHistory() bug, but it might help. It will help me if I decide to (automatically?) clean up history list imperfections.

Documented in TSE's Help:

TSE's built-in history lists have a history list number above 127. User/macro history lists have a history list number from 1 to 127. User/macro history lists also have a history list name, that is referenced by the GetFreeHistory() macro statement.

Reverse engineered:

When TSE is closed, its history lists are stored in the file tsehist.dat in binary mode -2. The first line contains a file type verification string starting at column 2. If the byte code value L of the first character of that first line is not 0, then it indicates, that user history list names occur at lines 2 to (L + 1), and that user history list values occur from line (L + 2) onwards.

Each history list has names and values that are each represented by a line: The byte code value N of the first character of the line is the history list number. The rest of the line contains a name or value of the history list. If such a line occurs before line number (L + 2) then it is one of the history list's names, otherwise it is one of the history list's values. Yes, as this implies, technically a user/macro history list can have multiple names: According to TSE's Help this should not be possible, but probably due to a (former?) bug in GetFreeHistory() they can exist. Because the macro language's other history statements work with TSE's fixed history list numbers or with the history list number they get from GetFreeHistory(), they do not have a problem.

When TSE starts, it internally loads tsehist.dat in buffer 6 (using binary mode -2), checks the verification string, reads byte code value L of the first character, and blanks the first line. The latter will frustrate any potential history list maintainers amongst us. Yes, we can retrieve L's initial value from the file, but TSE's autoloaded and start-up macros can immediately make that value outdated.

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. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

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 uses an ANSI compatible font like Courier New.

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.

ViewFindsId

Contrary to what the documentation says, you can also set ViewFindsId. The next ViewFinds() will use it.

WaitForKeyPressed

This function roughly does what its documentation states, but not exactly.

Windows

In Windows, smoothly increasing the function's wait-time increases its reponse-time stairs-like, in chunks at a time with a small transitory curve.

There is a pattern, but I have been unable to determine a hard algorithm. For example, this test macro produced this test output .

Note that the shortest time the function waits is 107 milliseconds.

Linux

In Linux this function very predictably reacts in chunks of 50 milliseconds to its parameter, with an in-between reaction for values of 50 * N + 1 for N > 0.

Any value from 0 to 50 is interpreted as 50. Any value from 52 to 100 is interpreted as 100. Any value from 102 to 150 is interpreted as 150. Any value from 152 to 200 is interpreted as 200. Etc.

Note that the shortest time the function waits is 50 milliseconds.

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.

WIN32

The WIN32 compiler directive was named in an era where Linux TSE did not exist. In practice "#ifdef WIN32" was used to distinguish between the 16-bit TSE versions up to and including TSE 2.5 and the 32-bit versions of TSE from TSE v2.6 onwards. Since TSE still ran in a console, and still can, the distinction between Dos and Windows was and is not relevant. All Linux TSE versions are 32-bit, and being "new" they are most compatible with the 32-bit versions of TSE.

For old macros that use "#ifdef WIN32" and "#ifndef WIN32" there is an elegant solution. Make this the first conditional compiler directive:

          #ifdef LINUX
            #define WIN32 FALSE
          #endif
        

This changes the meaning of "#ifdef WIN32" to "Is this a 32-bit TSE version?".

This one addition will automagically make all the existing "#ifdef WIN32" and "#ifndef WIN32" work for Linux too, while still being backwards compatible.

WordLeft

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

WordRight

This function has an optional integer parameter that indicates how many times to perform the action. Parameter value 0 is unsafe: the function's action and return value are undefined and can change.

WordSet

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