diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index 6f91561c9..f4cbff8b6 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -3653,7 +3653,7 @@ Not tried: a `Property Let` or a method of a class with such a parameter, an `Op ## Calling a method or a property of a UDT on an element of an array of that UDT ends in an access violation **Describe the bug** -A method or property declared inside a `Type` (a twinBASIC extension), called on an element of an array of that type (`a(1).Sum`, `a(1).Bump`), ends in an access violation: `NATIVE EXCEPTION: ACCESS_VIOLATION` in the IDE run, error -2147467259 in a `[TestCase]`, and 0xC0000005 in a built exe. It happens for a local, static, module-level, dynamic and parameter array, in 32-bit and 64-bit builds, whatever the UDT's size (one byte included) and even when the method does not touch `Me`. The same call on a scalar variable, on a UDT member of another UDT, or on an element of an array declared inside a UDT works. In a build with LLVM the call does not fail, but gives wrong results. Observed with the reproducer's regression tests (`tbrun --tests`), `tbrun` and `tbrun --exe`. Found while documenting the GdiPlus package, where `types(1).CloseSubpath` on the `PathPointType` array that `GraphicsPath.GetTypes` returns, and `pts(1).Diff(pts(2))` on `GpPointF`, end this way. +A method or property declared inside a `Type` (a twinBASIC extension), called on an element of an array of that type (`a(1).Sum`, `a(1).Bump`), ends in an access violation: `NATIVE EXCEPTION: ACCESS_VIOLATION` in the IDE run, error -2147467259 in a `[TestCase]`, and 0xC0000005 in a built exe. It happens for a local, static, module-level, dynamic and parameter array, in 32-bit and 64-bit builds, whatever the UDT's size (one byte included) and even when the method does not touch `Me`. A `Type_Conversion` procedure, which the compiler calls for you, ends the same way, whether the conversion is explicit (`CStr(a(1))`) or implicit (`s = a(1)`, `Debug.Print a(1)`, an argument for a `String` parameter). The same call on a scalar variable, on a UDT member of another UDT, or on an element of an array declared inside a UDT works. In a build with LLVM the call does not fail, but gives wrong results. Observed with the reproducer's regression tests (`tbrun --tests`), `tbrun` and `tbrun --exe`. Found while documenting the GdiPlus package, where `types(1).CloseSubpath` on the `PathPointType` array that `GraphicsPath.GetTypes` returns, and `pts(1).Diff(pts(2))` on `GpPointF`, end this way. **To Reproduce** Steps to reproduce the behavior: @@ -3664,25 +3664,130 @@ Steps to reproduce the behavior: Debug.Print "a(1).Sum = " & a(1).Sum ``` 2. Run it (F5). `Sub Main` stops with `NATIVE EXCEPTION: ACCESS_VIOLATION` at the line of `a(1).Sum`. -3. Run its test cases (`node scripts/tbrun.mjs --tests`; BETA 1005 has no way to run them in the IDE): 12 of the 18 fail with `(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)`. +3. Run its test cases (`node scripts/tbrun.mjs --tests`; BETA 1005 has no way to run them in the IDE): 20 of the 32 fail with `(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION)`. **Expected behavior** -`a(1).Sum` returns 5, as `p.Sum` does for a scalar `Pair` variable `p`, and `a(1).Bump` increments `a(1).X`. UDT methods are a twinBASIC extension (VB6 has no methods in a `Type`), so there is no VB6 behaviour to compare with. +`a(1).Sum` returns 5, as `p.Sum` does for a scalar `Pair` variable `p`, and `a(1).Bump` increments `a(1).X`. `CStr(a(1))` calls the `Type_Conversion` of the element, as `CStr(c)` does for a variable. UDT methods are a twinBASIC extension (VB6 has no methods in a `Type`), so there is no VB6 behaviour to compare with. **Desktop:** - OS: Windows 10 Pro 22H2 (build 19045) - - twinBASIC compiler version: BETA 1010 + - twinBASIC compiler version: BETA 1034 **Additional context** -Each of these fails: a property, a `Sub`, a `Function` that ignores `Me`, and a `Function` that takes a UDT argument (`a(1).Diff(a(2))`); a lower bound of 0 or 1; a variable index; the element as a `With` object; a static array; a dynamic array; a module-level array; and an array parameter (`b(1).Sum` in a procedure that takes `ByRef b() As Pair`); a UDT of one `Byte`. These work: a scalar variable; a UDT member of a UDT (`h.One.Sum`); an element of an array declared inside a UDT (`h.Many(1).Sum`); an element copied to a variable first; and the element's plain fields (`a(1).X`). A UDT passed `ByVal` from an array element is a different defect, with an entry of its own (`udt-array-element-byval-argument-stack`): it needs no method, and the fault comes after the call returns. +Each of these fails: a property, a `Sub`, a `Function` that ignores `Me`, and a `Function` that takes a UDT argument (`a(1).Diff(a(2))`); a `Type_Conversion`, as `CStr(a(1))`, `s = a(1)`, `Debug.Print a(1)` and as an argument for a `ByVal String` parameter; a lower bound of 0 or 1; a variable index; the element as a `With` object; a static array; a dynamic array; a module-level array; and an array parameter (`b(1).Sum` in a procedure that takes `ByRef b() As Pair`); a UDT of one `Byte`. These work: a scalar variable; a UDT member of a UDT (`h.One.Sum`); an element of an array declared inside a UDT (`h.Many(1).Sum`); an element copied to a variable first; the element's plain fields (`a(1).X`); and a `Type_Assignment`, which an element gets right (`a(1) = 5`). A UDT passed `ByVal` from an array element is a different defect, with an entry of its own (`udt-array-element-byval-argument-stack`): it needs no method, and the fault comes after the call returns. With LLVM (`tbrun --llvm`): the property of an element of a 32-bit build returns garbage (`17820977` for a UDT holding 5 and 0), and in a 64-bit build returns the right value; in both builds `a(1).Bump` runs but leaves `a(1).X` unchanged, as if it worked on a copy. A scalar variable is right in both. LLVM also cannot compile a method called on an element of an array inside a UDT (`h.A(1).Bump`: "a feature used in your code is not yet supported"). Severity: high for any program that keeps UDTs with methods in an array, which a loop over points or rectangles does: it ends at the first call, with no hint why. Copying the element to a variable first (`e = a(i)`, `e.Sum`) avoids it, for a method that does not change the element. -Not tried: a generic UDT (member procedures are not supported on generic UDTs), a UDT with `Type_Assignment`, an array of arrays, an array of UDTs held in a `Variant`, the call through `Declare` members, and the generated code. +Not tried: a generic UDT (member procedures are not supported on generic UDTs), an array of arrays, an array of UDTs held in a `Variant`, the call through `Declare` members, and the generated code. + + + +--- + +## `Type_Initialize` does not run for the elements of a UDT array or for a UDT member of a UDT, though `Type_Terminate` does + +**Describe the bug** +`Type_Initialize` of a UDT (a twinBASIC extension) runs for a variable, a module-level variable and a class field of the type. It does not run for an element of an array of the type, nor for a UDT that is a member of another UDT, and `Type_Terminate` still runs for each of those, on a value that was never initialized: `Dim t(2) As Counted` runs `Type_Initialize` 0 times and `Type_Terminate` 3 times, with no error. `ReDim` and `ReDim Preserve` also discard elements without running `Type_Terminate`, and an array declared inside a UDT runs neither procedure. Observed with the reproducer's regression tests (`tbrun --tests`) and with the same counts in an IDE run and a built exe (`tbrun --exe`), in 32-bit and 64-bit builds. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `udt-array-element-initialize-not-run.twinproj` (attached as `udt-array-element-initialize-not-run.zip`). Its module `Startup` declares the UDT `Counted`, whose `Type_Initialize` counts its calls and sets `X` to 42, and whose `Type_Terminate` counts its calls. Its `Sub Main` is, in short: + ``` + Private Sub Declared() + Dim t(2) As Counted + End Sub + + Private Sub Read() + Dim t(2) As Counted + Debug.Print "t(1).X = " & CStr(t(1).X) + End Sub + + Public Sub Main() + Inits = 0 + Terms = 0 + Declared + Debug.Print "Type_Initialize ran " & CStr(Inits) & " times, Type_Terminate " & CStr(Terms) & " times" + Read + End Sub + ``` +2. Run it (F5). It prints: + ``` + Type_Initialize ran 0 times, Type_Terminate 3 times + t(1).X = 0 + ``` +3. Run its test cases (`node scripts/tbrun.mjs --tests`): 14 of the 21 fail, each with `Actual: 0 [Long]`. + +**Expected behavior** +`Type_Initialize` and `Type_Terminate` run in pairs, as they do for a variable: `Dim c As Counted` runs each once. So `Dim t(2) As Counted` runs each 3 times and `t(1).X` is 42, `ReDim Preserve` runs `Type_Initialize` for each new element, `ReDim` and `ReDim Preserve` run `Type_Terminate` for each element they discard, and a UDT inside a UDT is initialized and terminated with the UDT that holds it. UDT lifecycle procedures are a twinBASIC extension, so there is no VB6 behaviour to compare with. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`Type_Initialize` does not run for: an element of a local fixed-size array, of a dynamic array after `ReDim`, of a module-level array, of a `Static` array and of a `Private` array field of a class; an element added by `ReDim Preserve`; a UDT member of a UDT (`h.u`), an element of an array of such a UDT (`t(1).u`), and an element of an array declared inside a UDT (`h.a(1)`). `Type_Terminate` does not run for: an element discarded by `ReDim` or dropped by `ReDim Preserve`, and an element of an array declared inside a UDT. These work: `Type_Initialize` and `Type_Terminate` for a local variable and a class field (`Public u As Counted`), `Type_Initialize` for a module-level variable, and `Type_Terminate` for each element when an array goes out of scope and on `Erase`. + +When counting these, keep the reads apart: a procedure that reads a field of an element of a dynamic array (`n = t(1).X`) or of a class field (`n = o.u.X`) runs one more `Type_Initialize` and `Type_Terminate`, on an address that is neither the element's nor the field's, which looks like a hidden temporary of the type. A read of an element of a fixed-size array does not. The reproducer reads and counts in different procedures for this reason. + +The array and UDT-member counts are the same with LLVM (`tbrun --llvm`). + +Severity: high for a UDT that owns something, which is what the two procedures are for: `Type_Terminate` runs on a handle or pointer that `Type_Initialize` never set, and a resource held by an element `ReDim` discards is leaked. Nothing reports either. A `Type_Terminate` that does nothing when the members are all zero avoids the first: they are zero when it runs on a value `Type_Initialize` did not run for. + +Not tried: an array held in a `Variant`, an array passed `ByVal` or returned from a function, `Array(Of T)`, a generic UDT, and the generated code. + + + +--- + +## A UDT with two `Type_Conversion` overloads and none `As Variant` fails code generation when it is converted to a `Variant` + +**Describe the bug** +A UDT with two or more `Type_Conversion` overloads, none of them `As Variant`, compiles with no diagnostic when it is converted to a `Variant`, and the build then fails: `[LINKER] compilation (codegen) error detected in 'Startup.Mainrootmain' at line #24`, `[LINKER] FAILED due to compilation errors`, and an IDE run does not start. `Debug.Print c`, `v = c` and `CVar(c)` each do this. The same conversion passed to a `ByVal` `Variant` parameter is reported properly, as TB5001. Observed with `tbbuild --build` and `tbrun`, in 32-bit and 64-bit builds. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `udt-two-conversions-variant-codegen.twinproj` (attached as `udt-two-conversions-variant-codegen.zip`). Its module `Startup` is: + ``` + Public Type Two + X As Long + + Private Function Type_Conversion() As String + Return "Two " & CStr(Me.X) + End Function + + Private Function Type_Conversion() As Long + Return Me.X + End Function + End Type + + Public Sub Main() + Dim c As Two + c.X = 7 + Debug.Print c + End Sub + ``` +2. Compile the project: no diagnostic. Run it (F5) or build it. +3. The build fails with the two `[LINKER]` lines above. + +**Expected behavior** +A diagnostic in the editor at `Debug.Print c`, as a `ByVal` `Variant` parameter gets: `TB5001 Validation of call to 'TakeV' failed. Argument for 'v': cannot coerce type 'Two' to 'Variant'`. A conversion with more than one candidate and nothing to choose between them is an error in the source, not a failure of the code generator. UDT conversions are a twinBASIC extension, so there is no VB6 behaviour to compare with. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +These fail the same way: `v = c` for a `Variant` `v`, `CVar(c)`, and overloads `As Long` and `As Double`, or three overloads, `As String`, `As Long` and `As Double`. These work: one overload (`Debug.Print c` with one `As Long`, and `v = c` and a `ByVal` `Variant` parameter with one `As String`); one of two overloads `As Variant`, which they then use (`As String` and `As Variant` for all three, `As Long` and `As Variant` for `Debug.Print c`); and a `String` or a `Long` target with no `Variant` overload (`s = c`, `n = c`, `"x" & c`, `CStr(c)`, `CLng(c)`). An element of an array of the UDT does not get this far when the target is named (`CStr(a(1))`), because of the entry above on calling a method on an element, which is a separate defect; `Debug.Print a(1)` gives this code generation error. + +The same with LLVM (`tbrun --llvm`). Raised on Discord on 2026-10-21 in "What defines "enhanced-UDT types" -vs- other "normal" UDTs?", with a proper error named there as the preferred result. + +Severity: low. The build fails, so it cannot be missed, and it loses nothing, but the message names no cause, and the source line it gives is the one thing to go on. An overload `As Variant`, or naming the target (`CStr(c)`), avoids it. - +Not tried: a `Type_Assignment` beside the overloads, a `Variant` target inside a generic procedure, overloads of an object or a UDT type, and the generated code. + + --- @@ -11650,3 +11755,973 @@ Severity: a silent different result. A missing `Set` that VB6 reports at once go +--- + +## The next project opened after a DEP / LAA reload and New Project Options takes the earlier project's whole Settings, silently + +**Describe the bug** +After an open that reloaded the project for its DEP / LAA settings, and then **Continue** in New Project Options, the next project opened in that IDE, of any kind, loads with the whole `Settings` of the project New Project Options made. Its sources and resources are its own. The tree's root shows the earlier name and nothing says so: no message, no Debug Console line, the project is not marked changed, and the title bar shows the opened file's name. **Ctrl+S** then writes those Settings into the file, the project's name, id, startup object, build path and references, so a saved project that was never edited loses its own. It was observed in an IDE with an `APPDATA` of its own, comparing the Settings the IDE holds and the file on disk, key by key. + +**To Reproduce** +Steps to reproduce the behavior: +1. Have three saved projects: `Old.twinproj`, with *Large Address Aware (LAA)* set to **No** in Project Settings, and `Newer.twinproj` and `Target.twinproj`, with it set to **Yes**. +2. Start the IDE, cancel the New / Open Project dialog, and open `Old.twinproj`. The IDE starts with LAA on, so the project is loaded twice, which shows only as the progress box starting again. +3. Open `Newer.twinproj`, which puts the IDE back to LAA on. +4. **File > New Project...**, double-click **Standard EXE**, type `Zzz` as the Project Name, press **Continue**. +5. **File > Open Project...**, `Target.twinproj`. The title bar reads `Target.twinproj`, with no bullet, and nothing is asked. The Project Explorer's root is `Zzz`, and Project Settings shows the name `Zzz`, the title `Zzz`, startup object `Form1` and the new project's references and id. +6. Press **Ctrl+S**. The file on disk now has all of those in place of its own. + +With a VB6 import the steps are shorter: open `Old.twinproj`, close it, **File > New Project...**, **Import from VBP...**, **Continue**, close it with **Discard Changes**, open `Target.twinproj`. + +**Expected behavior** +A project opens with the Settings in its file. The Settings from New Project Options belong to the project that dialog made, and to the one load it starts. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Two page variables make it, and the build box plays no part. `g_LastMassagedSettings` is set by every **Continue** of New Project Options. `g_UseLastMassagedSettings` is set by every DEP / LAA reload. The IDE clears the pair only when it uses it, and it uses it at the next project load of any kind, when both are set and no dialog is showing, by answering the compiler's massage request with the whole earlier Settings. So the reload has to come first, and nothing may use up the pair before the open. Step 3 matters: with the IDE left at LAA off, a new project reloads, and that reload uses the pair. An import sets the flags itself, so its steps need no second project. + +The opened project can be anything: a saved `.twinproj` with LAA on or off, a `.vbp` from Recent (VB6) or from a row of Recent, or **Import from folder...**. Key by key, 18 keys of `Settings` came from the earlier project and none from the opened one, among them `project.name`, `project.appTitle`, `project.id`, `project.startupObject`, `project.buildPath`, `project.references`, `project.description`, `project.largeAddressAware`, the version numbers and `project.warnings`. Which saved projects set the flag: any that differs from the IDE's current DEP / LAA. A project made by **New** has LAA on, because **Continue** turns it on, so it does not at a start. + +What comes out right: New Project Options alone, with no reload before it; a reload alone; a new project made while the IDE runs with LAA off (its own reload uses the pair); the open after the one that took the Settings (the pair is used once); another `.vbp` by the card (the dialog takes precedence). Closing without a save leaves the file as it was. + +Severity: high. A wrong project configuration with nothing shown, and the next save replaces the project's own Settings, its id included, with another project's. + + + +--- + +## A recovered project is not marked changed, and Save overwrites the damaged file with what was recovered + +**Describe the bug** +A `.twinproj` whose structure is damaged opens after the compiler's box *bad file format*: *WARNING: failed to deserialize file system. Some files might be lost or truncated.* The project that opens holds only what was read, and is not marked changed. **Ctrl+S** writes it over the damaged file at once, with no question; so do **Add > Import...** and a change in Project Settings that restarts the compiler, with nothing pressed that says Save. What the file still held is gone, and no backup is left beside it. Damage anywhere in the `Sources` tree loses every source, so a project cut after five of its six files opens with none. Damage before the first entry opens as `RECOVERED_PROJECT`, with new Settings. It was observed on copies of a throwaway project saved once by the IDE, comparing the file before and after each step. + +**To Reproduce** +Steps to reproduce the behavior: +1. Copy a saved project with several sources, and cut the copy in the middle of its second source (any cut, wrong length or wrong kind in the `Sources` tree does it). +2. **File > Open Project...**, the copy. Press **OK** on *bad file format*. +3. The project opens under the file's name, not marked changed. Its Settings are kept, and it has no sources. +4. Press **Ctrl+S**. Nothing is asked, and the file is overwritten: it now holds no sources at all. + +Instead of step 4, on a project cut after its sources (`Sources` whole, a resource cut): **Add > Import...**, any text file. The file is overwritten without its resources. Or on the first copy: Project Settings, untick a reference, **Apply Changes**: the file is overwritten as the compiler restarts. + +**Expected behavior** +The damaged file stays as it is until the user chooses to replace it: a recovered project should be marked changed and saved with Save As, or Save should ask first, or a backup of the damaged file should be left beside it. The box should say what was lost. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Closing the project leaves the file as it was, and **Save Project As** to a new name keeps it too: that is the one save that does. Building, running, changing the build box and Restart Compiler leave it as well. After an edit, **Save** in the close question overwrites it. In all 19 kinds of damage tried, opening and closing left the file byte for byte unchanged. + +What each damage keeps: a cut in the root's header or at the first entry gives `RECOVERED_PROJECT`, no sources, and new Settings of 114 bytes, the box shows twice because those Settings have LAA off, and **Ctrl+S** leaves a file of 366 bytes. A cut just after `Settings`, or at any file or subfolder of `Sources`, keeps the Settings and no source, even the sources read whole before the cut. A cut in `Resources` keeps every source and loses the resources. A byte flipped inside a source's text shows no box at all, and that source is silently changed. Damage inside the `Settings` entry is the entry *A project damaged inside its Settings shows the bad file format box again after every OK*. + +Severity: high. Data is lost: the warning is shown, but it does not say that a save will replace the file, and the save needs no answer. + + + +--- + +## A resource whose type is a string imports with an empty name, and the build then ends in an access violation + +**Describe the bug** +Importing a VB6 project whose `.res` holds a resource with a string type (`CUSTOM`, `MYDATA`), with a numeric or a string id, gives a file with an **empty name** in `Resources\`: however many such resources the type has, the Project Explorer shows one blank row, and the IDE reads it as 0 bytes. The import log says `0 ERRORS/WARNINGS`. A save keeps each one's bytes under an empty name. A build then ends in `NATIVE EXCEPTION: ACCESS_VIOLATION` in `twinBASIC_win32.dll`, and no exe is written. It was observed by importing a `.vbp` through the card, reading the tree and the saved file, and building a Standard EXE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Make a Standard EXE `.vbp` with one module and a `.res` compiled from `101 CUSTOM "data.bin"`, and two more resources of the type `CUSTOM`. +2. **File > New Project...**, **Import from VBP...**, the `.vbp`, **Continue**. +3. Under `Resources\CUSTOM` the Project Explorer shows one row with no name. `__IMPORT_LOG.md` says `IMPORT FINISHED WITH 0 ERRORS/WARNINGS`. +4. Save the project, and press Build. The console prints `NATIVE EXCEPTION: ACCESS_VIOLATION {no-basic-code}`, and no exe is written. + +**Expected behavior** +Each resource keeps its id as a file name, as one of a numeric type does (`RCDATA\#201`, `RCDATA\RCNAMED`, `#300\#301`), and the project builds. A project packed by hand with `Resources\CUSTOM\#101`, `CUSTOM\NAMEDRES` and `MYDATA\#401` builds, and the exe holds CUSTOM / 101, CUSTOM / NAMEDRES and MYDATA / 401 with their bytes, so the importer's name is the whole defect. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +It is the type's being a string, not the id and not the word `CUSTOM`: `RT_RCDATA` and a numeric type that is not a standard one are right, with either kind of id. The tree's map for the type has one key, `""`, which is why three resources show as one row. The saved file holds all three, each with its bytes, and a second save after a reopen keeps them. One resource of that shape is enough for the crash. The build's crash turns TRACE-MODE on for the session, the compiler restarts from the file, and the project stays open and unharmed. A build before the first save only asks to save. + +Severity: high. The import reports no problem, what the IDE shows is not what the file holds, and the project cannot be built. + + + +--- + +## After New Project Options, the first open of a saved project with LAA off fails with MISMATCHED LAA OR DEP AWARENESS + +**Describe the bug** +With New Project Options used first and no DEP / LAA reload yet, opening a saved project with *Large Address Aware (LAA)* off fails: *Failed to load the project (error code MISMATCHED LAA OR DEP AWARENESS)*, with **Remove It** and **Keep It**, and no project is open. The file is unchanged, and opening it a second time works. Nothing about the project is wrong, and **Remove It** would take it off Recent. It was observed in an IDE with an `APPDATA` of its own, recording the loads the page starts. + +**To Reproduce** +Steps to reproduce the behavior: +1. Start the IDE. **File > New Project...**, **Standard EXE**, **Continue**. +2. **File > Close Project**. +3. **File > Open Project...**, a saved project with *Large Address Aware (LAA)* set to **No** in Project Settings. +4. The box above shows, and tB Services reads UNAVAILABLE. Press **Keep It**. +5. Open the same project again: it opens. + +**Expected behavior** +The project opens at the first try, as it does when New Project Options has not been used: it is reloaded for its DEP / LAA settings, and that is all. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +The same two variables as the entry *The next project opened after a DEP / LAA reload and New Project Options takes the earlier project's whole Settings, silently*, in the other order. The dialog leaves `g_LastMassagedSettings` set, forced to LAA on. The open's own reload sets `g_UseLastMassagedSettings`, and so it sends the earlier Settings, LAA on, to an IDE now running LAA off. That second mismatch is the box. The pair is used up by it, which is why a second open works. The same file with no dialog before it opens at once. + +Severity: low. It cannot be missed and loses nothing, but the message blames the project, and its first answer removes it from Recent. + + + +--- + +## A project damaged inside its Settings shows the bad file format box again after every OK + +**Describe the bug** +Opening a `.twinproj` that is cut, or has a wrong length, inside its `Settings` entry shows the compiler's box *bad file format*: *WARNING: failed to deserialize file system. Some files might be lost or truncated.* After **OK** the box comes back, and it goes on coming back. Behind it the page shows *Please wait...* with no close box, the UI is locked, and no project opens. There is no way out on the page: a person has to end the IDE. It was observed in an IDE on a private desktop, pressing **OK** on each box. + +**To Reproduce** +Steps to reproduce the behavior: +1. Copy a saved project, and cut the copy in the middle of its `Settings` entry. +2. **File > Open Project...**, the copy. +3. Press **OK** on *bad file format*. The box comes back about two seconds later. +4. Go on pressing **OK**. It does not end. + +**Expected behavior** +The box once, then either the failure box the IDE shows for any other file it cannot read (*Failed to load the project*, with **Remove It** and **Keep It**), or a recovered project as for damage elsewhere in the file. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +40 boxes in 88 s were counted, with the page's *Compiler crash loop detected. Restarting in SAFE mode.* logged about every fifth one, and it was still going. `#appOverlay` stays up, `lockUICount` is 1, and the file is unchanged. A cut just after `Settings` opens with the Settings kept and no sources, and a cut before `Settings` opens as `RECOVERED_PROJECT`. Whether the window's close box works during the loop was not tried: nothing was tried from outside the page. + +Severity: medium. Nothing is lost, but the IDE has to be ended from outside. + + + +--- + +## In the New / Open Project dialog, Enter on a button Tab highlighted also opens the selected item + +**Describe the bug** +In the New / Open Project dialog **Tab** moves a highlight over **Browse...**, **Open** and **Cancel** without moving the focus, and **Enter** then presses that button **and** opens the selected item. **Open** runs twice. **Browse...** shows the picker and opens the item. **Cancel** closes the dialog and opens the item: with a saved project open, it closes that project with no question and shows New Project Options, so a person who chose Cancel by keyboard is left without the project. **Enter** on a focused card has a defect of its own: it opens the selected card, not the focused one. It was observed with real keys, recording each call of the page's open. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a saved project and change nothing. +2. **File > New Project...**. The focus is in the search box and **Standard EXE** is selected. +3. Press **Tab** four times. The highlight is on **Cancel**. +4. Press **Enter**. The project closes, nothing is asked, and New Project Options opens for Standard EXE. +5. Press **Cancel** there. The IDE is empty, and tB Services reads UNAVAILABLE. + +For the card: click **ActiveX DLL**, click in the search box, press **Tab** (the focus is on the **Standard EXE** card), press **Enter**. ActiveX DLL opens. + +**Expected behavior** +**Enter** does what the highlighted button does and nothing else, as a click on it does: **Cancel** keeps the project, as a click on it and **Esc** both do. **Enter** on a focused card opens that card. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +With three **Tab** presses the highlight is on **Open**, and **Enter** calls the open twice: two `closeProjectSafely`, two loads. A real run ended in one project with one options dialog, with no harm. Unsaved edits are not lost: with a changed project the question *You've made changes to this project. Save now?* comes first, and its **Cancel** keeps everything. The same in Samples, and with no project open. **Space** on a focused card selects it, and **Enter** then opens that card. + +Severity: medium. Nothing unsaved is lost and the file is as it was, but Cancel closes the open project. + + + +--- + +## Import from VBP... and Import from folder... close the open project before their picker opens + +**Describe the bug** +In the New / Open Project dialog the cards **Import from VBP...** and **Import from folder...** close the open project first and show their picker afterwards. Cancelling the picker leaves no project and no dialog: the person changed their mind, and the project is gone from the window, with tB Services UNAVAILABLE. It was observed with real clicks, the picker answered with Cancel, recording the order of the calls. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a saved project. +2. **File > New Project...**, double-click **Import from VBP...** (or **Import from folder...**). +3. The project closes, and then the picker opens. Press Cancel in it. +4. There is no project and no dialog, and tB Services reads UNAVAILABLE. + +**Expected behavior** +The project stays open until a file has been chosen, as with **File > Open Project...** and **Browse...**: cancelling either leaves the project open. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Nothing unsaved is lost: with unsaved edits the question comes first, and its **Cancel** keeps the project (the dialog is gone); after **Discard Changes** and a cancelled picker nothing is left, as the person chose. + +Severity: low. The person sees the project go and reopens it. + + + +--- + +## The Project Name box filters keys, not text, so a pasted name keeps a space or a dot + +**Describe the bug** +The **Project Name** box of New Project Options takes out of what is typed the characters a project name cannot hold, key by key: a space, a dot, a leading underscore. Text that arrives any other way is not filtered, so a pasted `My Project.x` stands, and becomes `project.name`, `project.description`, `project.appTitle` and the project's root folder. It compiles and builds with no diagnostic, and the exe is named `My Project.x_win32.exe`. It was observed by typing with real keys, and by inserting text as a paste does. + +**To Reproduce** +Steps to reproduce the behavior: +1. **File > New Project...**, double-click **Standard EXE**. +2. In **Project Name**, type `My Project.x_1`. The box reads `MyProjectx_1`. +3. Select the text and paste `My Project.x`. The box reads `My Project.x`. +4. Press **Continue**. The project's root folder is `My Project.x`. + +**Expected behavior** +The same filter for text that is pasted as for text that is typed, or a refusal at **Continue**. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +A leading digit is allowed when typed (`9name` stands). `_lead` typed gives `lead`. The project compiled, saved, and built `Build\My Project.x_win32.exe` with no diagnostic shown. No ActiveX build was tried, where the name is also the type library's. + +Severity: low. Nothing is lost. + + + +--- + +## The New / Open Project dialog has keyboard dead ends + +**Describe the bug** +Three places of the New / Open Project dialog do not answer the keyboard. **Up** and **Down** do nothing in the search box, which has the focus when the dialog opens, so the arrows do not move the selection until the focus has left it. No key reaches the rail, so the section cannot be changed without the mouse. In **Recent**, **Tab** sends the focus to the page's body and then behind the dialog, to a panel header, and from there **Esc**, **Enter** and the arrows all do nothing: the keyboard is lost until a click. It was observed with real keys, reading the focus and the selection after each. + +**To Reproduce** +Steps to reproduce the behavior: +1. **File > New Project...**. Press **Down**: the selection stays on **Standard EXE**. +2. Press **Tab**, and any of **Down**, **Left**, **Up**, **Right** and **Tab**: the section stays **New**. +3. **File > Open Recent...**. Press **Tab** twice. The focus is on a panel header behind the dialog. +4. Press **Down**, **Up**, **Enter** or **Esc**. Nothing happens, and the dialog stays. Click in the dialog: the arrows work again. + +**Expected behavior** +**Up** and **Down** move the selection from the search box, as they do in a list with a search box above it. A key reaches the rail. **Tab** stays inside the dialog while it is modal, in **Recent** as in **New**. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +**Tab**, then the arrows, **Enter**, is the way that works with the keyboard alone, in **New** and **Samples** only. In **Recent** the search box is the only focusable element. Typing text and pressing **Esc** also takes the focus out of the box, to the dialog, where the arrows work and wrap. + +Severity: low. Nothing is lost, but the dialog cannot be used with the keyboard alone. + + + +--- + +## --no-save-ide-settings does not guard three settings, and is read after the first-run questions + +**Describe the bug** +`--no-save-ide-settings` stops most saves of the IDE's settings, each behind `g_DontSaveIdeSettings`, but three are not behind it: `NEWFRONTPAGE` (closing the New / Open Project dialog), `OBJECT_BROWSER` (a change in the Object Browser) and `PUBLISHER` (the Package Publishing sign-in). All three reach the registry whatever the flag says. The flag is also read only when `parseCommandLine` runs, after the first-run questions and IDE Options, so a first run writes `LICENCEAGREEMENTV3`, `ALLOWERRORREPORTINGV3` and `GENERAL`, and every page start writes `ACTIVE_PANEL_LAYOUT_NAME`, `WEBPANEL_SETTINGS` and `GENERAL2`, with the flag on the command line. It was observed through a proxy in front of the host object, which recorded each `SaveIDESetting` call and dropped it, so nothing reached the registry. + +**To Reproduce** +Steps to reproduce the behavior: +1. Start the IDE with `--no-save-ide-settings`, with no project. +2. In the page, wrap `hostAppObject.SaveIDESetting` so that it records its calls. +3. Press **Cancel** in the New / Open Project dialog. `SaveIDESetting("NEWFRONTPAGE", '{"section":"new"}')` is called, though `g_DontSaveIdeSettings` is true. +4. Open a project, and change the Object Browser's view: `SaveIDESetting("OBJECT_BROWSER", ...)` is called. +5. For the order: a first run (no `GENERAL`, `LICENCEAGREEMENTV3` or `ALLOWERRORREPORTINGV3` saved) shows the licence agreement with `g_DontSaveIdeSettings` still false, and **I AGREE** saves `LICENCEAGREEMENTV3` and `ALLOWERRORREPORTINGV3`. + +**Expected behavior** +No save of a setting under the flag, as it says, and the flag read before anything can save. A tool that starts an IDE with it counts on leaving the user's settings alone. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +The saves the flag does guard, once `parseCommandLine` has run: `LICENCEAGREEMENTV3`, `ALLOWERRORREPORTINGV3`, `GENERAL`, `GENERAL2`, `LAYOUT`, `LAYOUTv2`, `ACTIVE_PANEL_LAYOUT_NAME`, `WEBPANEL_SETTINGS`, `CUSTOM_PANEL_LAYOUTS`, `CUSTOM_KEYBOARD_SHORTCUT_GROUPS`, `ACTIVE_KEYBOARD_SHORTCUT_GROUPS`, `FORMS_windowsPalette`, `targetArchitectureMemory`, `LicenceKey` and `NEWYEAR_JAN2026_1`. `--no-update-recent-files` has the same gap: **X** on a Recent row calls `RemoveRecentsList`, which that flag does not test. + +Severity: low. A person who passes the flag finds three settings changed, and a test harness has to drop those saves itself. + + + +--- + +## A Recent path with a # shows cut in two + +**Describe the bug** +A row of **Recent** in the New / Open Project dialog shows a path holding `#` in two pieces, cut at the `#`, which is not shown: `C:\dev\C#\app.twinproj` reads `C:\dev\C` over `\app.twinproj`. The row opens the whole path. It was observed with the recent list answered in the page. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project saved at a path with a `#` in it, such as `C:\dev\C#\app.twinproj`, and close it. +2. **File > Open Recent...**. The row reads `C:\dev\C`, and under it `\app.twinproj`. + +**Expected behavior** +The whole path, as a row of any other path shows. The `#` separates a template folder's name from its second line (`_Standard EXE#(Console App)`), and it means nothing in a project's path. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +**Open** on the row opens the whole path. A search for `c#` finds the row, with nothing marked. + +Severity: cosmetic. + + + +--- + +## New / Open Project search rows show the internal path + +**Describe the bug** +In the New / Open Project dialog, a search row of one of the IDE's own items shows the internal path the dialog keeps for it, as a line under the name: `$\Sample 10. twinBASIC IDE Addin` for a sample, with the folder's spaces, and `$` alone for **Import from VBP...**. The `$` can be searched: `$` as a search lists 34 rows. It was observed with real keys. + +**To Reproduce** +Steps to reproduce the behavior: +1. **File > New Project...**, and type `sample 10` in the search box. The row's second line reads `$\Sample 10. twinBASIC IDE Addin`. +2. Search for `vbp`. The row's second line reads `$`. +3. Search for `$`. 34 rows are listed. + +**Expected behavior** +No such line for an item with no path a person can use, or the section's name, and no match on the internal prefix. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +A row of a Recent project shows its real path. + +Severity: cosmetic. + + + +--- + +## Two search words in one line of a row lose the first mark + +**Describe the bug** +The New / Open Project dialog's search marks the words it found in each line of a row, and when two words are in the same line, only the last is marked. For `standard console`, the row **Standard EXE (Console App)** has its path line marked only at `Console`, though `Standard` is in that line too. It was observed by reading the marks of each row. + +**To Reproduce** +Steps to reproduce the behavior: +1. **File > New Project...**, and type `standard console` in the search box. +2. The row's first line marks `Standard`, its second marks `Console`, and its path line, `$\_Standard EXE#(Console App)`, marks `Console` and not `Standard`. + +**Expected behavior** +Every word found is marked, in each line that holds it. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Each line is marked separately, so words in different lines are each marked. A single word is marked wherever it is. + +Severity: cosmetic. + + + +--- + +## Cancel in New Project Options of an import leaves the next project's box drawn as an import + +**Describe the bug** +After **Import from VBP...**, **Cancel** in New Project Options leaves the IDE in its import state. The next template's or sample's New Project Options is drawn as an import's, with a *Unicode Common Control Notifications* row and no *Include Common Controls* row, and **Continue** there makes a different project from the one the same card makes: it has no WinNativeCommonCtls reference, its description and title are the template's text and not the name typed in the box, and `runtime.useUnicodeCommonControlNotifications` is set. Nothing says so. It was observed with real clicks, comparing the box's rows and the new project's Settings, key by key, with the same card in a fresh IDE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Start the IDE. **File > New Project...**, double-click **Import from VBP...**, choose any `.vbp`. +2. Press **Cancel** in New Project Options. +3. **File > New Project...**, double-click **Standard EXE**. The box lists *Project Name*, *Option Explicit ON*, *Visual Styles*, *DPI Awareness* and *Unicode Common Control Notifications*. The same for **Samples**, **HelloWorld**. +4. Type `CardName` as the Project Name, and press **Continue**. +5. In Project Settings the project's description and title read `New project`, and its references have no WinNativeCommonCtls. + +**Expected behavior** +The box lists *Include Common Controls* and no *Unicode Common Control Notifications* row, as the same card does in a fresh IDE, and **Continue** gives the project it gives there: the WinNativeCommonCtls reference, and `CardName` as the description and the title. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`loadProjectVBP` sets `g_IsLoadingVBP`, and the only clear is inside the **Continue** callback that `showNewProjectOptions` is given. The box's **Cancel** runs `hideModalDialog`, `hideModalProgressIndicator`, `closeProjectNow` and `root.webSocket = undefined`, and `closeProjectNow` does not clear it. So the state holds through any number of Cancels, until a **Continue** in any box, and the next box after that is right. + +Compared key by key with the same card in a fresh IDE, the project differs in `project.appTitle`, `project.description`, `project.references` (the WinNativeCommonCtls reference is missing), `runtime.useUnicodeCommonControlNotifications` (set to true, and absent from the other) and, as for any project, `project.id`. The project is otherwise normal: it opens with the template's sources, tB Services reads OPERATIONAL, and it is not marked changed. + +**Cancel** in the import's own Windows picker does not do it: the flag stays false, because `loadProjectVBP` has not run, and the next box is right. + +Severity: low. The box shows the difference and nothing is lost, but the controls of WinNativeCommonCtls are missing from the new project, and the name typed is not its title. + + + +--- + +## A failed Save As still moves the project to the new path + +**Describe the bug** +When **File > Save Project As...** fails, the IDE shows its alert, and the project has moved all the same: the title bar and the project's path name the file that was not written, and a plain Save then goes there. While the cause lasts each Save fails with the same alert. Once it has gone the project is written to the new path, and the file the reader opened stays as it was. It was observed by reading the title bar, the page's path and the files on disk. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a saved project and type something in a source file. +2. **File > Save Project As...**, and give a path in a folder that does not exist. +3. The alert reads `save project failed, result code 2, GetLastErr: ERROR_PATH_NOT_FOUND`. The title bar now names the new file, which does not exist. +4. Press Ctrl+S: the same alert, for the new path. +5. Make the folder and press Ctrl+S: the project is written there, and the file that was opened is not touched. + +**Expected behavior** +A Save As that fails leaves the project where it was: the title bar and the path keep the old file, and a plain Save goes there. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Save As over a read-only file does the same with `ERROR_ACCESS_DENIED`. A Save As back to the original puts it right. A Save As to a path that can be written is right. + +`_RootConnection.saveProjectAs` sets `projectFilePath` and calls `syncTitleBar` in its request's callback without looking at the result. The same callback calls `AddRecentsList`, so by the code the path that failed also goes in the recent list; the lane runs with `--no-update-recent-files`, so that part was not tried. + +Severity: low. There is an alert each time and nothing is lost, but a reader who does not look at the title bar is no longer saving the file they opened. + + + +--- + +## Close Project, Save: a save that fails drops the close + +**Describe the bug** +**File > Close Project** on a changed project asks whether to save. When **Save** is pressed and the save fails, the alert opens, and the close is dropped: the project stays open and changed, the question is not asked again, and the reader must choose Close Project again. It was observed with the project's file read-only. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a saved project and type something in a source file. +2. Make the `.twinproj` file read-only. +3. **File > Close Project**, and press **Save** on the question. +4. The alert reads `save project failed, result code 2, GetLastErr: ERROR_ACCESS_DENIED`. +5. After OK the project is still open and marked changed, and nothing more happens. + +**Expected behavior** +The IDE asks again, or says that the project was not closed, so that a reader who walked away from a close does not find the project still open with no word of why. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`closeProjectSafely` passes `closeProjectNow` to `askServerToSaveProject` as its callback, and that function shows the alert and calls no callback on a failure. With a file that can be written, the same steps save it and close the project. + +Severity: low. Nothing is lost: the file is unchanged and the edit is still in the editor. + + + +--- + +## Start in Safe Mode never runs and never ends, and Stop does nothing + +**Describe the bug** +With the build box on **safeMode**, **Start** prints two lines and goes no further: the program does not run and the IDE stays in run mode. **Stop** changes nothing. The second line waits for a background compilation that Safe Mode will never make. Only a restart of the compiler, or a switch of the box, ends it. It was observed by reading the DEBUG CONSOLE and the IDE's run state over 35 s. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project with a `Sub Main`. +2. Choose **safeMode** in the build box, and press **OK** on the box that opens. +3. Press **Start**. The DEBUG CONSOLE prints `Executing 'Main'...` and `WARNING: execution delayed; waiting for background compilation to finish`, and nothing more. +4. Press **Stop**. Nothing changes, and the IDE stays in run mode. +5. Press **Restart the compiler**: the run ends. + +**Expected behavior** +**Start** is disabled in Safe Mode, or says that a program cannot run there. Failing that, **Stop** ends the wait. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Safe Mode's box says that the services are not started, and yet the status bar reads OPERATIONAL, since the IDE makes all four connections to the compiler. With the box on win32 the same project runs to its end. Saving still works during the wait. + +Severity: low. It is plain that nothing runs and nothing is lost, but the message points at a compile that will never come. + + + +--- + +## A linked package's missing file gives Restart a bare shutdown box and a wait of about 80 s + +**Describe the bug** +A project references a linked package, and its `.twinpack` file has gone from `%APPDATA%\twinBASIC\packages`. **Restart the compiler** then opens a native box titled `shutdown` that says only `Run-time error '-2147467259'` and `Unspecified error`, naming neither the package nor the file. After **OK** the IDE shows *Please wait...* with no explanation for about 80 s before the compiler is back. Opening the same project shows the reference's error at once. It was observed with a package of the test's own. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project that references a linked package, and type something in a source file. +2. Delete the package's `.twinpack` from `%APPDATA%\twinBASIC\packages`. +3. Press **Restart the compiler**. About a second later the box opens. The status bar reads LIMITED, and the page *Please wait... 5.0%*. +4. Press **OK**. About 80 s later the status bar reads OPERATIONAL, the project is back with its edit, and the References view shows the package's row with `ERROR unable to load this reference`. + +**Expected behavior** +The restart reaches that row without the wait, as opening the project does, or a message names the package whose file is missing. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +The box is opened by `twinBASIC.exe`. Left unanswered it holds the restart: 79 s on the status bar reads UNAVAILABLE and the page *Please wait... 0.0%*, and they stay so. During the wait the window's X asks about the unsaved changes, and **Cancel** there gives the reader the editor, so the text can be copied, but a save does not work until a compiler is back. With the file there, Restart brings the compiler back at once. + +This replaces an earlier draft which had the restart never coming back: that run had this box open on a private desktop, where nobody could see it. + +Severity: low. Nothing is lost once the box is answered, but the box and the wait say nothing of the cause. + + + +--- + +## TRACE mode auto-activated by a native exception takes effect only at the next restart + +**Describe the bug** +After a native exception the DEBUG CONSOLE says `[IDE] auto-activated TRACE-MODE in this session for extra debugging info on further native exceptions`, and the Help menu's item reads `Compiler services TRACE mode: Enabled`. The compiler that is running has not been told: a second exception in it prints the same one line, with no dump, and the dump comes only after the compiler has been restarted. Nothing says so. It was observed by raising the same access violation three times. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project with a Sub that calls `RtlMoveMemory` with two different bad addresses, such as 16 and 32. With 0 and 0 it does nothing. +2. Run that Sub. The console prints one line, `0001 ... NATIVE EXCEPTION: ACCESS_VIOLATION ...`, and the two `[IDE]` lines. Help reads Enabled. +3. Run it again. The console prints `0002 ... NATIVE EXCEPTION: ACCESS_VIOLATION ...`, and no dump. +4. Press **Restart the compiler** and run it again. The exception's line is followed by a line per thread: `thread 000A: CompilerOrganizationParsingEnd`, `>>> thread 0008: RuntimeExpressionEvaluationExecutionStart, Crash`, and so on. + +**Expected behavior** +The next exception after "auto-activated" prints the dump, or the line says that the mode starts at the next restart. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`event_output` sets `g_TraceMode` and restarts nothing, and the compiler is told the flag only in `RequestPreInitialize`, at its start. The numbering of the exceptions starts again with each compiler. + +Severity: low. It costs only the trace output of the exceptions in between. + + + +--- + +## With DevTools open, a compiler restart stops at a debugger statement in the IDE's code + +**Describe the bug** +With DevTools open on the IDE's page, a restart of the compiler stops at a `debugger;` statement in the IDE's own code. The compiler has already been ended, so the status bar reads UNAVAILABLE with four `Disconnected` lines, with no *Please wait...* and no line in the DEBUG CONSOLE, and nothing moves. Closing DevTools lets it go on. It was observed by opening DevTools with a key bound to `tbIdeDevTools_Show`, and pressing **Restart the compiler**. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project, and open DevTools. +2. Press **Restart the compiler**. Nothing moves; DevTools shows the page paused in `restartCompiler`. +3. Close DevTools, or resume in it. The restart goes on, and the project is back about 3 s later. + +**Expected behavior** +The restart goes on with DevTools open: a shipped build holds no `debugger;` statement in a path a reader can reach. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`restartCompiler` holds the statement just before `isRestarting=true`, so a crash of the compiler and a switch of the build box take the same path. The code has more such statements, in `saveProjectMetaState`'s `.meta` branch and in a dialog's close box. With DevTools closed, Restart brings the compiler back. + +Severity: low. Nothing is lost, and a person can press Resume, but a reader who is told to open DevTools to look into a problem meets it at once. + + + +--- + +## With bin\twinBASIC_ide_win32.dll missing, the box names bin\twinBASIC_win64.dll + +**Describe the bug** +When `bin\twinBASIC_ide_win32.dll` is missing from an install, `twinBASIC.exe` opens a box that says it failed to load `bin\twinBASIC_win64.dll`, a file that is not the missing one and that is there. A reader who follows the box looks for the wrong file. It was observed in a copy of the install with the one file renamed. + +**To Reproduce** +Steps to reproduce the behavior: +1. In a copy of the install, rename `bin\twinBASIC_ide_win32.dll`. +2. Start `twinBASIC.exe`. A box titled `twinBASIC Compiler` reads `ERROR: failed to load the twinBASIC DLL:`, `bin\twinBASIC_win64.dll`, `The file could not be found. See https://twinbasic.com/missing` and `The application will now exit.` +3. Press **OK**: the process ends. + +**Expected behavior** +The box names `bin\twinBASIC_ide_win32.dll`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`twinBASIC.exe` holds the name `bin\twinBASIC_ide_win32.dll` and, in the box's text, a fixed `bin\twinBASIC_win64.dll`; its init call's box names `twinBASIC_win64` the same way. With `bin\twinBASIC_win32.exe` and its variants renamed, the box names that file. + +Severity: low. The box shows, and the link is right, but the file it names misleads. + + + +--- + +## With bin\twinBASIC_win32.dll missing, a box titled shutdown names no file + +**Describe the bug** +When `bin\twinBASIC_win32.dll` is missing from an install, `twinBASIC.exe` opens a box titled `shutdown` that reads `Run-time error '-353703418'` and `Unable to load DLL`. It names no file and gives no link, where the boxes for its two neighbours name the file and give `https://twinbasic.com/missing`. It was observed in a copy of the install with the one file renamed. + +**To Reproduce** +Steps to reproduce the behavior: +1. In a copy of the install, rename `bin\twinBASIC_win32.dll`. +2. Start `twinBASIC.exe`. The box opens. +3. Press **OK**: the process ends. + +**Expected behavior** +A box like its neighbours': the file that is missing, and the link. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +With `bin\twinBASIC_win32.exe` and its variants renamed, the box names the file and gives the link. + +Severity: low. The box shows and nothing is lost, but it does not say what to restore. + + + +--- + +## With the compiler variant the IDE starts missing, the page goes quiet after one host box + +**Describe the bug** +When `bin\twinBASIC_win32_noDEP_LAA.exe` is missing, the IDE starts, and opening a project gives one box from the host, naming the file. After **OK** the page says nothing at all: *Please wait...* goes, no project opens, nothing retries, there is no line in the DEBUG CONSOLE and no message in the page, and the status bar reads UNAVAILABLE with four `Not initialized` lines for good. It was observed in a copy of the install with the one file renamed. + +**To Reproduce** +Steps to reproduce the behavior: +1. In a copy of the install, rename `bin\twinBASIC_win32_noDEP_LAA.exe`. +2. Start the IDE and open a project with Large Address Aware on. +3. A box titled `twinBASIC IDE` reads `ERROR: failed to load the twinBASIC EXE:`, the file, `The file could not be found. See https://twinbasic.com/missing` and `Please close the IDE, and restore any missing files.` +4. Press **OK**. The IDE is left with no project and no message. + +**Expected behavior** +The page says that the compiler could not be started, and keeps saying it, since the one box is gone once it has been answered. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +`createCompilerInstance` swallows exceptions and, when the host answers 0, only hides the progress box. The host's check at its own start looks for `twinBASIC_win32.exe` alone, so with only this variant gone the IDE starts. + +Severity: low. The box names the file, and nothing is lost, but a reader who has pressed **OK** has nothing left to read. + + + +--- + +## A compiler connection that cannot be made restarts the compiler every 5 s, and the crash-loop message never fires + +**Describe the bug** +When the page cannot make its connection to the compiler, the IDE starts a new compiler every 5 s for as long as it is open, each with a `restarting from FILE` line, and the crash-loop test that would stop it after a few tries is never reached. So there is no `Compiler crash loop detected` message, no Safe Mode and no other word, and the status bar stays on UNAVAILABLE. It was observed with `--debug` on the IDE's command line, which makes a connection impossible. + +**To Reproduce** +Steps to reproduce the behavior: +1. Start the IDE with `--debug` on its command line. The compiler then starts with a console window, and the page never connects. +2. Open a project. +3. Watch the DEBUG CONSOLE: a `restarting from FILE` line every 5 s, for as long as the IDE is open. + +**Expected behavior** +A connection that keeps failing counts towards the crash-loop test, so that the reader gets the message, or the IDE stops after a few tries and says why. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +The root socket's `onerror` empties `autoRestartHistory` and calls `connectionStateChanged` 5 s later, so the list that the crash-loop test counts is empty each time it is read. `--debug` is a switch for the developers, and is only the way to reach this: by the code, a connection that fails for another reason takes the same path. + +Severity: low. Nothing is lost, but the loop runs for ever with nothing to say what is wrong. + + + +--- + +## Start in a project with no Main writes "(compile error: Main)", which gives no reason + +**Describe the bug** +Since BETA 1034, **Start** in a project whose startup object is `Sub Main` and which has no procedure called `Main` writes `(compile error: Main)` to the DEBUG CONSOLE. BETA 1033 wrote `(compile error: Unrecognized symbol 'Main')`. The line now names the symbol and leaves out what is wrong with it. + +**To Reproduce** +Steps to reproduce the behavior: +1. Make a new Standard EXE project, and replace its module with: + ``` + Module Startup + Public Sub Greet(ByVal name As String) + Debug.Print name + End Sub + End Module + ``` +2. Press F5. +3. See the DEBUG CONSOLE: + ``` + Executing 'Main'... + (compile error: Main) + [DEBUGGER] global variables have been cleared + ``` + +**Expected behavior** +The second line reads `(compile error: Unrecognized symbol 'Main')`, as in BETA 1033. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +F6's messages did not change: a procedure with a required argument still gives `(compile error: Too few arguments, expected 1 in call to 'Greet')` on both builds, and a method of a class still gives `Unrecognized member`. The change may share its cause with the nested TB5079 message of BETA 1034, since both are the same message; that is a guess from the text, not something this reproducer shows. + +Severity: low. Nothing is lost, but a reader who has not met the message cannot tell what it means. + + + +--- + +## A DLL project that names no startup object shows Default: (none), but Start runs Main + +**Describe the bug** +When `project.startupObject` is not in a project's `Settings` file, Project Settings shows **Default: (none)** for an ActiveX DLL, but **Start** does what **Sub Main** does: it looks for `Main`, does not find it, and writes a compile error. Only a `Settings` file that says `(none)` gives `[DEBUGGER] No startup object set`. The ActiveX DLL, ActiveX Control and Standard DLL templates and the samples of those kinds leave the key out, so this is what **Start** does in a new project of any of them. + +**To Reproduce** +Steps to reproduce the behavior: +1. Make a new project from the **ActiveX DLL** template. +2. Open Project Settings. **Startup Object** shows **Default: (none)**. Close it. +3. Press F5. +4. See the DEBUG CONSOLE: + ``` + Executing 'Main'... + (compile error: Main) + [DEBUGGER] global variables have been cleared + ``` +5. Choose **(none)** for **Startup Object**, save, and press F5 again. The DEBUG CONSOLE shows `[DEBUGGER] No startup object set`. + +**Expected behavior** +A project with no startup object in its `Settings` file behaves as the default Project Settings shows, so step 4 gives the line of step 5. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +The same on BETA 1033, where step 4's second line is `(compile error: Unrecognized symbol 'Main')`. The IDE's table of defaults gives `(none)` for every build type but Standard EXE, and the setting's description says the same, so it looks as though the compiler defaults to `Main` whatever the build type and the IDE does not send the default it shows. Seen on Samples 3, 4, 5, 7, 9 and 10, each with no key. + +Severity: low. Nothing runs either way; the reader gets an error about a procedure the project type does not need. + + + +--- + +## The completion list never asks for the type of the row with lazy index 0 + +**Describe the bug** +In the completion list, one row never gets its type. A variable on that row shows `variable` with nothing after it, and a function shows no text at all, while every other row shows its type. Which row it is depends on the compile, and it stays the same when the list is closed and opened again, or when the selection moves to the row. + +**To Reproduce** +Steps to reproduce the behavior: +1. Make a new project with this module: + ``` + Module Things + Public Alpha As Long + Public Bravo As Long + Public Charlie As Long + Public Delta As Long + Public Echo As Long + Public Foxtrot As Long + End Module + ``` +2. In a procedure of another module, type `Things.` so that the list opens. +3. See that five rows read `variable Long`, and that one reads only `variable`. + +**Expected behavior** +Every row shows its type. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +The same on BETA 1033. The list gets each row's type by asking for it, by the row's lazy index, as the row is first drawn. The page tests that index for truth before it asks, and the indexes count from 0, so the row whose index is 0 is never asked for. The compiler itself is right: `textDocument/lazyCompletion` for that row returns its type, as the reproducer's control shows. So the test should be that the row has an index, not that the index is true. The row's `uri` and `line` come in the same answer, so that row has no location either. The indexes do not follow the list's order, which is why the row seems to move, and it goes unnoticed when it is out of sight or is a kind of name with no type. + +Severity: low. One row lacks its type, and nothing else. + + + +--- + +## Sample 22: the Uninstall button of TBSERVICE001 registers the event log of TBSERVICE002 + +**Describe the bug** +In Sample 22, *Windows Service Complex Example*, the event log of the second service is registered by the wrong button. `btnInstall001_Click` installs TBSERVICE001 and registers its event log. `btnInstall002_Click` installs TBSERVICE002 and registers none. `btnUninstall001_Click` uninstalls TBSERVICE001 and then calls `RegisterEventLog("Application\TBSERVICE002")`, so the line looks as if it belongs in `btnInstall002_Click`. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open Sample 22. +2. Open `Sources/FORMS/MainForm.twin`. +3. Read `btnInstall001_Click`, `btnInstall002_Click` and `btnUninstall001_Click`. + +**Expected behavior** +`btnInstall002_Click` registers the event log of TBSERVICE002, as `btnInstall001_Click` does for TBSERVICE001, and `btnUninstall001_Click` registers nothing. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Read from the sample's source only. It has not been run, because that needs the sample's services installed on the machine with administrator rights, so what the Event Viewer shows for TBSERVICE002 is not known. + +Severity: low. It is a sample, but a reader copies a sample's pattern. + + + +--- + +## A Friend member answers a late-bound call, from outside the project too + +**Describe the bug** +A `Friend` member is meant for the project that declares it, and the type library leaves it out. But the `IDispatch` that the compiler writes for a class finds `Friend` members. So a late-bound call to one works, through an `Object` or a `Variant` variable or `CallByName`, and it works for code in another project that holds the object. VB6 raises error 438, *Object doesn't support this property or method*. A `Private` member raises 438 in both. + +A project that references a package can also use the `Friend` members of the package's classes, early-bound and late-bound. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `friend-late-bound.twinproj`. `Widget` has a `Friend Function GetValue`, and `FriendLib`, a package of the project, has a class `Locked` with a `Friend Function FriendFn`. +2. Run: + + ```vb + Dim O As Object = New Widget + Debug.Print O.GetValue() + Debug.Print CallByName(O, "GetValue", VbMethod) + ``` +3. See `7` twice, where 438 is expected. +4. See that `L.FriendFn()` in the module `FromTheProject`, on a `Locked` from the package, compiles. + +What decides it: +- A `Friend` Function, Property and variable are each found. A `Private` member raises 438, and a `Public` one works. +- The IDE, a built exe, win32 and win64 give the same results. +- From another project: an ActiveX DLL with a `[DllExport]` function that returns a class as `Object`, called from a second project through a `Declare`. Late-bound, the second project called a `Friend` Function and a `Friend` Property, and wrote and read a `Friend` variable. A `Private` variable raised 438, and the DLL's type library lists none of those `Friend` members. +- Not affected, and the same as VB6: a `Friend` Function with no arguments, called through a class that implements the class, raises error 97. Other signatures have a defect of their own in a 32-bit build, in the entry on the stack below. + +**Expected behavior** +Error 438 for each late-bound call, as VB6 gives, and a compile error for a `Friend` member of a package's class used from the project that references the package. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +#2409 asks for `Friend` members of a package to be hidden from IntelliSense. This entry is about the calls themselves. + +Severity: low to medium. Code that works does not stop working, but `Friend` does not keep a member private to the project, and a client of an ActiveX DLL can reach members that the type library does not show. + + + +--- + +## TB65535 gives Internal Error - Bad Error Info Locale Data in place of the missing member's name + +**Describe the bug** +When a class leaves out a member of a class or interface it implements, TB65535 no longer says which. BETA 1033 reports *Missing implementation of member Function F() As Long*, and BETA 1034 reports *Internal Error - Bad Error Info Locale Data*. The error is still in the right place, on the name after `Implements`. Only its text is lost, and it was the text that named the member. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `missing-implementation-message.twinproj`: + + ```vb + Class Base + Public Function F() As Long + End Function + End Class + + Class Partial + Implements Base + End Class + ``` +2. See TB65535 on `Base` in `Implements Base`, with the text *Internal Error - Bad Error Info Locale Data*. + +What decides it: +- BETA 1010 and BETA 1033 give the *Missing implementation of member* text, in the same harness, on the same project. +- A `Public` Function and a `Public` variable give it, with one error for each missing member, so a variable gives two: its Get and its Let. + +**Expected behavior** +*Missing implementation of member Function F() As Long*, as on BETA 1033. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +BETA 1034 takes the text of each error from the language pack, and the message has to be filled in with the member's name, which may be how it was lost. That is a guess from outside the compiler. + +Severity: low. The build stops where it should, but a class with several members has to be read by hand to find the missing one. + + + +--- + +## A Friend procedure called through a class that implements its class damages the stack in a 32-bit build + +**Describe the bug** +A class that implements another class need not provide its `Friend` procedures (#750). A call to one, through a variable of the implemented class, is meant to raise error 97, *Can not call friend function on object which is not an instance of defining class*. In a 32-bit build, the code that raises it removes one stack slot besides `Me`, whatever the procedure takes. So only a procedure that takes exactly one slot works. Any other leaves the stack wrong: a built exe ends with an access violation, or stops with an error box after the error was handled, and a run in the IDE does not return. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `implements-friend-stub-stack.twinproj`, or use this, built as win32: + + ```vb + Class Target + Public Function Pub() As Long + End Function + Friend Function F(ByVal X As Long) As Long + End Function + End Class + + Class Impl + Implements Target + Private Function Target_Pub() As Long + End Function + End Class + + Module Startup + Public Sub Main() + Dim T As Target + Set T = New Impl + On Error Resume Next + Dim L As Long + L = T.F(5) + Debug.Print Err.Number + End Sub + End Module + ``` +2. Build and run the exe. It ends with an access violation (0xC0000005), and the line after the call never runs. + +What decides it, on win32: +- One stack slot, and error 97 is raised and caught: a `Sub` with one `ByVal Long` or one `ByRef Long`, a `Function` with no arguments (Long, or String), a `Property Get`, a `Property Let` with one `Long`. A function's return value counts as one slot, which is why a `Function` with no arguments works and one with an argument does not. +- Two slots or more, and the run does not return (*no answer from the compiler*, in the compiler's test mode) or the exe ends with an access violation: a `Sub` with two `Long`, a `Sub` with one `ByVal Currency`, a `Function` with one `Long` argument. +- No slot, a `Sub` with no arguments: in an IDE run neither handler catches the error, and it stops the run in the caller. In an exe the handler runs and `Err.Number` is 97, but when the procedure returns the program stops with a box, *Run-time error '97'*, and its caller never resumes. +- win64: every one of these raises 97 and is caught, in the IDE (20 of 20 cases: ten signatures, each under `On Error Resume Next` and under `On Error GoTo`). +- VB6: the six shapes in its project (no arguments, one `Long`, two `Long`, one `Currency`, and a `Function` with none and with one) each raise 97, it is caught, and the program goes on (`bug_repro.mjs vb6`). + +**Expected behavior** +Error 97, caught by the caller's handler, whatever the signature, as win64 and VB6 do. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034 + +**Additional context** +Inferred from the pattern above and not read from the generated code: one stub, with a fixed `ret 8`, stands in for every `Friend` procedure that an implementing class leaves out. It would have to remove what each procedure takes. + +Severity: high. The program ends with an access violation for a call whose whole purpose is to raise an error that it can handle, and unsaved data is lost. + + diff --git a/WIP.Harness.md b/WIP.Harness.md index 481f0d6a0..e84b34900 100644 --- a/WIP.Harness.md +++ b/WIP.Harness.md @@ -303,7 +303,7 @@ front of the compiler. exit code 1 if the project has errors: ```sh -node scripts/tbbuild.mjs C:/probe/AttributeExplore.twinproj +node scripts/tbbuild.mjs "$TEMP/probe/AttributeExplore.twinproj" ``` It finds the IDE itself --- the newest `twinBASIC_IDE_BETA_` on @@ -1209,12 +1209,32 @@ browser target, auto-attaches to every target with `waitForDebuggerOnStart`, add with `Page.addScriptToEvaluateOnNewDocument` and resumes it. The page is `about:blank` when attached, and never navigates twice. The script puts a setter on `window.hostAppObject`, which `main2.js` assigns, and wraps the host object: `GetIDESetting` answers `""` for the blanked keys and `"{}"` for `GENERAL` and -`GENERAL2`, every other call goes through with `this` bound to the real object. The keys it left -unanswered are the licence's, `LICENCEAGREEMENTV3`, `ALLOWERRORREPORTINGV3`, `PUBLISHER` and `NEWYEAR_*`. +`GENERAL2`, `SaveIDESetting` of those keys does nothing, and every other call goes through with `this` +bound to the real object. The keys it left unanswered are the licence's, `LICENCEAGREEMENTV3`, +`PUBLISHER` and `NEWYEAR_*`. +**No error report leaves a tool's IDE.** The IDE posts one to `https://twinbasic.com/notify` for each +native exception and page error when `ALLOWERRORREPORTINGV3` says `AGREED_`, and a reproducer that crashes +the compiler is such an exception, so a machine whose owner agreed reported its test crashes (until +2026-11-04). The hook answers that key `DISAGREED_TOOLS`, and `window.fetch` answers a request for that +address itself and counts it in `__tbDefaults.reports`, in case a page turns reporting on. +`test/ide-folders.test.mjs` tests both with a stand-in `fetch`, which is why it never sends anything. +The saves are dropped for two keys' sake. `main.js` tests `g_DontSaveIdeSettings`, which is what +`--no-save-ide-settings` sets, before every save but those of `OBJECT_BROWSER`, `NEWFRONTPAGE` and +`PUBLISHER`, so a tool's IDE wrote the first two to the user's registry on BETA 1034: `NEWFRONTPAGE` as +soon as the New / Open Project dialog changed section or closed. `PUBLISHER` is written only when +someone signs in on the Package Publishing panel. +**The flag does not cover a first run.** The page reads it in `parseCommandLine`, after the first-run +questions, so while the licence agreement, the error-reporting question and the first-run IDE Options +are up, `g_DontSaveIdeSettings` is false and `LICENCEAGREEMENTV3`, `ALLOWERRORREPORTINGV3` and `GENERAL` are +written (`bugs/no-save-flag-unguarded-saves`). A tool's IDE is not a first run, because the hook answers +`GENERAL` and the machine's registry holds both answers. A test or a shot that makes one answers those keys +`""` from a proxy in front of the hook, in from the document's start, and that proxy blocks every +`SaveIDESetting` (`test/ide/starting-kit.mjs`). A new build can add a key, which is read from the user's registry until it is blanked: BETA 1023 added `OBJECT_BROWSER` (the Object Browser's view, filters, sorts and pane sizes) and BETA 1033 `NEWFRONTPAGE` (the New / Open Project dialog's section), both blanked since 2026-11-02. After a new BETA, list the keys the page passes -to `GetIDESetting` (a search of the install's `ide/*.js` for the call) and compare them with `BLANKED_KEYS`. +to `GetIDESetting` (a search of the install's `ide/*.js` for the call) and compare them with `BLANKED_KEYS`, and list +the `SaveIDESetting` calls that do not test `g_DontSaveIdeSettings`. The recent lists are left alone: `test/ide/recent-projects-copies.test.mjs` needs the IDE's own, and `shoot_docs.mjs`'s `PAGE_DEFAULTS` empties them where a picture shows them. diff --git a/WIP.HelpAddin.md b/WIP.HelpAddin.md index 0eb16981f..534b17e12 100644 --- a/WIP.HelpAddin.md +++ b/WIP.HelpAddin.md @@ -1286,7 +1286,7 @@ not: a restart saves a harness about a second against opening a new IDE. and puts context menus and hovers at the pointer's position times the zoom; the font options scale text but not icons or controls. Emulated device scaling (what a WebView2 `ZoomFactor` does) was right throughout, so whole-IDE zoom needs that host call - upstream. The probe and pictures are in `.claude/tooling-review-scratch/zoom/`. + upstream. The probe and pictures are in `.claude/scratch/zoom/`. **Lookup:** diff --git a/WIP.IDE.md b/WIP.IDE.md index 16aecc9d1..9f41cea10 100644 --- a/WIP.IDE.md +++ b/WIP.IDE.md @@ -1,7 +1,7 @@ # The IDE section — finishing it -The IDE section (`docs/IDE/`, `/tB/IDE`) is being rewritten so that every page is fit for -the book. The goal is that `docs/_book.yml` holds no IDE entry under `left_out:`. This file +The IDE section (`docs/IDE/`, `/tB/IDE`) has been rewritten so that every page is fit for +the book, and `docs/_book.yml` holds no IDE entry under `left_out:`: keep it so. This file covers the structure, the conventions, where each fact comes from, and how a chapter is written. Owner decisions: @@ -12,6 +12,10 @@ written. Owner decisions: The section is not bound to its current structure. The existing pages are raw material. +Where the work stands, what is next and what is open is in the kit, `TODO.md` in +`.claude/scratch/ide-docs/` (gitignored), under *RESUME HERE*: read it before anything +else. The kit also holds each chapter's fact sheet and its briefs. + ## What "book-ready" means for a page - No heading without text under it, no TODO, and no list of labels copied off a screenshot. @@ -30,7 +34,7 @@ children. Second person ("you") is allowed here, as in tutorials. | # | Chapter | Permalink | Takes in | |---|---|---|---| | 1 | A tour of the IDE (landing page) | `/tB/IDE` | `index.md` | -| 2 | Starting a project | `/tB/IDE/Starting` | New Project, Splash Screen, FAQ's first-launch and VBP-import parts | +| 2 | Starting a project | `/tB/IDE/Starting/` + children (**written**) | New Project, Splash Screen, FAQ's first-launch and VBP-import parts | | 3 | Projects and files | `/tB/IDE/Projects` | Project Explorer (file and object views, Add, context menu; no resource editors: images preview, the rest open as text), references and packages (links to Features/Packages), Export Project (from Menu/File) | | 3a | Project Settings (reference) | `/tB/IDE/Project-Settings` | `Project Settings.md`, kept whole; **its anchors stay**, as 19 links use them | | 4 | Writing code | `/tB/IDE/Code` + children | Editor, Find/Replace, Outline, Open Editors, History, Diagnostics, `Features/Compiler-IDE/CodeLens` | @@ -38,17 +42,16 @@ children. Second person ("you") is allowed here, as in tutorials. | 6 | Running and debugging | `/tB/IDE/Debugging` + children | Menu/Debug's run-time error walkthrough and Debugger Options, Menu/Run, Debug Console, Variables, Watches, Call Stack, Memory, `Features/Compiler-IDE/Debugging` | | 7 | Building | `/tB/IDE/Building` | Toolbar's Build configuration, Menu/File's Build and Clean, Package Publishing, links to LLVM and Project-Types | | 8 | Customising the IDE | `/tB/IDE/Customising/` + children (**written**) | IDE Options (all of them), themes and custom `.theme` files, panel layouts, keyboard shortcuts, language (from Menu/Window and Menu/Tools) | -| 9 | Add-ins | `/tB/IDE/AddIns/` | AddIns/* as they are, Community given content | -| 10 | When something goes wrong | `/tB/IDE/Troubleshooting` | Status Bar's service states, Restart the compiler, TRACE mode, error reporting | +| 9 | Add-ins | `/tB/IDE/AddIns/` | AddIns/* (no community add-in is listed yet) | +| 10 | When something goes wrong | `/tB/IDE/Troubleshooting/` + children (**written**) | Status Bar's service states, Restart the compiler, TRACE mode, error reporting | **Part B, Reference.** Written impersonally. One page per menu, plus the Toolbar, the Status Bar and the keyboard shortcuts, under `/tB/IDE/Reference/…`. Each entry gives the item, its shortcut, one or two sentences, and a link to the chapter that explains it. -- The keymap is shown as a table, not the JSON dump `Menu/Window.md` holds. The table can be - generated from `main.js` if keeping it by hand proves error-prone. Decide that when the page is - written. -- Webpage and the other panes that are not in a chapter get a reference entry. +- The keymap is a table on its own page, `Reference/Keyboard-Shortcuts.md`, generated from + `main.js` by `ide_js.mjs keymap`: never edit its rows by hand. +- Webpage, the one pane that is not in a chapter, has a page of its own here. **Moving pages.** When a page moves, its old permalink goes into `redirect_from:`, and every inbound link is changed to the new canonical URL. The 61 inbound links are in @@ -134,13 +137,13 @@ for contributors. - **`shoot_docs.mjs` and WIP.Screenshots.md** hold the states the screenshots already set up, which are often the quickest way to reach a dialog. A new picture is a new shot there, taken in both themes; never make a picture by hand. -- The kit `.claude/tooling-review-scratch/ide-docs/ide-driving-survey.md` (gitignored) +- The kit `.claude/scratch/ide-docs/ide-driving-survey.md` (gitignored) maps the debugger, Settings and Export internals. ## Writing a chapter 1. **Fact sheet.** A Sonnet agent reads `main.js` and the existing pages and writes - `.claude/tooling-review-scratch/ide-docs/-facts.md`. Each item in it has its source, + `.claude/scratch/ide-docs/-facts.md`. Each item in it has its source, a command id or a regex, and is marked *read* or *needs trying*. 2. **Trying.** The *needs trying* items are tried in a lane IDE. Use an Opus agent, one at a time, recording the registry hashes before and after as for any harness run. diff --git a/WIP.Screenshots.md b/WIP.Screenshots.md index b48031096..6e941c7e3 100644 --- a/WIP.Screenshots.md +++ b/WIP.Screenshots.md @@ -143,6 +143,38 @@ Measured: its file is left as it is** (`nearly`: one size, no channel more than 16 levels apart, at most 0.5% of the pixels apart), reported `unchanged but for a capture's noise`, with a `.noise` picture under `--diffs`. The same test decides that a light picture is the dark one. +- **A picture's two themes say the same.** A shot's passes show one state in two colours, so + a picture whose text differs is a wrong state, not the theme, and fails as one of another size + does, naming the words on each side, and is not kept (`runJob`, with or without `--svg`). A + size alone misses it: Options_ActiveX once showed an import's rows at the dark picture's size. + `TEXT_IN_CLIP` reads the page, not the SVG, as each capture is chosen, since the same text wraps + and is cut short differently in each theme: each block of text inside the clip, across the shadow + roots, with the text boxes' values and each closed select's choice. A line shows where part + of it is inside the clip and inside its ancestors' clipping boxes and is on top there, and a + block counts all its text once any line of it shows, so a line of code the clip cuts off + reads the same in both. A block is `whole` when a line of it shows its full height and half + its width, or 40 px of it, and otherwise `cut`. `themeTextDifference` + (`scripts/lib/shot-policy.mjs`) ignores white space and refuses only what one side has `whole` + and the other shows none of, since Light lays a window out a few pixels differently and a row at + the clip's edge shows more in one theme (Paused_Line's `price: 3`, Editor_Panel's `25`). A + composite's layer is not read; its parts are, as they are captured. A shot whose words must + differ says so in `themeWords`, naming them: First_Run_Options and Menu_Tools_IDEOptions show the + theme's own name (`SHOWS_ITS_THEME`). A window taken once has no pair. +- **The tool's folder is virtual in a picture** (owner). Nothing writes at a drive's root, so + `PACKAGE_ROOT` is `TOOL_ROOT`, `.claude\tbshots` in the checkout, and every picture shows it as + `SHOWN_ROOT`, `C:\tbshots`, a folder that does not exist (`scripts/lib/shot-root.mjs`). + `Lane`'s `onAttach` installs the rewrite before a project loads, and `capture` again, for a + page that has reloaded: the DOM's text setters and the page's `TEXTtoHTML`, whose `\` no pattern + would find, rewrite what they are given at once, because the console measures a row in the + task that writes it and a path 40 characters longer leaves it a line too tall; a + MutationObserver on each root takes the rest. A text box's value is rewritten just before each + capture and SVG and put back after, so that the page never reads the shown name back and acts on + it. A part that starts with a dot may have no slash before it, because Markdown, which draws + the import log, reads `\.` as an escape. A capture fails when the real folder is in the page's + text, its text boxes, the SVG or a window's texts: what cannot be rewritten is fixed, and the guard + is not relaxed. `startTidy` sweeps the folder's `ProjectState` entries; `asTempFolder` allows + exactly this one folder outside the temp folder, so two checkouts' shoot_docs runs at once + would refuse each other's. - **The reference is the picture at `HEAD`, then the file** (owner; `pictureFate` in `scripts/lib/shot-revision.mjs`). Compared with the file alone, a picture drifted from `HEAD` over the runs of one retake, each step within noise of the file before it, and a run @@ -257,9 +289,11 @@ Exit codes: 0 every picture written or unchanged, 1 a picture failed, 2 the tool restarted on an empty licence key in the page) and `splash` (the demo, for the splash alone), `programs`, `codelens`, `memory`, `package` and `fusion` (pictures of a running program), `package-server`, `settings-linked` and `package-check`, `import`, `webpage` and `web` (a - Chrome, not an IDE), `featuremap`, `code-chapter`, `debugging`, `forms-chapter`, `projects`, `projects-refs`, - `projects-start`, `customcontrols`, `code` and `sample9` (all described - below). A setup is `{name, start, prepare}`. + Chrome, not an IDE), `featuremap`, `tour`, `code-chapter`, `debugging`, `forms-chapter`, `projects`, `projects-refs`, + `projects-start`, the `building`, `starting` and `troubleshooting` setups, `customcontrols`, `code` and `sample9` (all described + below). A setup is `{name, start, prepare}`, with `closing` to read what only its page can say + before the IDE ends, `end` to check or give back what it holds after, and `noPage` for a setup with + no IDE page, whose `start` returns a stand-in for the connection and whose shots are `once`. Every add-in a setup loads has its `SaveSetting` key snapshotted, emptied and restored (`tbDocsHelp`, `GlobalSearchAddIn`). - **Jobs**: `--jobs N` (default 6) runs setups at once, each IDE in a slot of its own from @@ -309,7 +343,9 @@ Exit codes: 0 every picture written or unchanged, 1 a picture failed, 2 the tool ## Standing rules for what a picture shows (owner) - **A state is reproduced as the IDE reaches it, never by editing what it draws.** The tool - inspects the IDE's page first to see how a state can be reached. + inspects the IDE's page first to see how a state can be reached. An image taken from the files + under `ide/`, such as a badge's, counts as reached the way the IDE reaches it; what the page's + DOM holds must be captured. - **A picture of a running program comes from a program written to illustrate what the picture depicts, run while the picture is taken.** A running program's window is taken once, as the program draws it. @@ -331,6 +367,17 @@ scroll, advanced hover info, inline hints, folding always shown and the debugger are switched on in the page only; the memory figures, which change from run to run, are fixed in the page once real ones are shown, as History's times are. +## The tour + +`IDE/Images/IDE_Tour.png` (2560x1760, setup `tour`, fixture `test/shots/forms/` FormsDemo), the picture +the IDE's landing page opens on, is the whole window in the default layout with `frmMain` in its +designer, so that the Toolbox and Properties have something to show. Its status bar is in the picture, +so the compiler is restarted on `licenceKey = ""` as in `community`, and the take fails on any other +badge. A bar's label stands on the bar and a panel's in its own empty part; the Toolbox and Properties +are full, so theirs stand on the ground round the form, each with an arrow. The anchors are read from +the page, so that the labels follow the layout. `IDE/Images/IDE.png`, the window with no project, is +now on First Launch. + ## The "Writing code" chapter Setup `code-chapter` (three parts): FeatureTour, the feature map's Inventory project, with @@ -507,8 +554,8 @@ Six setups, their fixtures under `test/shots/building/`, copied from the Buildin minute (so that a second build finds its file in use), `ax` is ShotActiveX, an ActiveX DLL whose class has GUIDs of its own (`B01D...B001` to `B003`, not the lanes', so that a lane and a shot run at once never share a registration), and `pkg` is MyPackage, a PRIVATE package. Each project is -staged in a folder of the tool's own on `PACKAGE_ROOT` named for its setup (`C:\tbshots\Building` -and so on), with the templates' build path, so that a console line names no user; the setup's +staged in a folder of the tool's own on `PACKAGE_ROOT` named for its setup (shown as +`C:\tbshots\Building` and so on), with the templates' build path, so that a console line names no user; the setup's `end` removes the folder once its IDE has ended. Its 14 pictures are under `docs/IDE/Building/Images/`. @@ -557,6 +604,114 @@ are under `docs/IDE/Building/Images/`. IDE's own `showModalNotification` with the success text. The pane floats at the bottom left, out from behind the boxes. +## The "Starting a project" chapter + +Four setups, their fixtures under `test/shots/starting/`: `Throwaway` is the Starting lanes' +`test/ide/probes/starting`, `Ledger` (a form, a module and a class, no manifest) and `Missing` (a +`.vbp` naming `Gone.bas`) come from the trial's cases C, G and K. The `.vbp` fixtures are staged +with CRLF, and every project is in a folder of the tool's own on `PACKAGE_ROOT`, named for its +setup, which the setup's `end` removes. Its 14 pictures are under `docs/IDE/Starting/Images/`. + +- **`starting-first-run`**: the licence agreement, the reporting question and the first-run IDE + Options. The IDE reads `--no-save-ide-settings` in `parseCommandLine`, after those dialogs, so + they run over the guard of `scripts/lib/shot-first-run.mjs`, in from the document's start + (`Page.addScriptToEvaluateOnNewDocument`, then `Page.reload`), in front of the defaults hook: + it answers the three first-run keys, and records and blocks every `SaveIDESetting` and every + other call that writes or ends the IDE. A second proxy under the hook records any of those + that reached it. The setup's `end` prints the record, and fails on an entry in `through` or on a + changed hash of `IDESettings` (`settingsHash`, `reg query` read in memory, nothing written). +- **The guard's answers follow a real call to the host.** The page reads its theme files by host + calls made before it asks for `GENERAL`, and the host answers in order, so a first start draws + its dialogs in the theme. An answer made in the page alone overtakes those reads: + `buildThemeData` finds no theme, and the dialogs have no frame and no background. +- **Each take makes a first start and ends with a page past it** (`SETTLED`: agreed, reporting + off), however it goes, so that no first-run page runs on. The reporting question is answered + **NO**: it leads to the same IDE Options, and `g_ErrorReporting` is never set. +- **Light**: a first start is always in the dark theme, so the Light picture is that dialog with + the theme switched by its own command at the agreement, which is why the IDE Options read + Light. The clip is the dialog and 40 CSS px of the banner round it. +- **`starting`**: Enter Licence Key, the search, Recent (VB6) and the three New Project Options + boxes, each cancelled, so that no ActiveX project is ever made; the shot checks the licence box + is empty. The picker `OpenProjectVBP` is answered from `__shotAnswers`, VB6's list from + `__shotVb6Recents` (`PAGE_DEFAULTS`). A box's clip starts at the next whole CSS pixel: it can + start half a pixel into the progress box behind it. +- **`starting-new`**: a new Standard EXE and an import, each made again by its take, since a take + leaves the project changed. Its compiler starts on `licenceKey = ""` in the page, as in + `community`, and the take fails on any badge but COMMUNITY EDITION. `FormDesignerId` is a new + GUID in each new project and the log's first line holds the time, so each is set in the page + once the real one shows (`fixDesignerId`, `fixImportTime`). `Title_Unsaved` is a composite of two + crops of `#rootMenuText`. +- **`starting-boxes`**: the compiler's two native boxes, `once: true`, `window: true`, found by + `compilerBox` and closed with `BM_CLICK`. Each is a window alone. Over a maximized IDE the box + stands on the text of the progress box, and it is unaware of the display's scaling, so PrintWindow + draws it at 96 DPI in the corner of a bitmap of its scaled size, black round it (`drawnPart`), + where the IDE's window is 1.5x. It is made the active window, and sent `WM_UPDATEUISTATE` to hide + its focus cues, which came and went from run to run. +- **`Damaged_Box`**: `Throwaway` packed, its `Settings` entry moved to the front (impexp writes it + last, the IDE first) and the file cut in the middle of its last source, so that `Settings` is + read whole: damage inside that entry is an endless loop of the box. It is opened from the + tool's own Recent list and closed unsaved, and the take fails if the file changed. +- **Nothing of a project reaches the registry.** These setups add `AddRecentsList` and + `SaveProjectMetaState` to `PAGE_DEFAULTS`' blocked calls. + +## The "When something goes wrong" chapter + +Two setups, the fixture `test/shots/troubleshooting/Trouble`, the lanes' `test/ide/probes/troubleshooting` +under another name: `Sub Crash` calls `RtlMoveMemory` with two bad addresses at line 10 of `MainMod`, the +line the page quotes. Its 8 pictures are under `docs/IDE/Troubleshooting/Images/`. + +- **`troubleshooting`**: one IDE, the project in a folder of the tool's own on `PACKAGE_ROOT`, its + compiler restarted on `licenceKey = ""` as in `community` (`communityCompiler`), and + `AddRecentsList` and `SaveProjectMetaState` blocked as in `starting`. Every take that shows the + status bar fails on any licence badge but COMMUNITY EDITION, after its restarts too (`communityBadge`). + It calls `defaultLayout` once as it prepares, because `Native_Exception`'s float ends with that + call and the call moves Light's panels a device pixel: without it a picture came out one way + alone and another after that take. +- **`Services_Limited_Tooltip`, `Please_Wait`**: `whileConnectHeld`, which `Services_Limited` also + uses, restarts with the button and holds one of the page's three `connect` calls. The tooltip's + picture holds `lspSocket.connect`, so that FS reads OPERATIONAL and LSP and DEBUGGER Disconnected + (LSP reads Connecting only once the call is made), and it shows the badge's own `title` in the + replica. Its clip takes in the licence badge whole, which the tooltip stands over, and stops 3 CSS + px above the tooltip, since a line of the console is cut there. `Please_Wait` holds `fs.connect`: + the box stays at 10.0%, no missing package and no native box needed, and the clip is the editor's + width and 190 CSS px above and below the box. +- **`Crash_Loop`** (the whole window): a restart by the button starts the count again with its one + entry, then three kills, each by the pid in `g_CurrentCompilerProcessId` (`killTree`), the first + two waiting for the project to be back. The take then has the float and the box together, its + console's times set in the page, and ends on the box's OK and Ctrl+F2, about eleven seconds + (`leaveSafeMode`). Start is never pressed in Safe Mode. +- **`Safe_Mode_Bar`** is a composite as `Title_Unsaved` is, the left 474 CSS px of the toolbar over the + left 474 of the status bar: in a whole window the box and the badge are at opposite ends and shrink + with the page. The box is set in the page, OK is pressed, and each theme's take comes back to win32. +- **`Native_Exception`**: `Crash` run as a line of the console runs it, the console floated 1060 CSS + px wide so that each line is one row. `debugConsoleContent` keeps a row's height from when it was + first measured, in whatever width the console had, and draws only the rows that show, so the lines + are read from its `dataNodes` and the take calls `recalcCachedItemHeights`, as a resize does, and + waits for three rows of one line each. The time and the address differ from run to run and from + boot to boot, and are set in the page to the page's values (`fixNativeLine`). The two yellow lines + come only from the first exception with `g_TraceMode` off, and it then stays on: the take fails + unless there are one red line and two yellow ones, and ends with TRACE mode off by Help's + command, with the restart that brings. +- **Lane.close and the exception.** `Lane.close` counts the console's `NATIVE EXCEPTION` lines and + throws; a kill writes none. `Native_Exception` asserts that its own one is the only one and + clears the console, and that is the whole allowance: any other exception stays in the console, and + `closing` fails the setup on it, since `endRun` only prints what `Lane.close` throws. +- **`Menu_Help_Trace`**: `menuShot` with `Menu_Help`'s clip, TRACE mode switched by `before` and back + by `after`, each with its restart; its `check` fails the picture unless the item reads Enabled with + its `codicon-check`. +- **`Floating_Boxes`**: three messages the IDE gives, sent by `msgBox` in the take. The middle + one, `msgBox2` (`msgBox1` is the newest, at the bottom), is the IDE's *Backend compiler seems to be + busy* message, the only one here long enough for a click to change what shows; it gets a real + click. The boxes last 60 s, so the take closes them with their own close calls. +- **`closing`** prints `__tbDefaults.reports`. The defaults hook answers `ALLOWERRORREPORTINGV3` no, so + `postError` returns at once and the exceptions explain 0: anything else fails the setup, as does + a request for the report's address, which CDP's `Fetch` stops and counts, and a JavaScript dialog. + A take fails on a dialog too (`noAlert`): an alert cannot be captured. +- **`troubleshooting-box`** (`noPage`): the lane's copy with `bin\twinBASIC_ide_win32.dll` renamed, + `launchOnDesktop` with `IDE_FLAGS`, and `boxPicture` of `starting-boxes`, `once`, `window`. The take + fails unless the box's text is the one the page describes; it names `bin\twinBASIC_win64.dll`, + which is there. OK ends the process, and one that outlives the box is ended by its pid. + ## Special IDE states - **`Services_Unavailable`**: the badge in the `no-project` setup. With no project the badge's @@ -586,7 +741,7 @@ are under `docs/IDE/Building/Images/`. the IDE starts another. `Network.setBlockedURLs` does not stop a WebSocket, and throttling the page's CPU (`Emulation.setCPUThrottlingRate` 40) holds LIMITED but makes every evaluation time out. The shot holds the page's `fs.connect` call of a restart (the toolbar's button) until the - capture and then makes it as the IDE did. The restart's DEBUG CONSOLE line names the project's + capture and then makes it as the IDE did (`whileConnectHeld`). The restart's DEBUG CONSOLE line names the project's folder, under the user's, and Clear does nothing until the compiler is connected, so `nameUserAsUserIn(c, ".debugConsoleOuter")` replaces the name there in the page; the console is cleared with its button once the compiler is back. @@ -676,7 +831,7 @@ are under `docs/IDE/Building/Images/`. - **`Toolbar_4`**: setup `designer`, the sample run with Start (`context.codeExecuting`) and stopped after. - **`8d74d820`** (setup `package`) and **Fusion's `569099635`** (setup `fusion`): the paths the - console names are on `PACKAGE_ROOT`, `\tbshots` at the root of the temp folder's drive + console names are on `PACKAGE_ROOT`, shown as `C:\tbshots` (`Lane.open`'s `buildPath`; the fixture's `project.fusionBuildPath` in the staged copy), removed at the end unless it was there before. - **A Fusion host needs a Fusion reference that holds an ActiveX control.** `settings-fusion`'s @@ -729,9 +884,10 @@ Taken on BETA 1010, in both themes where the theme shows. then renames the user in the page. - **`f48a7254`** starts at the question's left edge: the row being ticked shows an animated spinner no two captures hold alike. **`e2a65dfe`**: the completion list is the IDE's own - (`.debugConsoleEntryIntellisenseBox`), shows types as `...` until the compiler gives them - (now and then never: the dot is retyped), and is placed by the list the name before the dot - opened unless that is closed first. It is 500 px wide (its CSS); `triggerIntellisense` + (`.debugConsoleEntryIntellisenseBox`), shows types as `...` until the compiler gives them, + never asks for the row with lazy index 0 (`bugs/completion-row-zero-no-type`: the shot asks for + it, and retyping the dot would not help, since the indexes stay), and is placed by the list the + name before the dot opened unless that is closed first. It is 500 px wide (its CSS); `triggerIntellisense` places it at the text cursor's box as drawn at the time, which is at the dot or, now and then, already after it (the picture 1198 px wide against 1184): the shot moves it to where the cursor's box stood at the dot (owner). @@ -748,7 +904,7 @@ Taken on BETA 1010. - **`16833fae`** (setup `import`): the sample open, and `test/shots/vbp` (ImportDemo: a form, a module, `..\Shared\modShared.bas` for `{PARENT-FOLDER}`) staged with CRLF on - `PACKAGE_ROOT\Projects` (the path the dialog shows, `C:\tbshots\Projects\ImportDemo\ImportDemo.vbp`), + `PACKAGE_ROOT\Projects` (the dialog shows it as `C:\tbshots\Projects\ImportDemo\ImportDemo.vbp`), with a `.res` the tool writes (`scripts/lib/res-file.mjs`: three icons and the fixture's manifest). The IDE's Open dialog is native, so the take calls `importFileByPath` with the file, what the IDE calls with the file chosen; the form's two files are ticked with real diff --git a/WIP.Wisdom.md b/WIP.Wisdom.md index b90810a62..be0975374 100644 --- a/WIP.Wisdom.md +++ b/WIP.Wisdom.md @@ -24,6 +24,13 @@ reviewer applying the rule will read its "tell Claude: …" instructions as a scope violation and propose moving the *public page* to a maintainer file. That is declined: leave `docs/Documentation/Wisdom.md` where it is. +## What each phase takes + +- **`export` keeps everything it can read**: every text and announcement channel and every thread, a forum post nobody answered included. Discord's `message_count` leaves out a thread's first message, so that post counts 0, and `min_message_count` stays 0. The `github-.*` channels are bot feeds and stay excluded. The bot has no access to `#bugs_old`, `#ideas_old`, `#jon-work`, `#advent_of_code_2024`, `#moderator-only`, `#supporter-lounge` and `#vip-lounge`, and the owner has decided they are not needed (banter, or no longer relevant). They stay in `denied.json` and are tried on every run. +- **`process` skips a thread of one message** (PLAN-2), so a post nobody answered is in `raw` and never reaches the hounds (owner). +- **`extract` reads threads, and the months of a text channel.** `process.unmined_channels` in `config.jsonc` names the channels with no month files: `#general` and `#off-topic` (owner: wait for the yield of the small channels), `#docs` (owner: followed closely, and it holds nothing that would change how the work is done) and `#rules`. Before taking `#general` off that list, split its months by size: a month is several hundred KB, where the largest month read so far is 56 KB. +- **Never run `process --force` without `--channel`.** The frontmatter of about 1,800 threads and their entries in `extract-state.json` both lack `last_message_id`, which is why they compare equal. Rendering such a thread again writes the field, the thread then counts as changed, and the next `extract` reads them all. A channel with no month files gets them from a plain `process`. + ## Phase 3 --- Extract invocation Phase 3 runs Claude agents over the processed thread `.md` files and drafts documentation additions. The default flow is **incremental**: only threads whose Discord-side content changed since the last successful merge are re-extracted. With the incremental `export` and `process` phases, the routine update is three commands with no flags: @@ -39,8 +46,10 @@ The `extract` step is a three-stage flow that Claude runs: prep → workflow → 1. **Prep**: `node wisdom/wisdom.mjs extract` filters threads against `wisdom/data/findings/extract-state.json` (the per-thread `last_message_id` + `message_count` watermark, advanced on each successful merge). Only changed threads survive the filter. Shared reference files (`package-summary.txt`, `page-index.json`) are written once; per-batch files contain thread file paths, per-thread file sizes, config, and a `mode` field (`incremental`, `since`, `all`, or `force`) consumed by the merge step. - If the filter is empty (no threads have changed), prep exits with `No new threads since the last successful merge` and the merge step is unnecessary. 2. **Run the workflow**: check which prep layout was produced: - - **Single-batch** (≤200 threads) --- `extract-prep.json` exists: read it, pass its parsed contents as `args` to the Workflow tool with `scriptPath: "wisdom/extract/workflow.mjs"`. Write the returned `{ additions: [...] }` array to `wisdom/data/findings/extract-results-0.json`. - - **Multi-batch** (>200 threads) --- `extract-manifest.json` exists: read it, then for each entry in `manifest.batches`, read the corresponding `extract-batch-{i}.json`, pass it as `args` to the Workflow tool, and write the returned additions array to `extract-results-{i}.json`. Skip batches whose result file already exists (resumability). + - **Single-batch** (≤200 threads) --- `extract-prep.json` exists: read it, pass its parsed contents as `args` to the Workflow tool with `scriptPath: "wisdom/extract/workflow.mjs"`. Write the returned `{ additions: [...], read: [...] }` object to `wisdom/data/findings/extract-results-0.json`. + - **Multi-batch** (>200 threads) --- `extract-manifest.json` exists: read it, then for each entry in `manifest.batches`, read the corresponding `extract-batch-{i}.json`, pass it as `args` to the Workflow tool, and write the returned object to `extract-results-{i}.json`. Skip batches whose result file already exists (resumability). + - **Do not paste a batch into `args`**: it is about 20 KB of paths. Write a copy of `workflow.mjs` under `.claude/` with the `var data = ...` line holding the batch's JSON, pass that as `scriptPath`, and take the result from the `result` key of the task's output file, never from the notification, which escapes `>` as `>`. A new prompt or agent setting gets a trial on two files first. + - **Read `git status` after a run**: an agent has Bash, so it stays read-only by its instructions alone, while it reads public chat. Start with a clean working tree. 3. **Merge**: `node wisdom/wisdom.mjs extract --merge` reads every `extract-results-*.json`, grafts the additions into `staging.md` per the merge semantics in [wisdom/PLAN-3.md](wisdom/PLAN-3.md#merge-semantics), and advances `extract-state.json`. Atomic write via temp+rename; previous `staging.md` retained as `staging.md.bak` for one generation. 4. **Review**: `wisdom/data/findings/staging.md` is the long-lived human-review file --- grouped by target page, with `[DUPLICATE?]` markers for cross-thread overlaps, `[REFINED?]` markers for previously-processed findings whose source thread has grown, and an `Unmapped Findings` section for findings with no existing doc page. The reviewer can append `[LOCKED]` to any section header to prevent auto-replacement on the next merge. diff --git a/WIP.md b/WIP.md index acabbe465..45054d151 100644 --- a/WIP.md +++ b/WIP.md @@ -92,7 +92,7 @@ The rest of this file is the maintenance guide for updating existing pages or ad - `docs/Reference/Procedures and Functions.md` — alphabetical index of procedures/functions. - `docs/LLVM/` — the LLVM section: compiling with the LLVM back end. A top-level section between Features and Reference Section in the nav, at `nav_order: 6` (Features is 5). `index.md` is the landing page and `Getting-Started.md` the only page so far, with its screenshots under `Images/`. Everything it describes arrived in **BETA 984**. Its language claims are checked on BETA 1034 by `node test/llvm/run.mjs` (its README lists each claim and its page; run it after a new BETA), its callouts by their reproducers, and both with `tbrun --llvm` and `--exe`: the comma, space and comma-space spellings of `[CompilerOptions]` all turn LLVM on (`+optimize` alone does not, and an attribute replaces the project's options, so `[CompilerOptions("+optimize")]` is non-LLVM under project-wide LLVM), `[CompilerOptions("")]` turns it off under project-wide LLVM, the long CPU-flag sample runs, an error is lost between procedures when either the callee or the caller is LLVM-compiled, `Err.Raise` is unreadable in an LLVM handler (`-353703423`), a `Double` `^` overflow raises no error and gives 0, and in 32-bit code not compiled for SSE2 (`+sse2` or a flag that includes it) a `*`, `+` or `-` overflow raises none either and gives infinity, while a 64-bit build raises 6 and keeps the old value (`FloatingPointErrorChecks(True)` changes nothing), and `Debug.Assert` is skipped and its condition not evaluated, and an exe built with LLVM ends with an access violation and no box at the first `GoSub` it runs, 32-bit and 64-bit, while an IDE run is right (the page's WARNING and the GoSub...Return page's, `bugs/gosub-llvm-exe-crash`), and a 64-bit LLVM call of a `Declare` that passes `ByVal 0&` for a ByRef eighth parameter of a 32-bit type can end with an access violation in the API, in an exe and in an IDE run, depending on what the stack held (the first to seventh parameters and a `ByRef LongPtr` eighth are right; the page's WARNING and the Declare page's, `bugs/llvm64-byval-to-byref-declare-crash`), and a `For i = 1 To 7 Step x` whose `x` is a `ByVal Long` or `Single` parameter, in a procedure with a `ByRef ... As Boolean` or `As Integer` parameter after `x`, runs with a wrong step (or a wrong start or end, when those are parameters too) in an exe and in an IDE run, silently; the value depends on where the caller's argument for the `ByRef` parameter is stored, 20 in a 64-bit build and 25 in a 32-bit one for a local (a second parameter of another type, `ByVal`, or placed first, an `Integer`, `Double` or `Currency` step, a `Do` loop, no `Step` and a literal `Step` are right; the page's WARNING and the For...Next page's, `bugs/llvm-for-step-constant`). Also on 1034: with LLVM on under Compiler Options (DEBUG) for the whole project, an IDE run of a `[CompilerOptions("")]` procedure that calls an LLVM-compiled one ends in an access violation at the call (a built exe is fine; the page's WARNING). Its samples are marked `check_build`, which compiles them; `examples.bat --llvm` also builds them with LLVM. - **A new top-level section needs four things besides its folder**, and nothing checks the first and the third: a `nav_order` between its neighbours; an entry in `docs/_book.yml` for every page, in a part or in `left_out:` with a reason, which the build warns about under its `pdf:` summary when one is missing; a line in *Where content lives* in `docs/Documentation/Authoring.md`; and the page-count rise that `build.bat` writes to `builder/page-baseline.json`, committed with the pages. The Challenges and Videos sections are in `left_out:`, and so are the IDE pages that are still screenshots and labels; the IDE part names its pages one by one, so a new IDE page warns until it goes into the part or into `left_out:`. A link from the book to a left-out page opens the website, and the pass over `book.html` lists it as `OUT OF BOOK`. A section index lists its topics by hand and sets `has_toc: false`, or the template appends a second, automatic list of its children. + **A new top-level section needs four things besides its folder**, and nothing checks the first and the third: a `nav_order` between its neighbours; an entry in `docs/_book.yml` for every page, in a part or in `left_out:` with a reason, which the build warns about under its `pdf:` summary when one is missing; a line in *Where content lives* in `docs/Documentation/Authoring.md`; and the page-count rise that `build.bat` writes to `builder/page-baseline.json`, committed with the pages. The Challenges and Videos sections are in `left_out:`. The IDE part holds every IDE page, and names its chapters one by one, so a new page in a new IDE folder warns until it goes into the part. A link from the book to a left-out page opens the website, and the pass over `book.html` lists it as `OUT OF BOOK`. A section index lists its topics by hand and sets `has_toc: false`, or the template appends a second, automatic list of its children. - Footer rendering — [builder/template.mjs](builder/template.mjs)'s `renderFooterCustom()` renders the copyright line and, when `vba_attribution: true` is set in a page's frontmatter, an additional CC-BY-4.0 attribution line beneath it. - Contributor authoring guide --- see [Authoring a page](#authoring-a-page). @@ -129,11 +129,11 @@ path contains a username. node scripts/impexp.mjs export .twinproj # a project's or package's sources node scripts/census_attributes.mjs --out census.md # every attribute, by enclosing construct node scripts/sweep_attributes.mjs --out sweep.md --dump-results sweep.json # every attribute at every site, 8 to 16 min -node scripts/tbbuild.mjs C:/probe/Thing.twinproj # does it compile +node scripts/tbbuild.mjs "$TEMP/probe/Thing.twinproj" # does it compile node scripts/tbrun.mjs # what does it print ``` -- **Every IDE the tools start is given `--no-save-ide-settings` and `--no-update-recent-files`, and BETA 1007 is the oldest build any tool runs** (owner: "we never run anything before that"). The two flags are `IDE_FLAGS` in `scripts/lib/tb-ide.mjs`, passed by `launchIde` on both its branches; any new code that starts `twinBASIC.exe` passes them too. They keep the IDE from writing the user's options, panel layouts, recent list, remembered build targets and licence answers to the registry; what they leave is `ProjectState`, the `.twinproj` association, the `Window` key and `RemoveRecentsList`, which `scripts/lib/tb-registry.mjs` puts back or a test blocks in its page. **Never pass `--reset-ide-settings`** (it writes empty options to the real registry even after `--no-save-ide-settings`); an unknown `--` flag is ignored without a word; a second positional argument opens a modal box nobody can answer. An older install is passed over by `findIde`, and an older one named by `--ide` or `TB_IDE` is refused with exit 2 before an IDE starts (`MIN_BUILD` and `unsupportedIde` in `scripts/lib/tb-install.mjs`): there is no fallback and no switch to allow it, and the control for a probe is BETA 1007 itself. **The flags stop the IDE writing its settings, and a hook stops it reading them**: `launchIde` answers every IDE's `GetIDESetting` with "no saved value" for its options, panel layouts and keyboard groups, in the page only (`scripts/lib/tb-defaults.mjs`; the answer for `GENERAL` and `GENERAL2` must be `"{}"`, never `""`, which the IDE reads as a first run and opens its Options dialog), so every tool's IDE runs with the defaults; `attachIde` fails the run when the page did not run the hook, and `userSettings: true` is the opt-out, passed by `try-help-addin` only. The reasons and the registry's details are in [WIP.Harness.md](WIP.Harness.md#what-a-run-leaves-in-the-registry-and-putting-it-back). +- **Every IDE the tools start is given `--no-save-ide-settings` and `--no-update-recent-files`, and BETA 1007 is the oldest build any tool runs** (owner: "we never run anything before that"). The two flags are `IDE_FLAGS` in `scripts/lib/tb-ide.mjs`, passed by `launchIde` on both its branches; any new code that starts `twinBASIC.exe` passes them too. They keep the IDE from writing the user's options, panel layouts, recent list, remembered build targets and licence answers to the registry; what they leave is `ProjectState`, the `.twinproj` association, the `Window` key and `RemoveRecentsList`, which `scripts/lib/tb-registry.mjs` puts back or a test blocks in its page. **Never pass `--reset-ide-settings`** (it writes empty options to the real registry even after `--no-save-ide-settings`); an unknown `--` flag is ignored without a word; a second positional argument opens a modal box nobody can answer. An older install is passed over by `findIde`, and an older one named by `--ide` or `TB_IDE` is refused with exit 2 before an IDE starts (`MIN_BUILD` and `unsupportedIde` in `scripts/lib/tb-install.mjs`): there is no fallback and no switch to allow it, and the control for a probe is BETA 1007 itself. **The flags stop the IDE writing its settings, and a hook stops it reading them**: `launchIde` answers every IDE's `GetIDESetting` with "no saved value" for its options, panel layouts and keyboard groups, in the page only (`scripts/lib/tb-defaults.mjs`; the answer for `GENERAL` and `GENERAL2` must be `"{}"`, never `""`, which the IDE reads as a first run and opens its Options dialog), and drops its saves of those keys, because the flag does not guard two of them (`OBJECT_BROWSER`, `NEWFRONTPAGE`), so every tool's IDE runs with the defaults, and it answers `ALLOWERRORREPORTINGV3` no and refuses the error-report address, so **no tool's IDE reports its crashes to the developers**; `attachIde` fails the run when the page did not run the hook, and `userSettings: true` is the opt-out, passed by `try-help-addin` only. The reasons and the registry's details are in [WIP.Harness.md](WIP.Harness.md#what-a-run-leaves-in-the-registry-and-putting-it-back). - **Pack and unpack projects with `impexp`, never with the compiler executable's `import` or `export` verbs.** The tooling does both in process, through `packTree` and `unpackProject` in `scripts/lib/tb-project.mjs`; by hand it is `node scripts/impexp.mjs`. The compiler's `import` refuses a project that embeds a package (exit 999, twinbasic/twinbasic#841), and both verbs exit 0 after the failures they report. The reasons, and the path and stdin rules for anything that does run the executable (`bug_repro`'s `cli` mode), are in [WIP.Harness.md](WIP.Harness.md#getting-at-the-twin-sources). - **`tbbuild` exit codes:** 0 clean, 1 the project has errors, 2 the harness failed or the project file cannot be read (empty, cut short, not a project: it is read before an IDE starts), 3 the compile never settled or met its limit, 4 the project crashes the compiler, 5 the build failed after a clean compile (an LLVM refusal in its log included, an exe linked or not, and the build's limit). `--json` returns one object, `--keep` leaves the IDE running. **`--build` and `--llvm` (which implies it) build the project after a clean compile**, because LLVM code generation runs in a build and a compile never asks for it. They first stage a copy with an explicit build path (`%TEMP%\tbbuild\\`), so the given `.twinproj` is untouched. A plain `--build` is the control for an `--llvm` run. `--llvm` refuses a Community or Personal licence. - **It runs the IDE on a private Windows desktop**, so it cannot seize focus. Set `TBBUILD_SHOW=1` while working interactively and leave it unset for unattended runs: a wedged IDE nobody can see is expensive to diagnose. @@ -591,14 +591,14 @@ Why the report separates the wedged task from the merely blocked ones, and why - `addin-test.bat` — tests IDE add-ins by operating an IDE: every lane in `test/addin/lanes.mjs` builds the add-ins it tests into a private copy of the install, opens a project and checks what the add-in does. Outside every gate and outside CI for the same reasons as `examples.bat`; the eighteen lanes: Samples 10 and 15, the fourteen probe lanes behind the answers in [WIP.HelpAddin.md](WIP.HelpAddin.md) (`keys-help` is `keys` with the help add-in loaded), and the help add-in in `add-in/` twice, built with its committed copy of the symbol index, `add-in/Resources/SYMBOLS/symbols.json`, which a local build rewrites whenever the index changes. The `help` lane serves the built `docs/_site` on localhost to the add-in's pane; `help-offline` builds the add-in with a help archive of `docs/_site-offline` and the pane's pages come from the add-in's own server. So run `build.bat` first. Exit 0 every lane passed and the registry is as it was found, 1 a lane failed, 2 the harness failed, 3 the registry or a work folder was not put back. See [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) for its rules. - `ide-test.bat` --- the same runner for scenarios that operate the IDE itself rather than an add-in: every lane in `test/ide/lanes.mjs`. Same exit codes, same standing outside every gate and outside CI, same rules. - `try-help-addin.bat` --- opens one IDE on the user's desktop with the help add-in built and loaded, to try it by hand, and puts the registry back once it is closed (see [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler)). Needs `build.bat` first. Exit 0 closed and put back, 1 the add-in or the project did not compile, 2 the tool could not run, 3 not put back. -- `node scripts/shoot_docs.mjs` --- writes the documentation's IDE screenshots (plan and owner's decisions: [WIP.Screenshots.md](WIP.Screenshots.md)). Each picture is a shot in a table, `out` its path under `docs/` (`--only` matches it), in a setup (the main three below; the full list is in WIP.Screenshots.md), each one IDE on a **private** desktop started only when a shot in it is selected: `help` (builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo`, `TB_DOCS_HELP_REAL_TITLEBAR=0`: the nine `docs/IDE/AddIns/Images/Help_.png`), `no-project` (`Lane.openNoProject` with `includeProjects`: the menus, cut out with alpha, the dialogs that need no project, and the empty panels, each floated on its own, after the dialogs) and `project` (the demo without the add-in: About, which needs the compiler's licence answer, and IDE Options, whose font lists need a project). One setup is not an IDE: `web` starts puppeteer's Chrome through `launchOnDesktop` on a private desktop of its own (never headless, never on the user's desktop; native-window occlusion and occluded-window backgrounding switched off, or a capture waits forever or comes back blank) for the two public pages the FAQ shows, taken once; it needs the network, as do `webpage` (the Webpage pane on `https://docs.twinbasic.com/`, its page frozen from the pane's own DevTools target and shown in a frame of the IDE's page at the pane's place, as Help_Window's docs frame is shown by srcdoc, so that the SVG can draw both: `scripts/lib/shot-freeze.mjs`) and the package-server setups, which never publish. Every IDE gets `--force-device-scale-factor=1` (`Lane`'s `browserArgs`), else the page follows the display's scaling; the page is overridden to 1280x880 at 2x, and `PAGE_DEFAULTS` puts the IDE's options, panel layouts and keyboard groups to their defaults and the recent lists to the tool's own **in the page only**, nothing written because the IDE has `IDE_FLAGS` (the one recent-list call they do not guard, `RemoveRecentsList`, is replaced first). Each picture is a `Page.captureScreenshot` with a `clip`; a page showing it gives it `{:width height}` of half its pixel size. **Every picture is taken in two themes by one IDE**: dark as `X.png`, then, after `backToPrepared` and `tbTheme_SwitchToLightMode`, Light (not Classic) as `X.light.png` beside it, which the build pairs by name ([WIP.Build.md](WIP.Build.md#pictures-in-two-themes)); a light picture with the dark one's pixels is not kept, and one more than 32 device pixels off the dark one's size fails (a wrong state, not the theme). A label's pill follows the theme unless the shot names a `tone`. A shot's `annotate` list (arrows, rings, badges...) is drawn by `scripts/lib/shot-annotate.mjs` as one SVG layer over the page before the capture and removed after it, the clip growing to hold it. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`; a capture is not byte-stable, so `capture` captures back to back, with no rest, until one equals the file on disk or two agree, and, when the two that agree are not the file, goes on capturing further apart, for 5.5 s at most, until one is, keeping the last two that agreed, and a picture that differs from the file only by a capture's noise (16 grey levels, 0.5% of its pixels) is left as it is. **The reference is the picture at git `HEAD` first, then the file** (owner): compared with the file alone, a picture drifted from `HEAD` over the runs of one retake, each step within noise of the file before it. A capture equal to or within noise of `HEAD`'s picture leaves the file as `HEAD` has it, its SVG too (`HEAD`'s, or none), reported `restored` when it held anything else; one that is not, but is within noise of the file, leaves the file (an earlier run's real change); a picture `HEAD` lacks is compared with the file only (`pictureFate`, `scripts/lib/shot-revision.mjs`; the run's pictures at `HEAD` read in one `git cat-file --batch`). `--against ` takes another revision, `--against-disk` the file only. **`--cleanup`** (`--dry-run` to list) starts no IDE: it resets every PNG git reports modified that is within noise of `HEAD`'s, with its SVG, so that only real changes stay; one noise rule, `withinNoise` in `scripts/lib/shot-change.mjs`, for the capture, `--cleanup` and `sheet.mjs --against`. **The manifest, `test/shots/manifest.json`** (`scripts/lib/shot-manifest.mjs`; [WIP.ShotReview.md](WIP.ShotReview.md#remembered-overrides)), records the build of each picture a run writes, and holds each picture's allowances (its noise rule's limits and rectangles left out, its SVG's bar) and a review's decisions, each tied to the bytes it was made on; `decidePicture` and `decideSvg` (`scripts/lib/shot-policy.mjs`) judge with them, in a run, in `--cleanup` and in `replay.mjs`, so a run from a terminal and one from the review tool come out the same. It is read and written only for the default `--out`. **`--stage `** (a new or empty folder git ignores) writes nothing under `docs/`, to the bundle cache or to the manifest, and puts each capture, its SVG, bundle, difference pictures, strength maps and verdict there (`scripts/lib/shot-stage.mjs`), for the review tool to apply; **`--events`** prints a `@@shot-event` line per event, and then a line `stop` on stdin ends the run as at its normal end; `replay.mjs` takes both ([WIP.ShotReview.md](WIP.ShotReview.md#runs)). `--jobs N` (default 6) runs the setups at once, each in a slot of its own from `tbbroker`, a full run with `--svg` in about 12 minutes on BETA 1010 (`--jobs 1`: one at a time); `--diffs ` writes the old picture, the new one and an amplified difference map for each that changed, named with its theme. Settle by a condition, never a fixed sleep, and a picture that shows some state sets it itself rather than relying on the shots before it. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; a picture that holds DEBUG CONSOLE lines has their times (and a CodeLens run's time taken) set in the page once the real ones show. **A running program's window** (setup `programs`, fixture `test/shots/programs`) is taken **once** (`once: true`, the dark pass only) with `captureWindow`, cropped to its visible frame, at the display's DPI, shown at its size at 96 DPI; its shot says `window: true` in the table, and a shot whose take and table disagree fails. **`--list`** starts no IDE: it prints the table as one line of JSON (`[{out, setup, once, window}]`), which the review tool's page of every picture reads. **The Windows user name must never be in a published picture**: the visible text of the page, text boxes included, is searched for it before the first capture and again when a capture is chosen, with the picture's SVG under `--svg` (a page can draw again in between: Project Settings puts a real path back over the one the shot set), and for a program's window the texts of the window and its children, and the run fails when it is there. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in's detached window has a port from the broker (the help setup's slot carries one), since the add-in is told it before it binds it. **`--svg`** also draws each picture as SVG from the page as it stands at the capture (`scripts/svgshot/`: a DOM snapshot converted, its text set in per-picture subsets of the fonts the IDE drew it with, cut by `scripts/subset_font.py`, which needs Python with fontTools), compares it with the PNG, and writes `X.svg` or `X.light.svg` beside it only when no more than 0.25% of its pixels are far off; otherwise an older SVG is removed and the page shows the PNG; the SVG of a picture whose PNG is not rewritten stays as it is, with its bundle, unless a review accepts the new one (owner), but its bundle goes to the cache when the SVG drawn is the file's to the byte (owner, `bundleFollowsSvg`), and the SVG is written with it when the cached bundle cannot draw the picture again (owner, `staleBundle`) ([WIP.Build.md](WIP.Build.md#pictures-as-svg)). With `--svg` each picture's *bundle*, what its SVG is drawn from, is saved in the gitignored `.svgshot-bundles/` (`--bundles`; never under `docs/`, a bundle holds the user name), and **after a change to the converter, `node scripts/svgshot/replay.mjs` draws every SVG again from the bundles, with no IDE**, in about 20 s for the 370 (an SVG the same as its file is not compared again, `--recheck` compares all; the fonts' cuts are kept in `.svgshot-bundles/subsets/`; one browser per process, GPU off); it lists the pictures whose bundle cannot answer what the converter now asks, with the `--only` command that retakes them ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). `node scripts/svgshot/sheet.mjs --out ` writes a page of the pictures with no SVG (`--all`: every picture), each drawn again from its bundle beside its PNG and difference map, worst first; it is drawn without the user-name check (owner: the sheet is the owner's alone), so `--out` is refused under `docs/` and anywhere git does not ignore; what it and replay share is `scripts/svgshot/redraw.mjs`, and its pages are `scripts/svgshot/sheets.mjs`, made of the pieces in `scripts/lib/shot-views.mjs`. `node scripts/svgshot/bench.mjs` tests the converter's replicas of what Chromium draws outside the DOM (scrollbars, drop-down lists) in Edge, with no IDE ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. +- `node scripts/shoot_docs.mjs` --- writes the documentation's IDE screenshots (plan and owner's decisions: [WIP.Screenshots.md](WIP.Screenshots.md)). Each picture is a shot in a table, `out` its path under `docs/` (`--only` matches it), in a setup (the main three below; the full list is in WIP.Screenshots.md), each one IDE on a **private** desktop started only when a shot in it is selected: `help` (builds the add-in as `try-help-addin.bat` does, opens `test/addin/helpdemo`, `TB_DOCS_HELP_REAL_TITLEBAR=0`: the nine `docs/IDE/AddIns/Images/Help_.png`), `no-project` (`Lane.openNoProject` with `includeProjects`: the menus, cut out with alpha, the dialogs that need no project, and the empty panels, each floated on its own, after the dialogs) and `project` (the demo without the add-in: About, which needs the compiler's licence answer, and IDE Options, whose font lists need a project). One setup is not an IDE: `web` starts puppeteer's Chrome through `launchOnDesktop` on a private desktop of its own (never headless, never on the user's desktop; native-window occlusion and occluded-window backgrounding switched off, or a capture waits forever or comes back blank) for the two public pages the FAQ shows, taken once; it needs the network, as do `webpage` (the Webpage pane on `https://docs.twinbasic.com/`, its page frozen from the pane's own DevTools target and shown in a frame of the IDE's page at the pane's place, as Help_Window's docs frame is shown by srcdoc, so that the SVG can draw both: `scripts/lib/shot-freeze.mjs`) and the package-server setups, which never publish. Every IDE gets `--force-device-scale-factor=1` (`Lane`'s `browserArgs`), else the page follows the display's scaling; the page is overridden to 1280x880 at 2x, and `PAGE_DEFAULTS` puts the IDE's options, panel layouts and keyboard groups to their defaults and the recent lists to the tool's own **in the page only**, nothing written because the IDE has `IDE_FLAGS` (the one recent-list call they do not guard, `RemoveRecentsList`, is replaced first). Each picture is a `Page.captureScreenshot` with a `clip`; a page showing it gives it `{:width height}` of half its pixel size. **Every picture is taken in two themes by one IDE**: dark as `X.png`, then, after `backToPrepared` and `tbTheme_SwitchToLightMode`, Light (not Classic) as `X.light.png` beside it, which the build pairs by name ([WIP.Build.md](WIP.Build.md#pictures-in-two-themes)); a light picture with the dark one's pixels is not kept, and one more than 32 device pixels off the dark one's size fails (a wrong state, not the theme). So does a picture whose two themes say different things (`themeTextDifference`, `scripts/lib/shot-policy.mjs`): the page's text inside the clip is read at each pass's capture, white space ignored, and text one theme shows whole and the other shows none of fails it, naming the words on each side, with or without `--svg`, so that a line cut by the clip's edge is no difference; a shot whose text must differ names the words in `themeWords`. A label's pill follows the theme unless the shot names a `tone`. A shot's `annotate` list (arrows, rings, badges...) is drawn by `scripts/lib/shot-annotate.mjs` as one SVG layer over the page before the capture and removed after it, the clip growing to hold it. A picture is rewritten only when its bytes differ, and a second run reports every one `unchanged`; a capture is not byte-stable, so `capture` captures back to back, with no rest, until one equals the file on disk or two agree, and, when the two that agree are not the file, goes on capturing further apart, for 5.5 s at most, until one is, keeping the last two that agreed, and a picture that differs from the file only by a capture's noise (16 grey levels, 0.5% of its pixels) is left as it is. **The reference is the picture at git `HEAD` first, then the file** (owner): compared with the file alone, a picture drifted from `HEAD` over the runs of one retake, each step within noise of the file before it. A capture equal to or within noise of `HEAD`'s picture leaves the file as `HEAD` has it, its SVG too (`HEAD`'s, or none), reported `restored` when it held anything else; one that is not, but is within noise of the file, leaves the file (an earlier run's real change); a picture `HEAD` lacks is compared with the file only (`pictureFate`, `scripts/lib/shot-revision.mjs`; the run's pictures at `HEAD` read in one `git cat-file --batch`). `--against ` takes another revision, `--against-disk` the file only. **`--cleanup`** (`--dry-run` to list) starts no IDE: it resets every PNG git reports modified that is within noise of `HEAD`'s, with its SVG, so that only real changes stay; one noise rule, `withinNoise` in `scripts/lib/shot-change.mjs`, for the capture, `--cleanup` and `sheet.mjs --against`. **The manifest, `test/shots/manifest.json`** (`scripts/lib/shot-manifest.mjs`; [WIP.ShotReview.md](WIP.ShotReview.md#remembered-overrides)), records the build of each picture a run writes, and holds each picture's allowances (its noise rule's limits and rectangles left out, its SVG's bar) and a review's decisions, each tied to the bytes it was made on; `decidePicture` and `decideSvg` (`scripts/lib/shot-policy.mjs`) judge with them, in a run, in `--cleanup` and in `replay.mjs`, so a run from a terminal and one from the review tool come out the same. It is read and written only for the default `--out`. **`--stage `** (a new or empty folder git ignores) writes nothing under `docs/`, to the bundle cache or to the manifest, and puts each capture, its SVG, bundle, difference pictures, strength maps and verdict there (`scripts/lib/shot-stage.mjs`), for the review tool to apply; **`--events`** prints a `@@shot-event` line per event, and then a line `stop` on stdin ends the run as at its normal end; `replay.mjs` takes both ([WIP.ShotReview.md](WIP.ShotReview.md#runs)). `--jobs N` (default 6) runs the setups at once, each in a slot of its own from `tbbroker`, a full run with `--svg` in about 12 minutes on BETA 1010 (`--jobs 1`: one at a time); `--diffs ` writes the old picture, the new one and an amplified difference map for each that changed, named with its theme. Settle by a condition, never a fixed sleep, and a picture that shows some state sets it itself rather than relying on the shots before it. The page's animations and carets are switched off through a style sheet put into the document and every shadow root; a picture that holds DEBUG CONSOLE lines has their times (and a CodeLens run's time taken) set in the page once the real ones show. **A running program's window** (setup `programs`, fixture `test/shots/programs`) is taken **once** (`once: true`, the dark pass only) with `captureWindow`, cropped to its visible frame, at the display's DPI, shown at its size at 96 DPI; its shot says `window: true` in the table, and a shot whose take and table disagree fails. **`--list`** starts no IDE: it prints the table as one line of JSON (`[{out, setup, once, window}]`), which the review tool's page of every picture reads. **The Windows user name must never be in a published picture**: the visible text of the page, text boxes included, is searched for it before the first capture and again when a capture is chosen, with the picture's SVG under `--svg` (a page can draw again in between: Project Settings puts a real path back over the one the shot set), and for a program's window the texts of the window and its children, and the run fails when it is there. **A path a picture shows on the tool's own folder is virtual** (owner): the real folder is `.claude\tbshots` (`PACKAGE_ROOT`, gitignored, removed at the end of a run), every picture shows it as `C:\tbshots`, which is never created, and `scripts/lib/shot-root.mjs` rewrites it in the page from the moment the lane attaches, with the same kind of guard as the user name's. The tool finds each name it presses F1 on by searching the demo's source, not by line. The add-in's detached window has a port from the broker (the help setup's slot carries one), since the add-in is told it before it binds it. **`--svg`** also draws each picture as SVG from the page as it stands at the capture (`scripts/svgshot/`: a DOM snapshot converted, its text set in per-picture subsets of the fonts the IDE drew it with, cut by `scripts/subset_font.py`, which needs Python with fontTools), compares it with the PNG, and writes `X.svg` or `X.light.svg` beside it only when no more than 0.25% of its pixels are far off; otherwise an older SVG is removed and the page shows the PNG; the SVG of a picture whose PNG is not rewritten stays as it is, with its bundle, unless a review accepts the new one (owner), but its bundle goes to the cache when the SVG drawn is the file's to the byte (owner, `bundleFollowsSvg`), and the SVG is written with it when the cached bundle cannot draw the picture again (owner, `staleBundle`) ([WIP.Build.md](WIP.Build.md#pictures-as-svg)). With `--svg` each picture's *bundle*, what its SVG is drawn from, is saved in the gitignored `.svgshot-bundles/` (`--bundles`; never under `docs/`, a bundle holds the user name), and **after a change to the converter, `node scripts/svgshot/replay.mjs` draws every SVG again from the bundles, with no IDE**, in about 20 s for the 370 (an SVG the same as its file is not compared again, `--recheck` compares all; the fonts' cuts are kept in `.svgshot-bundles/subsets/`; one browser per process, GPU off); it lists the pictures whose bundle cannot answer what the converter now asks, with the `--only` command that retakes them ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). `node scripts/svgshot/sheet.mjs --out ` writes a page of the pictures with no SVG (`--all`: every picture), each drawn again from its bundle beside its PNG and difference map, worst first; it is drawn without the user-name check (owner: the sheet is the owner's alone), so `--out` is refused under `docs/` and anywhere git does not ignore; what it and replay share is `scripts/svgshot/redraw.mjs`, and its pages are `scripts/svgshot/sheets.mjs`, made of the pieces in `scripts/lib/shot-views.mjs`. `node scripts/svgshot/bench.mjs` tests the converter's replicas of what Chromium draws outside the DOM (scrollbars, drop-down lists) in Edge, with no IDE ([WIP.Screenshots.md](WIP.Screenshots.md#pictures-as-svg)). No wrapper; outside every gate and outside CI, as `try-help-addin.bat` is. Exit 0 every picture written or unchanged, 1 a picture failed, 2 the tool could not run, 3 not put back. Run `build.bat` first. - **The help add-in's project file is written by every build, CI's included.** The `addinProject` task (`builder/addin-project.mjs`) packs `add-in/` into `tB/IDE/AddIns/downloads/tbDocsHelp.twinproj` in the **online tree only**, which the Help Add-In page offers as a download; `_config.yml`'s `addin_project` declares both ends, so a fixture config gets none. It waits for the `helpArchive` task. It packs the files `git ls-files` lists there, read from the working folder, never a `Resources/HELP/` file from the disk, with CRLF made LF outside `Resources/` (so a Windows and a Linux build write the same bytes), the `tB/symbols.json` the same build wrote in place of the committed copy, and **the zip that build's archive task wrote** as `Resources/HELP/site.zip`, passed in memory (a build that wrote none --- `--no-help-archive`, `--serve`, another `--dest`, a fixture --- packs without it, and the summary line says `without the offline archive`). The download is about 61 MB with the archive, 1 MB without. The offline tree holds no copy (the project carries that tree's archive, so a copy there would be circular), and its links to the file are rewritten to the website by the general rule in `offline-rewrite.mjs`: a URL the offline tree lacks but the online tree has becomes `site.url` + path, and is no miss (`computeWebsiteUrl`; the offline link check's `forbidExempt` excuses exactly those links, `isWebsiteOnlyLink`). The pack is `packFiles` from `scripts/impexp.mjs`, in memory (never staged through `%TEMP%`), the one module under `scripts/` that `builder/` may import (`biome.jsonc`); `check_tree_fresh.mjs` watches it and `add-in/` for that reason. The publish allowlist exempts the one path in the trees only (`publishPolicyFor`'s `generated`); a `.twinproj` anywhere else, or in `docs/`, is refused. `--serve` writes it into its one tree; `--dry-run` writes nothing. A failure (no git) fails the build with exit 1 and the index audit names the missing file. **Don't build the DLLs in CI from it**: that needs a twinBASIC install; the download is the project, and the reader builds it. Why each of these: [WIP.Build.md](WIP.Build.md#the-help-add-ins-project-file); `test/addin-project.test.mjs` is the gate. - **The help archive is the `helpArchive` task of the build's graph.** `builder/help-archive-step.mjs` writes `add-in/Resources/HELP/site.zip` from `docs/_site-offline` through `lib/help-archive.mjs`, once every task that writes into the offline tree has finished (`flushJoin`, `scss`, `searchIndex`, `writeOffline`; the archive task lists exactly those) and **before** the Gantt injection. **The archive leaves out `HELP_ARCHIVE_SKIP`** (`builder/help-archive-step.mjs`): the Documentation Development section (`BuildInfo.html` and `assets/images/gantt.svg` with it, so the chart never reaches the zip), Videos and their poster frames, Challenges, and **every download, which stays online** (owner): a file readers download goes in a `downloads` folder beside its page, and the step fails the build when the Downloads page offers a file the archive would hold. The add-in's server redirects any file the archive lacks to the website, because pages link to all of these; a new entry needs nothing else. It reads the file back to verify it. A mismatch fails the build (exit 1). After the Gantt injection `recheckHelpArchive` checks that the offline tree on disk lists the same files as the archive and differs from it only in `BuildInfo.html` and `gantt.svg`; a task that wrote into the offline tree late fails the build there. The help add-in embeds the zip as a resource and serves the pages from it with its own server; a build of the add-in without it uses the live site. The file is gitignored (about 60 MB, 10 MB of it the search index's `search-index.js`, deflated), so a build of the add-in from a clean clone is the online one until `build.bat` has run. The download carries the same zip (previous bullet). **`--no-help-archive` turns the step off, and neither CI workflow may pass it** (`check_ci_workflows.mjs` refuses it, in `ALLOWED.forbiddenBuildFlags`), because CI's published download would then hold no archive. It runs only for the guarded source tree (`docs`) built into the default `docs/_site-offline` (not another `--dest`, so `compare_trees` and `check_links_diff` leave it alone), never under `--serve` or `--dry-run`, never without an offline tree, and not keyed on `$CI`. There is no command-line tool for the archive: the `help-offline` lane makes its own with `writeHelpArchive`. The zip's format is fixed by the add-in's reader, which does no inflating ([WIP.HelpAddin.md](WIP.HelpAddin.md), Stage 4, increment 5). - `node scripts/bug_repro.mjs` --- the reproducer projects under `bugs//` for the entries of [BUGS-TO-REPORT.md](BUGS-TO-REPORT.md): `new`, `pack` (impexp, then the zip, written in Node), `compile`, `build` and `run` through `tbbuild` and `tbrun`, `vb6` (builds the optional `vb6/` VB6 project beside the twinBASIC one in a temp copy, runs its exe on a private desktop, and prints its `out.txt`; a project with a form or a user control is built without Unattended Execution, which VB6 refuses for it; `pack` zips its sources into `-vb6.zip`; VB6 is started only by `scripts/lib/vb6.mjs`, never from a shell; a graphical defect's `images` key makes `run` and `vb6` keep what the `PngDump` modules of `test/repro-templates/png/` draw as `images/-tb.png` and `-vb6.png`, with a `-compare.png` of both, tested by `test/png.test.mjs`), `file` (moves a filed entry and its reproducer to `bugs/filed//`; `file --marked` does every marked entry), and `verify`, which reads each `bugs/*/repro.json` and `bugs/filed/*/repro.json` and says whether the bug still reproduces on the newest beta; a `lane` reproducer names the `ide-test` or `addin-test` lane and tests that assert its bug, which `verify` runs last through that runner, a `probe` one a script under `scripts/`, and a `test` one its own `[TestCase]` Subs and compile cases through `tbrun --tests`, judged by the `PASS` lines of what is fixed and the `FAIL` lines of the bug (`new --template tests` starts one). **No reproducer should need a person at the IDE** (owner): a bug the IDE has to be operated for gets a lane test, and `manual` is left for what cannot be automated at all. It has no wrapper. **`verify` is run by a person only**, never by a gate or CI: it needs a twinBASIC install, like `examples.bat`. Each `tbbuild` and `tbrun` it starts takes a slot of its own from `tbbroker`; `--jobs N` runs N at once. Its command-line cases are in `scripts/lib/cli-cases.mjs`, and need no IDE. - `node scripts/vb6run.mjs ` and `node scripts/vb6run.mjs --docs [--only ]` --- build and run VB6 code, so that what a sample prints in twinBASIC can be compared with VB6: a stand-alone file of statements or a `.bas` module, or `--docs`, which builds the documentation's `check_run` fences in VB6 and says for each whether it prints what its page says (`same`, `differs`, `not VB6`, `error`, `refused`; a `projname=` group is built as a project of its own, its `slot=file` fences translated into VB6 `.cls` and `.bas` components). It has no wrapper and is outside every gate and outside CI: it needs VB6 (`--vb6`, `VB6_EXE`, or the standard install folder). **Never start `VB6.EXE` from a shell**: in Git Bash `/make` is rewritten as a path and VB6 answers with a modal box on the desktop; the tool spawns it from Node with an argument array, and a sample that calls `MsgBox` or `InputBox` or contains `End` is refused. Its `Debug.Print` rewrite and its file translation have probes in `test/example-batches.test.mjs`, and its command-line cases are in `scripts/lib/cli-cases.mjs`; both need no VB6. What it relies on is in [WIP.ExamplesBuild.md](WIP.ExamplesBuild.md). - `node scripts/errors.mjs run|generate|write` --- the run-time errors of the documented procedures, as data under `errors/` ([WIP.Errors.md](WIP.Errors.md), [Tools-Defects](docs/Documentation/Tools-Defects.md#errorsmjs)): `run` builds and runs a probe of the cases in twinBASIC 32-bit and 64-bit (`tbrun --exe`), VB6 and 64-bit VBA (in 64-bit Access, `scripts/lib/vba.mjs`) and compares each with the data, `write` writes each page's Errors section. Its gate is `check_errors.mjs`; `run` is by hand, never CI. - `node scripts/recheck_callouts.mjs [--plan] [--kinds ] [--only ] [--dry-run] [--report ] [--from ]` --- after a new BETA, checks again every callout labelled with an older one by running what its marker names (`ide_test`/`addin_test` once per suite, judging lane reproducers from the same report through `scripts/lib/lane-report.mjs`, `bug_repro verify` for the other reproducers, `check_examples --json` for samples, `errors.mjs run` for tb and win64, `test/llvm/run.mjs --only`, `test/static-linking/run.mjs --only`, `package_tests --json`), relabels what holds (a data file's `beta:` too, then `errors.mjs write`), and lists the rest. What it reads and decides is `scripts/lib/recheck.mjs`, probed by `check_callout_evidence.mjs`; every tool's output stays in `%TEMP%\recheck-callouts\\`. By hand, never CI. -- `node scripts/ide_js.mjs ` --- reads, names and patches the IDE's minified `ide/main.js` ([Tools-Defects](docs/Documentation/Tools-Defects.md#ide_jsmjs)): `survey`, `units`, `list`, `name`, `seal`, `check`, `view`, `patch` and `shape`. It reads files and starts nothing; the rules for it are under [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) (*Fixing the IDE itself*). It has no wrapper and is outside every gate and CI, since it needs an install; its tests are `test/ide-js.test.mjs`, in `test.bat`, and its command-line cases are in `scripts/lib/cli-cases.mjs`. **After a new BETA, run `check` on it**, and `seal` the map once each `review` and `lost` name has been looked at. +- `node scripts/ide_js.mjs ` --- reads, names and patches the IDE's minified `ide/main.js` ([Tools-Defects](docs/Documentation/Tools-Defects.md#ide_jsmjs)): `survey`, `units`, `list`, `name`, `seal`, `check`, `view`, `patch`, `shape` and `keymap`, which writes the table of `docs/IDE/Reference/Keyboard-Shortcuts.md` from the build. It reads files and starts nothing; the rules for it are under [Driving the twinBASIC compiler](#driving-the-twinbasic-compiler) (*Fixing the IDE itself*). It has no wrapper and is outside every gate and CI, since it needs an install; its tests are `test/ide-js.test.mjs`, in `test.bat`, and its command-line cases are in `scripts/lib/cli-cases.mjs`. **After a new BETA, run `check` on it**, and `seal` the map once each `review` and `lost` name has been looked at, **and `keymap --check`**, then `keymap --update` if the group changed. Three generators sit outside that loop and produce committed artifacts rather than build output — none runs during a build, and none is needed for one. `python scripts/build_fonts.py` rebuilds the subset webfaces under `docs/assets/fonts/` and needs a network connection; `node scripts/build_dot_metrics.mjs` regenerates `builder/inter-metrics.json` from those webfaces and needs only a browser. See [Typography](#typography). `node indexer/twin-index.mjs derive` regenerates `builder/package-api.json`, the packages' declared API that the build's symbol index (`tB/symbols.json`, for the IDE help add-in) is annotated from; it needs a twinBASIC install (or `--release`), so **run it when the reference is re-indexed against a newer build** and commit it with the pages and the snapshots. See [WIP.HelpAddin.md, Stage 3](WIP.HelpAddin.md#stage-3-the-symbol-index-generated-by-the-docs-build). @@ -738,6 +738,7 @@ they are. ## Don'ts +- **Nothing writes outside the cloned repository and the temp folder** (owner). A tool, a test, a probe, a fixture and a session keep their files in this checkout (`.claude/`, which git ignores, for what is not committed) or under `%TEMP%`: never at a drive's root (`C:\probe`, `C:\tbshots`), on the Desktop, in the user's profile or in another checkout. A path that a picture or a page must show is a virtual one, rewritten at the capture, not a real folder. Reading is not writing: an install on the Desktop, the Windows folder, VB6 and the installed fonts stay readable. The one exception is `tbbroker`, installed in `%LOCALAPPDATA%\tbbroker` because every checkout shares it ([WIP.Broker.md](WIP.Broker.md)). The registry has its own rule: a run leaves it as it found it ([WIP.Harness.md](WIP.Harness.md#what-a-run-leaves-in-the-registry-and-putting-it-back)). - Don't commit `.claude/` or `CLAUDE.md` — both gitignored. (`WIP.md` is committed; `CLAUDE.md` is just a local `@WIP.md` import shim.) - Don't touch `_site/` or `_site-offline/` (build outputs, gitignored). - **Don't walk `docs/` for its markdown with a private `readdir`.** Call `markdownFiles` from [lib/markdown-files.mjs](lib/markdown-files.mjs), which never enters the build's output trees. A walk that does enter them crashes whenever a running `serve.bat` rewrites `_serve`; see [The code-region gate](WIP.Build.md#the-code-region-gate). Any other walk of `docs/` decides what is an output tree with the same module's `isOutputTree`, as `check_tree_fresh.mjs` does, rather than a list of its own. diff --git a/add-in/Resources/SYMBOLS/symbols.json b/add-in/Resources/SYMBOLS/symbols.json index 4c7f00bb7..c1bfbc32a 100644 --- a/add-in/Resources/SYMBOLS/symbols.json +++ b/add-in/Resources/SYMBOLS/symbols.json @@ -150,6 +150,7 @@ {"name":"Next","package":null,"container":null,"kind":"keyword","url":"/tB/Core/For-Each-Next"}, {"name":"For","package":null,"container":null,"kind":"statement","url":"/tB/Core/For-Next"}, {"name":"Next","package":null,"container":null,"kind":"keyword","url":"/tB/Core/For-Next"}, + {"name":"Friend","package":null,"container":null,"kind":"statement","url":"/tB/Core/Friend"}, {"name":"Function","package":null,"container":null,"kind":"statement","url":"/tB/Core/Function"}, {"name":"Get","package":null,"container":null,"kind":"statement","url":"/tB/Core/Get"}, {"name":"GoSub","package":null,"container":null,"kind":"statement","url":"/tB/Core/GoSub-Return"}, diff --git a/bugs/compiler-connection-retry-loop/repro.json b/bugs/compiler-connection-retry-loop/repro.json new file mode 100644 index 000000000..ae6e2cd50 --- /dev/null +++ b/bugs/compiler-connection-retry-loop/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "a compiler connection that cannot be made restarts the compiler every 5 s, and the crash-loop message never fires" + ], + "steps": "1. Start the IDE with --debug on its command line, which makes the compiler's connection impossible: the compiler starts with a console window, and the page never connects.\n2. Open a project. The status bar reads UNAVAILABLE, and a new compiler starts every 5 s with a restarting from FILE line each time, for as long as the IDE is open.\n3. The floating message Compiler crash loop detected never comes, and the IDE does not go to Safe Mode." +} diff --git a/bugs/completion-row-zero-no-type/repro.json b/bugs/completion-row-zero-no-type/repro.json new file mode 100644 index 000000000..7a541cb73 --- /dev/null +++ b/bugs/completion-row-zero-no-type/repro.json @@ -0,0 +1,6 @@ +{ + "mode": "lane", + "lane": "ide:completion-row-zero", + "tests": ["the row with lazy index 0 is never asked for, and shows no type"], + "steps": "1. Make a new project with a module Things that declares six Public variables As Long.\n2. In a procedure of another module, type Things. so that the completion list opens.\n3. Five rows read variable Long, and one reads only variable. Closing the list and typing the dot again changes nothing." +} diff --git a/bugs/damaged-settings-box-loop/repro.json b/bugs/damaged-settings-box-loop/repro.json new file mode 100644 index 000000000..49f96de56 --- /dev/null +++ b/bugs/damaged-settings-box-loop/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "a project damaged inside its Settings entry shows the bad file format box again after every OK" + ], + "steps": "1. Copy a saved project, and cut the copy in the middle of its Settings entry.\n2. File > Open Project..., the copy.\n3. OK on the box *bad file format*: the box comes back.\n4. It goes on coming back, with Please wait... on the page and no project. The test counts six boxes, then ends the IDE by its pid." +} diff --git a/bugs/devtools-stops-compiler-restart/repro.json b/bugs/devtools-stops-compiler-restart/repro.json new file mode 100644 index 000000000..1790e3206 --- /dev/null +++ b/bugs/devtools-stops-compiler-restart/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "with DevTools open, a compiler restart stops at a debugger statement in the IDE's code until DevTools closes" + ], + "steps": "1. Open a project, and open DevTools by a key bound to tbIdeDevTools_Show.\n2. Press Restart the compiler. The compiler is ended, the status bar reads UNAVAILABLE with four Disconnected lines, there is no Please wait... and no line in the DEBUG CONSOLE, and nothing moves.\n3. Close DevTools: the restart goes on, and the project is back about 3 s later.\nControl: Restart with DevTools closed." +} diff --git a/bugs/dll-startup-default-runs-main/repro.json b/bugs/dll-startup-default-runs-main/repro.json new file mode 100644 index 000000000..3d84ebdc7 --- /dev/null +++ b/bugs/dll-startup-default-runs-main/repro.json @@ -0,0 +1,6 @@ +{ + "mode": "lane", + "lane": "ide:samples", + "tests": ["a DLL whose Settings names no startup object shows (none) in Project Settings, and Start runs Main"], + "steps": "1. Make a new project from the ActiveX DLL template.\n2. Open Project Settings. Startup Object shows Default: (none). Close it.\n3. Press F5. The DEBUG CONSOLE shows Executing 'Main'..., then a compile error.\n4. Choose (none) for Startup Object, save, and press F5. The DEBUG CONSOLE shows [DEBUGGER] No startup object set." +} diff --git a/bugs/failed-save-as-moves-project/repro.json b/bugs/failed-save-as-moves-project/repro.json new file mode 100644 index 000000000..46c2e6d18 --- /dev/null +++ b/bugs/failed-save-as-moves-project/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "a failed Save As still moves the project: the title bar and the path name the new file, and a plain Save goes there", + "a failed Save As over a read-only file moves the project too, and Save As back to the original puts it right" + ], + "steps": "1. Open a saved project and type something in a source file.\n2. File > Save Project As..., and give a path in a folder that does not exist.\n3. An alert says save project failed, ERROR_PATH_NOT_FOUND. The title bar now names the new file, which does not exist.\n4. Ctrl+S: the same alert, for the new path.\n5. Make the folder and press Ctrl+S: the project is written there, and the file that was opened stays as it was.\nThe same comes from Save As over a read-only file, with ERROR_ACCESS_DENIED. Save As back to the original puts it right.\nControl: Save As to a good path writes the file and moves the project." +} diff --git a/bugs/failed-save-drops-close/repro.json b/bugs/failed-save-drops-close/repro.json new file mode 100644 index 000000000..ac0e50d91 --- /dev/null +++ b/bugs/failed-save-drops-close/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "Close Project, Save, and a save that fails drops the close: the project stays open and changed, and the reader must close again" + ], + "steps": "1. Open a saved project and type something in a source file.\n2. Make the .twinproj read-only.\n3. File > Close Project, and press Save on the question.\n4. An alert says save project failed, ERROR_ACCESS_DENIED. After OK the project is still open and marked changed, and nothing has asked what to do about the close.\nControl: the same with a file that can be written saves it and closes the project." +} diff --git a/bugs/filed/delegate-next-keyword/REPORT.md b/bugs/filed/delegate-next-keyword/REPORT.md new file mode 100644 index 000000000..7c036c000 --- /dev/null +++ b/bugs/filed/delegate-next-keyword/REPORT.md @@ -0,0 +1,48 @@ +Covered by the existing issue [twinbasic/twinbasic#2001](https://github.com/twinbasic/twinbasic/issues/2001). + +## After a Delegate, the next Friend, ReadOnly, WithEvents, Implements or Inherits is a syntax error + +**Describe the bug** +After a `Delegate` statement, the compiler no longer recognises five words. The next `Friend`, `Implements` or `Inherits` gives TB5182, *Syntax error. No handler for this symbol*, on the word, and the next `ReadOnly` or `WithEvents` gives it on the name that follows. The code is correct: the same line compiles when it is placed before the `Delegate`. + +Issue #2001 reports the `ReadOnly` half of this. This entry adds the other four words and what ends the effect. + +**To Reproduce** +Steps to reproduce the behavior: +1. Create a new project and add this class, or open `delegate-next-keyword.twinproj`, which also has every case below: + + ```vb + Class MyClass + Private Delegate Function TestDelegate () As Long + + Friend Sub Test() + End Sub + End Class + ``` +2. See TB5182 on `Friend`. +3. Replace `Friend` with `Public` or `Private`. The error goes away. + +What decides it, each a compile case of the project: +- The words affected: `Friend` (on a Sub, a Function, a Property or a variable), `ReadOnly`, `WithEvents`, `Implements`, `Inherits`. In a Module, `ReadOnly`. +- Not affected, directly after a `Delegate`: `Public`, `Private`, `Dim`, `Const`, `Static`, `Protected`, `Overridable`, `Sub`, `Function`, `Property`, `Declare`, `Event`, `Type`, `Enum`. +- Every form of `Delegate` does it: `Function` or `Sub`, `Public` or `Private`, with arguments or a calling convention, in a Class or a Module. +- A blank line, a comment, a variable, a `Const`, a `Type`, an `Enum`, an attribute or a second `Delegate` between the two leave the error in place. +- A procedure, a `Declare` or an `Event` between the two ends the effect. `Friend` after `Friend` does not fail a second time either, once the first has given its error. +- Each word compiles after a variable, a `Const` or a `Sub` when there is no `Delegate`, and before a `Delegate`. A class may use a delegate type declared lower down. +- The next block of the file is not affected: a Class after a Module that ends in a `Delegate` compiles. + +**Expected behavior** +No diagnostic. `Delegate` declarations have no effect on the lines after them. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1034, and BETA 1007; #2001 saw the `ReadOnly` half on BETA 644 + +**Additional context** +What a `Sub`, a `Declare` and an `Event` share, and a `Delegate` lacks, is not known from outside the compiler. `Friend` is not a reserved word (`Dim Friend As Long` compiles), so the fault may be in how the parser decides that such a word is a keyword. + +A way round it: put the `Delegate` after these statements, at the end of the class if necessary. + +Severity: low to medium. Nothing is miscompiled, but a correct line is refused with a message that does not point at the `Delegate`, and the reader has no reason to look there. + + diff --git a/bugs/filed/delegate-next-keyword/delegate-next-keyword.twinproj b/bugs/filed/delegate-next-keyword/delegate-next-keyword.twinproj new file mode 100644 index 000000000..cc3f42f30 Binary files /dev/null and b/bugs/filed/delegate-next-keyword/delegate-next-keyword.twinproj differ diff --git a/bugs/filed/delegate-next-keyword/repro.json b/bugs/filed/delegate-next-keyword/repro.json new file mode 100644 index 000000000..ffccc87e3 --- /dev/null +++ b/bugs/filed/delegate-next-keyword/repro.json @@ -0,0 +1,26 @@ +{ + "mode": "test", + "expect": { + "exit": 7, + "output": [ + "^FAIL Reported_FriendSub: .*TB5182", + "^FAIL After_FriendVariable: .*TB5182", + "^FAIL After_PrivateReadOnly: .*TB5182", + "^FAIL After_PrivateWithEvents: .*TB5182", + "^FAIL After_Implements: .*TB5182", + "^FAIL After_Inherits: .*TB5182", + "^FAIL After_ReadOnlyInModule: .*TB5182", + "^FAIL Still_AfterVariable: .*TB5182", + "^FAIL Still_AfterSecondDelegate: .*TB5182", + "^PASS Cleared_BySub$", + "^PASS Cleared_ByDeclare$", + "^PASS Cleared_ByEvent$", + "^PASS Control_FriendBeforeDelegate$", + "^PASS Control_FriendAfterVariable$", + "^PASS Control_ImplementsAfterVariable$", + "^PASS Control_ProtectedAfterDelegate$" + ] + }, + "issue": 2001, + "existing": true +} diff --git a/bugs/filed/delegate-next-keyword/src/Settings b/bugs/filed/delegate-next-keyword/src/Settings new file mode 100644 index 000000000..0bdf800dc --- /dev/null +++ b/bugs/filed/delegate-next-keyword/src/Settings @@ -0,0 +1,75 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "DelegateNextKeyword", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: After a Delegate, the next Friend, ReadOnly, WithEvents, Implements or Inherits is a syntax error", + "project.exportPathIsV2": true, + "project.id": "{69C4B5E7-9618-4A52-8917-BE6D714AE978}", + "project.name": "DelegateNextKeyword", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] Assert", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "Assert", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 24 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/filed/delegate-next-keyword/src/Sources/RegressionTests.twin b/bugs/filed/delegate-next-keyword/src/Sources/RegressionTests.twin new file mode 100644 index 000000000..d5abb7ecb --- /dev/null +++ b/bugs/filed/delegate-next-keyword/src/Sources/RegressionTests.twin @@ -0,0 +1,201 @@ +' Regression tests for the report, as compile cases: each line that ends in a +' comment ' CASE : must give that diagnostic on that line (a +' code, "error" for any error, or "none"). tbrun --tests checks them. +' Every line below is legal, so each is "none". A case that fails is the bug: after a +' Delegate, the compiler no longer knows Friend, ReadOnly, WithEvents, Implements or +' Inherits, until a procedure, a Declare or an Event has been read. + +Interface IThing Extends stdole.IUnknown + Sub Foo() +End Interface + +Class BaseClass + Public Sub Foo() + End Sub +End Class + +Class Thrower + Public Event Done() +End Class + +' --- the report on Discord, as it was written +Class MyClass + Private Delegate Function TestDelegate () As Long + + Friend Sub Test() ' CASE Reported_FriendSub: none + End Sub + +End Class + +' --- the words the Delegate affects +Class AfterFriendFunction + Private Delegate Function D() As Long + Friend Function F() As Long ' CASE After_FriendFunction: none + End Function +End Class +Class AfterFriendProperty + Private Delegate Function D() As Long + Friend Property Get P() As Long ' CASE After_FriendProperty: none + End Property +End Class +Class AfterFriendVariable + Private Delegate Function D() As Long + Friend V As Long ' CASE After_FriendVariable: none +End Class +Class AfterReadOnly + Private Delegate Function D() As Long + Private ReadOnly R As Long = 1 ' CASE After_PrivateReadOnly: none +End Class +Class AfterWithEvents + Private Delegate Function D() As Long + Private WithEvents W As Thrower ' CASE After_PrivateWithEvents: none +End Class +Class AfterImplements + Private Delegate Function D() As Long + Implements IThing ' CASE After_Implements: none + Private Sub Foo() Implements IThing.Foo + End Sub +End Class +Class AfterInherits + Private Delegate Function D() As Long + Inherits BaseClass ' CASE After_Inherits: none +End Class +Module AfterInModule + Private Delegate Function D() As Long + Private ReadOnly R As Long = 1 ' CASE After_ReadOnlyInModule: none +End Module + +' --- any form of Delegate does it +Class DelegateSub + Private Delegate Sub D() + Friend Sub S() ' CASE Delegate_Sub: none + End Sub +End Class +Class DelegateCDecl + Private Delegate Function D CDecl (ByVal A As Long) As Long + Friend Sub S() ' CASE Delegate_CDecl: none + End Sub +End Class +Module DelegatePublicInModule + Public Delegate Function D() As Long + Public ReadOnly R As Long = 1 ' CASE Delegate_PublicInModule: none +End Module + +' --- between the two, these leave it as it is +Class StillAfterVariable + Private Delegate Function D() As Long + Private X As Long + Friend Sub S() ' CASE Still_AfterVariable: none + End Sub +End Class +Class StillAfterConst + Private Delegate Function D() As Long + Private Const C As Long = 1 + Friend Sub S() ' CASE Still_AfterConst: none + End Sub +End Class +Class StillAfterType + Private Delegate Function D() As Long + Private Type T + A As Long + End Type + Friend Sub S() ' CASE Still_AfterType: none + End Sub +End Class +Class StillAfterEnum + Private Delegate Function D() As Long + Private Enum E + A + End Enum + Friend Sub S() ' CASE Still_AfterEnum: none + End Sub +End Class +Class StillAfterDelegate + Private Delegate Function D() As Long + Private Delegate Sub E() + Friend Sub S() ' CASE Still_AfterSecondDelegate: none + End Sub +End Class +Class StillWithAttribute + Private Delegate Function D() As Long + [Description("x")] + Friend Sub S() ' CASE Still_WithAttribute: none + End Sub +End Class + +' --- controls: these do not fail +Class ClearedBySub + Private Delegate Function D() As Long + Private Sub First() + End Sub + Friend Sub S() ' CASE Cleared_BySub: none + End Sub + Private ReadOnly R As Long = 1 ' CASE Cleared_StaysCleared: none +End Class +Class ClearedByDeclare + Private Delegate Function D() As Long + Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long + Friend Sub S() ' CASE Cleared_ByDeclare: none + End Sub +End Class +Class ClearedByEvent + Private Delegate Function D() As Long + Public Event Done() + Friend Sub S() ' CASE Cleared_ByEvent: none + End Sub +End Class +Class ClearedByFirstFriend + Private Delegate Function D() As Long + Public Sub First() + End Sub + Friend Sub Second() ' CASE Cleared_SecondMember: none + End Sub +End Class +Class FriendBeforeDelegate + Friend Sub S(ByVal Callback As D) ' CASE Control_FriendBeforeDelegate: none + End Sub + Private ReadOnly R As Long = 1 ' CASE Control_ReadOnlyBeforeDelegate: none + Private Delegate Function D() As Long +End Class +Class NoDelegate + Private X As Long + Friend Sub S() ' CASE Control_FriendAfterVariable: none + End Sub +End Class +Class NoDelegateImplements + Private X As Long + Implements IThing ' CASE Control_ImplementsAfterVariable: none + Private Sub Foo() Implements IThing.Foo + End Sub +End Class +Class NoDelegateInherits + Private X As Long + Inherits BaseClass ' CASE Control_InheritsAfterVariable: none +End Class +Class NoDelegateWithEvents + Private X As Long + Private WithEvents W As Thrower ' CASE Control_WithEventsAfterVariable: none +End Class +Class NoDelegateReadOnly + Private X As Long + Private ReadOnly R As Long = 1 ' CASE Control_ReadOnlyAfterVariable: none +End Class +Class NotAffected + Private Delegate Function D() As Long + Protected Sub S() ' CASE Control_ProtectedAfterDelegate: none + End Sub +End Class +Class NotAffectedStatic + Private Delegate Function D() As Long + Static Sub S() ' CASE Control_StaticAfterDelegate: none + End Sub +End Class + +' --- the next class starts clean: a Delegate in a Module of the same file +Module DelegatesHere + Public Delegate Function Callback(ByVal X As Long) As Long +End Module +Class AfterTheModule + Friend Sub S(ByVal C As Callback) ' CASE Control_NextBlockInTheFile: none + End Sub +End Class diff --git a/bugs/filed/delegate-next-keyword/src/Sources/Startup.twin b/bugs/filed/delegate-next-keyword/src/Sources/Startup.twin new file mode 100644 index 000000000..20233b3a9 --- /dev/null +++ b/bugs/filed/delegate-next-keyword/src/Sources/Startup.twin @@ -0,0 +1,6 @@ +Module Startup + + Public Sub Main() + End Sub + +End Module diff --git a/bugs/filed/fastcall-delegate-stdcall/REPORT.md b/bugs/filed/fastcall-delegate-stdcall/REPORT.md index a017c7f5f..1c6a320bc 100644 --- a/bugs/filed/fastcall-delegate-stdcall/REPORT.md +++ b/bugs/filed/fastcall-delegate-stdcall/REPORT.md @@ -41,4 +41,4 @@ Severity: the delegate is unusable on win32. What does not reproduce it: Both keywords are new in BETA 990 and 992; BETA 987 refuses them (TB5182), and BETA 983 reports errors for this project at compile time. Measured in the IDE's `[RunAfterBuild]` run, and in the compiled EXE that `tbrun` left, run from its `Sub Main` and writing to a file: the same five results both ways. - + diff --git a/bugs/filed/system-colour-extra-bytes-black/REPORT.md b/bugs/filed/system-colour-extra-bytes-black/REPORT.md index 8b6cb17ec..6043bc0af 100644 --- a/bugs/filed/system-colour-extra-bytes-black/REPORT.md +++ b/bugs/filed/system-colour-extra-bytes-black/REPORT.md @@ -37,4 +37,4 @@ The same difference exists for the high end of the index range: VB6 draws `&H800 The change is `TranslateColor2` in the VB package's `Graphics.twin`, which `Line`, `Circle` and `PSet` call on `Color`: for a top byte of `&H80` it returns the system colour when the value minus `&H80000000` is 0 to 30 and 0 otherwise, and for any other value it returns the low 24 bits. - + diff --git a/bugs/friend-late-bound/friend-late-bound.twinproj b/bugs/friend-late-bound/friend-late-bound.twinproj new file mode 100644 index 000000000..326d6dcff Binary files /dev/null and b/bugs/friend-late-bound/friend-late-bound.twinproj differ diff --git a/bugs/friend-late-bound/repro.json b/bugs/friend-late-bound/repro.json new file mode 100644 index 000000000..60d4439d8 --- /dev/null +++ b/bugs/friend-late-bound/repro.json @@ -0,0 +1,18 @@ +{ + "mode": "test", + "expect": { + "exit": 7, + "output": [ + "^FAIL LateBoundTests\\.LateBound_FriendFunction_Raises438: ", + "^FAIL LateBoundTests\\.LateBound_FriendProperty_Raises438: ", + "^FAIL LateBoundTests\\.LateBound_FriendVariable_Raises438: ", + "^FAIL LateBoundTests\\.CallByName_FriendFunction_Raises438: ", + "^FAIL Package_FriendFunctionFromTheProject: ", + "^FAIL Package_FriendVariableFromTheProject: ", + "^PASS LateBoundTests\\.Control_CallByName_PrivateFunction_Raises438$", + "^PASS LateBoundTests\\.Control_CallByName_PublicFunction_Works$", + "^PASS LateBoundTests\\.Control_EarlyBound_Friend_Works$", + "^PASS LateBoundTests\\.Control_ThroughImplements_FriendRaises97$" + ] + } +} diff --git a/bugs/friend-late-bound/src/Packages/FriendLib/Settings b/bugs/friend-late-bound/src/Packages/FriendLib/Settings new file mode 100644 index 000000000..7ab1f500e --- /dev/null +++ b/bugs/friend-late-bound/src/Packages/FriendLib/Settings @@ -0,0 +1,35 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "FriendLib", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Package TWINPACK", + "project.description": "One class with Friend members, for the reproducer", + "project.id": "{7E1D4A52-8B36-4C7F-A2D1-6F3E90B1C477}", + "project.licence": "MIT", + "project.name": "FriendLib", + "project.optionExplicit": true, + "project.packageVisibility": "PUBLIC", + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.versionMajor": 1, + "project.versionMinor": 0, + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/friend-late-bound/src/Packages/FriendLib/Sources/Locked.twin b/bugs/friend-late-bound/src/Packages/FriendLib/Sources/Locked.twin new file mode 100644 index 000000000..2b5b181d0 --- /dev/null +++ b/bugs/friend-late-bound/src/Packages/FriendLib/Sources/Locked.twin @@ -0,0 +1,11 @@ +' Friend members in a package. A project that references the package is another project, +' as an ActiveX DLL's client would be, so these should not be reachable from it. +Public Class Locked + + Friend Function FriendFn() As Long + Return 7 + End Function + + Friend Field As Long + +End Class diff --git a/bugs/friend-late-bound/src/Settings b/bugs/friend-late-bound/src/Settings new file mode 100644 index 000000000..5bd71436e --- /dev/null +++ b/bugs/friend-late-bound/src/Settings @@ -0,0 +1,88 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "FriendLateBound", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A Friend member answers a late-bound call, from outside the project too", + "project.exportPathIsV2": true, + "project.id": "{703E80DD-B8B8-4D61-9475-5742F30A84C4}", + "project.name": "FriendLateBound", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] Assert", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "Assert", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 24 + }, + { + "autoCtlExt": true, + "id": "{7E1D4A52-8B36-4C7F-A2D1-6F3E90B1C477}", + "licence": "MIT", + "name": "[IMPORTED] FriendLib", + "path32": "/Packages/FriendLib", + "path64": "/Packages/FriendLib", + "symbolId": "FriendLib", + "versionBuild": "0", + "versionMajor": "1", + "versionMinor": "0", + "versionRevision": "0" + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/friend-late-bound/src/Sources/RegressionTests.twin b/bugs/friend-late-bound/src/Sources/RegressionTests.twin new file mode 100644 index 000000000..68096cdd1 --- /dev/null +++ b/bugs/friend-late-bound/src/Sources/RegressionTests.twin @@ -0,0 +1,133 @@ +' The issue's regression tests. A Friend member is for the project that declares it. In VB6 +' the code below is what bugs/friend-late-bound/vb6/ runs: a late-bound call to a Friend +' member raises error 438, as a call to a Private one does. A [TestCase] that fails is the +' bug; a line ending in ' CASE : is judged against the compile, and +' "error" there means a call from the project into the package's Friend member should +' not compile. + +Class Widget + Friend Field As Long + Private mValue As Long + + Friend Function GetValue() As Long + Return 7 + End Function + + Friend Property Get Prop() As Long + Prop = mValue + End Property + + Friend Property Let Prop(ByVal V As Long) + mValue = V + End Property + + Public Function PubValue() As Long + Return 8 + End Function + + Private Function PrivValue() As Long + Return 9 + End Function +End Class + +Class Impl + Implements Widget + Private Function Widget_PubValue() As Long + Return 80 + End Function + Private Property Get Widget_Field() As Long + End Property + Private Property Let Widget_Field(ByVal RHS As Long) + End Property +End Class + +[TestFixture] +Module LateBoundTests + + Private Function ErrorOf(ByVal O As Object, ByVal Name As String) As Long + On Error Resume Next + Dim V As Variant + V = CallByName(O, Name, VbMethod) + ErrorOf = Err.Number + End Function + + [TestCase] + Public Sub LateBound_FriendFunction_Raises438() + Dim O As Object = New Widget + Dim V As Variant + On Error Resume Next + V = O.GetValue() + Dim Number As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 438, Number + End Sub + + [TestCase] + Public Sub LateBound_FriendProperty_Raises438() + Dim O As Object = New Widget + Dim V As Variant + On Error Resume Next + V = O.Prop + Dim Number As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 438, Number + End Sub + + [TestCase] + Public Sub LateBound_FriendVariable_Raises438() + Dim O As Object = New Widget + Dim V As Variant + On Error Resume Next + V = O.Field + Dim Number As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 438, Number + End Sub + + [TestCase] + Public Sub CallByName_FriendFunction_Raises438() + Assert.Strict.AreEqual 438, ErrorOf(New Widget, "GetValue") + End Sub + + [TestCase] + Public Sub Control_CallByName_PrivateFunction_Raises438() + Assert.Strict.AreEqual 438, ErrorOf(New Widget, "PrivValue") + End Sub + + [TestCase] + Public Sub Control_CallByName_PublicFunction_Works() + Assert.Strict.AreEqual 0, ErrorOf(New Widget, "PubValue") + End Sub + + [TestCase] + Public Sub Control_EarlyBound_Friend_Works() + Dim W As New Widget + Assert.Strict.AreEqual 7, W.GetValue() + End Sub + + [TestCase] + Public Sub Control_ThroughImplements_FriendRaises97() + Dim W As Widget = New Impl + Dim V As Long + On Error Resume Next + V = W.GetValue() + Dim Number As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, Number + End Sub + +End Module + +Module FromTheProject + + Public Function CallsThePackage() As Long + Dim L As New Locked + CallsThePackage = L.FriendFn() ' CASE Package_FriendFunctionFromTheProject: error + End Function + + Public Sub WritesThePackage() + Dim L As New Locked + L.Field = 1 ' CASE Package_FriendVariableFromTheProject: error + End Sub + +End Module diff --git a/bugs/friend-late-bound/src/Sources/Startup.twin b/bugs/friend-late-bound/src/Sources/Startup.twin new file mode 100644 index 000000000..20233b3a9 --- /dev/null +++ b/bugs/friend-late-bound/src/Sources/Startup.twin @@ -0,0 +1,6 @@ +Module Startup + + Public Sub Main() + End Sub + +End Module diff --git a/bugs/friend-late-bound/vb6/Impl.cls b/bugs/friend-late-bound/vb6/Impl.cls new file mode 100644 index 000000000..e98043866 --- /dev/null +++ b/bugs/friend-late-bound/vb6/Impl.cls @@ -0,0 +1,20 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Impl" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = True +Option Explicit + +Implements Widget + +Private Function Widget_PubValue() As Long + Widget_PubValue = 80 +End Function diff --git a/bugs/friend-late-bound/vb6/Module1.bas b/bugs/friend-late-bound/vb6/Module1.bas new file mode 100644 index 000000000..4c3b11a99 --- /dev/null +++ b/bugs/friend-late-bound/vb6/Module1.bas @@ -0,0 +1,51 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' What VB6 does with the calls the twinBASIC side of the report makes: a Friend member +' called early-bound, called late-bound, and called through a class that implements its class. +' Build and run it with: node scripts/bug_repro.mjs vb6 friend-late-bound + +Private Sub Report(ByVal What As String) + Print #9, What & ": " & Err.Number & " " & Err.Description + Err.Clear +End Sub + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + + Dim W As New Widget + Print #9, "early Friend Function = " & W.GetValue() + W.Prop = 11 + Print #9, "early Friend Property = " & W.Prop + + Dim O As Object + Set O = W + Dim V As Variant + On Error Resume Next + V = O.PubValue() + Report "late Public Function" + V = O.GetValue() + Report "late Friend Function" + V = O.Prop + Report "late Friend Property Get" + O.Prop = 1 + Report "late Friend Property Let" + V = CallByName(O, "GetValue", VbMethod) + Report "CallByName Friend Function" + + Dim Through As Widget + Set Through = New Impl + V = Through.PubValue() + Report "Implements, Public" + V = Through.GetValue() + Report "Implements, Friend Function" + V = Through.Prop + Report "Implements, Friend Property" + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/friend-late-bound/vb6/Probe.vbp b/bugs/friend-late-bound/vb6/Probe.vbp new file mode 100644 index 000000000..a8a3a574d --- /dev/null +++ b/bugs/friend-late-bound/vb6/Probe.vbp @@ -0,0 +1,9 @@ +Type=Exe +Module=Module1; Module1.bas +Class=Widget; Widget.cls +Class=Impl; Impl.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/friend-late-bound/vb6/Widget.cls b/bugs/friend-late-bound/vb6/Widget.cls new file mode 100644 index 000000000..c0cac5ebf --- /dev/null +++ b/bugs/friend-late-bound/vb6/Widget.cls @@ -0,0 +1,32 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Widget" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = True +Option Explicit + +Private mValue As Long + +Friend Function GetValue() As Long + GetValue = 7 +End Function + +Friend Property Get Prop() As Long + Prop = mValue +End Property + +Friend Property Let Prop(ByVal V As Long) + mValue = V +End Property + +Public Function PubValue() As Long + PubValue = 8 +End Function diff --git a/bugs/front-page-search-internal-paths/repro.json b/bugs/front-page-search-internal-paths/repro.json new file mode 100644 index 000000000..781097e78 --- /dev/null +++ b/bugs/front-page-search-internal-paths/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "search rows of the IDE's own items show the internal path, and $ alone for an import card" + ], + "steps": "1. File > New Project..., and type sample 10 in the search box: the row's grey line starts $\\Sample 10.\n2. Search for vbp: the row's grey line reads $.\n3. Search for $: 34 rows are listed.\nControl: a row of a Recent project shows its path." +} diff --git a/bugs/front-page-search-one-mark/repro.json b/bugs/front-page-search-one-mark/repro.json new file mode 100644 index 000000000..a6b780f18 --- /dev/null +++ b/bugs/front-page-search-one-mark/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "two search words in one line of a row lose the first mark" + ], + "steps": "1. File > New Project..., and type standard console in the search box.\n2. The row's grey line, $\\_Standard EXE#(Console App), marks Console and not Standard.\nControl: words in different lines of a row are each marked." +} diff --git a/bugs/implements-friend-stub-stack/implements-friend-stub-stack.twinproj b/bugs/implements-friend-stub-stack/implements-friend-stub-stack.twinproj new file mode 100644 index 000000000..d15b99bb8 Binary files /dev/null and b/bugs/implements-friend-stub-stack/implements-friend-stub-stack.twinproj differ diff --git a/bugs/implements-friend-stub-stack/repro.json b/bugs/implements-friend-stub-stack/repro.json new file mode 100644 index 000000000..76c65c1d4 --- /dev/null +++ b/bugs/implements-friend-stub-stack/repro.json @@ -0,0 +1,16 @@ +{ + "mode": "test", + "expect": { + "exit": 7, + "output": [ + "^PASS FriendThroughImplements\\.Control_SubOneLong_Raises97$", + "^PASS FriendThroughImplements\\.Control_SubOneByRef_Raises97$", + "^PASS FriendThroughImplements\\.Control_FunctionNoArguments_Raises97$", + "^PASS FriendThroughImplements\\.Control_FunctionReturnsString_Raises97$", + "^PASS FriendThroughImplements\\.Control_PropertyGet_Raises97$", + "^PASS FriendThroughImplements\\.Control_PropertyLet_Raises97$", + "^FAIL FriendThroughImplements\\.SubNoArguments_ErrorIsCaught: \\(runtime error 97", + "^FAIL FriendThroughImplements\\.FunctionOneArgument_ErrorIsCaught: no answer" + ] + } +} diff --git a/bugs/implements-friend-stub-stack/src/Settings b/bugs/implements-friend-stub-stack/src/Settings new file mode 100644 index 000000000..837f396ce --- /dev/null +++ b/bugs/implements-friend-stub-stack/src/Settings @@ -0,0 +1,75 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "ImplementsFriendStubStack", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A Friend procedure called through a class that implements its class damages the stack in a 32-bit build", + "project.exportPathIsV2": true, + "project.id": "{3D56D6B1-6F4F-4F6B-902C-CDF2FE669B2B}", + "project.name": "ImplementsFriendStubStack", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] Assert", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "Assert", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 24 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/implements-friend-stub-stack/src/Sources/RegressionTests.twin b/bugs/implements-friend-stub-stack/src/Sources/RegressionTests.twin new file mode 100644 index 000000000..507fd3d12 --- /dev/null +++ b/bugs/implements-friend-stub-stack/src/Sources/RegressionTests.twin @@ -0,0 +1,128 @@ +' A class that Implements another class need not provide its Friend procedures. A call to one +' through a variable of the implemented class raises error 97, from a stub the compiler writes. +' In a 32-bit build the stub removes one stack slot besides Me, whatever the procedure takes, so +' each case below that does not take exactly one slot leaves the stack wrong. Each asserts what +' is correct: error 97, caught here. A case that fails is the bug. +' +' Run with tbrun --tests. The last case does not return, which stops the run, so it stays last +' and its neighbours are not tried: bugs/implements-friend-stub-stack/vb6 has all of them. +' With --arch win64 every case passes. + +Class Target + Public Function Pub() As Long + End Function + + Friend Sub SubNoArguments() + End Sub + Friend Sub SubOneLong(ByVal X As Long) + End Sub + Friend Sub SubOneByRef(ByRef X As Long) + End Sub + Friend Function FunctionNoArguments() As Long + End Function + Friend Function FunctionReturnsString() As String + End Function + Friend Property Get PropertyGet() As Long + End Property + Friend Property Let PropertyLet(ByVal V As Long) + End Property + Friend Function FunctionOneArgument(ByVal X As Long) As Long + End Function +End Class + +Class Impl + Implements Target + Private Function Target_Pub() As Long + End Function +End Class + +[TestFixture] +Module FriendThroughImplements + + [TestCase] + Public Sub Control_SubOneLong_Raises97() + Dim T As Target = New Impl + On Error Resume Next + T.SubOneLong 5 + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + [TestCase] + Public Sub Control_SubOneByRef_Raises97() + Dim T As Target = New Impl + Dim L As Long + On Error Resume Next + T.SubOneByRef L + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + [TestCase] + Public Sub Control_FunctionNoArguments_Raises97() + Dim T As Target = New Impl + Dim L As Long + On Error Resume Next + L = T.FunctionNoArguments() + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + [TestCase] + Public Sub Control_FunctionReturnsString_Raises97() + Dim T As Target = New Impl + Dim S As String + On Error Resume Next + S = T.FunctionReturnsString() + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + [TestCase] + Public Sub Control_PropertyGet_Raises97() + Dim T As Target = New Impl + Dim L As Long + On Error Resume Next + L = T.PropertyGet + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + [TestCase] + Public Sub Control_PropertyLet_Raises97() + Dim T As Target = New Impl + On Error Resume Next + T.PropertyLet = 5 + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + [TestCase] + Public Sub SubNoArguments_ErrorIsCaught() + Dim T As Target = New Impl + On Error Resume Next + T.SubNoArguments + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + + ' Leave this one last: it does not return. + [TestCase] + Public Sub FunctionOneArgument_ErrorIsCaught() + Dim T As Target = New Impl + Dim L As Long + On Error Resume Next + L = T.FunctionOneArgument(5) + Dim N As Long = Err.Number + On Error GoTo 0 + Assert.Strict.AreEqual 97, N + End Sub + +End Module diff --git a/bugs/implements-friend-stub-stack/src/Sources/Startup.twin b/bugs/implements-friend-stub-stack/src/Sources/Startup.twin new file mode 100644 index 000000000..20233b3a9 --- /dev/null +++ b/bugs/implements-friend-stub-stack/src/Sources/Startup.twin @@ -0,0 +1,6 @@ +Module Startup + + Public Sub Main() + End Sub + +End Module diff --git a/bugs/implements-friend-stub-stack/vb6/Impl.cls b/bugs/implements-friend-stub-stack/vb6/Impl.cls new file mode 100644 index 000000000..36ee56eec --- /dev/null +++ b/bugs/implements-friend-stub-stack/vb6/Impl.cls @@ -0,0 +1,19 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Impl" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = True +Option Explicit + +Implements Target + +Private Function Target_Pub() As Long +End Function diff --git a/bugs/implements-friend-stub-stack/vb6/Module1.bas b/bugs/implements-friend-stub-stack/vb6/Module1.bas new file mode 100644 index 000000000..3b4afaab4 --- /dev/null +++ b/bugs/implements-friend-stub-stack/vb6/Module1.bas @@ -0,0 +1,78 @@ +Attribute VB_Name = "Module1" +Option Explicit + +' The calls the twinBASIC side of the report makes, each in a procedure of its own, followed by +' a return to Main, which is what goes wrong in a 32-bit twinBASIC build. +' Build and run it with: node scripts/bug_repro.mjs vb6 implements-friend-stub-stack + +Private Sub Case0() + Dim T As Target + Dim L As Long + Set T = New Impl + On Error Resume Next + T.SubNoArguments + Print #9, "Sub, no arguments: " & Err.Number & " " & Err.Description +End Sub + +Private Sub Case1() + Dim T As Target + Dim L As Long + Set T = New Impl + On Error Resume Next + T.SubOneLong 5 + Print #9, "Sub, one Long: " & Err.Number & " " & Err.Description +End Sub + +Private Sub Case2() + Dim T As Target + Dim L As Long + Set T = New Impl + On Error Resume Next + T.SubTwoLongs 1, 2 + Print #9, "Sub, two Longs: " & Err.Number & " " & Err.Description +End Sub + +Private Sub Case3() + Dim T As Target + Dim L As Long + Set T = New Impl + On Error Resume Next + T.SubOneCurrency 5 + Print #9, "Sub, one Currency: " & Err.Number & " " & Err.Description +End Sub + +Private Sub Case4() + Dim T As Target + Dim L As Long + Set T = New Impl + On Error Resume Next + L = T.FunctionNoArguments() + Print #9, "Function, no arguments: " & Err.Number & " " & Err.Description +End Sub + +Private Sub Case5() + Dim T As Target + Dim L As Long + Set T = New Impl + On Error Resume Next + L = T.FunctionOneArgument(5) + Print #9, "Function, one argument: " & Err.Number & " " & Err.Description +End Sub + +Sub Main() + On Error GoTo Fail + Open App.Path & "\out.txt" For Output As #9 + Case0 + Case1 + Case2 + Case3 + Case4 + Case5 + Print #9, "returned to Main" + Close #9 + Exit Sub +Fail: + On Error Resume Next + Print #9, "unhandled error " & Err.Number & ": " & Err.Description + Close #9 +End Sub diff --git a/bugs/implements-friend-stub-stack/vb6/Probe.vbp b/bugs/implements-friend-stub-stack/vb6/Probe.vbp new file mode 100644 index 000000000..b7200994e --- /dev/null +++ b/bugs/implements-friend-stub-stack/vb6/Probe.vbp @@ -0,0 +1,9 @@ +Type=Exe +Module=Module1; Module1.bas +Class=Target; Target.cls +Class=Impl; Impl.cls +Startup="Sub Main" +ExeName32="Probe.exe" +Name="Probe" +CompilationType=0 +OptimizationType=0 diff --git a/bugs/implements-friend-stub-stack/vb6/Target.cls b/bugs/implements-friend-stub-stack/vb6/Target.cls new file mode 100644 index 000000000..933a7febf --- /dev/null +++ b/bugs/implements-friend-stub-stack/vb6/Target.cls @@ -0,0 +1,30 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Target" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = True +Option Explicit + +Public Function Pub() As Long +End Function + +Friend Sub SubNoArguments() +End Sub +Friend Sub SubOneLong(ByVal X As Long) +End Sub +Friend Sub SubTwoLongs(ByVal X As Long, ByVal Y As Long) +End Sub +Friend Sub SubOneCurrency(ByVal X As Currency) +End Sub +Friend Function FunctionNoArguments() As Long +End Function +Friend Function FunctionOneArgument(ByVal X As Long) As Long +End Function diff --git a/bugs/import-cancel-leaves-import-mode/repro.json b/bugs/import-cancel-leaves-import-mode/repro.json new file mode 100644 index 000000000..f0eaed3a0 --- /dev/null +++ b/bugs/import-cancel-leaves-import-mode/repro.json @@ -0,0 +1,10 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "after Cancel in New Project Options of Import from VBP..., the next template's box is drawn as an import: a Unicode row, and no Include Common Controls", + "after that Cancel, a sample's box is drawn as an import too", + "Continue in that box gives a project with no WinNativeCommonCtls reference, the template's description and title in place of the name, and Unicode Common Control Notifications on; the project is otherwise normal" + ], + "steps": "1. File > New Project..., Import from VBP..., any .vbp. Press Cancel in New Project Options.\n2. File > New Project..., double-click Standard EXE. The box has a Unicode Common Control Notifications row and no Include Common Controls row. The same for a sample.\n3. Type a name and press Continue. The project has no WinNativeCommonCtls reference, its description and title are New project, not the name, and runtime.useUnicodeCommonControlNotifications is true.\n4. The next box is right again.\nControls: the same card in a fresh IDE; Cancel in the import's own picker leaves the next box right." +} diff --git a/bugs/import-card-closes-project-first/repro.json b/bugs/import-card-closes-project-first/repro.json new file mode 100644 index 000000000..af6114b5b --- /dev/null +++ b/bugs/import-card-closes-project-first/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "Import from VBP... closes the open project before its picker opens, and cancelling the picker leaves no project and no dialog", + "Import from folder... closes the open project before its picker opens, and cancelling the picker leaves no project and no dialog" + ], + "steps": "1. Open a saved project.\n2. File > New Project..., double-click Import from VBP... (or Import from folder...).\n3. The project closes, then the picker opens. Press Cancel in it.\n4. There is no project and no dialog, and tB Services reads UNAVAILABLE.\nControl: File > Open Project... cancelled, and Browse... cancelled, keep the project." +} diff --git a/bugs/laa-mismatch-after-new-project/repro.json b/bugs/laa-mismatch-after-new-project/repro.json new file mode 100644 index 000000000..cebc680c4 --- /dev/null +++ b/bugs/laa-mismatch-after-new-project/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "with New Project Options first and no reload yet, opening a saved project with LAA off fails with MISMATCHED LAA OR DEP AWARENESS, and a second open works" + ], + "steps": "1. Start the IDE. File > New Project..., Standard EXE, Continue.\n2. File > Close Project.\n3. File > Open Project..., a saved project with Large Address Aware (LAA) set to No.\n4. The box reads Failed to load the project (error code MISMATCHED LAA OR DEP AWARENESS), with Remove It and Keep It, and no project is open. Press Keep It.\n5. Open the same project again: it opens.\nControl: the same project, with no New Project Options before it, opens at once." +} diff --git a/bugs/launcher-box-names-wrong-file/repro.json b/bugs/launcher-box-names-wrong-file/repro.json new file mode 100644 index 000000000..0ae721b64 --- /dev/null +++ b/bugs/launcher-box-names-wrong-file/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting-missing", + "tests": [ + "with bin\\twinBASIC_ide_win32.dll missing, the box names bin\\twinBASIC_win64.dll, which is there" + ], + "steps": "1. In a copy of the install, rename bin\\twinBASIC_ide_win32.dll.\n2. Start twinBASIC.exe. A box titled twinBASIC Compiler says ERROR: failed to load the twinBASIC DLL: and names bin\\twinBASIC_win64.dll, a file that is there.\n3. Press OK: the process ends.\nControl: with bin\\twinBASIC_win32.exe and its variants renamed, the box names that file." +} diff --git a/bugs/missing-compiler-silent-stop/repro.json b/bugs/missing-compiler-silent-stop/repro.json new file mode 100644 index 000000000..e5247275f --- /dev/null +++ b/bugs/missing-compiler-silent-stop/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting-missing", + "tests": [ + "with the compiler variant the IDE starts missing, the page goes quiet after one host box: no retry, no line in the console, no message" + ], + "steps": "1. In a copy of the install, rename bin\\twinBASIC_win32_noDEP_LAA.exe, the variant the IDE starts for a project with Large Address Aware on.\n2. Start the IDE and open such a project. A box titled twinBASIC IDE names the file and says Please close the IDE, and restore any missing files.\n3. Press OK. Please wait... goes, no project opens, and the status bar reads UNAVAILABLE with four Not initialized lines for good. Nothing retries, and the page shows no message and no line in the DEBUG CONSOLE." +} diff --git a/bugs/missing-dll-box-names-no-file/repro.json b/bugs/missing-dll-box-names-no-file/repro.json new file mode 100644 index 000000000..53f787d2f --- /dev/null +++ b/bugs/missing-dll-box-names-no-file/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting-missing", + "tests": [ + "with bin\\twinBASIC_win32.dll missing, a box titled shutdown names no file and gives no link" + ], + "steps": "1. In a copy of the install, rename bin\\twinBASIC_win32.dll.\n2. Start twinBASIC.exe. A box titled shutdown says Run-time error '-353703418' and Unable to load DLL, with no file named and no link.\n3. Press OK: the process ends.\nControl: with bin\\twinBASIC_win32.exe and its variants renamed, the box names that file and gives the link." +} diff --git a/bugs/missing-implementation-message/missing-implementation-message.twinproj b/bugs/missing-implementation-message/missing-implementation-message.twinproj new file mode 100644 index 000000000..0c96712a2 Binary files /dev/null and b/bugs/missing-implementation-message/missing-implementation-message.twinproj differ diff --git a/bugs/missing-implementation-message/repro.json b/bugs/missing-implementation-message/repro.json new file mode 100644 index 000000000..09b6f6ecb --- /dev/null +++ b/bugs/missing-implementation-message/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "compile", + "expect": { + "exit": 1, + "diagnostics": ["TB65535"], + "output": ["TB65535 Internal Error - Bad Error Info Locale Data"] + } +} diff --git a/bugs/missing-implementation-message/src/Settings b/bugs/missing-implementation-message/src/Settings new file mode 100644 index 000000000..8371a083b --- /dev/null +++ b/bugs/missing-implementation-message/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "MissingImplementationMessage", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: TB65535 names no member on BETA 1034: Bad Error Info Locale Data", + "project.exportPathIsV2": true, + "project.id": "{B1DD4041-2EC5-42C5-848E-DFFFD5E974BC}", + "project.name": "MissingImplementationMessage", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/missing-implementation-message/src/Sources/Startup.twin b/bugs/missing-implementation-message/src/Sources/Startup.twin new file mode 100644 index 000000000..09a9366df --- /dev/null +++ b/bugs/missing-implementation-message/src/Sources/Startup.twin @@ -0,0 +1,17 @@ +' Implements of a class that leaves one of its Public members out. The error is right; on +' BETA 1034 its message no longer says which member, and BETA 1033 did. +Class Base + Public Function F() As Long + End Function +End Class + +Class Partial + Implements Base +End Class + +Module Startup + + Public Sub Main() + End Sub + +End Module diff --git a/bugs/missing-linked-package-restart-box/repro.json b/bugs/missing-linked-package-restart-box/repro.json new file mode 100644 index 000000000..138e2fdac --- /dev/null +++ b/bugs/missing-linked-package-restart-box/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "a linked package's missing file: Restart opens a box titled shutdown that names neither the package nor the file, and the compiler is back about 80 s after OK, the edit kept" + ], + "steps": "1. Open a project that references a linked package, its .twinpack in %APPDATA%\\twinBASIC\\packages, and type something in a source file.\n2. Delete the .twinpack.\n3. Press Restart the compiler. About a second later a native box opens, titled shutdown, with the text Run-time error '-2147467259' and Unspecified error, and one button, OK. The status bar reads LIMITED and the page Please wait... 5.0%.\n4. Press OK. Nothing explains the wait. About 80 s later the status bar reads OPERATIONAL, the project is back with the edit, and the References view shows the package's row with ERROR unable to load this reference.\nControl: Restart with the file there brings the compiler back at once." +} diff --git a/bugs/new-project-enter-acts-twice/repro.json b/bugs/new-project-enter-acts-twice/repro.json new file mode 100644 index 000000000..e8c6833da --- /dev/null +++ b/bugs/new-project-enter-acts-twice/repro.json @@ -0,0 +1,11 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "with the highlight moved to Open by Tab, Enter runs the open twice", + "with the highlight moved to Browse... by Tab, Enter opens the picker and the selected item", + "with the highlight moved to Cancel by Tab, Enter closes a saved project with no question and opens the selected card", + "Enter on a focused card opens the selected card, not the focused one" + ], + "steps": "1. Open a saved project and change nothing.\n2. File > New Project...\n3. Press Tab four times: the highlight is on Cancel.\n4. Press Enter: the project closes with no question, and New Project Options opens for the selected card.\nWith Tab pressed three times the highlight is on Open, and Enter runs the open twice; with two, on Browse..., and Enter shows the picker and opens the item.\nFor the card: click ActiveX DLL, click in the search box, press Tab (the focus is on Standard EXE), press Enter: ActiveX DLL opens.\nControl: Esc, and a click on Cancel, keep the project." +} diff --git a/bugs/new-project-keyboard-dead-ends/repro.json b/bugs/new-project-keyboard-dead-ends/repro.json new file mode 100644 index 000000000..8ef96f248 --- /dev/null +++ b/bugs/new-project-keyboard-dead-ends/repro.json @@ -0,0 +1,10 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "Up and Down do nothing in the search box, which has the focus when the dialog opens", + "no key reaches the rail: Tab and the arrows leave the section as it was", + "in Recent, Tab sends the focus out of the dialog, and the keyboard does nothing until a click" + ], + "steps": "1. File > New Project...: press Down, and the selection stays on Standard EXE.\n2. Press Tab and the arrows: the section stays New.\n3. File > Open Recent...: press Tab twice, and the focus is on a panel header behind the dialog.\n4. Down, Up, Enter and Esc do nothing until a click in the dialog.\nControl: Tab, then the arrows, move the selected item in New." +} diff --git a/bugs/no-save-flag-unguarded-saves/repro.json b/bugs/no-save-flag-unguarded-saves/repro.json new file mode 100644 index 000000000..97a944b02 --- /dev/null +++ b/bugs/no-save-flag-unguarded-saves/repro.json @@ -0,0 +1,11 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "with --no-save-ide-settings, closing the New / Open Project dialog calls SaveIDESetting for NEWFRONTPAGE all the same", + "with --no-save-ide-settings, a change in the Object Browser calls SaveIDESetting for OBJECT_BROWSER all the same", + "with --no-save-ide-settings, saving the publisher's details calls SaveIDESetting for PUBLISHER all the same", + "--no-save-ide-settings is read only after the first-run questions: the licence answers are saved with it on the command line" + ], + "steps": "With --no-save-ide-settings on the command line, and a proxy in front of the host object that records each SaveIDESetting call and drops it:\n1. Cancel in the New / Open Project dialog calls SaveIDESetting for NEWFRONTPAGE.\n2. A change of view in the Object Browser calls it for OBJECT_BROWSER.\n3. savePublisherInfo calls it for PUBLISHER.\n4. On a first run g_DontSaveIdeSettings is still false at the licence agreement, and I AGREE calls it for LICENCEAGREEMENTV3 and ALLOWERRORREPORTINGV3.\nControl: a build target chosen for a saved project is not saved." +} diff --git a/bugs/open-takes-earlier-settings/repro.json b/bugs/open-takes-earlier-settings/repro.json new file mode 100644 index 000000000..d84819c39 --- /dev/null +++ b/bugs/open-takes-earlier-settings/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "after a DEP / LAA reload and then New Project Options, the next project opened takes the earlier project's whole Settings, silently, and Ctrl+S writes them into its file", + "the same steps by an import: a project with LAA off, Import from VBP..., then any project, and closing without a save leaves its file as it was" + ], + "steps": "1. Have three saved projects: Old.twinproj with Large Address Aware (LAA) set to No, and Newer.twinproj and Target.twinproj with it set to Yes.\n2. Start the IDE and open Old.twinproj: it loads twice, for its LAA.\n3. Open Newer.twinproj.\n4. File > New Project..., Standard EXE, type Zzz as the name, Continue.\n5. Open Target.twinproj: its title bar is its own, with no bullet, nothing is asked, and the IDE holds the Settings of Zzz.\n6. Ctrl+S writes them into Target.twinproj.\nBy an import: open Old.twinproj, close it, Import from VBP..., Continue, close it with Discard Changes, open Target.twinproj.\nControls: New Project Options alone, a reload alone, a new project made while the IDE runs with LAA off, and the open after the one that took the Settings each leave a project its own Settings." +} diff --git a/bugs/project-name-paste-unfiltered/repro.json b/bugs/project-name-paste-unfiltered/repro.json new file mode 100644 index 000000000..8f0344195 --- /dev/null +++ b/bugs/project-name-paste-unfiltered/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "a name pasted into Project Name keeps its space and its dot, and becomes the project's name, title and root folder; it builds with no diagnostic" + ], + "steps": "1. File > New Project..., double-click Standard EXE.\n2. In Project Name, type My Project.x_1: the box reads MyProjectx_1.\n3. Select the text and paste My Project.x: the box reads My Project.x.\n4. Continue: the project's name, title and root folder are My Project.x, and it builds Build\\My Project.x_win32.exe with no diagnostic.\nControl: typed text is filtered." +} diff --git a/bugs/recent-path-hash-cut/repro.json b/bugs/recent-path-hash-cut/repro.json new file mode 100644 index 000000000..97aba91e5 --- /dev/null +++ b/bugs/recent-path-hash-cut/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "a Recent path with a # shows cut in two, at the #, and opens from the whole path" + ], + "steps": "1. Have C:\\dev\\C#\\app.twinproj in the recent list.\n2. File > Open Recent...: the row reads C:\\dev\\C over \\app.twinproj.\n3. Open on the row opens the whole path.\nControl: a path with no # shows whole." +} diff --git a/bugs/recovered-project-save-overwrites/repro.json b/bugs/recovered-project-save-overwrites/repro.json new file mode 100644 index 000000000..6252aa755 --- /dev/null +++ b/bugs/recovered-project-save-overwrites/repro.json @@ -0,0 +1,11 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "a project cut inside its Sources tree opens with every source gone and is not marked changed, and Ctrl+S overwrites the damaged file with no question", + "a project cut at its first entry opens as RECOVERED_PROJECT, and Ctrl+S overwrites the file with that empty project", + "Add > Import... in a project cut after its sources overwrites the damaged file, with nothing pressed that says Save", + "a change in Project Settings that restarts the compiler overwrites the damaged file, with nothing pressed that says Save" + ], + "steps": "1. Copy a saved project with several sources, and cut the copy in the middle of its second source.\n2. File > Open Project..., the copy; OK on the box *bad file format*.\n3. The project opens under the file's name, not marked changed, with its Settings and no source.\n4. Ctrl+S: nothing is asked, and the file is overwritten with no source.\nThe same with Add > Import... of any file, on a project cut after its sources, and with a reference unticked in Project Settings and Apply Changes.\nControl: closing leaves the damaged file as it was, and Save Project As to a new name keeps it too." +} diff --git a/bugs/res-string-type-empty-name/repro.json b/bugs/res-string-type-empty-name/repro.json new file mode 100644 index 000000000..126bcf54d --- /dev/null +++ b/bugs/res-string-type-empty-name/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:starting", + "tests": [ + "a .res resource whose type is a string imports with an empty name, one blank 0-byte row for all of them, and a Standard EXE then ends its build in ACCESS_VIOLATION with no exe" + ], + "steps": "1. Make a Standard EXE .vbp with a .res that holds three resources of the type CUSTOM.\n2. File > New Project..., Import from VBP..., the .vbp, Continue.\n3. Resources\\CUSTOM shows one row with no name, which reads as 0 bytes, and the import log says 0 ERRORS/WARNINGS.\n4. Save the project: it holds all three, each under an empty name.\n5. Build: NATIVE EXCEPTION: ACCESS_VIOLATION, and no exe.\nControl: resources of numeric types keep their ids, and the project builds." +} diff --git a/bugs/safe-mode-start-never-runs/repro.json b/bugs/safe-mode-start-never-runs/repro.json new file mode 100644 index 000000000..53ab75669 --- /dev/null +++ b/bugs/safe-mode-start-never-runs/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "Start in Safe Mode never runs and never ends, and Stop does nothing; a restart ends it" + ], + "steps": "1. Open a project with a Sub Main.\n2. Choose safeMode in the build box, and press OK on the box that opens.\n3. Press Start. The DEBUG CONSOLE prints Executing 'Main'... and WARNING: execution delayed; waiting for background compilation to finish, and nothing more; the IDE stays in run mode.\n4. Press Stop: nothing changes.\n5. Restart the compiler: the run ends.\nControl: Start with the box on win32 runs the program to its end." +} diff --git a/bugs/sample22-event-log-wrong-button/repro.json b/bugs/sample22-event-log-wrong-button/repro.json new file mode 100644 index 000000000..1fa395b0b --- /dev/null +++ b/bugs/sample22-event-log-wrong-button/repro.json @@ -0,0 +1,6 @@ +{ + "mode": "lane", + "lane": "ide:samples", + "tests": ["Sample 22's Uninstall button of service 1 registers the event log of service 2"], + "steps": "1. Open Sample 22.\n2. Open Sources/FORMS/MainForm.twin.\n3. btnUninstall001_Click calls RegisterEventLog(\"Application\\TBSERVICE002\"), and btnInstall002_Click calls nothing of the kind." +} diff --git a/bugs/start-no-main-error-text/repro.json b/bugs/start-no-main-error-text/repro.json new file mode 100644 index 000000000..1618079ee --- /dev/null +++ b/bugs/start-no-main-error-text/repro.json @@ -0,0 +1,6 @@ +{ + "mode": "lane", + "lane": "ide:samples", + "tests": ["Start with no Main writes a compile error that gives no reason"], + "steps": "1. Make a new Standard EXE project, and replace its module with:\n Module Startup\n Public Sub Greet(ByVal name As String)\n Debug.Print name\n End Sub\n End Module\n2. Press F5. The DEBUG CONSOLE shows Executing 'Main'..., then (compile error: Main). BETA 1033 wrote (compile error: Unrecognized symbol 'Main')." +} diff --git a/bugs/trace-auto-activate-needs-restart/repro.json b/bugs/trace-auto-activate-needs-restart/repro.json new file mode 100644 index 000000000..5e62c18a7 --- /dev/null +++ b/bugs/trace-auto-activate-needs-restart/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:troubleshooting", + "tests": [ + "a native exception auto-activates TRACE mode and Help says so, but a second exception in that compiler still prints no dump" + ], + "steps": "1. Open a project with a Sub that calls RtlMoveMemory with two different bad addresses, such as 16 and 32 (0 and 0 does nothing).\n2. Run that Sub. The DEBUG CONSOLE prints one NATIVE EXCEPTION line and says [IDE] auto-activated TRACE-MODE in this session for extra debugging info on further native exceptions. The Help menu's item reads Compiler services TRACE mode: Enabled.\n3. Run it again: the same one line, numbered 0002, and no dump.\n4. Press Restart the compiler and run it again: the exception line is followed by a line per thread.\nControl: step 4." +} diff --git a/bugs/udt-array-element-initialize-not-run/repro.json b/bugs/udt-array-element-initialize-not-run/repro.json new file mode 100644 index 000000000..ea2c4662a --- /dev/null +++ b/bugs/udt-array-element-initialize-not-run/repro.json @@ -0,0 +1,29 @@ +{ + "mode": "test", + "expect": { + "exit": 7, + "output": [ + "^PASS RegressionTests\\.Variable_BothRun$", + "^PASS RegressionTests\\.Variable_SeesInitialize$", + "^PASS RegressionTests\\.ModuleVariable_SeesInitialize$", + "^PASS RegressionTests\\.ClassField_SeesInitialize$", + "^PASS RegressionTests\\.FixedArray_TerminateRunsForEachElement$", + "^PASS RegressionTests\\.DynamicArray_TerminateRunsForEachElement$", + "^PASS RegressionTests\\.Erase_TerminateRunsForEachElement$", + "^FAIL RegressionTests\\.FixedArray_InitializeRunsForEachElement: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.FixedArray_ElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.DynamicArray_InitializeRunsForEachElement: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.DynamicArray_ElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ReDimPreserve_NewElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ModuleArray_ElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.StaticArray_ElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ClassArray_ElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.UdtMember_SeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ArrayOfUdtMember_SeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ArrayInUdt_ElementSeesInitialize: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ArrayInUdt_TerminateRunsForEachElement: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ReDim_TerminateRunsForDiscardedElements: .*Actual: 0 \\[Long\\]", + "^FAIL RegressionTests\\.ReDimPreserve_TerminateRunsForDroppedElements: .*Actual: 0 \\[Long\\]" + ] + } +} diff --git a/bugs/udt-array-element-initialize-not-run/src/Settings b/bugs/udt-array-element-initialize-not-run/src/Settings new file mode 100644 index 000000000..506646d53 --- /dev/null +++ b/bugs/udt-array-element-initialize-not-run/src/Settings @@ -0,0 +1,75 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "UdtArrayElementInitializeNotRun", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: `Type_Initialize` does not run for the elements of a UDT array or for a UDT member of a UDT, though `Type_Terminate` does", + "project.exportPathIsV2": true, + "project.id": "{1947399F-6750-4966-9538-C0E3190024F7}", + "project.name": "UdtArrayElementInitializeNotRun", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + }, + { + "id": "{9847A10D-8987-44B2-8B87-EA77DEF7F1D9}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] Assert", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "Assert", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 24 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/udt-array-element-initialize-not-run/src/Sources/Fields.twin b/bugs/udt-array-element-initialize-not-run/src/Sources/Fields.twin new file mode 100644 index 000000000..f34d68860 --- /dev/null +++ b/bugs/udt-array-element-initialize-not-run/src/Sources/Fields.twin @@ -0,0 +1,9 @@ +' A class holding the UDT: its field runs Type_Initialize, the elements of its array do not. +Class Fields + Public u As Counted + Private a(2) As Counted + + Public Function Element() As Long + Return a(1).X + End Function +End Class diff --git a/bugs/udt-array-element-initialize-not-run/src/Sources/RegressionTests.twin b/bugs/udt-array-element-initialize-not-run/src/Sources/RegressionTests.twin new file mode 100644 index 000000000..e28be62d6 --- /dev/null +++ b/bugs/udt-array-element-initialize-not-run/src/Sources/RegressionTests.twin @@ -0,0 +1,186 @@ +' Regression tests: each [TestCase] asserts the correct result. The controls (a variable, a +' module variable, a class field, Type_Terminate for the elements of an array when the array +' goes) pass. Where the UDT is an element of an array, or a member of another UDT, +' Type_Initialize does not run, and where ReDim discards elements Type_Terminate does not: +' those cases fail, in a 32-bit and a 64-bit build. +[TestFixture] +Module RegressionTests + + Private Sub Reset() + Inits = 0 + Terms = 0 + End Sub + + ' --- what the counted cases declare: none of them reads an element --- + + Private Sub DeclareVariable() + Dim c As Counted + End Sub + + Private Sub DeclareFixedArray() + Dim t(2) As Counted + End Sub + + Private Sub DeclareDynamicArray() + Dim t() As Counted + ReDim t(2) + End Sub + + Private Sub DeclareArrayHolder() + Dim h As ArrayHolder + End Sub + + Private Function StaticElement() As Long + Static t(2) As Counted + Return t(1).X + End Function + + ' --- controls: these pass --- + + [TestCase] + Public Sub Variable_BothRun() + Reset + DeclareVariable + Assert.Strict.AreEqual 1&, Inits + Assert.Strict.AreEqual 1&, Terms + End Sub + + [TestCase] + Public Sub Variable_SeesInitialize() + Dim c As Counted + Assert.Strict.AreEqual 42&, c.X + End Sub + + [TestCase] + Public Sub ModuleVariable_SeesInitialize() + Assert.Strict.AreEqual 42&, ModuleVariable.X + End Sub + + [TestCase] + Public Sub ClassField_SeesInitialize() + Dim o As New Fields + Assert.Strict.AreEqual 42&, o.u.X + End Sub + + [TestCase] + Public Sub FixedArray_TerminateRunsForEachElement() + Reset + DeclareFixedArray + Assert.Strict.AreEqual 3&, Terms + End Sub + + [TestCase] + Public Sub DynamicArray_TerminateRunsForEachElement() + Reset + DeclareDynamicArray + Assert.Strict.AreEqual 3&, Terms + End Sub + + [TestCase] + Public Sub Erase_TerminateRunsForEachElement() + Dim t() As Counted + ReDim t(2) + Reset + Erase t + Assert.Strict.AreEqual 3&, Terms + End Sub + + ' --- Type_Initialize does not run: these fail --- + + [TestCase] + Public Sub FixedArray_InitializeRunsForEachElement() + Reset + DeclareFixedArray + Assert.Strict.AreEqual 3&, Inits + End Sub + + [TestCase] + Public Sub FixedArray_ElementSeesInitialize() + Dim t(2) As Counted + Assert.Strict.AreEqual 42&, t(1).X + End Sub + + [TestCase] + Public Sub DynamicArray_InitializeRunsForEachElement() + Reset + DeclareDynamicArray + Assert.Strict.AreEqual 3&, Inits + End Sub + + [TestCase] + Public Sub DynamicArray_ElementSeesInitialize() + Dim t() As Counted + ReDim t(2) + Assert.Strict.AreEqual 42&, t(1).X + End Sub + + [TestCase] + Public Sub ReDimPreserve_NewElementSeesInitialize() + Dim t() As Counted + ReDim t(0) + ReDim Preserve t(2) + Assert.Strict.AreEqual 42&, t(2).X + End Sub + + [TestCase] + Public Sub ModuleArray_ElementSeesInitialize() + Assert.Strict.AreEqual 42&, ModuleArray(1).X + End Sub + + [TestCase] + Public Sub StaticArray_ElementSeesInitialize() + Assert.Strict.AreEqual 42&, StaticElement() + End Sub + + [TestCase] + Public Sub ClassArray_ElementSeesInitialize() + Dim o As New Fields + Assert.Strict.AreEqual 42&, o.Element() + End Sub + + [TestCase] + Public Sub UdtMember_SeesInitialize() + Dim h As Holder + Assert.Strict.AreEqual 42&, h.u.X + End Sub + + [TestCase] + Public Sub ArrayOfUdtMember_SeesInitialize() + Dim t(2) As Holder + Assert.Strict.AreEqual 42&, t(1).u.X + End Sub + + [TestCase] + Public Sub ArrayInUdt_ElementSeesInitialize() + Dim h As ArrayHolder + Assert.Strict.AreEqual 42&, h.a(1).X + End Sub + + ' --- Type_Terminate does not run: these fail --- + + [TestCase] + Public Sub ArrayInUdt_TerminateRunsForEachElement() + Reset + DeclareArrayHolder + Assert.Strict.AreEqual 3&, Terms + End Sub + + [TestCase] + Public Sub ReDim_TerminateRunsForDiscardedElements() + Dim t() As Counted + ReDim t(2) + Reset + ReDim t(1) + Assert.Strict.AreEqual 3&, Terms + End Sub + + [TestCase] + Public Sub ReDimPreserve_TerminateRunsForDroppedElements() + Dim t() As Counted + ReDim t(3) + Reset + ReDim Preserve t(1) + Assert.Strict.AreEqual 2&, Terms + End Sub + +End Module diff --git a/bugs/udt-array-element-initialize-not-run/src/Sources/Startup.twin b/bugs/udt-array-element-initialize-not-run/src/Sources/Startup.twin new file mode 100644 index 000000000..c983b52ba --- /dev/null +++ b/bugs/udt-array-element-initialize-not-run/src/Sources/Startup.twin @@ -0,0 +1,54 @@ +' A UDT whose Type_Initialize and Type_Terminate count their calls, and the places +' RegressionTests declares it. Type_Initialize also sets X, so a UDT it did not run +' for shows X = 0. +' +' A procedure that reads a field of an element of a dynamic array, or of a class +' field, runs one more Initialize and Terminate, on another address, so no procedure +' here both reads a UDT and is counted. +Module Startup + + Public Inits As Long + Public Terms As Long + + Public Type Counted + X As Long + + Private Sub Type_Initialize() + Inits = Inits + 1 + Me.X = 42 + End Sub + + Private Sub Type_Terminate() + Terms = Terms + 1 + End Sub + End Type + + Public Type Holder + u As Counted + End Type + + Public Type ArrayHolder + a(2) As Counted + End Type + + Public ModuleVariable As Counted + Public ModuleArray(2) As Counted + + Private Sub Declared() + Dim t(2) As Counted + End Sub + + Private Sub Read() + Dim t(2) As Counted + Debug.Print "t(1).X = " & CStr(t(1).X) + End Sub + + Public Sub Main() + Inits = 0 + Terms = 0 + Declared + Debug.Print "Type_Initialize ran " & CStr(Inits) & " times, Type_Terminate " & CStr(Terms) & " times" + Read + End Sub + +End Module diff --git a/bugs/udt-array-element-initialize-not-run/udt-array-element-initialize-not-run.twinproj b/bugs/udt-array-element-initialize-not-run/udt-array-element-initialize-not-run.twinproj new file mode 100644 index 000000000..dc6303f5d Binary files /dev/null and b/bugs/udt-array-element-initialize-not-run/udt-array-element-initialize-not-run.twinproj differ diff --git a/bugs/udt-array-element-method-call-crash/repro.json b/bugs/udt-array-element-method-call-crash/repro.json index cba4a52b4..163160fc5 100644 --- a/bugs/udt-array-element-method-call-crash/repro.json +++ b/bugs/udt-array-element-method-call-crash/repro.json @@ -9,6 +9,12 @@ "^PASS RegressionTests\\.ArrayInUdtField_Element_PropertyGet$", "^PASS RegressionTests\\.ElementCopiedFirst_PropertyGet$", "^PASS RegressionTests\\.Element_Field$", + "^PASS RegressionTests\\.Scalar_ExplicitConversion$", + "^PASS RegressionTests\\.Scalar_ImplicitConversion$", + "^PASS RegressionTests\\.UdtField_Conversion$", + "^PASS RegressionTests\\.ArrayInUdtField_Element_Conversion$", + "^PASS RegressionTests\\.ElementCopiedFirst_Conversion$", + "^PASS RegressionTests\\.Element_TypeAssignment$", "^FAIL RegressionTests\\.LocalArray_Element_PropertyGet: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", "^FAIL RegressionTests\\.LocalArray_Element_Sub: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", "^FAIL RegressionTests\\.LocalArray_Element_FunctionThatIgnoresMe: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", @@ -20,7 +26,15 @@ "^FAIL RegressionTests\\.DynamicArray_Element_PropertyGet: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", "^FAIL RegressionTests\\.ModuleLevelArray_Element_PropertyGet: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", "^FAIL RegressionTests\\.ArrayParameter_Element_PropertyGet: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", - "^FAIL RegressionTests\\.OneByteUdt_Element_PropertyGet: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$" + "^FAIL RegressionTests\\.OneByteUdt_Element_PropertyGet: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.LocalArray_Element_ExplicitConversion: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.LocalArray_Element_ImplicitConversion: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.LocalArray_Element_ConvertedByDebugPrint: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.LocalArray_Element_ConvertedForAnArgument: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.StaticArray_Element_Conversion: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.DynamicArray_Element_Conversion: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.ModuleLevelArray_Element_Conversion: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$", + "^FAIL RegressionTests\\.ArrayParameter_Element_Conversion: \\(runtime error -2147467259: NATIVE EXCEPTION: ACCESS_VIOLATION\\)$" ] } } diff --git a/bugs/udt-array-element-method-call-crash/src/Sources/RegressionTests.twin b/bugs/udt-array-element-method-call-crash/src/Sources/RegressionTests.twin index 5eeff4625..b871ab5da 100644 --- a/bugs/udt-array-element-method-call-crash/src/Sources/RegressionTests.twin +++ b/bugs/udt-array-element-method-call-crash/src/Sources/RegressionTests.twin @@ -150,4 +150,120 @@ Module RegressionTests Assert.Strict.AreEqual True, f(1).IsClosed End Sub + ' --- Type_Conversion and Type_Assignment: the controls pass --- + + [TestCase] + Public Sub Scalar_ExplicitConversion() + Dim c As Converts + c.X = 5 + Assert.Strict.AreEqual "C5", CStr(c) + End Sub + + [TestCase] + Public Sub Scalar_ImplicitConversion() + Dim c As Converts + c.X = 5 + Dim s As String + s = c + Assert.Strict.AreEqual "C5", s + End Sub + + [TestCase] + Public Sub UdtField_Conversion() + Dim h As ConvertsHolder + h.One.X = 5 + Assert.Strict.AreEqual "C5", CStr(h.One) + End Sub + + [TestCase] + Public Sub ArrayInUdtField_Element_Conversion() + Dim h As ConvertsHolder + h.Many(1).X = 5 + Assert.Strict.AreEqual "C5", CStr(h.Many(1)) + End Sub + + [TestCase] + Public Sub ElementCopiedFirst_Conversion() + Dim a(1 To 3) As Converts + a(1).X = 5 + Dim e As Converts + e = a(1) + Assert.Strict.AreEqual "C5", CStr(e) + End Sub + + [TestCase] + Public Sub Element_TypeAssignment() + Dim a(1 To 3) As Assigns + a(1) = 5 + Assert.Strict.AreEqual 5&, a(1).X + End Sub + + ' --- Type_Conversion on an element: these fail --- + + [TestCase] + Public Sub LocalArray_Element_ExplicitConversion() + Dim a(1 To 3) As Converts + a(1).X = 5 + Assert.Strict.AreEqual "C5", CStr(a(1)) + End Sub + + [TestCase] + Public Sub LocalArray_Element_ImplicitConversion() + Dim a(1 To 3) As Converts + a(1).X = 5 + Dim s As String + s = a(1) + Assert.Strict.AreEqual "C5", s + End Sub + + [TestCase] + Public Sub LocalArray_Element_ConvertedByDebugPrint() + Dim a(1 To 3) As Converts + a(1).X = 5 + Debug.Print a(1) + End Sub + + [TestCase] + Public Sub LocalArray_Element_ConvertedForAnArgument() + Dim a(1 To 3) As Converts + a(1).X = 5 + Assert.Strict.AreEqual "C5", Same(a(1)) + End Sub + + Private Function Same(ByVal s As String) As String + Same = s + End Function + + [TestCase] + Public Sub StaticArray_Element_Conversion() + Static a(1 To 3) As Converts + a(1).X = 5 + Assert.Strict.AreEqual "C5", CStr(a(1)) + End Sub + + [TestCase] + Public Sub DynamicArray_Element_Conversion() + Dim a() As Converts + ReDim a(1 To 3) + a(1).X = 5 + Assert.Strict.AreEqual "C5", CStr(a(1)) + End Sub + + [TestCase] + Public Sub ModuleLevelArray_Element_Conversion() + ModuleConverts(1).X = 5 + Assert.Strict.AreEqual "C5", CStr(ModuleConverts(1)) + End Sub + + [TestCase] + Public Sub ArrayParameter_Element_Conversion() + Dim a(1 To 3) As Converts + a(1).X = 5 + Assert.Strict.AreEqual "C5", TextOfFirst(a) + End Sub + + Private Function TextOfFirst(ByRef b() As Converts) As String + TextOfFirst = CStr(b(1)) + End Function + End Module diff --git a/bugs/udt-array-element-method-call-crash/src/Sources/Startup.twin b/bugs/udt-array-element-method-call-crash/src/Sources/Startup.twin index e5db0fda0..4175ae5ad 100644 --- a/bugs/udt-array-element-method-call-crash/src/Sources/Startup.twin +++ b/bugs/udt-array-element-method-call-crash/src/Sources/Startup.twin @@ -38,6 +38,31 @@ Module Startup Many(1 To 3) As Pair End Type + ' A conversion is a member procedure the compiler calls for you. + Public Type Converts + X As Long + + Private Function Type_Conversion() As String + Return "C" & CStr(Me.X) + End Function + End Type + + Public Type ConvertsHolder + One As Converts + Many(1 To 3) As Converts + End Type + + ' Type_Assignment on an element works: the element's address reaches it. + Public Type Assigns + X As Long + + Private Sub Type_Assignment(ByVal RHS As Long) + Me.X = RHS + End Sub + End Type + + Public ModuleConverts(1 To 3) As Converts + Public ModuleLevel(1 To 3) As Pair Public Sub Main() diff --git a/bugs/udt-array-element-method-call-crash/udt-array-element-method-call-crash.twinproj b/bugs/udt-array-element-method-call-crash/udt-array-element-method-call-crash.twinproj index 5fa2513db..6e8098ec6 100644 Binary files a/bugs/udt-array-element-method-call-crash/udt-array-element-method-call-crash.twinproj and b/bugs/udt-array-element-method-call-crash/udt-array-element-method-call-crash.twinproj differ diff --git a/bugs/udt-two-conversions-variant-codegen/repro.json b/bugs/udt-two-conversions-variant-codegen/repro.json new file mode 100644 index 000000000..7a4e0472a --- /dev/null +++ b/bugs/udt-two-conversions-variant-codegen/repro.json @@ -0,0 +1,7 @@ +{ + "mode": "build", + "expect": { + "exit": 5, + "message": "compilation \\(codegen\\) error detected in 'Startup\\.Mainrootmain'" + } +} diff --git a/bugs/udt-two-conversions-variant-codegen/src/Settings b/bugs/udt-two-conversions-variant-codegen/src/Settings new file mode 100644 index 000000000..5fe132a4a --- /dev/null +++ b/bugs/udt-two-conversions-variant-codegen/src/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "UdtTwoConversionsVariantCodegen", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Reproduces: A UDT with two `Type_Conversion` overloads and none `As Variant` fails code generation when it is converted to a `Variant`", + "project.exportPathIsV2": true, + "project.id": "{1D84B1D8-B6D7-44DA-A1BA-A4FBB6D84AA4}", + "project.name": "UdtTwoConversionsVariantCodegen", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/bugs/udt-two-conversions-variant-codegen/src/Sources/Startup.twin b/bugs/udt-two-conversions-variant-codegen/src/Sources/Startup.twin new file mode 100644 index 000000000..884a6ef3f --- /dev/null +++ b/bugs/udt-two-conversions-variant-codegen/src/Sources/Startup.twin @@ -0,0 +1,27 @@ +' A UDT with two Type_Conversion overloads, neither As Variant, converted to a Variant. +' Debug.Print c, v = c and CVar(c) each compile with no diagnostic and are a codegen error +' when the procedure is built: "compilation (codegen) error detected in 'Startup.Mainrootmain'". +' Passing c to a ByVal Variant parameter is TB5001, as it should be. +' Fine: one conversion; an overload As Variant among them, which is then the one used; +' a String or a Long target (s = c, n = c, "x" & c, CStr(c), CLng(c)). +Module Startup + + Public Type Two + X As Long + + Private Function Type_Conversion() As String + Return "Two " & CStr(Me.X) + End Function + + Private Function Type_Conversion() As Long + Return Me.X + End Function + End Type + + Public Sub Main() + Dim c As Two + c.X = 7 + Debug.Print c + End Sub + +End Module diff --git a/bugs/udt-two-conversions-variant-codegen/udt-two-conversions-variant-codegen.twinproj b/bugs/udt-two-conversions-variant-codegen/udt-two-conversions-variant-codegen.twinproj new file mode 100644 index 000000000..6b718afe9 Binary files /dev/null and b/bugs/udt-two-conversions-variant-codegen/udt-two-conversions-variant-codegen.twinproj differ diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 462e57154..a55988689 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1505, - "staticFiles": 1132 + "pages": 1516, + "staticFiles": 1215 } diff --git a/builder/symbol-baseline.json b/builder/symbol-baseline.json index d7b51802c..6cc20194b 100644 --- a/builder/symbol-baseline.json +++ b/builder/symbol-baseline.json @@ -121,6 +121,7 @@ "/tB/Core/Exponent", "/tB/Core/For-Each-Next", "/tB/Core/For-Next", + "/tB/Core/Friend", "/tB/Core/Function", "/tB/Core/Get", "/tB/Core/GoSub-Return", diff --git a/docs/Documentation/Book-Configuration.md b/docs/Documentation/Book-Configuration.md index 7c799b401..29e00c4b8 100644 --- a/docs/Documentation/Book-Configuration.md +++ b/docs/Documentation/Book-Configuration.md @@ -87,7 +87,7 @@ These are warnings: the exit code does not change. A build with nothing to repor A link from inside the book to a left-out page opens that page on the website, since a site path goes nowhere in a PDF. The link check's pass over `book.html` lists each such link as `OUT OF BOOK`: _site-pdf/book.html: - OUT OF BOOK https://docs.twinbasic.com/tB/IDE/Project/Splash -- not in the book; opens the website + OUT OF BOOK https://docs.twinbasic.com/tB/IDE/Reference/StatusBar -- not in the book; opens the website Look there first for a left-out page that belongs in the book after all. diff --git a/docs/Documentation/Tools-Defects.md b/docs/Documentation/Tools-Defects.md index 94976763c..80dbdce0c 100644 --- a/docs/Documentation/Tools-Defects.md +++ b/docs/Documentation/Tools-Defects.md @@ -216,6 +216,7 @@ Exit codes: **0** every selected callout and data file held and was relabelled, node scripts/ide_js.mjs view ... | --all --out [--ide ] [--map ] node scripts/ide_js.mjs patch --out | --copy-to [--ide ] [--map ] node scripts/ide_js.mjs shape [--ide ] [--map ] + node scripts/ide_js.mjs keymap [--check | --update] [--ide ] Reads, names and patches the twinBASIC IDE's own JavaScript, `ide/main.js` in the install, so that an IDE defect in `BUGS-TO-REPORT.md` can carry a *fix shape*: the function that is wrong, in a readable form, changed so that the defect goes, and tested on a patched copy of the install. The library is `scripts/ide-js/lib.mjs`. @@ -243,6 +244,7 @@ Reads, names and patches the twinBASIC IDE's own JavaScript, `ide/main.js` in th | `view ...` | The readable view: each unit with the map's names put in (only the `exact` and `carried` ones), pretty-printed by prettier, under a comment that names the build, where the unit is in `main.js`, its shape, the names applied and any not applied. `--all --out ` writes the whole file so, about 50,000 lines. | | `patch ` | Splices a fix into `main.js`. `` holds whole units in the map's names, as the view prints them: top-level functions, and a class holding each method that is fixed. For each unit, the map's names are turned back into the build's minified names, the unit's tokens are compared with the fix's (a string by its value, so the view's quotes are no difference), and each run of tokens that differs becomes one edit of the minified text. The patched file is parsed again, and each patched unit must have the fix's shape, so the patched `main.js` runs the code the fix shows. Each edit is printed before and after, in the minified text. Every difference becomes an edit, so a fix patched into a later build whose unit changed takes that change back out: run `shape` on that build first. | | `shape ` | The fix shape, as markdown for the defect's entry: for each unit of the fix, which names are the map's and what the build calls them, and a diff of the unit as the build has it against the fix, both in the map's names and printed alike. | +| `keymap` | Prints the build's built-in keyboard shortcuts as the table of [`IDE/Reference/Keyboard-Shortcuts.md`](../../tB/IDE/Reference/Keyboard-Shortcuts), one row for each command, by name. `--check` compares that page's table with the build, prints what differs and exits 1 if anything does; `--update` writes the build's table into the page and leaves the rest of it alone. Run `--check` after a new BETA. | **Only a copy of an install is ever patched.** `patch --copy-to ` copies the install into a folder of its own under ``, which must be under the system temp folder, without its `projects\` and `addins\` folders, writes the patched `main.js` into the copy and prints the copy's `twinBASIC.exe`. Give that to the IDE tests, `ide-test.bat --ide \twinBASIC.exe --only `, to see the defect's test report that it no longer reproduces while the controls pass; delete the folder afterwards. `patch --out ` writes the patched `main.js` alone, and refuses a file inside the repository or inside an install that is not under the temp folder. `view --out` refuses the repository too: no copy of `main.js`, whole or viewed, belongs in it. A fix is kept as `bugs//fix.js` beside the defect's reproducer. diff --git a/docs/Documentation/Wisdom.md b/docs/Documentation/Wisdom.md index ee44cb204..98c9a6337 100644 --- a/docs/Documentation/Wisdom.md +++ b/docs/Documentation/Wisdom.md @@ -101,7 +101,7 @@ Exit codes: **0** the command finished, or a dry run did; **2** a refused comman ### Phase 1 --- Export -Fetches messages from Discord channels and forum threads. +Fetches messages from Discord channels and threads. ``` node wisdom/wisdom.mjs export @@ -134,7 +134,7 @@ Converts raw JSON into structured Markdown with YAML frontmatter. node wisdom/wisdom.mjs process ``` -Outputs one `.md` file per forum thread under `wisdom/data/threads//`, plus one per text channel. Each file has frontmatter (thread ID, channel, tags, reaction counts, `has_answer` flag) and a rendered message body with timestamps and author names. +Outputs one `.md` file per thread under `wisdom/data/threads//`, plus one per text or announcement channel. A channel is also written month by month into its folder, each month shaped as a thread, and those months are what Phase 3 reads; `process.unmined_channels` in `config.jsonc` names the channels left out of that. Each file has frontmatter (thread ID, channel, tags, reaction counts, `has_answer` flag) and a rendered message body with timestamps and author names. | Flag | Effect | |------|--------| @@ -228,9 +228,9 @@ It also defines `runConcurrent(items, concurrency, fn)`, a worker pool. If any w ### Phase 1 control flow -1. **Discover** (`discord/discover.mjs`): fetch the channel list from `/guilds/{id}/channels`, filter by type (text/forum) and exclude patterns. For forums, paginate `/channels/{id}/threads/archived/public` and (bot-only) `/guilds/{id}/threads/active`, deduplicate, and filter by `min_message_count`. +1. **Discover** (`discord/discover.mjs`): fetch the channel list from `/guilds/{id}/channels`, filter by type (text, announcement, forum) and exclude patterns. Any of those channels can hold threads, so for each one paginate `/channels/{id}/threads/archived/public`, add its threads from (bot-only) `/guilds/{id}/threads/active`, deduplicate, and filter by `min_message_count`. Discord's count leaves out a thread's first message, so the default of 0 keeps a forum post nobody answered. 2. **Fetch members** (`discord/discover.mjs`): paginate `/guilds/{id}/members`. A user token, or a bot the endpoint refuses, gets an empty map, and messages then show authors by global name rather than server nickname. Write `guild.json` and `members.json`. -3. **Build target list**: merge text channels and forum threads. Targets that returned 403 before (tracked in `denied.json`) sort to the end. +3. **Build target list**: merge the text and announcement channels and the threads. Targets that returned 403 before (tracked in `denied.json`) sort to the end. 4. **Fetch messages** (`discord/messages.mjs`): run targets through `runConcurrent`. For each target: - Skip it without a request if its output file exists, its snowflake is recorded, and the `last_message_id` discovery reported is no newer. - Otherwise call `fetchMessages(client, channelId, afterSnowflake)`: @@ -252,12 +252,12 @@ Every file the export writes goes to a `.tmp` file first and is then renamed ove - Skip if `--channel` filter does not match `thread.parent_id`. - Skip if `--since` and thread creation date is before the cutoff. - Skip if output `.md` exists and its mtime >= input mtime (unless `--force`). - - `filterMessages()` (`filter.mjs`): keep only type 0 (DEFAULT) and type 19 (REPLY). + - `filterMessages()` (`filter.mjs`): keep only type 0 (DEFAULT) and type 19 (REPLY), and put the message a type 21 (THREAD_STARTER_MESSAGE) stub refers to in the stub's place, since that message is in the parent channel and not in the thread. - `shouldSkipThread()`: drop threads with one or fewer messages or all-bot authors. - `buildFrontmatter()` (`frontmatter.mjs`): thread_id, title, channel, tags, created/archived timestamps, message_count, last_message_id (highest snowflake --- the Phase 3 watermark), reply_count, reaction aggregates, has_answer flag. - `renderMessages()` (`render.mjs`): each message becomes `**Author** _timestamp_` + content + attachments + reactions. A reply gets a header with a truncated quote of the parent. - Write `threads//--.md`. -4. Iterate `raw/channels/*.json` --- same pipeline with simpler frontmatter (channel_id, title, message_count). One output file per text channel. +4. Iterate `raw/channels/*.json` --- same pipeline with simpler frontmatter (channel_id, title, message_count). One output file per text channel, and, unless `process.unmined_channels` names the channel, one per calendar month under `/`, with the frontmatter of a thread whose ID is `-`. A reply's parent is looked up in the whole channel, so a reply to an earlier month keeps its quote. ### Phase 3 control flow @@ -289,7 +289,7 @@ Phase 3 has three stages: prep (Node.js), workflow (Claude agents), merge (Node. **Stage 2 --- Draft.** One Sonnet agent per group, only if Stage 1 produced findings above the confidence threshold. The agent resolves each finding to a documentation file through `page-index.json`, reads the target page(s), and returns `{ additions: [...] }` via structured output. Each addition has: `thread_path`, `target_page` (repo-relative path or "UNMAPPED"), `section` (after-remarks/example/see-also/new-section), `draft` (exact Markdown to insert), `confidence`, `date_earliest`, `date_latest`, `reviewer_note`. -3. **Assemble**: flatten all additions, take thread IDs from the file paths, and return `{ additions: [...] }`. +3. **Assemble**: flatten all additions, take thread IDs from the file paths, and return `{ additions: [...], read: [...] }`, where `read` lists the threads whose agents finished. #### Merge @@ -306,7 +306,7 @@ Phase 3 has three stages: prep (Node.js), workflow (Claude agents), merge (Node. - UNMAPPED sections sort before mapped sections. - Atomic write: back up the existing `staging.md` as `staging.md.bak`, write to `.tmp`, rename. -3. **Advance state**: for each thread that contributed additions, `recordEmission()` stores its current watermark values and the `(target_page, section, finding_ids)` tuples in the emission log. Then `extract-state.json` is written atomically. +3. **Advance state**: for each thread in `read` or contributing additions, `recordEmission()` stores its current watermark values and the `(target_page, section, finding_ids)` tuples in the emission log. A thread that gave no additions is recorded as well, or every later run would read it again; one whose agent died is in neither list, so the next run reads it. Then `extract-state.json` is written atomically. ### staging.md format diff --git a/docs/Features/Advanced/Classes-and-Modules.md b/docs/Features/Advanced/Classes-and-Modules.md index b271ac7d8..a49c289af 100644 --- a/docs/Features/Advanced/Classes-and-Modules.md +++ b/docs/Features/Advanced/Classes-and-Modules.md @@ -118,6 +118,10 @@ A private module or class won't have its members entered into the type library i In a class, module-level variables can be declared as `ReadOnly`, e.g. `Private ReadOnly mStartDate As Date`. This allows more complex constant assignments: a built-in function's result can set it inline, `Private ReadOnly mStartDate As Date = Now()` --- a procedure of the project cannot, see [What an Initializer Can Use](../Language/Inline-Initialization#what-an-initializer-can-use) --- or `ReadOnly` constants can be set in `Class_Initialize` or `Sub New(...)` (see parameterized class constructors above), but everywhere else, they can only be read, not changed. + +> [!NOTE] BETA 1034 +> The first `ReadOnly` variable after a [`Delegate`](../../tB/Core/Delegate) statement is refused with TB5182, *Syntax error. No handler for this symbol*, on the variable's name. Declare it before the `Delegate`. + ## Get/Let/Set Notification for Class Public Variables When have a variable in a class such as `Public myVar As Long`, it's treated as a property you can use the standard syntax to get or set. tB adds an optional notification event for when the Get/Let/Set occurs that's accessed through the regular `Handles` syntax: diff --git a/docs/Features/Advanced/Static-Linking.md b/docs/Features/Advanced/Static-Linking.md index 110ba7732..a762ebb53 100644 --- a/docs/Features/Advanced/Static-Linking.md +++ b/docs/Features/Advanced/Static-Linking.md @@ -212,7 +212,7 @@ End Module 5 ``` -The New Project dialog has two larger [samples](../../tB/IDE/Project/New#samples): **Static Library Example (SQLITE3)**, which links SQLite, and **Static Library Example (libdeflate)**, which links the libdeflate compression library from `.a` files. +The New Project dialog has two larger [samples](../../tB/IDE/Starting/Samples): **Static Library Example (SQLITE3)**, which links SQLite, and **Static Library Example (libdeflate)**, which links the libdeflate compression library from `.a` files. ## See Also diff --git a/docs/Features/Compiler-IDE/IDE-Features.md b/docs/Features/Compiler-IDE/IDE-Features.md index f538d41cc..b96e15595 100644 --- a/docs/Features/Compiler-IDE/IDE-Features.md +++ b/docs/Features/Compiler-IDE/IDE-Features.md @@ -49,7 +49,7 @@ On the Form Designer, control with `Visible = False` are faded to visually indic ### New Code-Based Project Explorer -A new code structure based Project Explorer: +The Project Explorer has a code structure based view, beside the classic file view. [The two views](../../tB/IDE/Projects/Project-Explorer#the-two-views) describes them. ![The Project Explorer in code-based view, grouping the project into Modules, Forms, Interfaces and User Defined Types, each item beside the file it comes from, above the collapsed VBA, VBRUN and VB packages](../Images/9a5c50d5-a9f8-44a7-96f7-ae84548bd7ef.png){:width="423" height="526"} @@ -59,7 +59,7 @@ The classic file-based view is still used by default, you can activate the new v ## View Forms and Packages as JSON -Project forms and packages are stored as JSON format data, and you can view this by right-click in Project Explorer and selecting **View As JSON**. This is particularly interesting for packages as it exposes the entire code in a more parseable format. +Project forms and packages are stored as JSON, and **View As JSON** in the Project Explorer shows that text. For a package, this exposes its code in a form that a program can read. [View as JSON and Markdown preview](../../tB/IDE/Projects/Files-and-Folders#view-as-json-and-markdown-preview) describes the command. ![The Project Explorer with the Packages folder open, and the right-click menu of CustomControlsPackage with View As JSON highlighted](../Images/22660f54-ff5d-4b21-93d3-39715f1f35ed.png){:width="486" height="409"} diff --git a/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png b/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png index 5e0d45a8f..9ab16666c 100644 Binary files a/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png and b/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png differ diff --git a/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.svg b/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.svg index 25bc6b326..1fe49355c 100644 --- a/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.svg +++ b/docs/Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.svg @@ -1 +1 @@ -DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [IDLE]VARIABLESResourcesSourcesIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESTB0001 Use of legacy DefType statements is strongly discouragedTB0002 This variable has been auto-declared by the compiler due to Option Explicit being OFFTB0003 Datatype has not been explicitly declared so will default to VariantTB0004 [Exit Sub/Function] inside a Property-Get procedure is only supported for backwards compatibility. Use [Exit Property] instead.TB0005 #Warning directive was encounteredTB0006 Record length is ignored in binary modeTB0007 Mismatched datatype hintTB0008 Suspicious callTB0009 Member is unimplementedTB0010 Duplicate DLL declaration detectedTB0011 Use of legacy date/time property-let assignments are strongly discouragedTB0012 Possible misuse of hexadecimal literal notationTB0013 It is strongly recommended to specify an [ InterfaceId() ] attribute for this classTB0014 The inline ByVal attribute is ignored on User Defined TypesTB0015 Use of legacy DLL {LEGACY.DLL} has been redirected to {MODERN.DLL}TB0016 Implicit creation of variable via ReDimWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGHINTWARNING +DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESResourcesSourcesIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESTB0001 Use of legacy DefType statements is strongly discouragedTB0002 This variable has been auto-declared by the compiler due to Option Explicit being OFFTB0003 Datatype has not been explicitly declared so will default to VariantTB0004 [Exit Sub/Function] inside a Property-Get procedure is only supported for backwards compatibility. Use [Exit Property] instead.TB0005 #Warning directive was encounteredTB0006 Record length is ignored in binary modeTB0007 Mismatched datatype hintTB0008 Suspicious callTB0009 Member is unimplementedTB0010 Duplicate DLL declaration detectedTB0011 Use of legacy date/time property-let assignments are strongly discouragedTB0012 Possible misuse of hexadecimal literal notationTB0013 It is strongly recommended to specify an [ InterfaceId() ] attribute for this classTB0014 The inline ByVal attribute is ignored on User Defined TypesTB0015 This Declare statement has been redirected to an internal twinBASIC implementation, supporting both 32-bit and 64-bitTB0016 Implicit creation of variable via ReDimWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGHINTWARNING diff --git a/docs/Features/Images/0aeb25f6-d864-4ebb-a9f5-bbd7b5d242e8.png b/docs/Features/Images/0aeb25f6-d864-4ebb-a9f5-bbd7b5d242e8.png index 91a4d6db7..b853d0b7b 100644 Binary files a/docs/Features/Images/0aeb25f6-d864-4ebb-a9f5-bbd7b5d242e8.png and b/docs/Features/Images/0aeb25f6-d864-4ebb-a9f5-bbd7b5d242e8.png differ diff --git a/docs/Features/Images/3fa1cf2b-0af5-44ae-ae6a-3c0662f51f57.png b/docs/Features/Images/3fa1cf2b-0af5-44ae-ae6a-3c0662f51f57.png index a2d9b4c6c..844ae6f16 100644 Binary files a/docs/Features/Images/3fa1cf2b-0af5-44ae-ae6a-3c0662f51f57.png and b/docs/Features/Images/3fa1cf2b-0af5-44ae-ae6a-3c0662f51f57.png differ diff --git a/docs/Features/Images/4829696d-788b-40ee-bebd-5afa44477460.png b/docs/Features/Images/4829696d-788b-40ee-bebd-5afa44477460.png index 8ca4c5565..e7f3c0424 100644 Binary files a/docs/Features/Images/4829696d-788b-40ee-bebd-5afa44477460.png and b/docs/Features/Images/4829696d-788b-40ee-bebd-5afa44477460.png differ diff --git a/docs/Features/Images/4ad9c774-b31d-47d3-9963-6d99ac4f37bb.png b/docs/Features/Images/4ad9c774-b31d-47d3-9963-6d99ac4f37bb.png index b67e2bf02..972f42ce6 100644 Binary files a/docs/Features/Images/4ad9c774-b31d-47d3-9963-6d99ac4f37bb.png and b/docs/Features/Images/4ad9c774-b31d-47d3-9963-6d99ac4f37bb.png differ diff --git a/docs/Features/Images/599a66ad-31d5-449f-bbf5-00963fe9aa2a.png b/docs/Features/Images/599a66ad-31d5-449f-bbf5-00963fe9aa2a.png index 6a2e1ad9f..7135f2acf 100644 Binary files a/docs/Features/Images/599a66ad-31d5-449f-bbf5-00963fe9aa2a.png and b/docs/Features/Images/599a66ad-31d5-449f-bbf5-00963fe9aa2a.png differ diff --git a/docs/Features/Images/5fc60b7b-4f54-445c-8504-451019b7ec55.png b/docs/Features/Images/5fc60b7b-4f54-445c-8504-451019b7ec55.png index 3cbedba58..d3800f842 100644 Binary files a/docs/Features/Images/5fc60b7b-4f54-445c-8504-451019b7ec55.png and b/docs/Features/Images/5fc60b7b-4f54-445c-8504-451019b7ec55.png differ diff --git a/docs/Features/Images/80185a8d-2952-415f-bc02-ec3ddea89568.png b/docs/Features/Images/80185a8d-2952-415f-bc02-ec3ddea89568.png index 2661c8fd5..940c68253 100644 Binary files a/docs/Features/Images/80185a8d-2952-415f-bc02-ec3ddea89568.png and b/docs/Features/Images/80185a8d-2952-415f-bc02-ec3ddea89568.png differ diff --git a/docs/Features/Images/85f25aa2-abc8-4d42-8510-078f8ee4a324.png b/docs/Features/Images/85f25aa2-abc8-4d42-8510-078f8ee4a324.png index c37684b62..a38083e22 100644 Binary files a/docs/Features/Images/85f25aa2-abc8-4d42-8510-078f8ee4a324.png and b/docs/Features/Images/85f25aa2-abc8-4d42-8510-078f8ee4a324.png differ diff --git a/docs/Features/Images/IDE-FeatureMap.light.png b/docs/Features/Images/IDE-FeatureMap.light.png index 065d9ada5..617599a37 100644 Binary files a/docs/Features/Images/IDE-FeatureMap.light.png and b/docs/Features/Images/IDE-FeatureMap.light.png differ diff --git a/docs/Features/Images/IDE-FeatureMap.light.svg b/docs/Features/Images/IDE-FeatureMap.light.svg index 1c4574a31..7731e8e89 100644 --- a/docs/Features/Images/IDE-FeatureMap.light.svg +++ b/docs/Features/Images/IDE-FeatureMap.light.svg @@ -1 +1 @@ -tB Services: OPERATIONALULTIMATE EDITION175.6MBin use  |  100ui objects  |  16gdi objectsFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesInventory.twinMainModule.twinTypes.twinOUTLINEInventoryHISTORY09:12Inventory.Add10:05Inventory.Find11:48Inventory.TotalDIAGNOSTICS0300Inventory.twinMainModule.twin[18,13]TB0003Datatype has not been ex…[19,13]TB0003Datatype has not been ex…[6,13]TB0003Datatype has not been expl…EDITOR 1Inventory(general)ItemByIndexInventory.twin1920212223242526272829303132333435363738394041424344454647484950515253545556DimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIf🠈 items(i).Quantity > 100EndIf🠈 items(i).Quantity > 0Next🠈 i = 0 To count - 1Total=subtotalEndFunction🠈 TotalPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenFind=iExitForEndIf🠈 items(i).Name = nameNext🠈 i = 0 To count - 1EndFunction🠈 FindPublicSubClear()Eraseitemscount=0EndSub🠈 ClearPublicFunctionItemByIndex(ByValpositionAsLong)AsStockItemItemByIndex=items(position)EndFunction🠈 ItemByIndexEndClass🠈 Inventory16PublicFunctionTotal(ByValtaxRateAsDouble)AsDouble1ClassInventorytypeStockItemin component FeatureTour.Typescontains elements[offset &H0000]Name As String[offset &H0004]Quantity As Long[offset &H0008]Price As DoubleLenB() = &H0010Len() = &H0010UDT Alignment = &H0008(offsets and lengths calculated for current Win32 target)Sticky scrollColour matchingUnicode in the editorIndent guidesInline code hintsAdvanced info popupMini-mapFolding controlsMemory usage and object countsOutlineHistoryDiagnostics +tB Services: OPERATIONALULTIMATE EDITION175.6MBin use  |  100ui objects  |  16gdi objectsFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesInventory.twinMainModule.twinTypes.twinOUTLINEInventoryHISTORY09:12Inventory.Add10:05Inventory.Find11:48Inventory.TotalDIAGNOSTICS0300Inventory.twinMainModule.twin[18,13]TB0003Datatype has not been ex…[19,13]TB0003Datatype has not been ex…[6,13]TB0003Datatype has not been expl…EDITOR 1Inventory(general)ItemByIndexInventory.twin1920212223242526272829303132333435363738394041424344454647484950515253545556DimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIf🠈 items(i).Quantity > 100EndIf🠈 items(i).Quantity > 0Next🠈 i = 0 To count - 1Total=subtotalEndFunction🠈 TotalPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenFind=iExitForEndIf🠈 items(i).Name = nameNext🠈 i = 0 To count - 1EndFunction🠈 FindPublicSubClear()Eraseitemscount=0EndSub🠈 ClearPublicFunctionItemByIndex(ByValpositionAsLong)AsStockItemItemByIndex=items(position)EndFunction🠈 ItemByIndexEndClass🠈 Inventory16PublicFunctionTotal(ByValtaxRateAsDouble)AsDouble1ClassInventorytypeStockItemin component FeatureTour.Typescontains elements[offset &H0000]Name As String[offset &H0004]Quantity As Long[offset &H0008]Price As DoubleLenB() = &H0010Len() = &H0010UDT Alignment = &H0008(offsets and lengths calculated for current Win32 target)Sticky scrollColour matchingUnicode in the editorIndent guidesInline code hintsAdvanced info popupMini-mapFolding controlsMemory usage and object countsOutlineHistoryDiagnostics diff --git a/docs/Features/Images/IDE-FeatureMap.png b/docs/Features/Images/IDE-FeatureMap.png index 28f775609..2fb990bf8 100644 Binary files a/docs/Features/Images/IDE-FeatureMap.png and b/docs/Features/Images/IDE-FeatureMap.png differ diff --git a/docs/Features/Images/IDE-FeatureMap.svg b/docs/Features/Images/IDE-FeatureMap.svg index 7ed478746..38cb1921a 100644 --- a/docs/Features/Images/IDE-FeatureMap.svg +++ b/docs/Features/Images/IDE-FeatureMap.svg @@ -1 +1 @@ -tB Services: OPERATIONALULTIMATE EDITION175.6MBin use  |  100ui objects  |  16gdi objectsFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesInventory.twinMainModule.twinTypes.twinOUTLINEInventoryHISTORY09:12Inventory.Add10:05Inventory.Find11:48Inventory.TotalDIAGNOSTICS0300Inventory.twinMainModule.twin[18,13]TB0003Datatype has not been expli…[19,13]TB0003Datatype has not been expli…[6,13]TB0003Datatype has not been explic…EDITOR 1Inventory(general)ItemByIndexInventory.twin1920212223242526272829303132333435363738394041424344454647484950515253545556DimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIf🠈 items(i).Quantity > 100EndIf🠈 items(i).Quantity > 0Next🠈 i = 0 To count - 1Total=subtotalEndFunction🠈 TotalPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenFind=iExitForEndIf🠈 items(i).Name = nameNext🠈 i = 0 To count - 1EndFunction🠈 FindPublicSubClear()Eraseitemscount=0EndSub🠈 ClearPublicFunctionItemByIndex(ByValpositionAsLong)AsStockItemItemByIndex=items(position)EndFunction🠈 ItemByIndexEndClass🠈 Inventory16PublicFunctionTotal(ByValtaxRateAsDouble)AsDouble1ClassInventorytypeStockItemin component FeatureTour.Typescontains elements[offset &H0000]Name As String[offset &H0004]Quantity As Long[offset &H0008]Price As DoubleLenB() = &H0010Len() = &H0010UDT Alignment = &H0008(offsets and lengths calculated for current Win32 target)Sticky scrollColour matchingUnicode in the editorIndent guidesInline code hintsAdvanced info popupMini-mapFolding controlsMemory usage and object countsOutlineHistoryDiagnostics +tB Services: OPERATIONALULTIMATE EDITION175.6MBin use  |  100ui objects  |  16gdi objectsFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesInventory.twinMainModule.twinTypes.twinOUTLINEInventoryHISTORY09:12Inventory.Add10:05Inventory.Find11:48Inventory.TotalDIAGNOSTICS0300Inventory.twinMainModule.twin[18,13]TB0003Datatype has not been expli…[19,13]TB0003Datatype has not been expli…[6,13]TB0003Datatype has not been explic…EDITOR 1Inventory(general)ItemByIndexInventory.twin1920212223242526272829303132333435363738394041424344454647484950515253545556DimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIf🠈 items(i).Quantity > 100EndIf🠈 items(i).Quantity > 0Next🠈 i = 0 To count - 1Total=subtotalEndFunction🠈 TotalPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenFind=iExitForEndIf🠈 items(i).Name = nameNext🠈 i = 0 To count - 1EndFunction🠈 FindPublicSubClear()Eraseitemscount=0EndSub🠈 ClearPublicFunctionItemByIndex(ByValpositionAsLong)AsStockItemItemByIndex=items(position)EndFunction🠈 ItemByIndexEndClass🠈 Inventory16PublicFunctionTotal(ByValtaxRateAsDouble)AsDouble1ClassInventorytypeStockItemin component FeatureTour.Typescontains elements[offset &H0000]Name As String[offset &H0004]Quantity As Long[offset &H0008]Price As DoubleLenB() = &H0010Len() = &H0010UDT Alignment = &H0008(offsets and lengths calculated for current Win32 target)Sticky scrollColour matchingUnicode in the editorIndent guidesInline code hintsAdvanced info popupMini-mapFolding controlsMemory usage and object countsOutlineHistoryDiagnostics diff --git a/docs/Features/Images/bc9f3756-a14b-4ee7-b819-6822497b640a.png b/docs/Features/Images/bc9f3756-a14b-4ee7-b819-6822497b640a.png index d1f121e16..e1f9e80c8 100644 Binary files a/docs/Features/Images/bc9f3756-a14b-4ee7-b819-6822497b640a.png and b/docs/Features/Images/bc9f3756-a14b-4ee7-b819-6822497b640a.png differ diff --git a/docs/Features/Images/fddbffa9-2b71-47f5-b925-e67fc66b9e5c.png b/docs/Features/Images/fddbffa9-2b71-47f5-b925-e67fc66b9e5c.png index d8d68e585..a4af188f8 100644 Binary files a/docs/Features/Images/fddbffa9-2b71-47f5-b925-e67fc66b9e5c.png and b/docs/Features/Images/fddbffa9-2b71-47f5-b925-e67fc66b9e5c.png differ diff --git a/docs/Features/Language/Inheritance.md b/docs/Features/Language/Inheritance.md index 06e0691a1..d224e566d 100644 --- a/docs/Features/Language/Inheritance.md +++ b/docs/Features/Language/Inheritance.md @@ -203,7 +203,7 @@ Rover says: WOOF! Misty says: meow ``` -The classes come from Sample 23, which also has an `AnimalWatcher` class that handles the `Spoke` event, and a longer demonstration routine. To open it, choose **File → New Project**, then the **Samples** section, and pick the card **OOP Inheritance Example (Animals)**, the last one. The [New Project](../../tB/IDE/Project/New#samples) page lists every sample. +The classes come from Sample 23, which also has an `AnimalWatcher` class that handles the `Spoke` event, and a longer demonstration routine. To open it, choose **File → New Project**, then the **Samples** section, and pick the card **OOP Inheritance Example (Animals)**, the last one. The [Samples](../../tB/IDE/Starting/Samples) page lists every sample. ## Constructors in Derived Classes {: index="TB5103" index_also="TB5073" } diff --git a/docs/Features/Language/UDTs.md b/docs/Features/Language/UDTs.md index 037d2b28a..ac92ec8af 100644 --- a/docs/Features/Language/UDTs.md +++ b/docs/Features/Language/UDTs.md @@ -60,7 +60,11 @@ UDTs of these types are still stack allocated structs that can be used with stan > [!WARNING] BETA 1034 -> A method or property of a UDT called on an element of an array of the UDT, such as `a(1).Sum`, ends the program with an access violation, in 32-bit and 64-bit builds. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first. +> A method or property of a UDT called on an element of an array of the UDT, such as `a(1).Sum`, ends the program with an access violation, in 32-bit and 64-bit builds. So does a **Type_Conversion** of an element, as in `CStr(a(1))` or `Debug.Print a(1)`. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first. + + +> [!WARNING] BETA 1034 +> **Type_Initialize** does not run for an element of an array of the UDT, or for a UDT that is a member of another UDT. **Type_Terminate** still runs for each of them, on a value that was never initialized, and no error is raised. A variable of the type and a class field of the type are not affected. Write **Type_Terminate** to do nothing when the members are all zero. > [!NOTE] BETA 1034 diff --git a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.png b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.png index 5f50cb1a5..046ceb5be 100644 Binary files a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.png and b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.png differ diff --git a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.svg b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.svg index 0eb7a5c19..9c3f91e54 100644 --- a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.svg +++ b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it. diff --git a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png index cf8c06b4e..f522b1073 100644 Binary files a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png and b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png differ diff --git a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.svg b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.svg index dc8b64b18..6821a41af 100644 --- a/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.svg +++ b/docs/Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it. diff --git a/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.png b/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.png index ddb81d6ab..20a6c57a8 100644 Binary files a/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.png and b/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.png differ diff --git a/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.svg b/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.svg index b4e068a29..9fa34455b 100644 --- a/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.svg +++ b/docs/Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.svg @@ -1 +1 @@ -DIAGNOSTICS0000CALL STACKVARIABLESPROPERTIES↪ LOGIN to see any PRIVATE packages you've madeImport from file...WinDevLibWinRegWindows Development Library for twinBASIC v9.5.7489.5.748.0fafaloneWinReg1.0.1.1forliny4 +DIAGNOSTICS0000CALL STACKVARIABLESPROPERTIES↪ LOGIN to see any PRIVATE packages you've madeImport from file...WinDevLibWinRegWindows Development Library for twinBASIC v9.5.7529.5.752.0fafaloneWinReg1.0.1.1forliny4 diff --git a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.png b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.png index 837a593ea..a24738f55 100644 Binary files a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.png and b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.png differ diff --git a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.svg b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.svg index e9a42442e..67e1ea1ce 100644 --- a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.svg +++ b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.svg @@ -1 +1 @@ -ViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWEDITOR 1PROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesfrmMain.tbformfrmMain.twinGreeting.twinIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESProject SettingsAllGeneralLibrary ReferencesBuildStartupCompilerOptimizationDebuggerRuntimeVersion InfoExportTracingRegistrationFeature FlagsCompiler WarningsIDE EnvironmentPackage PublishingLibrary ReferencesLibrary ReferencesAvailable PackagesAppGlobalClassProjectcefPackagecefPackagecefPackageWinNativeCommonCtlsCustomControlsCustomControlsPackagetbIDEAssertVBWebView2PackageWinEventLogLibWinNamedPipesLibWinServicesLib[BUILT-IN] twinBASIC - App global class object1.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1091.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1451.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v491.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Common Controls Compatibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - CustomControls DESIGNER Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - CustomControls Package0.0.0.5[BUILT-IN][BUILT-IN] twinBASIC - IDE Extensibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - Unit Testing Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - VB Compatibility Package (Forms)0.0.0.31[BUILT-IN][BUILT-IN] twinBASIC - WebView2 Package0.0.0.3[BUILT-IN][BUILT-IN] twinBASIC - WinEventLogLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinNamedPipesLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinServicesLib0.1.0.0[BUILT-IN](Filter)73611635644143222122Select references that will be used during compilation of this projectEnabled LibrariesAvailable COM ReferencesLibrary SymbolEmbeddedVersionPublisher123 +ViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWEDITOR 1PROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesfrmMain.tbformfrmMain.twinGreeting.twinIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESProject SettingsAllGeneralLibrary ReferencesBuildStartupCompilerOptimizationDebuggerRuntimeVersion InfoExportTracingRegistrationFeature FlagsCompiler WarningsIDE EnvironmentPackage PublishingLibrary ReferencesLibrary ReferencesAvailable PackagesAppGlobalClassProjectcefPackagecefPackagecefPackageWinNativeCommonCtlsCustomControlsCustomControlsPackagetbIDEAssertVBWebView2PackageWinEventLogLibWinNamedPipesLibWinServicesLib[BUILT-IN] twinBASIC - App global class object1.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1091.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1451.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v491.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Common Controls Compatibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - CustomControls DESIGNER Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - CustomControls Package0.0.0.5[BUILT-IN][BUILT-IN] twinBASIC - IDE Extensibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - Unit Testing Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - VB Compatibility Package (Forms)0.0.0.31[BUILT-IN][BUILT-IN] twinBASIC - WebView2 Package0.0.0.3[BUILT-IN][BUILT-IN] twinBASIC - WinEventLogLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinNamedPipesLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinServicesLib0.1.0.0[BUILT-IN](Filter)73611635644143222122Select references that will be used during compilation of this projectEnabled LibrariesAvailable COM ReferencesLibrary SymbolEmbeddedVersionPublisher123 diff --git a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.png b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.png index cd3b0bc8e..561e50696 100644 Binary files a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.png and b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.png differ diff --git a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.svg b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.svg index 77f3554ca..7600bea60 100644 --- a/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.svg +++ b/docs/Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.svg @@ -1 +1 @@ -ViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWEDITOR 1PROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesfrmMain.tbformfrmMain.twinGreeting.twinIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESProject SettingsAllGeneralLibrary ReferencesBuildStartupCompilerOptimizationDebuggerRuntimeVersion InfoExportTracingRegistrationFeature FlagsCompiler WarningsIDE EnvironmentPackage PublishingLibrary ReferencesLibrary ReferencesAvailable PackagesAppGlobalClassProjectcefPackagecefPackagecefPackageWinNativeCommonCtlsCustomControlsCustomControlsPackagetbIDEAssertVBWebView2PackageWinEventLogLibWinNamedPipesLibWinServicesLib[BUILT-IN] twinBASIC - App global class object1.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1091.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1451.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v491.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Common Controls Compatibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - CustomControls DESIGNER Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - CustomControls Package0.0.0.5[BUILT-IN][BUILT-IN] twinBASIC - IDE Extensibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - Unit Testing Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - VB Compatibility Package (Forms)0.0.0.31[BUILT-IN][BUILT-IN] twinBASIC - WebView2 Package0.0.0.3[BUILT-IN][BUILT-IN] twinBASIC - WinEventLogLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinNamedPipesLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinServicesLib0.1.0.0[BUILT-IN](Filter)73611635644143222122Select references that will be used during compilation of this projectEnabled LibrariesAvailable COM ReferencesLibrary SymbolEmbeddedVersionPublisherPath123 +ViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWEDITOR 1PROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesfrmMain.tbformfrmMain.twinGreeting.twinIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESProject SettingsAllGeneralLibrary ReferencesBuildStartupCompilerOptimizationDebuggerRuntimeVersion InfoExportTracingRegistrationFeature FlagsCompiler WarningsIDE EnvironmentPackage PublishingLibrary ReferencesLibrary ReferencesAvailable PackagesAppGlobalClassProjectcefPackagecefPackagecefPackageWinNativeCommonCtlsCustomControlsCustomControlsPackagetbIDEAssertVBWebView2PackageWinEventLogLibWinNamedPipesLibWinServicesLib[BUILT-IN] twinBASIC - App global class object1.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1091.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v1451.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Chromium Embedded Framework Package v491.0.0.0[BUILT-IN][BUILT-IN] twinBASIC - Common Controls Compatibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - CustomControls DESIGNER Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - CustomControls Package0.0.0.5[BUILT-IN][BUILT-IN] twinBASIC - IDE Extensibility Package0.0.0.1[BUILT-IN][BUILT-IN] twinBASIC - Unit Testing Package0.0.0.24[BUILT-IN][BUILT-IN] twinBASIC - VB Compatibility Package (Forms)0.0.0.31[BUILT-IN][BUILT-IN] twinBASIC - WebView2 Package0.0.0.3[BUILT-IN][BUILT-IN] twinBASIC - WinEventLogLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinNamedPipesLib0.1.0.0[BUILT-IN][BUILT-IN] twinBASIC - WinServicesLib0.1.0.0[BUILT-IN](Filter)73611635644143222122Select references that will be used during compilation of this projectEnabled LibrariesAvailable COM ReferencesLibrary SymbolEmbeddedVersionPublisherPath123 diff --git a/docs/Features/Packages/Import-export tool.md b/docs/Features/Packages/Import-export tool.md index 1088d576f..2b3a78e4d 100644 --- a/docs/Features/Packages/Import-export tool.md +++ b/docs/Features/Packages/Import-export tool.md @@ -358,7 +358,7 @@ for the save before committing. ``` 2. Choose **File → New Project**, then **Import from folder...** (see [New - Project](../../tB/IDE/Project/New#import-from-folder)). + Project](../../tB/IDE/Starting/New-Project#import-from-folder)). 3. In the *Browse For Folder* dialog, choose the clone's `src` folder. The project opens unsaved, with no file behind it yet. 4. Save it with Ctrl+S. The project has no file, so **Save Project** diff --git a/docs/IDE/AddIns/Community/index.md b/docs/IDE/AddIns/Community/index.md index 9b0818f91..c7d15ade2 100644 --- a/docs/IDE/AddIns/Community/index.md +++ b/docs/IDE/AddIns/Community/index.md @@ -1,14 +1,17 @@ --- title: Community Add Ins parent: Add Ins -# nav_order: 1 +nav_order: 3 permalink: /tB/IDE/AddIns/Community --- +# Community add-ins + +Community add-ins are the ones that twinBASIC users write and share, as distinct from [Global Search](GlobalSearch), which ships with the IDE, and the [Help add-in](Help). + > [!WARNING] -> AddIns are a compiled executable and therefore should be treated with the same security measures you would with any application you haven't written yourself. -> Be sure to take the necessary precautions needed. +> An add-in is a compiled DLL that runs inside the IDE, with your rights. Treat it as you would any program that you have not written yourself. -See Sample 10, the card **twinBASIC IDE Addin** in the **Samples** section of the New Project dialog, to get started writing your own, then be sure to add it back here. +No add-in is listed here yet. To share one, add it to this page: [Authoring](../../../Documentation/Development/Authoring) describes how to change a page of this documentation. - +To write one, start from Sample 10, the card **twinBASIC IDE Addin** in the **Samples** section of the **New / Open Project** dialog. See [Samples](../Starting/Samples) and the [tbIDE package](../../Packages/tbIDE/). diff --git a/docs/IDE/AddIns/GlobalSearch.md b/docs/IDE/AddIns/GlobalSearch.md index 13bbc73f3..db0d9b616 100644 --- a/docs/IDE/AddIns/GlobalSearch.md +++ b/docs/IDE/AddIns/GlobalSearch.md @@ -1,39 +1,34 @@ --- title: Global Search parent: Add Ins -nav_order: 1 +nav_order: 2 permalink: /tB/IDE/AddIns/GlobalSearch --- -## Global Search +# Global Search -This AddIn is supplied with the twinBASIC IDE. +Searches a project's files from a tool window of its own. The add-in ships with the IDE, in the install's `addins\win32` and `addins\win64` folders, so there is nothing to install. -Latest Release -: v1.0.0.0 - -Developer -: twinBASIC - -The global search add in will contain a [Toolbar](../Reference/Toolbar) item. +The add-in adds one button to the [toolbar](../Reference/Toolbar), which opens the window. ![A single toolbar button labelled Global Search.](Images/Toolbar_GlobalSearch.png){:width="102" height="35"} -![The Global Search tool window before a search: an empty search box with a magnifier icon, and four clear checkboxes labelled In packages, Match case, Whole word and Exclude comments.](Images/GlobalSearch.png){:width="352" height="124"} +Type a search term, such as `Button1`, into the text box, and the add-in lists the matches. The four boxes under it set what it searches and how: -Options +![The Global Search tool window before a search, with an empty search box and four clear checkboxes.](Images/GlobalSearch.png){:width="352" height="124"} -- In packages -- Match case -- Whole word -- Exclude comments +**In packages** +: Searches the project's packages as well. -Type a search term, such as Button1, into the text box, and the add-in lists the matches. +**Match case** +: Takes upper and lower case as different. -![The Global Search window after searching for Button1 with In packages ticked, in the add-in's own project. Each result is a card giving the file name, its size in bytes, its path, a match count and the matching line of code with Button1 highlighted, followed by its line and column position. Two cards are listed, both for Constants.twin in the packages, each with one match, vbDefaultButton1 at line 128, column 18.](Images/GlobalSearch_2.png){:width="352" height="389"} +**Whole word** +: Matches whole words only. -## Download +**Exclude comments** +: Leaves out matches inside comments. -This add-in is bundled with twinBASIC. You can download it from [https://github.com/twinbasic/twinbasic/releases][tB] +Each result is a card that gives the file's name, its size in bytes and its path, the number of matches in it, and each matching line with its line and column. -[tB]: https://github.com/twinbasic/twinbasic/releases \ No newline at end of file +![The Global Search window after a search for Button1, with In packages ticked and two result cards.](Images/GlobalSearch_2.png){:width="352" height="389"} diff --git a/docs/IDE/AddIns/index.md b/docs/IDE/AddIns/index.md index 98f14a1c5..27f972833 100644 --- a/docs/IDE/AddIns/index.md +++ b/docs/IDE/AddIns/index.md @@ -8,7 +8,7 @@ permalink: /tB/IDE/AddIns/ An addin is a Standard DLL that exports `tbCreateCompilerAddin` and returns an object implementing the [**AddIn**](../../Packages/tbIDE/AddIn) interface. Through the [**Host**](../../Packages/tbIDE/Host) object the IDE passes at startup, an addin can reach the toolbar, tool windows, debug console, current project, keyboard shortcuts, and themes. The [**tbIDE package**](../../Packages/tbIDE/) documents the full API. -The New Project dialog's **Samples** section holds seven addin samples, numbers 10 to 16, the cards named *twinBASIC IDE Addin*, alone or with a variant in brackets (see [New Project](../Project/New#samples)), covering patterns from simple toolbar buttons to HTML DOM-backed tool windows. Each is a complete addin project, with the tbIDE package referenced and the build path set, and Sample 10, *twinBASIC IDE Addin*, is the plainest one to start from. A project started from the **Standard DLL** template has neither setting: [Building and loading an addin](../../Packages/tbIDE/#building-and-loading-an-addin) says what to add. Community addins are listed on the [**Community**](Community) page. +The New Project dialog's **Samples** section holds seven addin samples, numbers 10 to 16, the cards named *twinBASIC IDE Addin*, alone or with a variant in brackets (see [Samples](../Starting/Samples)), covering patterns from simple toolbar buttons to HTML DOM-backed tool windows. Each is a complete addin project, with the tbIDE package referenced and the build path set, and Sample 10, *twinBASIC IDE Addin*, is the plainest one to start from. A project started from the **Standard DLL** template has neither setting: [Building and loading an addin](../../Packages/tbIDE/#building-and-loading-an-addin) says what to add. Community addins are listed on the [**Community**](Community) page. twinBASIC supports two addin install locations. The IDE install directory is available to all user accounts on the machine but may require reinstallation after an IDE update. A per-user application data folder persists across IDE upgrades and requires no administrator rights. diff --git a/docs/IDE/Building/Build-Configuration.md b/docs/IDE/Building/Build-Configuration.md index ebeac8811..dad6e5eff 100644 --- a/docs/IDE/Building/Build-Configuration.md +++ b/docs/IDE/Building/Build-Configuration.md @@ -7,10 +7,10 @@ permalink: /tB/IDE/Building/Build-Configuration index: - win32 - win64 - - safe mode - 32-bit - 64-bit - target architecture +index_also: safe mode --- # Build configuration diff --git a/docs/IDE/Code/Diagnostics.md b/docs/IDE/Code/Diagnostics.md index a8cf8aea7..6e2d13309 100644 --- a/docs/IDE/Code/Diagnostics.md +++ b/docs/IDE/Code/Diagnostics.md @@ -105,6 +105,10 @@ Most errors are explained on the page of the statement, type or feature they con **TB5182**, *Syntax error. No handler for this symbol* : A statement or an attribute where the language does not allow it. A line at the top level of a `.twin` file, outside a **Module** or **Class** block, is one case. An [attribute](../../Core/Attributes) written where it does not apply is another. + +> [!NOTE] BETA 1034 +> The error can also appear on a correct line: the next **Friend**, **ReadOnly**, **WithEvents**, **Implements** or **Inherits** after a [**Delegate**](../../Core/Delegate) statement gives it. Move the **Delegate** after that line. + > [!NOTE] BETA 1034 > For a type name, TB5079 reads *Unrecognized symbol 'Unrecognized datatype symbol 'NoSuchType''*: the message for a type is put inside the message for a name. diff --git a/docs/IDE/Code/Editor.md b/docs/IDE/Code/Editor.md index d5bc89b1f..3879a5645 100644 --- a/docs/IDE/Code/Editor.md +++ b/docs/IDE/Code/Editor.md @@ -344,7 +344,7 @@ Two things protect your work: - Closing a project that has unsaved changes asks **You've made changes to this project. Save now?** - A restart of the compiler starts from the project in memory, so unsaved edits survive it. -If the IDE reports that it timed out, or failed, while it synchronised, saved or verified the open documents, it tells you to make backups of your recently edited documents by hand. Copy the text of the files you changed into another file or program before you go on. +If the IDE reports that it timed out, or failed, while it synchronised the open documents, reloaded the project from memory or saved the open documents, it tells you to make backups of your recently edited documents by hand. Copy the text of the files you changed into another file or program before you go on. [When the compiler does not answer](../Troubleshooting/Compiler-Services#when-the-compiler-does-not-answer) lists the boxes. ## See Also diff --git a/docs/IDE/Code/Images/Diagnostics_Menu.light.png b/docs/IDE/Code/Images/Diagnostics_Menu.light.png index 88092135a..e70bfda17 100644 Binary files a/docs/IDE/Code/Images/Diagnostics_Menu.light.png and b/docs/IDE/Code/Images/Diagnostics_Menu.light.png differ diff --git a/docs/IDE/Code/Images/Diagnostics_Menu.light.svg b/docs/IDE/Code/Images/Diagnostics_Menu.light.svg index d0799f5f7..5e47aac7a 100644 --- a/docs/IDE/Code/Images/Diagnostics_Menu.light.svg +++ b/docs/IDE/Code/Images/Diagnostics_Menu.light.svg @@ -1 +1 @@ -DIAGNOSTICS1411Helpers.twinInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpretted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to VariantCopyCopy AllCopy All ErrorsCollapse allExpand all +DIAGNOSTICS1411Helpers.twinInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpreted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016Implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to VariantCopyCopy AllCopy All ErrorsCollapse allExpand all diff --git a/docs/IDE/Code/Images/Diagnostics_Menu.png b/docs/IDE/Code/Images/Diagnostics_Menu.png index 617e5eb8a..d22875ec3 100644 Binary files a/docs/IDE/Code/Images/Diagnostics_Menu.png and b/docs/IDE/Code/Images/Diagnostics_Menu.png differ diff --git a/docs/IDE/Code/Images/Diagnostics_Menu.svg b/docs/IDE/Code/Images/Diagnostics_Menu.svg index 45f3e35e2..bf1e293f3 100644 --- a/docs/IDE/Code/Images/Diagnostics_Menu.svg +++ b/docs/IDE/Code/Images/Diagnostics_Menu.svg @@ -1 +1 @@ -DIAGNOSTICS1411Helpers.twinInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpretted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to VariantCopyCopy AllCopy All ErrorsCollapse allExpand all +DIAGNOSTICS1411Helpers.twinInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpreted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016Implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to VariantCopyCopy AllCopy All ErrorsCollapse allExpand all diff --git a/docs/IDE/Code/Images/Diagnostics_Rows.light.png b/docs/IDE/Code/Images/Diagnostics_Rows.light.png index bf9334447..767d6479a 100644 Binary files a/docs/IDE/Code/Images/Diagnostics_Rows.light.png and b/docs/IDE/Code/Images/Diagnostics_Rows.light.png differ diff --git a/docs/IDE/Code/Images/Diagnostics_Rows.light.svg b/docs/IDE/Code/Images/Diagnostics_Rows.light.svg index 647345786..ae025847c 100644 --- a/docs/IDE/Code/Images/Diagnostics_Rows.light.svg +++ b/docs/IDE/Code/Images/Diagnostics_Rows.light.svg @@ -1 +1 @@ -Helpers(general)(declaration)1234567891011121314151617ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)▶   run Helpers.WithWarningReplaceDIAGNOSTICS1411Helpers.twin Jump to issue HelpInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpretted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to Variant +Helpers(general)(declaration)1234567891011121314151617ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)▶   run Helpers.WithWarningReplaceDIAGNOSTICS1411Helpers.twin Jump to issue HelpInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpreted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016Implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to Variant diff --git a/docs/IDE/Code/Images/Diagnostics_Rows.png b/docs/IDE/Code/Images/Diagnostics_Rows.png index 143ef498d..baf59ffb3 100644 Binary files a/docs/IDE/Code/Images/Diagnostics_Rows.png and b/docs/IDE/Code/Images/Diagnostics_Rows.png differ diff --git a/docs/IDE/Code/Images/Diagnostics_Rows.svg b/docs/IDE/Code/Images/Diagnostics_Rows.svg index 002558ebe..2a49ff8d9 100644 --- a/docs/IDE/Code/Images/Diagnostics_Rows.svg +++ b/docs/IDE/Code/Images/Diagnostics_Rows.svg @@ -1 +1 @@ -Helpers(general)(declaration)1234567891011121314151617ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)▶   run Helpers.WithWarningReplaceDIAGNOSTICS1411Helpers.twin Jump to issue HelpInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpretted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to Variant +Helpers(general)(declaration)1234567891011121314151617ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)▶   run Helpers.WithWarningReplaceDIAGNOSTICS1411Helpers.twin Jump to issue HelpInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpreted as …[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[16,15]TB0016Implicit creation of variable via ReDim [made][22,21]TB5079Unrecognized symbol 'Undefined1'[18,13]TB0003Datatype has not been explicitly declared so will default to Variant[19,13]TB0003Datatype has not been explicitly declared so will default to Variant[6,13]TB0003Datatype has not been explicitly declared so will default to Variant diff --git a/docs/IDE/Code/Images/Object_Browser.light.svg b/docs/IDE/Code/Images/Object_Browser.light.svg index 332ba26c7..216e8919b 100644 --- a/docs/IDE/Code/Images/Object_Browser.light.svg +++ b/docs/IDE/Code/Images/Object_Browser.light.svg @@ -1 +1 @@ -tB Services: OPERATIONALULTIMATE EDITIONEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXEDITOR 1Inventory(general)TotalInventory.twin23456789101112131415161718192021222324252627281Privateitems()AsStockItemPrivatecountAsLongPublicSubAdd(ByValnameAsString,ByValquantityAsLong,ByValpriceAsDouble)ReDimPreserveitems(count)Withitems(count)            .Name=name            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfClassInventoryDEBUG CONSOLEDIAGNOSTICS0411Helpers.twinInventory.twinMainModule.twin[16,15]TB0003Datatype has not been explicit…[18,13]TB0003Datatype has not been explicit…[19,13]TB0003Datatype has not been explicit…[6,13]TB0003Datatype has not been explicitl…CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesEv.twinHelpers.twinInventory.twinMainModule.twinTypes.twinWatcher.twinPROPERTIESObject BrowserGo to DefinitionCloseProject / LibrarySearch components and membersComponents9 itemsMembers5 itemsDescriptionFeatureTour (project)BrowseResultsType a name to filter…AaEColourCEvCEv2MHelpersCInventoryMMainModuleTStockItemMTypesCWatcherSAddSClearƒFindƒItemByIndexƒTotalFunction TotalPublicFeatureTour · InventoryFeatureTour._Inventory.Total(ByVal taxRate As Double) As DoubleSource: /FeatureTour/Sources/Inventory.twin (line 16) +tB Services: OPERATIONALULTIMATE EDITIONEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXEDITOR 1Inventory(general)TotalInventory.twin23456789101112131415161718192021222324252627281Privateitems()AsStockItemPrivatecountAsLongPublicSubAdd(ByValnameAsString,ByValquantityAsLong,ByValpriceAsDouble)ReDimPreserveitems(count)Withitems(count)            .Name=name            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfClassInventoryDEBUG CONSOLEDIAGNOSTICS0411Helpers.twinInventory.twinMainModule.twin[16,15]TB0003Datatype has not been explicit…[18,13]TB0003Datatype has not been explicit…[19,13]TB0003Datatype has not been explicit…[6,13]TB0003Datatype has not been explicitl…CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesEv.twinHelpers.twinInventory.twinMainModule.twinTypes.twinWatcher.twinPROPERTIES2 of 2TwiceReplaceObject BrowserGo to DefinitionCloseProject / LibrarySearch components and membersComponents9 itemsMembers5 itemsDescriptionFeatureTour (project)BrowseResultsType a name to filter…AaEColourCEvCEv2MHelpersCInventoryMMainModuleTStockItemMTypesCWatcherSAddSClearƒFindƒItemByIndexƒTotalFunction TotalPublicFeatureTour · InventoryFeatureTour._Inventory.Total(ByVal taxRate As Double) As DoubleSource: /FeatureTour/Sources/Inventory.twin (line 16) diff --git a/docs/IDE/Code/Images/Object_Browser.svg b/docs/IDE/Code/Images/Object_Browser.svg index d179ba621..ebdeceb9a 100644 --- a/docs/IDE/Code/Images/Object_Browser.svg +++ b/docs/IDE/Code/Images/Object_Browser.svg @@ -1 +1 @@ -tB Services: OPERATIONALULTIMATE EDITIONEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXEDITOR 1Inventory(general)TotalInventory.twin23456789101112131415161718192021222324252627281Privateitems()AsStockItemPrivatecountAsLongPublicSubAdd(ByValnameAsString,ByValquantityAsLong,ByValpriceAsDouble)ReDimPreserveitems(count)Withitems(count)            .Name=name            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfClassInventoryDEBUG CONSOLEDIAGNOSTICS0411Helpers.twinInventory.twinMainModule.twin[16,15]TB0003Datatype has not been explicitly…[18,13]TB0003Datatype has not been explicitly…[19,13]TB0003Datatype has not been explicitly…[6,13]TB0003Datatype has not been explicitly …CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesEv.twinHelpers.twinInventory.twinMainModule.twinTypes.twinWatcher.twinPROPERTIESObject BrowserGo to DefinitionCloseProject / LibrarySearch components and membersComponents9 itemsMembers5 itemsDescriptionFeatureTour (project)BrowseResultsType a name to filter…AaEColourCEvCEv2MHelpersCInventoryMMainModuleTStockItemMTypesCWatcherSAddSClearƒFindƒItemByIndexƒTotalFunction TotalPublicFeatureTour · InventoryFeatureTour._Inventory.Total(ByVal taxRate As Double) As DoubleSource: /FeatureTour/Sources/Inventory.twin (line 16) +tB Services: OPERATIONALULTIMATE EDITIONEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXEDITOR 1Inventory(general)TotalInventory.twin23456789101112131415161718192021222324252627281Privateitems()AsStockItemPrivatecountAsLongPublicSubAdd(ByValnameAsString,ByValquantityAsLong,ByValpriceAsDouble)ReDimPreserveitems(count)Withitems(count)            .Name=name            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfClassInventoryDEBUG CONSOLEDIAGNOSTICS0411Helpers.twinInventory.twinMainModule.twin[16,15]TB0003Datatype has not been explicitly…[18,13]TB0003Datatype has not been explicitly…[19,13]TB0003Datatype has not been explicitly…[6,13]TB0003Datatype has not been explicitly …CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERFeatureTourImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesEv.twinHelpers.twinInventory.twinMainModule.twinTypes.twinWatcher.twinPROPERTIES2 of 2TwiceReplaceObject BrowserGo to DefinitionCloseProject / LibrarySearch components and membersComponents9 itemsMembers5 itemsDescriptionFeatureTour (project)BrowseResultsType a name to filter…AaEColourCEvCEv2MHelpersCInventoryMMainModuleTStockItemMTypesCWatcherSAddSClearƒFindƒItemByIndexƒTotalFunction TotalPublicFeatureTour · InventoryFeatureTour._Inventory.Total(ByVal taxRate As Double) As DoubleSource: /FeatureTour/Sources/Inventory.twin (line 16) diff --git a/docs/IDE/Code/IntelliSense.md b/docs/IDE/Code/IntelliSense.md index efe671e2c..f8bbfcb0d 100644 --- a/docs/IDE/Code/IntelliSense.md +++ b/docs/IDE/Code/IntelliSense.md @@ -34,6 +34,10 @@ The list holds the names that **start with** the word you have typed. The match A row can show `...` for a moment in place of a type. The compiler sends the types a little after the names. + +> [!NOTE] BETA 1034 +> One row of a list never gets its type: a variable shows `variable` with nothing after it, and a function shows no text. Which row depends on the compile, and opening the list again does not change it. The row can be out of sight, or a kind of name that has no type, so a list often looks complete. + ### Choose an entry Press Up or Down to select a row. When the list opens, no row is selected. diff --git a/docs/IDE/Customising/IDE-Options.md b/docs/IDE/Customising/IDE-Options.md index 41892740a..980d012a4 100644 --- a/docs/IDE/Customising/IDE-Options.md +++ b/docs/IDE/Customising/IDE-Options.md @@ -133,7 +133,7 @@ The mode applies to the code editor and to the entry box of the Debug Console. < : Default on. When you open a project, the editors that were open when you closed it come back, in their order, with the same tab selected, each cursor where it was and each folded section still folded. The watches and the Debug Console history come back whatever this option says. The option applies when a project is opened, not when the compiler restarts. **Focus The 'Recent' Tab On IDE Startup** -: Default off. The dialog that opens when the IDE starts opens on its **Recent** tab. +: Default off. The dialog that opens when the IDE starts opens on its **Recent** section, until the dialog has been closed once. Every closing saves the section the dialog was on, and from then on the dialog opens on that section, whatever this option says. [New Project](../Starting/New-Project#the-section-is-remembered) describes it. **Ask Before Delete Files** : Default on. Deleting files in the Project Explorer shows a confirmation box first. With the option off, the files are deleted without a question. diff --git a/docs/IDE/Customising/Keyboard-Shortcuts.md b/docs/IDE/Customising/Keyboard-Shortcuts.md index 19d3f1885..3bac8d651 100644 --- a/docs/IDE/Customising/Keyboard-Shortcuts.md +++ b/docs/IDE/Customising/Keyboard-Shortcuts.md @@ -15,7 +15,7 @@ Every IDE command can have keys, and you can change them. You write your changes The keys come from a stack of groups, merged in order: -1. The built-in group, **Default Built-in Keyboard Shortcuts**, is always first. It is always on and you cannot switch it off. It binds 228 commands with 265 keys. +1. The built-in group, **Default Built-in Keyboard Shortcuts**, is always first. It is always on and you cannot switch it off. [Keyboard shortcuts](../Reference/Keyboard-Shortcuts), in the reference, lists its commands and keys. 2. After it come the groups you create, in the order you created them, but only the ones that are switched on. A later group changes what the groups before it say. So you never switch between whole sets of shortcuts. You leave the built-in group on, and add a group that holds only your differences. A group can replace the keys of a command, add keys to it, or remove its keys. @@ -59,7 +59,7 @@ Text that is not valid JSON turns red. The IDE ignores a group whose text is not ## The text of a group -A group is a JSON object. Each key names a command, and each value is a list of key strings. The commands are named in the default group, which is listed on the [Window menu](../Reference/Menu/Window#keyboard-shortcuts) page and is the place to look up the name of a command. +A group is a JSON object. Each key names a command, and each value is a list of key strings. The commands are named in the default group, which is listed in the [reference](../Reference/Keyboard-Shortcuts) and is the place to look up the name of a command. | Entry | Meaning | |---|---| @@ -120,7 +120,8 @@ When a command fails, the Debug Console prints `command failed:` and the command ## See Also -- [Window menu](../Reference/Menu/Window#keyboard-shortcuts) -- the **Keyboard Shortcuts** entry, and the list of the default shortcuts +- [Keyboard shortcuts reference](../Reference/Keyboard-Shortcuts) -- every command the default group binds, with its keys +- [Window menu](../Reference/Menu/Window#keyboard-shortcuts) -- the **Keyboard Shortcuts** entry - [Panel layouts](Panel-Layouts) -- the commands that switch layouts - [Themes](Themes) -- the commands that switch themes - [Customising the IDE](.) -- where the settings are kept diff --git a/docs/IDE/Customising/Panel-Layouts.md b/docs/IDE/Customising/Panel-Layouts.md index 9ac7c1ee7..9fa2fc864 100644 --- a/docs/IDE/Customising/Panel-Layouts.md +++ b/docs/IDE/Customising/Panel-Layouts.md @@ -41,7 +41,7 @@ The first ten saved layouts have commands that switch to them: `tbPanels_SetActi ### The built-in layouts **Default Built-in Layout** -: The toolbar sits at the top. The Toolbox and the Editor are on the left of the middle. The Debug Console and Problems, and Call Stack and Variables, are on the right of the middle. The Project Explorer and Properties are in a column on the far side. +: The toolbar sits at the top. Under it, the Toolbox and the Editor sit side by side, above a row that holds the Debug Console, Diagnostics, Call Stack and Variables. The Project Explorer and Properties are in a column on the right. **Full Screen Editor Layout** : Docks only the toolbar, the Toolbox, the Editor and Properties, and the Editor takes most of the room. The other panels are removed from the window. A panel you open with its show command comes back as a floating panel, and the layout in use becomes **Custom Layout (Unsaved)**. diff --git a/docs/IDE/Customising/Themes.md b/docs/IDE/Customising/Themes.md index 953009c8b..c1761132f 100644 --- a/docs/IDE/Customising/Themes.md +++ b/docs/IDE/Customising/Themes.md @@ -18,7 +18,7 @@ Point at **Window → Theme**. The submenu lists the themes, and a tick marks th ![The Window menu with Theme highlighted and its submenu open to the right, listing Aurora (Dark), Classic (Light), a ticked Dark, High Contrast Dark (Dark), High Contrast Light (Light) and Light, with Reload from disk below a separator.](../Menu/Images/Menu_Window_Theme.png){:width="361" height="288"} -You can also click the icon with the tooltip **Change IDE Theme** in the menu bar. It opens the same list. +You can also click the icon with the tooltip **Change IDE Theme** in the toolbar. It opens the same list. The IDE ships with these themes: diff --git a/docs/IDE/Customising/index.md b/docs/IDE/Customising/index.md index 61ac7fb5e..0f59f4a70 100644 --- a/docs/IDE/Customising/index.md +++ b/docs/IDE/Customising/index.md @@ -28,7 +28,7 @@ Each part of the IDE that can be zoomed has a setting of its own. CtrlEDITOR 1MainModule(general)MainInventory.twinMainModule.twin123456789101112131415ModuleMainModulePublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)first=stock.ItemByIndex(stock.Find("Widget"))Debug.Printfirst.Quantity, costEndSubEndModule▶   run MainModule.MainCALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsfirst:{user defined type, 16 bytes}StockItemstock:{object: 441954992}Inventorycost:EmptyVariant [Empty] +EDITOR 1MainModule(general)MainInventory.twinMainModule.twin123456789101112131415ModuleMainModulePublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)first=stock.ItemByIndex(stock.Find("Widget"))Debug.Printfirst.Quantity, costEndSubEndModule▶   run MainModule.MainCALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsfirst:{user defined type, 16 bytes}StockItemstock:{object: 441954992}Inventorycost:EmptyVariant [Empty] diff --git a/docs/IDE/Debugging/Images/CallStack_Frames.png b/docs/IDE/Debugging/Images/CallStack_Frames.png index 20766f40e..aa8f9dd8b 100644 Binary files a/docs/IDE/Debugging/Images/CallStack_Frames.png and b/docs/IDE/Debugging/Images/CallStack_Frames.png differ diff --git a/docs/IDE/Debugging/Images/CallStack_Frames.svg b/docs/IDE/Debugging/Images/CallStack_Frames.svg index 44bc358bf..79b8da51c 100644 --- a/docs/IDE/Debugging/Images/CallStack_Frames.svg +++ b/docs/IDE/Debugging/Images/CallStack_Frames.svg @@ -1 +1 @@ -EDITOR 1MainModule(general)MainInventory.twinMainModule.twin123456789101112131415ModuleMainModulePublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)first=stock.ItemByIndex(stock.Find("Widget"))Debug.Printfirst.Quantity, costEndSubEndModule▶   run MainModule.MainCALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsfirst:{user defined type, 16 bytes}StockItemstock:{object: 441954992}Inventorycost:EmptyVariant [Empty] +EDITOR 1MainModule(general)MainInventory.twinMainModule.twin123456789101112131415ModuleMainModulePublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)first=stock.ItemByIndex(stock.Find("Widget"))Debug.Printfirst.Quantity, costEndSubEndModule▶   run MainModule.MainCALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsfirst:{user defined type, 16 bytes}StockItemstock:{object: 441954992}Inventorycost:EmptyVariant [Empty] diff --git a/docs/IDE/Debugging/Images/Error_Panel.light.png b/docs/IDE/Debugging/Images/Error_Panel.light.png index 3a8d769b0..f1cb06b6d 100644 Binary files a/docs/IDE/Debugging/Images/Error_Panel.light.png and b/docs/IDE/Debugging/Images/Error_Panel.light.png differ diff --git a/docs/IDE/Debugging/Images/Error_Panel.light.svg b/docs/IDE/Debugging/Images/Error_Panel.light.svg index 6b9a5cc63..92d1bab4f 100644 --- a/docs/IDE/Debugging/Images/Error_Panel.light.svg +++ b/docs/IDE/Debugging/Images/Error_Panel.light.svg @@ -1 +1 @@ -EDITOR 1Checks(general)FillTableChecks.twin5678910111213141516171819PublicSubFillTable()Dima(5)AsLongDimiAsLongDimidxAsLongDimtotalAsLongFori=1To6idx=Choose(i,1,3,5,7,2,4)a(idx)=i*10total=total+iDebug.Print"i = "&i&", idx = "&idx&", total = "&totalNextiDebug.Print"loop finished, i = "&i&", total = "&totalEndSub' A program that the program starts, short-lived and with no window: the PROCESS box.Run-time error 9 (00000009)DESCRIPTION:Subscript out of rangeTry Again (Resume)Ignore (Resume Next)StopSearch OnlineCALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]12:1FillTableChecks.twinVARIABLESLocalsa:{array of 6 elements}Long()i:4Longidx:7Longtotal:6Long +EDITOR 1Checks(general)FillTableChecks.twin5678910111213141516171819PublicSubFillTable()Dima(5)AsLongDimiAsLongDimidxAsLongDimtotalAsLongFori=1To6idx=Choose(i,1,3,5,7,2,4)a(idx)=i*10total=total+iDebug.Print"i = "&i&", idx = "&idx&", total = "&totalNextiDebug.Print"loop finished, i = "&i&", total = "&totalEndSub' A program that the program starts, short-lived and with no window: the PROCESS box.Run-time error 9 (00000009)DESCRIPTION:Subscript out of rangeTry Again (Resume)Ignore (Resume Next)StopSearch OnlineCALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]12:1FillTableChecks.twinVARIABLESLocalsa:{array of 6 elements}Long()i:4Longidx:7Longtotal:6Long diff --git a/docs/IDE/Debugging/Images/Error_Panel.png b/docs/IDE/Debugging/Images/Error_Panel.png index 9ec737160..04e7419c3 100644 Binary files a/docs/IDE/Debugging/Images/Error_Panel.png and b/docs/IDE/Debugging/Images/Error_Panel.png differ diff --git a/docs/IDE/Debugging/Images/Error_Panel.svg b/docs/IDE/Debugging/Images/Error_Panel.svg index bdec56864..7337eb4cc 100644 --- a/docs/IDE/Debugging/Images/Error_Panel.svg +++ b/docs/IDE/Debugging/Images/Error_Panel.svg @@ -1 +1 @@ -EDITOR 1Checks(general)FillTableChecks.twin5678910111213141516171819PublicSubFillTable()Dima(5)AsLongDimiAsLongDimidxAsLongDimtotalAsLongFori=1To6idx=Choose(i,1,3,5,7,2,4)a(idx)=i*10total=total+iDebug.Print"i = "&i&", idx = "&idx&", total = "&totalNextiDebug.Print"loop finished, i = "&i&", total = "&totalEndSub' A program that the program starts, short-lived and with no window: the PROCESS box.Run-time error 9 (00000009)DESCRIPTION:Subscript out of rangeTry Again (Resume)Ignore (Resume Next)StopSearch OnlineCALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]12:1FillTableChecks.twinVARIABLESLocalsa:{array of 6 elements}Long()i:4Longidx:7Longtotal:6Long +EDITOR 1Checks(general)FillTableChecks.twin5678910111213141516171819PublicSubFillTable()Dima(5)AsLongDimiAsLongDimidxAsLongDimtotalAsLongFori=1To6idx=Choose(i,1,3,5,7,2,4)a(idx)=i*10total=total+iDebug.Print"i = "&i&", idx = "&idx&", total = "&totalNextiDebug.Print"loop finished, i = "&i&", total = "&totalEndSub' A program that the program starts, short-lived and with no window: the PROCESS box.Run-time error 9 (00000009)DESCRIPTION:Subscript out of rangeTry Again (Resume)Ignore (Resume Next)StopSearch OnlineCALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]12:1FillTableChecks.twinVARIABLESLocalsa:{array of 6 elements}Long()i:4Longidx:7Longtotal:6Long diff --git a/docs/IDE/Debugging/Images/Paused_Line.light.png b/docs/IDE/Debugging/Images/Paused_Line.light.png index 96d42c18b..06487aff2 100644 Binary files a/docs/IDE/Debugging/Images/Paused_Line.light.png and b/docs/IDE/Debugging/Images/Paused_Line.light.png differ diff --git a/docs/IDE/Debugging/Images/Paused_Line.light.svg b/docs/IDE/Debugging/Images/Paused_Line.light.svg index 91c46480f..2c7ff33cd 100644 --- a/docs/IDE/Debugging/Images/Paused_Line.light.svg +++ b/docs/IDE/Debugging/Images/Paused_Line.light.svg @@ -1 +1 @@ -EDITOR 1Inventory(general)TotalInventory.twin222324252627282930313233343536161718192021Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:0Longnote:EmptyVariant [Empty]price:3Double +EDITOR 1Inventory(general)TotalInventory.twin222324252627282930313233343536161718192021Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:0Longnote:EmptyVariant [Empty]price:3Double diff --git a/docs/IDE/Debugging/Images/Paused_Line.png b/docs/IDE/Debugging/Images/Paused_Line.png index e818e2d72..6a04266a2 100644 Binary files a/docs/IDE/Debugging/Images/Paused_Line.png and b/docs/IDE/Debugging/Images/Paused_Line.png differ diff --git a/docs/IDE/Debugging/Images/Paused_Line.svg b/docs/IDE/Debugging/Images/Paused_Line.svg index 7547e5edd..688f2f3c7 100644 --- a/docs/IDE/Debugging/Images/Paused_Line.svg +++ b/docs/IDE/Debugging/Images/Paused_Line.svg @@ -1 +1 @@ -EDITOR 1Inventory(general)TotalInventory.twin222324252627282930313233343536161718192021Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:0Longnote:EmptyVariant [Empty]price:3Double +EDITOR 1Inventory(general)TotalInventory.twin222324252627282930313233343536161718192021Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:0Longnote:EmptyVariant [Empty]price:3Double diff --git a/docs/IDE/Debugging/Images/Paused_Panels.light.png b/docs/IDE/Debugging/Images/Paused_Panels.light.png index 00ae8cb4c..b1b3de349 100644 Binary files a/docs/IDE/Debugging/Images/Paused_Panels.light.png and b/docs/IDE/Debugging/Images/Paused_Panels.light.png differ diff --git a/docs/IDE/Debugging/Images/Paused_Panels.light.svg b/docs/IDE/Debugging/Images/Paused_Panels.light.svg index a540112f0..8a475d9a0 100644 --- a/docs/IDE/Debugging/Images/Paused_Panels.light.svg +++ b/docs/IDE/Debugging/Images/Paused_Panels.light.svg @@ -1 +1 @@ -tB Services: OPERATIONALULTIMATE EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWEDITOR 1Inventory(general)TotalInventory.twin1617181920212223242526272829303132333435363738PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1DEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: WidgetCALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Doublesubtotal:360Variant [Double]taxRate:0.2DoubleWATCHESitems(i).Quantity:8Longsubtotal:360Variant [Double] +tB Services: OPERATIONALULTIMATE EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWEDITOR 1Inventory(general)TotalInventory.twin1617181920212223242526272829303132333435363738PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1DEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: WidgetCALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Doublesubtotal:360Variant [Double]taxRate:0.2DoubleWATCHESitems(i).Quantity:8Longsubtotal:360Variant [Double] diff --git a/docs/IDE/Debugging/Images/Paused_Panels.png b/docs/IDE/Debugging/Images/Paused_Panels.png index 9274edaf1..4c154040e 100644 Binary files a/docs/IDE/Debugging/Images/Paused_Panels.png and b/docs/IDE/Debugging/Images/Paused_Panels.png differ diff --git a/docs/IDE/Debugging/Images/Paused_Panels.svg b/docs/IDE/Debugging/Images/Paused_Panels.svg index ea44de47f..39dd224c8 100644 --- a/docs/IDE/Debugging/Images/Paused_Panels.svg +++ b/docs/IDE/Debugging/Images/Paused_Panels.svg @@ -1 +1 @@ -tB Services: OPERATIONALULTIMATE EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWEDITOR 1Inventory(general)TotalInventory.twin161718192021222324252627282930313233343536373839PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenDEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: WidgetCALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Doublesubtotal:360Variant [Double]taxRate:0.2DoubleWATCHESitems(i).Quantity:8Longsubtotal:360Variant [Double] +tB Services: OPERATIONALULTIMATE EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWEDITOR 1Inventory(general)TotalInventory.twin161718192021222324252627282930313233343536373839PublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thennote="Grüße 👋 こんにちは"Debug.Printnote&": "&items(i).NameEndIfEndIfNextTotal=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenDEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: WidgetCALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [EXECUTING]24:1TotalInventory.twin9:1MainMainModule.twinVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Doublesubtotal:360Variant [Double]taxRate:0.2DoubleWATCHESitems(i).Quantity:8Longsubtotal:360Variant [Double] diff --git a/docs/IDE/Debugging/Running.md b/docs/IDE/Debugging/Running.md index 119d3c531..6dd6246cf 100644 --- a/docs/IDE/Debugging/Running.md +++ b/docs/IDE/Debugging/Running.md @@ -59,7 +59,15 @@ What runs first is the project's [Startup Object](../Project-Settings#startup-ob - **A form.** The console shows `Executing '[frmMain].StartupShow()'...`, where `frmMain` is the form's name, then the output of the form. The run lasts until every form has closed, and the console shows `[DEBUGGER] Waiting for remaining forms to close...` meanwhile. - **(None)**, which an ActiveX DLL can have. **Start** is enabled, but it only writes `[DEBUGGER] No startup object set` to the console, and nothing runs. -**Sub Main** with no procedure called `Main` in the project writes `(compile error: Unrecognized symbol 'Main')` to the console, with no dialog. [Runtime Command Line Args](../Project-Settings#runtime-command-line-args) sets the text that **Command$** returns in the run. +**Sub Main** with no procedure called `Main` in the project writes a compile error to the console, with no dialog. [Runtime Command Line Args](../Project-Settings#runtime-command-line-args) sets the text that **Command$** returns in the run. + + +> [!NOTE] BETA 1034 +> The compile error reads `(compile error: Main)`, which does not say that the project has no procedure called `Main`. + + +> [!NOTE] BETA 1034 +> A project whose `Settings` file names no startup object runs `Main` whatever it builds, although [Project Settings](../Project-Settings#startup-object) shows **Default: (none)** for a DLL. The templates for a DLL and a control name none, so **Start** on a new project of those kinds ends with this compile error. Choose **(none)** in the list to have **Start** say that there is nothing to run. Every run that ends writes `(time taken: s)`, followed by `[DEBUGGER] global variables have been cleared`. A run that you stop at a pause writes `aborted` in place of the time. @@ -67,12 +75,14 @@ Every run that ends writes `(time taken: s)`, followed by `[DEBUGGER] global Start shows no dialog for a compile error, and it opens no panel. The [DIAGNOSTICS](../Code/Diagnostics) panel lists the errors. What the console and the editor show depends on where the error is. -- **In a procedure that `Main` calls.** The console shows `[LINKER] compilation (codegen) error detected in 'Startup.Main' at line #42`, where 42 is the line of the call. The editor opens `Main`'s module and stops at its `Sub` line with the error panel, titled **ERROR OCCURRED**. Nothing in `Main` has run. -- **In `Main` itself.** No panel opens. The console shows `[LINKER] compilation (codegen) error detected in '{temp_component}.{temp_procedure}' at line #1`, then `(compile error: codegen error; check for compilation errors)`, and the program is idle again. +- **In a procedure that `Main` calls, an error of syntax**, such as `a = = 1`. The console shows `[LINKER] compilation (codegen) error detected in 'Startup.Main' at line #42`, where 42 is the line of the call. The editor opens `Main`'s module and stops at its `Sub` line with the error panel, titled **ERROR OCCURRED**. Nothing in `Main` has run. +- **In a procedure that runs, an error of name or member**, such as a name that does not exist (`a = NoSuchThing`) or a member of a `Long`. This holds for a procedure that `Main` calls, a method of a class, and `Form_Load` of the start form. The panel is titled **ERROR OCCURRED** and its description reads `COMPILATION ERROR DETECTED. CHECK THE DIAGNOSTICS AT LINE #`, where *n* is the line of the error. The editor opens the file of the failing procedure and stops at its `Sub` line. The caller has run up to the call, so what it printed before the call is in the console. The console shows `[LINKER] compilation (codegen) error detected in 'Startup.Helper' at line #1`. +- **In `Main` itself**, or in a module-level `Const` that `Main` uses. No panel opens. The console shows `[LINKER] compilation (codegen) error detected in '{temp_component}.{temp_procedure}' at line #1`, then `(compile error: codegen error; check for compilation errors)`, and the program is idle again. +- **In a procedure that nothing calls.** The program runs and ends as usual. The error is in the DIAGNOSTICS panel only. > [!NOTE] BETA 1034 -> The panel for a compile error gives the description `COMPILATION (CODEGEN) ERROR AT LINE #42. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S`, and the line it names is the line of the call, not the line of the error. A compile error in your code is not a defect to report. Read the real error in the DIAGNOSTICS panel and fix it. +> The panel for a syntax error in a procedure that `Main` calls gives the description `COMPILATION (CODEGEN) ERROR AT LINE #42. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S`, and the line it names is the line of the call, not the line of the error. A compile error in your code is not a defect to report. Read the real error in the DIAGNOSTICS panel and fix it. ## Run Or Preview @@ -137,7 +147,7 @@ The next F5 starts a new run of the edited code. At the panel that a ## Restarting the compiler -**Restart the compiler** starts a new compiler process and loads the project into it from the IDE's memory, so unsaved edits survive. The console writes `restarting from MEMORY []`. The IDE clears the Call Stack, the Variables panel, the Memory panel and the list of running processes. It keeps the breakpoints, including those you set since you last saved, and the watches. After a restart, F5 stops again at the same breakpoint, the watches have values again, and a watch that was expanded is still expanded. +**Restart the compiler** starts a new compiler process and loads the project into it from the IDE's memory, so unsaved edits survive. The console writes `restarting from MEMORY []`. The IDE clears the Call Stack, the Variables panel, the Memory panel and the list of running processes. It keeps the breakpoints, including those you set since you last saved, and the watches. After a restart, F5 stops again at the same breakpoint, the watches have values again, and a watch that was expanded is still expanded. [Compiler services](../Troubleshooting/Compiler-Services#restart-the-compiler) lists everything a restart keeps and loses, and the ways to cause one. Closing a project that has unsaved changes asks **You've made changes to this project. Save now?** and offers **Save**, **Discard Changes** and **Cancel**. Setting or clearing a breakpoint counts as a change. A project with nothing changed closes with no prompt, and the console is cleared. diff --git a/docs/IDE/Forms/Images/Designer_GridOptions.png b/docs/IDE/Forms/Images/Designer_GridOptions.png index cac57c04f..0212b69fd 100644 Binary files a/docs/IDE/Forms/Images/Designer_GridOptions.png and b/docs/IDE/Forms/Images/Designer_GridOptions.png differ diff --git a/docs/IDE/Forms/Images/Preview_Window.png b/docs/IDE/Forms/Images/Preview_Window.png index 69de62a36..5e1a08ed6 100644 Binary files a/docs/IDE/Forms/Images/Preview_Window.png and b/docs/IDE/Forms/Images/Preview_Window.png differ diff --git a/docs/IDE/Images/CallStack.light.png b/docs/IDE/Images/CallStack.light.png index 753518114..f167e43f7 100644 Binary files a/docs/IDE/Images/CallStack.light.png and b/docs/IDE/Images/CallStack.light.png differ diff --git a/docs/IDE/Images/CallStack.light.svg b/docs/IDE/Images/CallStack.light.svg index 11225e87a..8ca184058 100644 --- a/docs/IDE/Images/CallStack.light.svg +++ b/docs/IDE/Images/CallStack.light.svg @@ -1 +1 @@ -CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [IDLE]Active Threads +CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]Active Threads diff --git a/docs/IDE/Images/CallStack.png b/docs/IDE/Images/CallStack.png index a66ce0c5f..571c36ec0 100644 Binary files a/docs/IDE/Images/CallStack.png and b/docs/IDE/Images/CallStack.png differ diff --git a/docs/IDE/Images/CallStack.svg b/docs/IDE/Images/CallStack.svg index 6cb19a022..820d507df 100644 --- a/docs/IDE/Images/CallStack.svg +++ b/docs/IDE/Images/CallStack.svg @@ -1 +1 @@ -CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [IDLE]Active Threads +CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]Active Threads diff --git a/docs/IDE/Images/IDE.light.png b/docs/IDE/Images/IDE.light.png index 43b5d8712..1f320d785 100644 Binary files a/docs/IDE/Images/IDE.light.png and b/docs/IDE/Images/IDE.light.png differ diff --git a/docs/IDE/Images/IDE.light.svg b/docs/IDE/Images/IDE.light.svg index 0a241df34..5c58df552 100644 --- a/docs/IDE/Images/IDE.light.svg +++ b/docs/IDE/Images/IDE.light.svg @@ -1 +1 @@ -tB Services: UNAVAILABLEtB Licence: NOT READYFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpwin32100%PREVIEWTOOLBOXEDITOR 1CALL STACKtbProject_Close +tB Services: UNAVAILABLEtB Licence: NOT READYFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpwin32100%PREVIEWTOOLBOXEDITOR 1DEBUG CONSOLE>DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIEStbProject_Close diff --git a/docs/IDE/Images/IDE_Tour.light.png b/docs/IDE/Images/IDE_Tour.light.png new file mode 100644 index 000000000..6293a441b Binary files /dev/null and b/docs/IDE/Images/IDE_Tour.light.png differ diff --git a/docs/IDE/Images/IDE_Tour.light.svg b/docs/IDE/Images/IDE_Tour.light.svg new file mode 100644 index 000000000..90dd73352 --- /dev/null +++ b/docs/IDE/Images/IDE_Tour.light.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXCOMMONCONTROLSMorecomponents...EDITOR 1frmMain.tbformDEBUG CONSOLE>DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERFormsDemoImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesfrmMain.tbformfrmMain.twinMyReport.tbreportMyReport.twinStartup.twinPROPERTIESPROPERTIESEVENTSDESIGNAPPEARANCENamefrmMainFormDesignerId{F0A3F101-3B2E-4D69-8A14-7E91C2D4C615}MDIChildFalseCaptionfrmMainAppearance1 - vbAppear3dBorderStyle2 - vbSizableBackColor&H(BGR)8000000FForeColor&H(BGR)000000FontNameSegoe UIFontSize9FontBoldFalsefrmMain    FormFile+ menu380 x 270Menu barToolbarToolboxEditorPropertiesProject ExplorerDebug ConsoleDiagnosticsCall StackVariablesStatus bar diff --git a/docs/IDE/Images/IDE_Tour.png b/docs/IDE/Images/IDE_Tour.png new file mode 100644 index 000000000..3fe103418 Binary files /dev/null and b/docs/IDE/Images/IDE_Tour.png differ diff --git a/docs/IDE/Images/IDE_Tour.svg b/docs/IDE/Images/IDE_Tour.svg new file mode 100644 index 000000000..7c2817e0a --- /dev/null +++ b/docs/IDE/Images/IDE_Tour.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXCOMMONCONTROLSMorecomponents...EDITOR 1frmMain.tbformDEBUG CONSOLE>DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERFormsDemoImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesfrmMain.tbformfrmMain.twinMyReport.tbreportMyReport.twinStartup.twinPROPERTIESPROPERTIESEVENTSDESIGNAPPEARANCENamefrmMainFormDesignerId{F0A3F101-3B2E-4D69-8A14-7E91C2D4C615}MDIChildFalseCaptionfrmMainAppearance1 - vbAppear3dBorderStyle2 - vbSizableBackColor&H(BGR)8000000FForeColor&H(BGR)000000FontNameSegoe UIFontSize9FontBoldFalsefrmMain    FormFile+ menu380 x 270Menu barToolbarToolboxEditorPropertiesProject ExplorerDebug ConsoleDiagnosticsCall StackVariablesStatus bar diff --git a/docs/IDE/Images/New_Project.light.png b/docs/IDE/Images/New_Project.light.png index 46786195d..ed444896b 100644 Binary files a/docs/IDE/Images/New_Project.light.png and b/docs/IDE/Images/New_Project.light.png differ diff --git a/docs/IDE/Images/New_Project.light.svg b/docs/IDE/Images/New_Project.light.svg index ee1dcaa24..fe5a5720b 100644 --- a/docs/IDE/Images/New_Project.light.svg +++ b/docs/IDE/Images/New_Project.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) diff --git a/docs/IDE/Images/New_Project.png b/docs/IDE/Images/New_Project.png index 5cf3eaf05..4f1be06bb 100644 Binary files a/docs/IDE/Images/New_Project.png and b/docs/IDE/Images/New_Project.png differ diff --git a/docs/IDE/Images/New_Project.svg b/docs/IDE/Images/New_Project.svg index c7083f94e..e4e3d6d4d 100644 --- a/docs/IDE/Images/New_Project.svg +++ b/docs/IDE/Images/New_Project.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) diff --git a/docs/IDE/Images/New_Project_Recent_1.light.png b/docs/IDE/Images/New_Project_Recent_1.light.png index 91c17943c..fefedf22a 100644 Binary files a/docs/IDE/Images/New_Project_Recent_1.light.png and b/docs/IDE/Images/New_Project_Recent_1.light.png differ diff --git a/docs/IDE/Images/New_Project_Recent_1.light.svg b/docs/IDE/Images/New_Project_Recent_1.light.svg index 8df892a43..df082ec56 100644 --- a/docs/IDE/Images/New_Project_Recent_1.light.svg +++ b/docs/IDE/Images/New_Project_Recent_1.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...CancelNewSamplesRecentNothing here yet.Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry.Open a project and it will appear here.Open +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...CancelNewSamplesRecentNothing here yet.Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry.Open a project and it will appear here.Open diff --git a/docs/IDE/Images/New_Project_Recent_1.png b/docs/IDE/Images/New_Project_Recent_1.png index 229d4097c..92d38e8f7 100644 Binary files a/docs/IDE/Images/New_Project_Recent_1.png and b/docs/IDE/Images/New_Project_Recent_1.png differ diff --git a/docs/IDE/Images/New_Project_Recent_1.svg b/docs/IDE/Images/New_Project_Recent_1.svg index 52bd84342..53802969a 100644 --- a/docs/IDE/Images/New_Project_Recent_1.svg +++ b/docs/IDE/Images/New_Project_Recent_1.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...CancelNewSamplesRecentNothing here yet.Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry.Open a project and it will appear here.Open +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...CancelNewSamplesRecentNothing here yet.Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry.Open a project and it will appear here.Open diff --git a/docs/IDE/Images/New_Project_Recent_2.light.png b/docs/IDE/Images/New_Project_Recent_2.light.png index b6349aafa..57b2e077e 100644 Binary files a/docs/IDE/Images/New_Project_Recent_2.light.png and b/docs/IDE/Images/New_Project_Recent_2.light.png differ diff --git a/docs/IDE/Images/New_Project_Recent_2.light.svg b/docs/IDE/Images/New_Project_Recent_2.light.svg index 775c0b920..2ee7da1d1 100644 --- a/docs/IDE/Images/New_Project_Recent_2.light.svg +++ b/docs/IDE/Images/New_Project_Recent_2.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentC:\Projects\InvoiceManager\InvoiceManager.twinprojD:\Work\ImageViewer\ImageViewer.twinprojSearch projects, samples, recent...BETA 10338262Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentC:\Projects\InvoiceManager\InvoiceManager.twinprojD:\Work\ImageViewer\ImageViewer.twinprojSearch projects, samples, recent...BETA 10348262Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry. diff --git a/docs/IDE/Images/New_Project_Recent_2.png b/docs/IDE/Images/New_Project_Recent_2.png index be6cd81c4..9c703e23b 100644 Binary files a/docs/IDE/Images/New_Project_Recent_2.png and b/docs/IDE/Images/New_Project_Recent_2.png differ diff --git a/docs/IDE/Images/New_Project_Recent_2.svg b/docs/IDE/Images/New_Project_Recent_2.svg index a366da31d..810b860de 100644 --- a/docs/IDE/Images/New_Project_Recent_2.svg +++ b/docs/IDE/Images/New_Project_Recent_2.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentC:\Projects\InvoiceManager\InvoiceManager.twinprojD:\Work\ImageViewer\ImageViewer.twinprojSearch projects, samples, recent...BETA 10338262Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentC:\Projects\InvoiceManager\InvoiceManager.twinprojD:\Work\ImageViewer\ImageViewer.twinprojSearch projects, samples, recent...BETA 10348262Double-click an item or press Enterto open it.Projects you have opened recently. Click the X to remove an entry. diff --git a/docs/IDE/Images/New_Project_Samples.light.png b/docs/IDE/Images/New_Project_Samples.light.png index 8d42d089d..cfadc1154 100644 Binary files a/docs/IDE/Images/New_Project_Samples.light.png and b/docs/IDE/Images/New_Project_Samples.light.png differ diff --git a/docs/IDE/Images/New_Project_Samples.light.svg b/docs/IDE/Images/New_Project_Samples.light.svg index e19ccef90..0acced744 100644 --- a/docs/IDE/Images/New_Project_Samples.light.svg +++ b/docs/IDE/Images/New_Project_Samples.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. diff --git a/docs/IDE/Images/New_Project_Samples.png b/docs/IDE/Images/New_Project_Samples.png index db1234f3d..155a483e1 100644 Binary files a/docs/IDE/Images/New_Project_Samples.png and b/docs/IDE/Images/New_Project_Samples.png differ diff --git a/docs/IDE/Images/New_Project_Samples.svg b/docs/IDE/Images/New_Project_Samples.svg index 61783e0e2..b72a86c20 100644 --- a/docs/IDE/Images/New_Project_Samples.svg +++ b/docs/IDE/Images/New_Project_Samples.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. diff --git a/docs/IDE/Menu/Add-Ins.md b/docs/IDE/Menu/Add-Ins.md index 18de10aaa..4d7e8b361 100644 --- a/docs/IDE/Menu/Add-Ins.md +++ b/docs/IDE/Menu/Add-Ins.md @@ -10,16 +10,18 @@ redirect_from: # Add-Ins Menu -![The Add-Ins menu open on a greyed-out placeholder line reading no addins loaded, and nothing else.](Images/Menu_Add-Ins.png){:width="158" height="57"} +The Add-Ins menu lists the add-ins that are loaded. [Add Ins](../../AddIns/) explains where an add-in goes and when it loads. The compiler loads the add-ins, so the list is empty until a project is open. -{no addins loaded} +![The Add-Ins menu with one entry, GlobalSearchAddIn AddIn, with a tick beside it.](Images/Menu_Add-Ins_GlobalSearch.png){:width="219" height="57"} -Once you open a project: +Each add-in has one item, under its own name, with a tick. A DLL that failed to load is listed too, as *Unknown Addin*. See [When an addin does not load](../../AddIns/#when-an-addin-does-not-load). -![The Add-Ins menu open on a single available entry, GlobalSearchAddIn AddIn, with a tick beside it.](Images/Menu_Add-Ins_GlobalSearch.png){:width="219" height="57"} +The menu cannot switch an add-in off. Choosing an item only shows a floating message, in the IDE's words: -Clicking on this menu option shows +![A floating message reading Sorry, this menu option has not been implemented yet.](Images/GlobalSearch-Popup.png){:width="502" height="52"} -> 🛈 Sorry, this menu option has not been implemented yet +To load a project without its add-ins, hold Shift while it loads. See [Add-ins that fail to load](../../Troubleshooting/Diagnostic-Tools#add-ins-that-fail-to-load). -![A notification strip with an information icon at the left and a close cross at the right, reading Sorry, this menu option has not been implemented yet.](Images/GlobalSearch-Popup.png){:width="502" height="52"} +With no add-in loaded, the menu holds one greyed-out line. + +![The Add-Ins menu open on a greyed-out line reading no addins loaded.](Images/Menu_Add-Ins.png){:width="158" height="57"} diff --git a/docs/IDE/Menu/File.md b/docs/IDE/Menu/File.md index bfd923c67..b9509bc62 100644 --- a/docs/IDE/Menu/File.md +++ b/docs/IDE/Menu/File.md @@ -19,10 +19,10 @@ The commands from **Close Project** to **Clean** work on the open project, and a ## Open and close **New Project...** (Ctrl+N) and **Open Recent...** -: Open the **New / Open Project** dialog, on its **New** section and on its **Recent** section. Nothing is closed until you press **Open** in it. See [New Project](../../Project/New) and [Open a project](../../Projects/Saving-and-Exporting#open-a-project). +: Open the **New / Open Project** dialog, on its **New** section and on its **Recent** section. Nothing is closed until you press **Open** in it. See [New Project](../../Starting/New-Project) and [Opening projects](../../Starting/Opening-Projects). **Open Project...** (Ctrl+O) -: Opens a project file, or a `.vbp` file, which is imported as a new project. See [Open a project](../../Projects/Saving-and-Exporting#open-a-project). +: Opens a project file, or a `.vbp` file, which is imported as a new project. See [Opening projects](../../Starting/Opening-Projects) and [Importing a VB6 project](../../Starting/Importing-VB6). **Close Project** : Closes the project, after a question if it has unsaved changes. It has no key. See [Close Project and Exit](../../Projects/Saving-and-Exporting#close-project-and-exit). diff --git a/docs/IDE/Menu/Help.md b/docs/IDE/Menu/Help.md index 0d85cca85..57b58bdfc 100644 --- a/docs/IDE/Menu/Help.md +++ b/docs/IDE/Menu/Help.md @@ -14,22 +14,42 @@ redirect_from: ## About twinBASIC... -![The About twinBASIC dialog. Beneath the twinBASIC logo it gives the version of the IDE and the copyright as 2026 twinBASIC Ltd, followed by an italic paragraph thanking the community and a SPECIAL THANKS line marked coming soon. A VIP Gold Supporters band heads a grid of four sponsor logos and two empty See your logo here placeholders. Below that, ten third-party open-source libraries are listed against the licence file each one ships --- Monaco, Codicon, LLVM, LodePNG, Spectrum, Grapick, ChartJS, jQuery, VbQRCodegen and LZ4 --- above a green band naming the licence edition, and a Close button.](Images/Menu_Help_About.png){:width="501" height="880"} +Opens a box that gives the version of the IDE, the copyright, the VIP Gold supporters, the third-party libraries that the IDE uses, and the edition your licence has. Each library is listed with its licence file, in the `ide\licences\` folder of the install. Click a supporter's logo to open that supporter's website in your default browser; for a supporter who has given no website, a floating message says so. **Close**, Enter and Esc close the box. It works with no project, and has no key. + +![The About twinBASIC box, with the twinBASIC logo at the top, a band of supporters' logos in the middle, a list of libraries below it, and a green band above the Close button.](Images/Menu_Help_About.png){:width="501" height="880"} ## Licence Agreement... +Shows the twinBASIC licence agreement, as the first start does, but with one **Close** button, and under the text the line the IDE stored when you agreed. It works with no project and has no key. See [The licence agreement](../../Starting/First-Launch#the-licence-agreement). + ## Automatic IDE Error Reporting... +Asks whether the IDE may send anonymous error reports to twinBASIC Ltd. The dialog's own title reads *Automatic Error Reporting*. The question switches reporting off as it opens, and **YES** switches it on again. **NO** and Esc leave it off. The command has no key. See [The error reporting question](../../Starting/First-Launch#the-error-reporting-question). + ## Help & Support (Discord Server)... +Opens `https://discord.gg/UaW9GgKKuE`, the twinBASIC Discord server, in your default browser. The Discord icon in the [status bar](../StatusBar#links) does the same. The command has no key. See [Reporting a problem](../../Troubleshooting/#reporting-a-problem). + ## Help & Support (GitHub repository)... +Opens `https://github.com/twinbasic/twinbasic`, the repository's front page, in your default browser. It is not the form for a new issue. The GitHub icon in the status bar does the same. The command has no key. + ## Twitter (News Feed)... +Opens `https://twitter.com/waynephillipsea`, the news feed, in your default browser. The Twitter icon in the status bar does the same. The command has no key. + ## Purchase A Licence... +Opens `https://twinbasic.com/preorder.html`, the address that the **Purchase a licence** button of the Enter Licence Key dialog opens. It has no key. See [Purchase A Licence](../../Starting/First-Launch#purchase-a-licence). + ## Enter Licence Key... +Opens the dialog that takes a licence key. It works with no project, and has no key. With no project loaded, the IDE holds the key until a project loads. See [Enter Licence Key](../../Starting/First-Launch#enter-licence-key). + ## Buy us a Coffee! (Ko-Fi)... +Opens `https://ko-fi.com/twinbasic` in your default browser. The Ko-fi icon in the status bar does the same. The command has no key. + ## Compiler services TRACE mode: Disabled + +Switches TRACE mode and restarts the compiler. The item reads **Compiler services TRACE mode: Enabled**, with a tick, while the mode is on. It is a diagnostic for the developers of twinBASIC, it is not saved, and the IDE starts with it off. A native exception in your program also turns it on. The command has no key. See [TRACE mode](../../Troubleshooting/Diagnostic-Tools#trace-mode). diff --git a/docs/IDE/Menu/Images/Menu_Help_About.light.png b/docs/IDE/Menu/Images/Menu_Help_About.light.png index c5bbf7052..378b03f6f 100644 Binary files a/docs/IDE/Menu/Images/Menu_Help_About.light.png and b/docs/IDE/Menu/Images/Menu_Help_About.light.png differ diff --git a/docs/IDE/Menu/Images/Menu_Help_About.light.svg b/docs/IDE/Menu/Images/Menu_Help_About.light.svg index f8d727b41..2c5efbd9d 100644 --- a/docs/IDE/Menu/Images/Menu_Help_About.light.svg +++ b/docs/IDE/Menu/Images/Menu_Help_About.light.svg @@ -1 +1 @@ -Add-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1033)Inventory(general)(declaration)Collection"Widget""Sprocket""Gasket"ObjectCreateObject("Scripting.Dictionary")"Widget",4.5"Sprocket",12"Gasket",0.75Variantnamesitem,prices(item)&TotalPrice(prices),vbInformation[Description("Adds up the prices held in a Dictionary.")]TotalPrice(ByValpricesAsObject)AsDoubleVariantDoubleprices.KeysDIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [IDLE]VARIABLESAbout twinBASIC...Closeversion: twinBASIC IDE BETA 1033© 2026 TWINBASIC LTDIn the development of twinBASIC, the essence of community support has been thecornerstone of our progress and success. This project thrives not just on code andtechnology, but on the invaluable contributions, insights, and feedback provided by adiverse and active community of developers, users, and enthusiasts.SPECIAL THANKS: ... coming soon :)VIP Gold SupportersSee your logo here!Become a VIP Gold supporterSee your logo here!Become a VIP Gold supporterPortions of this software utilize third-party open-source libraries. Namely:MONACO - CODE EDITOR (see file ide\licences\monaco.txt)CODICON - ICONS (see file ide\licences\codicon.txt)LLVM - OPTIMIZING BACKEND COMPILER (see file ide\licences\llvm.txt)LODEPNG - PNG DECODER (see file ide\licences\lodepng.txt)SPECTRUM - JS COLOR PICKER (see file ide\licences\spectrum.txt)GRAPICK - JS GRADIENT PICKER (see file ide\licences\grapick.txt)CHARTJS - JS CHARTS (see file ide\licences\chartjs.txt)JQUERY - JS HELPER (see file ide\licences\jquery.txt)VBQRCODEGEN - QRCODE library by wqweto (see file ide\licences\VbQRCodegen.txt)LZ4 - LZ4 COMPRESSION LIBRARY (see file ide\licences\lz4.txt)LICENCE: ULTIMATE EDITION +Add-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)Inventory(general)(declaration)Collection"Widget""Sprocket""Gasket"ObjectCreateObject("Scripting.Dictionary")"Widget",4.5"Sprocket",12"Gasket",0.75Variantnamesitem,prices(item)&TotalPrice(prices),vbInformation[Description("Adds up the prices held in a Dictionary.")]TotalPrice(ByValpricesAsObject)AsDoubleVariantDoubleprices.KeysDIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESAbout twinBASIC...Closeversion: twinBASIC IDE BETA 1034© 2026 TWINBASIC LTDIn the development of twinBASIC, the essence of community support has been thecornerstone of our progress and success. This project thrives not just on code andtechnology, but on the invaluable contributions, insights, and feedback provided by adiverse and active community of developers, users, and enthusiasts.SPECIAL THANKS: ... coming soon :)VIP Gold SupportersSee your logo here!Become a VIP Gold supporterSee your logo here!Become a VIP Gold supporterPortions of this software utilize third-party open-source libraries. Namely:MONACO - CODE EDITOR (see file ide\licences\monaco.txt)CODICON - ICONS (see file ide\licences\codicon.txt)LLVM - OPTIMIZING BACKEND COMPILER (see file ide\licences\llvm.txt)LODEPNG - PNG DECODER (see file ide\licences\lodepng.txt)SPECTRUM - JS COLOR PICKER (see file ide\licences\spectrum.txt)GRAPICK - JS GRADIENT PICKER (see file ide\licences\grapick.txt)CHARTJS - JS CHARTS (see file ide\licences\chartjs.txt)JQUERY - JS HELPER (see file ide\licences\jquery.txt)VBQRCODEGEN - QRCODE library by wqweto (see file ide\licences\VbQRCodegen.txt)LZ4 - LZ4 COMPRESSION LIBRARY (see file ide\licences\lz4.txt)LICENCE: ULTIMATE EDITION diff --git a/docs/IDE/Menu/Images/Menu_Help_About.png b/docs/IDE/Menu/Images/Menu_Help_About.png index fd2a8ab68..090db5683 100644 Binary files a/docs/IDE/Menu/Images/Menu_Help_About.png and b/docs/IDE/Menu/Images/Menu_Help_About.png differ diff --git a/docs/IDE/Menu/Images/Menu_Help_About.svg b/docs/IDE/Menu/Images/Menu_Help_About.svg index 32829997d..0efa31fbc 100644 --- a/docs/IDE/Menu/Images/Menu_Help_About.svg +++ b/docs/IDE/Menu/Images/Menu_Help_About.svg @@ -1 +1 @@ -Add-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1033)Inventory(general)(declaration)Collection"Widget""Sprocket""Gasket"ObjectCreateObject("Scripting.Dictionary")"Widget",4.5"Sprocket",12"Gasket",0.75Variantnamesitem,prices(item)&TotalPrice(prices),vbInformation[Description("Adds up the prices held in a Dictionary.")]TotalPrice(ByValpricesAsObject)AsDoubleVariantDoubleprices.KeysDIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [IDLE]VARIABLESAbout twinBASIC...Closeversion: twinBASIC IDE BETA 1033© 2026 TWINBASIC LTDIn the development of twinBASIC, the essence of community support has been thecornerstone of our progress and success. This project thrives not just on code andtechnology, but on the invaluable contributions, insights, and feedback provided by adiverse and active community of developers, users, and enthusiasts.SPECIAL THANKS: ... coming soon :)VIP Gold SupportersSee your logo here!Become a VIP Gold supporterSee your logo here!Become a VIP Gold supporterPortions of this software utilize third-party open-source libraries. Namely:MONACO - CODE EDITOR (see file ide\licences\monaco.txt)CODICON - ICONS (see file ide\licences\codicon.txt)LLVM - OPTIMIZING BACKEND COMPILER (see file ide\licences\llvm.txt)LODEPNG - PNG DECODER (see file ide\licences\lodepng.txt)SPECTRUM - JS COLOR PICKER (see file ide\licences\spectrum.txt)GRAPICK - JS GRADIENT PICKER (see file ide\licences\grapick.txt)CHARTJS - JS CHARTS (see file ide\licences\chartjs.txt)JQUERY - JS HELPER (see file ide\licences\jquery.txt)VBQRCODEGEN - QRCODE library by wqweto (see file ide\licences\VbQRCodegen.txt)LZ4 - LZ4 COMPRESSION LIBRARY (see file ide\licences\lz4.txt)LICENCE: ULTIMATE EDITION +Add-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)Inventory(general)(declaration)Collection"Widget""Sprocket""Gasket"ObjectCreateObject("Scripting.Dictionary")"Widget",4.5"Sprocket",12"Gasket",0.75Variantnamesitem,prices(item)&TotalPrice(prices),vbInformation[Description("Adds up the prices held in a Dictionary.")]TotalPrice(ByValpricesAsObject)AsDoubleVariantDoubleprices.KeysDIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESAbout twinBASIC...Closeversion: twinBASIC IDE BETA 1034© 2026 TWINBASIC LTDIn the development of twinBASIC, the essence of community support has been thecornerstone of our progress and success. This project thrives not just on code andtechnology, but on the invaluable contributions, insights, and feedback provided by adiverse and active community of developers, users, and enthusiasts.SPECIAL THANKS: ... coming soon :)VIP Gold SupportersSee your logo here!Become a VIP Gold supporterSee your logo here!Become a VIP Gold supporterPortions of this software utilize third-party open-source libraries. Namely:MONACO - CODE EDITOR (see file ide\licences\monaco.txt)CODICON - ICONS (see file ide\licences\codicon.txt)LLVM - OPTIMIZING BACKEND COMPILER (see file ide\licences\llvm.txt)LODEPNG - PNG DECODER (see file ide\licences\lodepng.txt)SPECTRUM - JS COLOR PICKER (see file ide\licences\spectrum.txt)GRAPICK - JS GRADIENT PICKER (see file ide\licences\grapick.txt)CHARTJS - JS CHARTS (see file ide\licences\chartjs.txt)JQUERY - JS HELPER (see file ide\licences\jquery.txt)VBQRCODEGEN - QRCODE library by wqweto (see file ide\licences\VbQRCodegen.txt)LZ4 - LZ4 COMPRESSION LIBRARY (see file ide\licences\lz4.txt)LICENCE: ULTIMATE EDITION diff --git a/docs/IDE/Menu/Project.md b/docs/IDE/Menu/Project.md index c6779f488..0f251775f 100644 --- a/docs/IDE/Menu/Project.md +++ b/docs/IDE/Menu/Project.md @@ -33,9 +33,7 @@ Every command needs an open project, and is greyed out until one is. ## Folders **Open Project Folder...** -: Opens the folder of the project's `.twinproj` file in Windows Explorer. For a project that has no file yet, it shows the notification **Project is not yet saved. Please save the project first.** It has no key. +: Opens the folder of the project's `.twinproj` file in Windows Explorer. For a project that has no file yet, it shows the notification **Project is not yet saved. Please save the project first.** It has no key. See [Open a folder](../../Projects/Saving-and-Exporting#open-a-folder). **Open Build Output Folder...** : Opens the folder of the file that the [Build Output Path](../../Project-Settings#build-output-path) names. It does not create the folder. It gives the same notification when the project has no file, and goes on giving it after the first save, and opens the old folder after a change of the path, until the project is opened again or the compiler restarts. It has no key. See [Open Build Output Folder](../../Building/Build-Output#open-build-output-folder). - -See [Open a folder](../../Projects/Saving-and-Exporting#open-a-folder). diff --git a/docs/IDE/Menu/View.md b/docs/IDE/Menu/View.md index 6a04b2770..9466c86ca 100644 --- a/docs/IDE/Menu/View.md +++ b/docs/IDE/Menu/View.md @@ -73,4 +73,4 @@ The last seventeen items show a panel. A panel that is in no layout in use comes : The controls you can place on a form, and the properties of the selected one. See [Toolbox](../../Forms/Toolbox) and [Properties](../../Forms/Properties). **WEBPAGE** -: A web browser pane inside the IDE. See [Webpage](../../Project/Webpage). +: A web browser pane inside the IDE. See [Webpage](../Webpage). diff --git a/docs/IDE/Menu/Window.md b/docs/IDE/Menu/Window.md index e5185443f..15c194fcc 100644 --- a/docs/IDE/Menu/Window.md +++ b/docs/IDE/Menu/Window.md @@ -45,737 +45,7 @@ Switches groups of keyboard shortcuts on and off. See [Keyboard shortcuts](../.. Opens a dialog that lists the built-in group, which is read-only, and the groups that have been created, and shows each as JSON text. See [Manage keyboard shortcuts](../../Customising/Keyboard-Shortcuts#manage-keyboard-shortcuts) and [The text of a group](../../Customising/Keyboard-Shortcuts#the-text-of-a-group). -The built-in group follows. The names are the IDE's command names, and each is followed by its keys. - -
-Default keyboard shortcuts - -```json -{ - "tbMisc_PreventDefaultKeyBehaviour": [ - "{KEYDOWN}{CTRL}A", - "{KEYDOWN}TAB" - ], - "tbEditor_SelectAll": [ - "{KEYDOWN}{CTRL}A" - ], - "tbEditor_ClipboardCopy": [ - "{KEYDOWN}{CTRL}C", - "{KEYDOWN}{CTRL}INSERT" - ], - "tbEditor_ClipboardCut": [ - "{KEYDOWN}{CTRL}X", - "{KEYDOWN}{SHIFT}DELETE" - ], - "tbEditor_ClipboardPaste": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}V" - ], - "tbEditor_Undo": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}Z" - ], - "tbEditor_Redo": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}Y" - ], - "tbEditor_DeleteLeft": [ - "{KEYDOWN}{KEYDOWNREPEAT}BACKSPACE", - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}BACKSPACE" - ], - "tbEditor_DeleteRight": [ - "{KEYDOWN}{KEYDOWNREPEAT}DELETE" - ], - "tbEditor_DeleteLeftWord": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}BACKSPACE" - ], - "tbEditor_DeleteRightWord": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}DELETE" - ], - "tbEditor_DeleteLines": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}DELETE" - ], - "tbEditor_CursorSelectLeft": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWLEFT" - ], - "tbEditor_CursorSelectRight": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWRIGHT" - ], - "tbEditor_CursorSelectUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWUP" - ], - "tbEditor_CursorSelectDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWDOWN" - ], - "tbEditor_CursorSelectLeftWord": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}ARROWLEFT" - ], - "tbEditor_CursorSelectRightWord": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}ARROWRIGHT" - ], - "tbEditor_CursorMoveLeft": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWLEFT" - ], - "tbEditor_CursorMoveRight": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWRIGHT" - ], - "tbEditor_CursorMoveUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP" - ], - "tbEditor_CursorMoveDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN" - ], - "tbEditor_CursorMoveLeftWord": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWLEFT" - ], - "tbEditor_CursorMoveRightWord": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWRIGHT" - ], - "tbEditor_CursorMoveStartOfLineHome": [ - "{KEYDOWN}HOME" - ], - "tbEditor_CursorMoveEndOfLine": [ - "{KEYDOWN}END" - ], - "tbEditor_CursorMoveTop": [ - "{KEYDOWN}{CTRL}HOME" - ], - "tbEditor_CursorMoveBottom": [ - "{KEYDOWN}{CTRL}END" - ], - "tbEditor_CursorSelectTop": [ - "{KEYDOWN}{CTRL}{SHIFT}HOME" - ], - "tbEditor_CursorSelectBottom": [ - "{KEYDOWN}{CTRL}{SHIFT}END" - ], - "tbEditor_CursorSelectLineStartHome": [ - "{KEYDOWN}{SHIFT}HOME" - ], - "tbEditor_CursorSelectLineEnd": [ - "{KEYDOWN}{SHIFT}END" - ], - "tbEditor_CursorMovePageUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}PAGEUP" - ], - "tbEditor_CursorMovePageDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}PAGEDOWN" - ], - "tbEditor_CursorSelectPageUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}PAGEUP" - ], - "tbEditor_CursorSelectPageDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}PAGEDOWN" - ], - "tbEditor_InsertLineBreak": [ - "{KEYDOWN}{KEYDOWNREPEAT}ENTER" - ], - "tbEditor_InsertSpace": [ - "{KEYDOWN}{KEYDOWNREPEAT}SPACE", - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}SPACE" - ], - "tbEditor_InsertComma": [ - "{KEYDOWN}{KEYDOWNREPEAT}," - ], - "tbEditor_InsertPeriod": [ - "{KEYDOWN}{KEYDOWNREPEAT}." - ], - "tbEditor_InsertEquals": [ - "{KEYDOWN}{KEYDOWNREPEAT}=" - ], - "tbEditor_InsertPlus": [ - "{KEYDOWN}{KEYDOWNREPEAT}+" - ], - "tbEditor_InsertMinus": [ - "{KEYDOWN}{KEYDOWNREPEAT}-" - ], - "tbEditor_InsertBackslash": [ - "{KEYDOWN}{KEYDOWNREPEAT}\\" - ], - "tbEditor_InsertSlash": [ - "{KEYDOWN}{KEYDOWNREPEAT}/" - ], - "tbEditor_InsertColon": [ - "{KEYDOWN}{KEYDOWNREPEAT}:" - ], - "tbEditor_InsertAmpersand": [ - "{KEYDOWN}{KEYDOWNREPEAT}&" - ], - "tbEditor_InsertExclamation": [ - "{KEYDOWN}{KEYDOWNREPEAT}!" - ], - "tbEditor_InsertMultiply": [ - "{KEYDOWN}{KEYDOWNREPEAT}*" - ], - "tbEditor_MoveToProcedurePrev": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWUP" - ], - "tbEditor_MoveToProcedureNext": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWDOWN" - ], - "tbEditor_AddCursorAbove": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{ALT}ARROWUP" - ], - "tbEditor_AddCursorBelow": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{ALT}ARROWDOWN" - ], - "tbEditor_AddCursorsAtLineEndsOfSelection": [ - "{KEYDOWN}{SHIFT}{ALT}I" - ], - "tbEditor_ClipboardCutPlain": [ - "{KEYDOWN}{CTRL}{SHIFT}X" - ], - "tbEditor_ClipboardPlainCopy": [ - "{KEYDOWN}{CTRL}{SHIFT}C" - ], - "tbEditor_ClipboardPlainPaste": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}V" - ], - "tbEditor_ClipboardPasteAsComment": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{ALT}V" - ], - "tbEditor_CommentSelection": [ - "{KEYDOWN}{CTRL}K" - ], - "tbEditor_UncommentSelection": [ - "{KEYDOWN}{CTRL}{SHIFT}K" - ], - "tbEditor_GoToDefinition": [ - "{KEYDOWN}{SHIFT}F2", - "{KEYDOWN}F12" - ], - "tbEditor_ToggleBookmark": [ - "{KEYDOWN}{CTRL}B" - ], - "tbEditor_PrevBookmark": [ - "{KEYDOWN}{CTRL}M" - ], - "tbEditor_Find": [ - "{KEYDOWN}{CTRL}F" - ], - "tbFindWidget_ShowFind": [ - "{KEYDOWN}{ALT}F" - ], - "tbFindWidget_ShowReplace": [ - "{KEYDOWN}{ALT}H" - ], - "tbEditor_FindWidget_SelectAllMatches": [ - "{KEYDOWN}{ALT}A" - ], - "tbEditor_FindReplace": [ - "{KEYDOWN}{CTRL}H" - ], - "tbEditor_LastPosition": [ - "{KEYDOWN}{CTRL}{SHIFT}F2" - ], - "tbEditor_InsertLineAbove": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}ENTER" - ], - "tbEditor_InsertLineBelow": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ENTER" - ], - "tbEditor_IndentLine": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}]" - ], - "tbEditor_OutdentLine": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}[" - ], - "tbEditor_Fold": [ - "{KEYDOWN}{CTRL}{" - ], - "tbEditor_Unfold": [ - "{KEYDOWN}{CTRL}}" - ], - "tbEditor_FoldProcedures": [ - "{KEYDOWN}{CTRL}{ALT}ARROWLEFT" - ], - "tbEditor_UnfoldProcedures": [ - "{KEYDOWN}{CTRL}{ALT}ARROWRIGHT" - ], - "tbEditor_ToggleLineComment": [ - "{KEYDOWN}{CTRL}/" - ], - "tbEditor_ShowContextMenu": [ - "{KEYDOWN}{SHIFT}F10" - ], - "tbEditor_JumpToNextProblem": [ - "{KEYDOWN}{KEYDOWNREPEAT}{ALT}F8" - ], - "tbEditor_JumpToPrevProblem": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}{ALT}F8" - ], - "tbEditor_FindWidget_Next": [ - "{KEYDOWN}{KEYDOWNREPEAT}{ALT}F3" - ], - "tbEditor_FindWidget_Prev": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}{ALT}F3" - ], - "tbEditor_FindSelectedNext": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}F3" - ], - "tbEditor_FindSelectedPrev": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}F3" - ], - "tbEditor_ShrinkSelection": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}{ALT}ARROWLEFT" - ], - "tbEditor_ExpandSelection": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}{ALT}ARROWRIGHT" - ], - "tbEditor_ExpandLineSelection": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}L" - ], - "tbEditor_CursorUndo": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}U" - ], - "tbEditor_CopyLineDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}{ALT}ARROWDOWN" - ], - "tbEditor_CopyLineUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}{ALT}ARROWUP" - ], - "tbEditor_MoveLinesDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}{ALT}ARROWDOWN" - ], - "tbEditor_MoveLinesUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}{ALT}ARROWUP" - ], - "tbEditor_RenameSymbol": [ - "{KEYDOWN}F2" - ], - "tbEditor_SelectAllMatches": [ - "{KEYDOWN}{CTRL}{SHIFT}L" - ], - "tbEditor_ToggleBlockComment": [ - "{KEYDOWN}{SHIFT}{ALT}A" - ], - "tbEditor_Indent": [ - "{KEYDOWN}{KEYDOWNREPEAT}TAB" - ], - "tbEditor_Outdent": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}TAB" - ], - "tbEditor_IncreaseFontSize": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}+" - ], - "tbEditor_DecreaseFontSize": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}-" - ], - "tbEditor_CodeSelector1_Dropdown": [ - "{KEYDOWN}{CTRL}1" - ], - "tbEditor_CodeSelector2_Dropdown": [ - "{KEYDOWN}{CTRL}2" - ], - "tbEditor_CodeSelector3_Dropdown": [ - "{KEYDOWN}{CTRL}3" - ], - "tbEditor_TurnShowCodeHintsOn": [ - "{KEYDOWN}CONTROL" - ], - "tbEditor_TurnShowCodeHintsOff": [ - "CONTROL" - ], - "tbEditor_ToggleWordWrap": [ - "{KEYDOWN}{ALT}Z" - ], - "tbEditor_CloseTab": [ - "{KEYDOWN}{CTRL}F4", - "{KEYDOWN}{CTRL}W" - ], - "tbEditor_ReopenLastClosedTab": [ - "{KEYDOWN}{CTRL}{SHIFT}T" - ], - "tbEditor_ActivateNextTab": [ - "{KEYDOWN}{CTRL}TAB" - ], - "tbEditor_ActivatePrevTab": [ - "{KEYDOWN}{CTRL}{SHIFT}TAB" - ], - "tbProject_SaveAllChanges": [ - "{KEYDOWN}{CTRL}S" - ], - "tbEditor_SwitchToggleFormDesignCode": [], - "tbEditor_SwitchToFormDesign": [ - "{KEYDOWN}{SHIFT}F7" - ], - "tbProjectExplorer_SelectedItemsClipboardCut": [ - "{KEYDOWN}{CTRL}X", - "{KEYDOWN}{SHIFT}DELETE" - ], - "tbFormDesigner_ClipboardCut": [ - "{KEYDOWN}{CTRL}X", - "{KEYDOWN}{SHIFT}DELETE" - ], - "tbFormDesigner_ClipboardCopy": [ - "{KEYDOWN}{CTRL}C", - "{KEYDOWN}{CTRL}INSERT" - ], - "tbProjectExplorer_SelectedItemsClipboardCopy": [ - "{KEYDOWN}{CTRL}C", - "{KEYDOWN}{CTRL}INSERT" - ], - "tbFormDesigner_ClipboardPaste": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}V" - ], - "tbProjectExplorer_ClipboardPaste": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}V" - ], - "tbFindWidget_Hide": [ - "{KEYDOWN}ESCAPE" - ], - "tbFindWidget_FindNext": [ - "{KEYDOWN}{KEYDOWNREPEAT}ENTER" - ], - "tbFindWidget_FindPrevious": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ENTER" - ], - "tbFindWidget_FindNextInSelection": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}F3" - ], - "tbFindWidget_FindPrevInSelection": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}F3" - ], - "tbFindWidget_HistoryCyclePrevious": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP" - ], - "tbFindWidget_HistoryCycleNext": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN" - ], - "tbProject_ShowReferences": [ - "{KEYDOWN}{CTRL}T" - ], - "tbFindReplace_Cancel": [ - "{KEYDOWN}ESCAPE" - ], - "tbFindReplace_Next": [ - "{KEYDOWN}{KEYDOWNREPEAT}F3" - ], - "tbFindReplace_Prev": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}F3" - ], - "tbFindReplace_ReplaceNext": [ - "{KEYDOWN}{ALT}R" - ], - "tbFindReplace_ReplaceAll": [ - "{KEYDOWN}{ALT}A" - ], - "tbFindReplace_FocusNext": [ - "{KEYDOWN}{ALT}N" - ], - "tbFindReplace_FocusFindWhat": [ - "{KEYDOWN}{ALT}F" - ], - "tbFindReplace_FocusReplaceWith": [ - "{KEYDOWN}{ALT}W" - ], - "tbFindReplace_FocusCurrentProcedure": [ - "{KEYDOWN}{ALT}P" - ], - "tbFindReplace_FocusCurrentModule": [ - "{KEYDOWN}{ALT}M" - ], - "tbFindReplace_FocusCurrentProject": [ - "{KEYDOWN}{ALT}C" - ], - "tbFindReplace_FocusDirection": [ - "{KEYDOWN}{ALT}D" - ], - "tbFindReplace_FocusWholeWordOnlyToggle": [ - "{KEYDOWN}{ALT}O" - ], - "tbFindReplace_FocusMatchCaseToggle": [ - "{KEYDOWN}{ALT}S" - ], - "tbFindReplace_FocusUsePatternMatchingToggle": [ - "{KEYDOWN}{ALT}U" - ], - "tbFormDesigner_Undo": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}Z" - ], - "tbFormDesigner_Redo": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}Y" - ], - "tbProject_Export": [ - "{KEYDOWN}{CTRL}E" - ], - "tbProject_FindInFiles": [ - "{KEYDOWN}{CTRL}{SHIFT}F" - ], - "tbDebug_StartOrContinue": [ - "{KEYDOWN}F5" - ], - "tbDebug_BreakInto": [ - "{KEYDOWN}{CTRL}CANCEL" - ], - "tbDebug_StepOver": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}F8", - "{KEYDOWN}{KEYDOWNREPEAT}F10" - ], - "tbDebug_StepInto": [ - "{KEYDOWN}{KEYDOWNREPEAT}F8", - "{KEYDOWN}{KEYDOWNREPEAT}F11" - ], - "tbDebug_StepOut": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}F8", - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}F11" - ], - "tbDebug_RunOrPreview": [ - "{KEYDOWN}F6" - ], - "tbBuild_SwitchToWin64": [ - "{KEYDOWN}{CTRL}F1" - ], - "tbBuild_SwitchToWin32": [ - "{KEYDOWN}{CTRL}F2" - ], - "tbHelp_ToggleExpandSignatureHelp": [ - "{KEYDOWN}F1" - ], - "tbDebug_SetNextExecutionStatement": [ - "{KEYDOWN}{CTRL}F9" - ], - "tbDebug_ToggleBreakpoint": [ - "{KEYDOWN}F9" - ], - "tbWatches_Add": [ - "{KEYDOWN}{SHIFT}F9" - ], - "tbIde_IncreaseFontSize": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{ALT}+" - ], - "tbIde_DecreaseFontSize": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{ALT}-" - ], - "tbIdeWindow_Close": [ - "{KEYDOWN}{ALT}F4" - ], - "tbDebugConsole_FocusPanel": [ - "{KEYDOWN}{CTRL}G" - ], - "tbFormDesigner_MenuEditorRename": [ - "{KEYDOWN}F2" - ], - "tbFormDesigner_MenuEditorDelete": [ - "{KEYDOWN}DELETE" - ], - "tbFormDesigner_MenuEditorMoveLeft": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWLEFT" - ], - "tbFormDesigner_MenuEditorMoveUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWUP" - ], - "tbFormDesigner_MenuEditorMoveRight": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWRIGHT" - ], - "tbFormDesigner_MenuEditorMoveDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}{CTRL}ARROWDOWN" - ], - "tbFormDesigner_MenuEditorToggleChecked": [ - "{KEYDOWN}{KEYDOWNREPEAT}SPACE" - ], - "tbFormDesigner_SelectAllControls": [ - "{KEYDOWN}{CTRL}A" - ], - "tbFormDesigner_DeleteSelectedControls": [ - "{KEYDOWN}DELETE" - ], - "tbFormDesigner_SelectParentOfSelectedControl": [ - "{KEYDOWN}ESCAPE" - ], - "tbFormDesigner_SelectToolboxPointer": [ - "{KEYDOWN}ESCAPE" - ], - "tbFormDesigner_AlignSelectedControlsLeft": [ - "{KEYDOWN}{ALT}ARROWLEFT" - ], - "tbFormDesigner_AlignSelectedControlsTop": [ - "{KEYDOWN}{ALT}ARROWUP" - ], - "tbFormDesigner_AlignSelectedControlsRight": [ - "{KEYDOWN}{ALT}ARROWRIGHT" - ], - "tbFormDesigner_AlignSelectedControlsBottom": [ - "{KEYDOWN}{ALT}ARROWDOWN" - ], - "tbFormDesigner_ResizeSelectedControlsWidest": [ - "{KEYDOWN}{CTRL}{SHIFT}ARROWRIGHT" - ], - "tbFormDesigner_ResizeSelectedControlsTallest": [ - "{KEYDOWN}{CTRL}{SHIFT}ARROWDOWN" - ], - "tbFormDesigner_ResizeSelectedControlsNarrowest": [ - "{KEYDOWN}{CTRL}{SHIFT}ARROWLEFT" - ], - "tbFormDesigner_ResizeSelectedControlsShortest": [ - "{KEYDOWN}{CTRL}{SHIFT}ARROWUP" - ], - "tbFormDesigner_ResizeSelectedControlsNarrower": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWLEFT" - ], - "tbFormDesigner_MoveSelectedControlsLeft": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWLEFT" - ], - "tbFormDesigner_ResizeSelectedControlsWider": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWRIGHT" - ], - "tbFormDesigner_MoveSelectedControlsRight": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWRIGHT" - ], - "tbFormDesigner_ResizeSelectedControlsShorter": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWUP" - ], - "tbFormDesigner_MoveSelectedControlsUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP" - ], - "tbFormDesigner_ResizeSelectedControlsTaller": [ - "{KEYDOWN}{KEYDOWNREPEAT}{SHIFT}ARROWDOWN" - ], - "tbFormDesigner_MoveSelectedControlsDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN" - ], - "tbFormDesigner_TurnShowTabIndexOn": [ - "{KEYDOWN}CONTROL" - ], - "tbFormDesigner_TurnShowTabIndexOff": [ - "CONTROL" - ], - "tbMenu_FocusAdjacentLeft": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWLEFT" - ], - "tbMenu_FocusAdjacentRight": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWRIGHT" - ], - "tbMenu_FocusAdjacentUp": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP" - ], - "tbMenu_FocusAdjacentDown": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN" - ], - "tbMenu_ExecuteFocusedEntry": [ - "{KEYDOWN}{KEYDOWNREPEAT}ENTER" - ], - "tbMisc_SaveProjectMetaStateInTwinproj": [ - "{KEYDOWN}{CTRL}{SHIFT}{ALT}M" - ], - "tbMisc_ShiftKeyStateDown": [ - "{KEYDOWN}SHIFT" - ], - "tbMisc_ShiftKeyStateUp": [ - "SHIFT" - ], - "tbMisc_AltKeyStateDown": [ - "{KEYDOWN}ALT" - ], - "tbMisc_AltKeyStateUp": [ - "ALT" - ], - "tbMenu_Cancel": [ - "{KEYDOWN}ESCAPE" - ], - "tbProject_Open": [ - "{KEYDOWN}{CTRL}O" - ], - "tbProject_New": [ - "{KEYDOWN}{CTRL}N" - ], - "tbProjectExplorer_ToggleFileMode": [ - "{KEYDOWN}{CTRL}R" - ], - "tbCallStack_ShowPanel": [ - "{KEYDOWN}{CTRL}L" - ], - "tbDebugConsole_ShowPanel": [ - "{KEYDOWN}{CTRL}G" - ], - "tbDebug_BreakpointsClear": [ - "{KEYDOWN}{CTRL}{SHIFT}F9" - ], - "tbProjectExplorer_SelectedItemsRename": [ - "{KEYDOWN}F2" - ], - "tbProjectExplorer_SelectedItemsDeletePermanently": [ - "{KEYDOWN}DELETE" - ], - "tbDebugConsole_HistoryCyclePrevious": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP" - ], - "tbDebugConsole_HistoryCycleNext": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN" - ], - "tbIntellisense_SelectPrevious": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP" - ], - "tbIntellisense_SelectNext": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN" - ], - "tbIntellisense_AcceptSelectedEntry": [ - "{KEYDOWN}TAB", - "SPACE", - "ENTER", - ",", - ".", - "+", - "-", - "&", - "(", - ")", - "!", - "*", - "\\", - "/", - ":", - "=" - ], - "tbDebugConsole_ExecuteEnteredLine": [ - "{KEYDOWN}ENTER" - ], - "tbDebugConsole_GoToDefinition": [ - "{KEYDOWN}{SHIFT}F2", - "{KEYDOWN}F12" - ], - "tbIntellisense_AcceptSelectedOrFirstEntry": [ - "{KEYDOWN}TAB", - "{KEYDOWN}SPACE" - ], - "tbIntellisense_Cancel": [ - "{KEYDOWN}ESCAPE", - "{KEYDOWN}ARROWLEFT", - "{KEYDOWN}ARROWRIGHT", - "{KEYDOWN}BACKSPACE" - ], - "tbSignatureHelpCancel": [ - "{KEYDOWN}ESCAPE", - "{KEYDOWN}ARROWUP", - "{KEYDOWN}ARROWDOWN" - ], - "tbIntellisense_ShowNow": [ - "{KEYDOWN}{CTRL}SPACE" - ], - "tbIntellisense_ShowAfterKeyPress": [ - "{ALLEXCEPT}ARROWUP|ARROWDOWN|ARROWLEFT|ARROWRIGHT|ESCAPE|SHIFT|CONTROL|ALT|TAB|SPACE|ENTER|BACKSPACE|F1|F2|F3|F4|F5|F6|F7|F8|F9|F10|F11|F12|F13|F14|F15|F16|F17|F18|F19|F20|DELETE|HOME|END|CANCEL|PAGEUP|PAGEDOWN|&|+|-|*|/|\\|^|CAPSLOCK" - ], - "tbDebugConsole_SelectAllEntryBox": [ - "{KEYDOWN}{CTRL}A" - ], - "tbPanels_SetActiveLayoutDefault": [ - "{KEYDOWN}{CTRL}#" - ], - "tbEditor_AddSelectionToNextFindMatch": [ - "{KEYDOWN}{CTRL}D" - ], - "tbToolbox_SelectNextTool": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWDOWN", - "{KEYDOWN}{KEYDOWNREPEAT}ARROWRIGHT" - ], - "tbToolbox_SelectPrevTool": [ - "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP", - "{KEYDOWN}{KEYDOWNREPEAT}ARROWLEFT" - ], - "tbBuild_Fusion": [ - "{KEYDOWN}{CTRL}{SHIFT}B" - ] -} -``` - -
+[Keyboard shortcuts](../Keyboard-Shortcuts) lists the built-in group as a table. ## Theme {: index_also="dark mode" } diff --git a/docs/IDE/Menu/index.md b/docs/IDE/Menu/index.md index 406609dd2..645a571f1 100644 --- a/docs/IDE/Menu/index.md +++ b/docs/IDE/Menu/index.md @@ -2,12 +2,31 @@ title: Menu parent: IDE Reference grand_parent: IDE -# nav_order: 1 +nav_order: 0 permalink: /tB/IDE/Reference/Menu redirect_from: - /tB/IDE/Project/Menu +has_toc: false --- # Menu -![The twinBASIC menu bar along the top of the IDE window, on its title bar: File, Edit, View, Project, Format, Debug, Run, Tools, Add-Ins, Window and Help, with the window's buttons at the right-hand end.](Images/Menu.png){:width="611" height="25"} +The menu bar runs along the top of the IDE window, in the title bar. Each menu has a page here, and each entry says what the item does, gives its key, and links to the chapter that explains it. + +![The twinBASIC menu bar, with the window's buttons at the right-hand end.](Images/Menu.png){:width="611" height="25"} + +- [**File**](Menu/File) --- opens, saves, exports and builds the project. +- [**Edit**](Menu/Edit) --- changes and searches text. +- [**View**](Menu/View) --- switches between code and designer, moves around the code, changes the zoom and shows the panels. +- [**Project**](Menu/Project) --- adds to the project, changes its references and settings, and opens its folders. +- [**Format**](Menu/Format) --- lines up and orders the controls selected in the form designer. +- [**Debug**](Menu/Debug) --- steps through a paused program, and holds the watches and breakpoints. +- [**Run**](Menu/Run) --- starts, pauses and ends a program. +- [**Tools**](Menu/Tools) --- the IDE Options, and the compiler. +- [**Add-Ins**](Menu/AddIns) --- the add-ins that are loaded. +- [**Window**](Menu/Window) --- panel layouts, keyboard shortcuts, themes and the language. +- [**Help**](Menu/Help) --- the About box, the licence, and the links to get help. + +Press and release Alt to open the **File** menu. While a menu is open, Up and Down move through its items, Left and Right move to the next menu or in and out of a submenu, Enter runs the item, and Esc closes the menu. + +While the pointer is over an item, the [status bar](StatusBar#status) shows the name of its command. That is the name that a [keyboard shortcut group](../Customising/Keyboard-Shortcuts#the-text-of-a-group) uses. diff --git a/docs/IDE/New Project.md b/docs/IDE/New Project.md deleted file mode 100644 index 8354042f4..000000000 --- a/docs/IDE/New Project.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -title: New Project -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/New -index: new project dialog ---- - -# New Project - -![The New / Open Project dialog on its New section, with a search box and the version of the IDE across the top and the sections New, Samples and Recent down the left, each with a count. Eight project templates sit as cards in three rows: Standard EXE, which is selected, ActiveX Control, ActiveX DLL, Standard DLL marked non-ActiveX, Standard EXE (+VBCCR) marked experimental, Standard EXE marked Console App, Import from VBP and Import from folder. Browse, Open and Cancel run along the bottom.](Images/New_Project.png){:width="970" height="728"} - -![The IDE File menu open with no project loaded. New Project, Open Project, Open Recent and Exit are available; Close Project, Save Project, Save Project As, Export Project, Save Current Document, Build and Clean are all greyed out.](Menu/Images/Menu_File.png){:width="190" height="337"} - -Shortcut: Ctrl+N - -The dialog lists its sections down the left, each with the number of entries it holds: **New**, **Samples**, **Recent**, and **Recent (VB6)**, which shows only when the machine has a VB6 history and lists the `.vbp` files VB6 opened recently. Double-click an entry, or select it and press Enter or **Open**, to open it; Up and Down move the selection. The search box at the top looks in every section at once: it lists the entries whose name or path holds every word typed, grouped by section, with the matching text marked, and Esc empties it. **Browse...** opens the Windows **Open** dialog, as **File → Open Project...** does. - -## Options -{: index="standard exe; create an ActiveX DLL; create ActiveX DLL" index_also="create a DLL project; create dll project; create a dll; create dll" } - -- Standard EXE -- ActiveX Control -- ActiveX DLL -- Standard DLL -- Standard EXE (+VBCCR) -- Standard EXE (Console App) -- Import from VBP... -- [Import from folder...](#import-from-folder) - -Browse \| Open \| Cancel - -## Import from folder - -Opens a project from a folder of source files, such as the folder that **File → Export Project** writes. It shows a *Browse For Folder* dialog titled *Select folder of the existing twinBASIC project...*: choose the folder that holds the project's `Settings` file. - -The project opens with no `.twinproj` file behind it: it is unsaved, and marked as changed. Because it has no file, the first **Save Project** (Ctrl+S) opens the *Save As* dialog to ask for one. The folder it is saved in becomes `${SourcePath}`, which the project's [*Export Path*](../Project-Settings#export-path) may use. - -If the folder holds the compiler packages under `Packages`, as an export by the IDE does, delete their folders first: imported with them, the project gets a copy of them that the compiler does not use. See [Packing the export back into a project](../Projects/Saving-and-Exporting#packing-the-export-back-into-a-project). [Keeping a project in Git from the IDE](../../../Features/Packages/Import-Export-Tool#keeping-a-project-in-git-from-the-ide) uses this command to open a fresh clone. - -## Samples - -![The same dialog on its Samples section, two scrolling columns of sample cards named without their numbers: Reports (Experimental), which is selected, then HelloWorld, WebView2 Examples, Chromium Embedded Framework Examples, GetIPAddresses, MyCodeLibrary and on, running past the bottom of the panel.](Images/New_Project_Samples.png){:width="970" height="728"} - -Each card shows its sample's name without its number. The cards are in the order of the numbers, by which the documentation names the samples (*Sample 6*, CustomControls). The numbering is the IDE's own: **1a** and **1b** sit between **1** and **2**. - -- **0.** Reports (Experimental) -- **1.** HelloWorld -- **1a.** WebView2 Examples -- **1b.** Chromium Embedded Framework Examples -- **2.** GetIPAddresses -- **3.** MyCodeLibrary -- **4.** MyVBEAddin (NEW with ToolWindow) -- **5.** MyCOMAddin -- **6.** CustomControls -- **7.** Package -- **8.** FilePropertyViewer (CustomControls) -- **9.** ActiveX Control WebView2 + Monaco -- **10.** twinBASIC IDE Addin -- **11.** twinBASIC IDE Addin (Chart) -- **12.** twinBASIC IDE Addin (Monaco) -- **13.** twinBASIC IDE Addin (ListView) -- **14.** twinBASIC IDE Addin (VirtualListView) -- **15.** twinBASIC IDE Addin (GlobalSearch) -- **16.** twinBASIC IDE Addin (TODO Widgets demo) -- **17.** Static Library Example (SQLITE3) -- **18.** Static Library Example (libdeflate) -- **19.** MDI Forms -- **20.** TreeView + ListView + ImageList -- **21.** Windows Service Simple Example -- **22.** Windows Service Complex Example (inc Event Logging and IPC) -- **23.** OOP Inheritance Example (Animals) - -## Recent - -If no project has been opened, or every entry has been removed, the section reads **Nothing here yet.** and **Open** is greyed out. - -![The same dialog on its Recent section with a count of 0 and nothing listed: Nothing here yet. Open a project and it will appear here. The Open button is greyed out.](Images/New_Project_Recent_1.png){:width="970" height="728"} - -Otherwise each recently opened project is a row that shows its path. - -![The Recent section holding two entries, the first selected with a blue outline, each with a clock icon at its left, the project's path, and a small X button at its right edge. The paths are C:\Projects\InvoiceManager\InvoiceManager.twinproj and D:\Work\ImageViewer\ImageViewer.twinproj.](Images/New_Project_Recent_2.png){:width="970" height="728"} - -_Removed paths_ - -Click the X at a row's right edge to remove the project from the list. diff --git a/docs/IDE/Project Settings.md b/docs/IDE/Project Settings.md index 1435c5d6b..ee44c83cd 100644 --- a/docs/IDE/Project Settings.md +++ b/docs/IDE/Project Settings.md @@ -144,7 +144,7 @@ The full path of the host EXE that the compiler creates for [Fusion](../../Featu ### Startup Object -The form that the program shows when it starts, or **Sub Main**. When it is not set, the default is **Sub Main** for an EXE and **(none)** for a DLL. For a Standard EXE the list does not offer **(none)**, and for a Package TWINPACK or a TypeLibrary TLB it offers nothing else. In the `Settings` file it is `project.startupObject`. +The form that the program shows when it starts, or **Sub Main**. When it is not set, the list shows **Default: Sub Main** for an EXE and **Default: (none)** for a DLL. [What Start runs](Debugging/Running#what-start-runs) says what **Start** does then. For a Standard EXE the list does not offer **(none)**, and for a Package TWINPACK or a TypeLibrary TLB it offers nothing else. In the `Settings` file it is `project.startupObject`. ### Icon Form diff --git a/docs/IDE/Projects/Adding-Files.md b/docs/IDE/Projects/Adding-Files.md index 8d68bcd22..41f09dcee 100644 --- a/docs/IDE/Projects/Adding-Files.md +++ b/docs/IDE/Projects/Adding-Files.md @@ -121,7 +121,7 @@ The tree shows the project's folders and files but not its `Packages`, `Settings > [!WARNING] BETA 1034 > Importing a project's files replaces a file of the same path without a question, and closes the tab open on it first. Unsaved changes in that tab are lost, and so is the saved copy of the file, because the import also saves the project. Do not tick a file whose path is already in your project. -This is for taking files from another project. To open a whole VB6 project as a new twinBASIC project, use **File → Open Project...** on its `.vbp`, or the **Import from VBP...** and **Import from folder...** tiles of the [New Project](../Project/New) dialog. The [FAQ](../../../FAQ#vb6-import) describes the VB6 import. A package is added as a package in [Project Settings](References-and-Packages#available-packages), not here. +This is for taking files from another project. To open a whole VB6 project as a new twinBASIC project, use **File → Open Project...** on its `.vbp`, or the **Import from VBP...** card of the [New Project](../Starting/New-Project) dialog. [Importing a VB6 project](../Starting/Importing-VB6) describes the import. The **Import from folder...** card opens the folder of a twinBASIC project, such as an export, not a VB6 one. A package is added as a package in [Project Settings](References-and-Packages#available-packages), not here. ### The project is saved diff --git a/docs/IDE/Projects/Images/Title_Changed.light.png b/docs/IDE/Projects/Images/Title_Changed.light.png index 9a72d2afa..089a9ee44 100644 Binary files a/docs/IDE/Projects/Images/Title_Changed.light.png and b/docs/IDE/Projects/Images/Title_Changed.light.png differ diff --git a/docs/IDE/Projects/Images/Title_Changed.light.svg b/docs/IDE/Projects/Images/Title_Changed.light.svg index 9dcee819a..0ec39f2f1 100644 --- a/docs/IDE/Projects/Images/Title_Changed.light.svg +++ b/docs/IDE/Projects/Images/Title_Changed.light.svg @@ -1 +1 @@ -• project.twinproj - twinBASIC (twinBASIC IDE BETA 1033) +• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034) diff --git a/docs/IDE/Projects/Images/Title_Changed.png b/docs/IDE/Projects/Images/Title_Changed.png index ed7e8bcb2..4d40051c1 100644 Binary files a/docs/IDE/Projects/Images/Title_Changed.png and b/docs/IDE/Projects/Images/Title_Changed.png differ diff --git a/docs/IDE/Projects/Images/Title_Changed.svg b/docs/IDE/Projects/Images/Title_Changed.svg index be9d7d17f..4cc498a9c 100644 --- a/docs/IDE/Projects/Images/Title_Changed.svg +++ b/docs/IDE/Projects/Images/Title_Changed.svg @@ -1 +1 @@ -• project.twinproj - twinBASIC (twinBASIC IDE BETA 1033) +• project.twinproj - twinBASIC (twinBASIC IDE BETA 1034) diff --git a/docs/IDE/Projects/Saving-and-Exporting.md b/docs/IDE/Projects/Saving-and-Exporting.md index ae503501a..888cc0f37 100644 --- a/docs/IDE/Projects/Saving-and-Exporting.md +++ b/docs/IDE/Projects/Saving-and-Exporting.md @@ -8,9 +8,9 @@ index: - save a project - save as - close a project - - recent projects - export project - unsaved project +index_also: recent projects --- # Saving and exporting @@ -44,9 +44,7 @@ These make a project changed: The IDE gives the compiler 10 seconds to save the open documents. If it does not answer, a box says that the IDE timed out while trying to save them, and advises you to make backups of your documents by hand. The compiler then has 20 seconds to write the project file. If it does not answer, a box says that the IDE timed out while trying to save the project, and that the compiler restarts and you should try again. Choose **OK** to restart it. -If the compiler can write no file, an alert says **save project failed**, with a result code and the Windows error, such as `ERROR_ACCESS_DENIED` or `ERROR_DISK_FULL`, and asks you to check that the path is writable. The project stays open, changed, with its dot. Nothing is lost. - -The compiler can also ask the IDE for another path: a box, **Failed to save the project. Check the project file path and adjust if necessary.**, holds the project's path in a text box, and offers **Try Again**, which saves to the path in the box and keeps it as the project's path, and **Cancel**. +If the compiler can write no file, an alert says **save project failed**, with a result code and the Windows error, such as `ERROR_ACCESS_DENIED` or `ERROR_DISK_FULL`, and asks you to check that the path is writable. The alert is the only sign of the failure: the IDE writes nothing to the console. The project stays open, changed, with its dot, and the file is as it was. Nothing is lost. [Saving](../Troubleshooting/Saving#a-save-that-fails) lists the causes and what to do, and describes two failures that leave the IDE in an odd state. ### Other commands that save @@ -58,7 +56,7 @@ Some commands save the project besides **Save Project**: **Save Project As** sav ## Save Project As -**File → Save Project As...** opens the Windows **Save As** dialog, with the project's name proposed. The project's file becomes the one you choose, and the IDE saves the project there. The old file stays as it was. The new path goes to the top of the [recent list](#recent-projects), and the title bar shows the new name. The folder of the new file becomes `${SourcePath}`, for the settings that use it. The command has no key. +**File → Save Project As...** opens the Windows **Save As** dialog, with the project's name proposed. The project's file becomes the one you choose, and the IDE saves the project there. The old file stays as it was. The new path goes to the top of the [recent list](#recent-projects), and the title bar shows the new name. The folder of the new file becomes `${SourcePath}`, for the settings that use it. The command has no key. If the save fails, see [A failed Save As](../Troubleshooting/Saving#a-failed-save-as). ## A project with no file @@ -82,12 +80,12 @@ The state is kept in the registry, in a value for each project path, under `Proj - **Discard Changes** closes it and leaves the file on disk as it was. - **Cancel** keeps the project open. -**File → Exit** (Alt+F4), and the close button of the window, ask the same question. If the save fails, the alert described above appears and the project stays open. Closing ends the compiler process of the project. **Close Project** has no key. +**File → Exit** (Alt+F4), and the close button of the window, ask the same question. If the save fails, the alert described above appears and the project stays open; see [A failed Close](../Troubleshooting/Saving#a-failed-close). Closing ends the compiler process of the project. **Close Project** has no key. ## Open a project -- **File → New Project...** (Ctrl+N) and **File → Open Recent...** open the **New / Open Project** dialog, which has the sections **New**, **Samples** and **Recent**. **Open Recent...** starts on the **Recent** section. A **Recent (VB6)** section shows too when the machine has a VB6 history. [New Project](../Project/New) describes the dialog. -- **File → Open Project...** (Ctrl+O) opens the Windows **Open** dialog. A `.vbp` file is imported as a VB6 project; the result is unsaved. The import keeps the `Attribute` lines of the VB6 files as they are, and the [FAQ](../../../FAQ#vb6-import) says what becomes of each file and each line. A project opened with **Import from folder...** is unsaved as well. +- **File → New Project...** (Ctrl+N) and **File → Open Recent...** open the **New / Open Project** dialog, which has the sections **New**, **Samples** and **Recent**. **Open Recent...** starts on the **Recent** section. A **Recent (VB6)** section shows too when the machine has a VB6 history. [New Project](../Starting/New-Project) describes the dialog. +- **File → Open Project...** (Ctrl+O) opens the Windows **Open** dialog. A `.vbp` file is imported as a VB6 project; the result is unsaved. [Importing a VB6 project](../Starting/Importing-VB6) says what becomes of each file and setting, and the [FAQ](../../../FAQ#vb6-import) what becomes of each `Attribute` line. A project opened with **Import from folder...** is unsaved as well. The project that is open is closed before the new one loads, and the question above comes at that point. In the **New / Open Project** dialog, it comes when you press **Open**, not before. @@ -95,7 +93,7 @@ The project that is open is closed before the new one loads, and the question ab The **Recent** tab lists the full paths of the projects you opened last, the newest first. A double-click, or **Open**, opens one and moves it to the top. The **X** at the end of a row, titled **Remove from this list**, removes the entry at once, with no question. **Save Project As** adds the new path to the top. The list holds 21 entries and is kept in the registry with the IDE's settings. -A project whose file is gone shows the box **Failed to load the project (error code ERROR_FILE_NOT_FOUND)**, with the path and the question **Do you want to remove it from the list of recently opened projects?** The **New / Open Project** dialog has closed by then. The buttons are **Remove It** and **Keep It**. **Keep It** moves the entry to the top. Either way the IDE is left with no project open, because the previous one was closed first. A damaged file opens a native Windows message box instead. +A project whose file is gone shows the box **Failed to load the project (error code ERROR_FILE_NOT_FOUND)**, with the path and the question **Do you want to remove it from the list of recently opened projects?** The **New / Open Project** dialog has closed by then. The buttons are **Remove It** and **Keep It**. **Keep It** leaves the entry on the list, at the top. Either way the IDE is left with no project open, because the previous one was closed first. [Opening projects](../Starting/Opening-Projects#when-a-project-will-not-open) lists the error codes, and says what a damaged file shows instead. ![The box Failed to load the project (error code ERROR_FILE_NOT_FOUND), with the path C:\Projects\Inventory\Inventory.twinproj, the question Do you want to remove it from the list of recently opened projects?, and the buttons Remove It and Keep It.](Images/Failed_To_Load.png){:width="530" height="173"} @@ -149,7 +147,7 @@ The `Packages` folder holds the compiler packages as well. For a project with th A `.twinproj` file that the IDE saved does not hold the compiler packages, and the `export` command of the tB executable does not write them. A package the project embeds is different: one added from TWINSERV or from a TWINPACK file is embedded by default (see [Linked Packages](../../../Features/Packages/Linked)), is part of the saved `.twinproj`, and has a folder under `Packages` too. -**Delete the compiler packages' folders before packing the export back into a project.** The IDE's own way to pack it is **Import from folder...** in the [New Project](../Project/New#import-from-folder) dialog, which opens the project unsaved; the first save asks for a file name. The [import/export script](../../../Features/Packages/Import-Export-Tool#usage)'s `import` packs it as well. Both pack every folder under `Packages` into the project, so the compiler packages become a copy inside it. Rebuilt by **Import from folder** with them, the project was 4,222,833 bytes, against 4,207 bytes without them and 2,956 bytes for the project as the IDE saved it. Both rebuilt projects open with no errors, and the script's project is the same 4.2 MB. +**Delete the compiler packages' folders before packing the export back into a project.** The IDE's own way to pack it is **Import from folder...** in the [New Project](../Starting/New-Project#import-from-folder) dialog, which opens the project unsaved; the first save asks for a file name. The [import/export script](../../../Features/Packages/Import-Export-Tool#usage)'s `import` packs it as well. Both pack every folder under `Packages` into the project, so the compiler packages become a copy inside it. Rebuilt by **Import from folder** with them, the project was 4,222,833 bytes, against 4,207 bytes without them and 2,956 bytes for the project as the IDE saved it. Both rebuilt projects open with no errors, and the script's project is the same 4.2 MB. The compiler does not use that copy. A function added to the copy of **VBA**, in `Packages\VBA\Sources\Math.twin`, is an unrecognized symbol (TB5079) to the code that calls it. But every later export writes the copy back into `Packages`: the added function is there again after each save, and the Debug Console reports `[EXPORT] COMPLETED (139 folders, 954 files)`, where the original project's export reported `(72 folders, 479 files)`. So after an IDE update, the export still holds the package source of the IDE that first exported the project, while the project compiles against the packages of the IDE in use. @@ -168,6 +166,6 @@ The `export` command of the tB executable and of the import/export script delete - [Project Explorer](Project-Explorer) -- the tree of the project's files - [Files and folders](Files-and-Folders) -- the commands that change files, and **Export...** for a few files - [Project Settings](../Project-Settings#export) -- Export Path, Export After Save and Export Verbose -- [New Project](../Project/New) -- the dialog that opens or imports a project +- [Starting a project](../Starting/) -- the dialog that opens or imports a project, and every way of opening one - [File menu](../Reference/Menu/File) -- the commands of the menu - [Import/Export Tool](../../../Features/Packages/Import-Export-Tool) -- keeping a project in Git as plain-text files diff --git a/docs/IDE/Projects/index.md b/docs/IDE/Projects/index.md index a98371308..4d2df15b6 100644 --- a/docs/IDE/Projects/index.md +++ b/docs/IDE/Projects/index.md @@ -53,5 +53,5 @@ A project holds these top-level folders. The Project Explorer draws all but `Set - [Project Settings](../Project-Settings) -- the settings of the project, with a section for each - [Writing code](../Code/) -- the editor that opens the code files - [Designing forms](../Forms/) -- the designer that opens the form files -- [New Project](../Project/New) -- making a project from a template, a sample or a folder +- [Starting a project](../Starting/) -- making a project from a template, a sample or a folder, and opening one - [Package Management](../../../Features/Packages/) -- the language side of packages diff --git a/docs/IDE/Reference/Keyboard-Shortcuts.md b/docs/IDE/Reference/Keyboard-Shortcuts.md new file mode 100644 index 000000000..3e12a5eff --- /dev/null +++ b/docs/IDE/Reference/Keyboard-Shortcuts.md @@ -0,0 +1,252 @@ +--- +title: Keyboard Shortcuts +parent: IDE Reference +grand_parent: IDE +nav_order: 14 +permalink: /tB/IDE/Reference/Keyboard-Shortcuts +--- + +# Keyboard shortcuts + +The IDE's built-in group of keyboard shortcuts, **Default Built-in Keyboard Shortcuts**. [Keyboard shortcuts](../Customising/Keyboard-Shortcuts) explains how to change them. + +Each row names a command and gives its keys. The names are the IDE's own, and they are what a group of your own uses. The same key can be on several rows, because what a key does depends on what has the focus: Ctrl+V runs one command in the code editor and another in the form designer. A command that is not listed here has no key. + +A key marked *on release* runs when the key is released, not when it goes down. **Window → Keyboard Shortcuts → Manage Keyboard Shortcuts...** shows the same group as text, with the exact key strings, which also say which keys repeat while they are held down. [Shortcut strings](../Customising/Keyboard-Shortcuts#shortcut-strings) explains them. + +| Command | Keys | +|---|---| +| `tbBuild_Fusion` | Ctrl+Shift+B | +| `tbBuild_SwitchToWin32` | Ctrl+F2 | +| `tbBuild_SwitchToWin64` | Ctrl+F1 | +| `tbCallStack_ShowPanel` | Ctrl+L | +| `tbDebug_BreakInto` | Ctrl+Break | +| `tbDebug_BreakpointsClear` | Ctrl+Shift+F9 | +| `tbDebug_RunOrPreview` | F6 | +| `tbDebug_SetNextExecutionStatement` | Ctrl+F9 | +| `tbDebug_StartOrContinue` | F5 | +| `tbDebug_StepInto` | F8, F11 | +| `tbDebug_StepOut` | Ctrl+Shift+F8, Shift+F11 | +| `tbDebug_StepOver` | Shift+F8, F10 | +| `tbDebug_ToggleBreakpoint` | F9 | +| `tbDebugConsole_ExecuteEnteredLine` | Enter | +| `tbDebugConsole_FocusPanel` | Ctrl+G | +| `tbDebugConsole_GoToDefinition` | Shift+F2, F12 | +| `tbDebugConsole_HistoryCycleNext` | Down | +| `tbDebugConsole_HistoryCyclePrevious` | Up | +| `tbDebugConsole_SelectAllEntryBox` | Ctrl+A | +| `tbDebugConsole_ShowPanel` | Ctrl+G | +| `tbEditor_ActivateNextTab` | Ctrl+Tab | +| `tbEditor_ActivatePrevTab` | Ctrl+Shift+Tab | +| `tbEditor_AddCursorAbove` | Ctrl+Alt+Up | +| `tbEditor_AddCursorBelow` | Ctrl+Alt+Down | +| `tbEditor_AddCursorsAtLineEndsOfSelection` | Shift+Alt+I | +| `tbEditor_AddSelectionToNextFindMatch` | Ctrl+D | +| `tbEditor_ClipboardCopy` | Ctrl+C, Ctrl+Insert | +| `tbEditor_ClipboardCut` | Ctrl+X, Shift+Delete | +| `tbEditor_ClipboardCutPlain` | Ctrl+Shift+X | +| `tbEditor_ClipboardPaste` | Ctrl+V | +| `tbEditor_ClipboardPasteAsComment` | Ctrl+Alt+V | +| `tbEditor_ClipboardPlainCopy` | Ctrl+Shift+C | +| `tbEditor_ClipboardPlainPaste` | Ctrl+Shift+V | +| `tbEditor_CloseTab` | Ctrl+F4, Ctrl+W | +| `tbEditor_CodeSelector1_Dropdown` | Ctrl+1 | +| `tbEditor_CodeSelector2_Dropdown` | Ctrl+2 | +| `tbEditor_CodeSelector3_Dropdown` | Ctrl+3 | +| `tbEditor_CommentSelection` | Ctrl+K | +| `tbEditor_CopyLineDown` | Shift+Alt+Down | +| `tbEditor_CopyLineUp` | Shift+Alt+Up | +| `tbEditor_CursorMoveBottom` | Ctrl+End | +| `tbEditor_CursorMoveDown` | Down | +| `tbEditor_CursorMoveEndOfLine` | End | +| `tbEditor_CursorMoveLeft` | Left | +| `tbEditor_CursorMoveLeftWord` | Ctrl+Left | +| `tbEditor_CursorMovePageDown` | Page Down | +| `tbEditor_CursorMovePageUp` | Page Up | +| `tbEditor_CursorMoveRight` | Right | +| `tbEditor_CursorMoveRightWord` | Ctrl+Right | +| `tbEditor_CursorMoveStartOfLineHome` | Home | +| `tbEditor_CursorMoveTop` | Ctrl+Home | +| `tbEditor_CursorMoveUp` | Up | +| `tbEditor_CursorSelectBottom` | Ctrl+Shift+End | +| `tbEditor_CursorSelectDown` | Shift+Down | +| `tbEditor_CursorSelectLeft` | Shift+Left | +| `tbEditor_CursorSelectLeftWord` | Ctrl+Shift+Left | +| `tbEditor_CursorSelectLineEnd` | Shift+End | +| `tbEditor_CursorSelectLineStartHome` | Shift+Home | +| `tbEditor_CursorSelectPageDown` | Shift+Page Down | +| `tbEditor_CursorSelectPageUp` | Shift+Page Up | +| `tbEditor_CursorSelectRight` | Shift+Right | +| `tbEditor_CursorSelectRightWord` | Ctrl+Shift+Right | +| `tbEditor_CursorSelectTop` | Ctrl+Shift+Home | +| `tbEditor_CursorSelectUp` | Shift+Up | +| `tbEditor_CursorUndo` | Ctrl+U | +| `tbEditor_DecreaseFontSize` | Ctrl+- | +| `tbEditor_DeleteLeft` | Backspace, Shift+Backspace | +| `tbEditor_DeleteLeftWord` | Ctrl+Backspace | +| `tbEditor_DeleteLines` | Ctrl+Shift+Delete | +| `tbEditor_DeleteRight` | Delete | +| `tbEditor_DeleteRightWord` | Ctrl+Delete | +| `tbEditor_ExpandLineSelection` | Ctrl+L | +| `tbEditor_ExpandSelection` | Shift+Alt+Right | +| `tbEditor_Find` | Ctrl+F | +| `tbEditor_FindReplace` | Ctrl+H | +| `tbEditor_FindSelectedNext` | Ctrl+F3 | +| `tbEditor_FindSelectedPrev` | Ctrl+Shift+F3 | +| `tbEditor_FindWidget_Next` | Alt+F3 | +| `tbEditor_FindWidget_Prev` | Shift+Alt+F3 | +| `tbEditor_FindWidget_SelectAllMatches` | Alt+A | +| `tbEditor_Fold` | Ctrl+{ | +| `tbEditor_FoldProcedures` | Ctrl+Alt+Left | +| `tbEditor_GoToDefinition` | Shift+F2, F12 | +| `tbEditor_IncreaseFontSize` | Ctrl++ | +| `tbEditor_Indent` | Tab | +| `tbEditor_IndentLine` | Ctrl+] | +| `tbEditor_InsertAmpersand` | & | +| `tbEditor_InsertBackslash` | \ | +| `tbEditor_InsertColon` | : | +| `tbEditor_InsertComma` | , | +| `tbEditor_InsertEquals` | = | +| `tbEditor_InsertExclamation` | ! | +| `tbEditor_InsertLineAbove` | Ctrl+Shift+Enter | +| `tbEditor_InsertLineBelow` | Ctrl+Enter | +| `tbEditor_InsertLineBreak` | Enter | +| `tbEditor_InsertMinus` | - | +| `tbEditor_InsertMultiply` | * | +| `tbEditor_InsertPeriod` | . | +| `tbEditor_InsertPlus` | + | +| `tbEditor_InsertSlash` | / | +| `tbEditor_InsertSpace` | Space, Shift+Space | +| `tbEditor_JumpToNextProblem` | Alt+F8 | +| `tbEditor_JumpToPrevProblem` | Shift+Alt+F8 | +| `tbEditor_LastPosition` | Ctrl+Shift+F2 | +| `tbEditor_MoveLinesDown` | Alt+Down | +| `tbEditor_MoveLinesUp` | Alt+Up | +| `tbEditor_MoveToProcedureNext` | Ctrl+Down | +| `tbEditor_MoveToProcedurePrev` | Ctrl+Up | +| `tbEditor_Outdent` | Shift+Tab | +| `tbEditor_OutdentLine` | Ctrl+[ | +| `tbEditor_PrevBookmark` | Ctrl+M | +| `tbEditor_Redo` | Ctrl+Y | +| `tbEditor_RenameSymbol` | F2 | +| `tbEditor_ReopenLastClosedTab` | Ctrl+Shift+T | +| `tbEditor_SelectAll` | Ctrl+A | +| `tbEditor_SelectAllMatches` | Ctrl+Shift+L | +| `tbEditor_ShowContextMenu` | Shift+F10 | +| `tbEditor_ShrinkSelection` | Shift+Alt+Left | +| `tbEditor_SwitchToFormDesign` | Shift+F7 | +| `tbEditor_SwitchToggleFormDesignCode` | none | +| `tbEditor_ToggleBlockComment` | Shift+Alt+A | +| `tbEditor_ToggleBookmark` | Ctrl+B | +| `tbEditor_ToggleLineComment` | Ctrl+/ | +| `tbEditor_ToggleWordWrap` | Alt+Z | +| `tbEditor_TurnShowCodeHintsOff` | Ctrl, on release | +| `tbEditor_TurnShowCodeHintsOn` | Ctrl | +| `tbEditor_UncommentSelection` | Ctrl+Shift+K | +| `tbEditor_Undo` | Ctrl+Z | +| `tbEditor_Unfold` | Ctrl+} | +| `tbEditor_UnfoldProcedures` | Ctrl+Alt+Right | +| `tbFindReplace_Cancel` | Esc | +| `tbFindReplace_FocusCurrentModule` | Alt+M | +| `tbFindReplace_FocusCurrentProcedure` | Alt+P | +| `tbFindReplace_FocusCurrentProject` | Alt+C | +| `tbFindReplace_FocusDirection` | Alt+D | +| `tbFindReplace_FocusFindWhat` | Alt+F | +| `tbFindReplace_FocusMatchCaseToggle` | Alt+S | +| `tbFindReplace_FocusNext` | Alt+N | +| `tbFindReplace_FocusReplaceWith` | Alt+W | +| `tbFindReplace_FocusUsePatternMatchingToggle` | Alt+U | +| `tbFindReplace_FocusWholeWordOnlyToggle` | Alt+O | +| `tbFindReplace_Next` | F3 | +| `tbFindReplace_Prev` | Shift+F3 | +| `tbFindReplace_ReplaceAll` | Alt+A | +| `tbFindReplace_ReplaceNext` | Alt+R | +| `tbFindWidget_FindNext` | Enter | +| `tbFindWidget_FindNextInSelection` | Ctrl+F3 | +| `tbFindWidget_FindPrevInSelection` | Ctrl+Shift+F3 | +| `tbFindWidget_FindPrevious` | Shift+Enter | +| `tbFindWidget_Hide` | Esc | +| `tbFindWidget_HistoryCycleNext` | Down | +| `tbFindWidget_HistoryCyclePrevious` | Up | +| `tbFindWidget_ShowFind` | Alt+F | +| `tbFindWidget_ShowReplace` | Alt+H | +| `tbFormDesigner_AlignSelectedControlsBottom` | Alt+Down | +| `tbFormDesigner_AlignSelectedControlsLeft` | Alt+Left | +| `tbFormDesigner_AlignSelectedControlsRight` | Alt+Right | +| `tbFormDesigner_AlignSelectedControlsTop` | Alt+Up | +| `tbFormDesigner_ClipboardCopy` | Ctrl+C, Ctrl+Insert | +| `tbFormDesigner_ClipboardCut` | Ctrl+X, Shift+Delete | +| `tbFormDesigner_ClipboardPaste` | Ctrl+V | +| `tbFormDesigner_DeleteSelectedControls` | Delete | +| `tbFormDesigner_MenuEditorDelete` | Delete | +| `tbFormDesigner_MenuEditorMoveDown` | Ctrl+Down | +| `tbFormDesigner_MenuEditorMoveLeft` | Ctrl+Left | +| `tbFormDesigner_MenuEditorMoveRight` | Ctrl+Right | +| `tbFormDesigner_MenuEditorMoveUp` | Ctrl+Up | +| `tbFormDesigner_MenuEditorRename` | F2 | +| `tbFormDesigner_MenuEditorToggleChecked` | Space | +| `tbFormDesigner_MoveSelectedControlsDown` | Down | +| `tbFormDesigner_MoveSelectedControlsLeft` | Left | +| `tbFormDesigner_MoveSelectedControlsRight` | Right | +| `tbFormDesigner_MoveSelectedControlsUp` | Up | +| `tbFormDesigner_Redo` | Ctrl+Y | +| `tbFormDesigner_ResizeSelectedControlsNarrower` | Shift+Left | +| `tbFormDesigner_ResizeSelectedControlsNarrowest` | Ctrl+Shift+Left | +| `tbFormDesigner_ResizeSelectedControlsShorter` | Shift+Up | +| `tbFormDesigner_ResizeSelectedControlsShortest` | Ctrl+Shift+Up | +| `tbFormDesigner_ResizeSelectedControlsTaller` | Shift+Down | +| `tbFormDesigner_ResizeSelectedControlsTallest` | Ctrl+Shift+Down | +| `tbFormDesigner_ResizeSelectedControlsWider` | Shift+Right | +| `tbFormDesigner_ResizeSelectedControlsWidest` | Ctrl+Shift+Right | +| `tbFormDesigner_SelectAllControls` | Ctrl+A | +| `tbFormDesigner_SelectParentOfSelectedControl` | Esc | +| `tbFormDesigner_SelectToolboxPointer` | Esc | +| `tbFormDesigner_TurnShowTabIndexOff` | Ctrl, on release | +| `tbFormDesigner_TurnShowTabIndexOn` | Ctrl | +| `tbFormDesigner_Undo` | Ctrl+Z | +| `tbHelp_ToggleExpandSignatureHelp` | F1 | +| `tbIde_DecreaseFontSize` | Ctrl+Alt+- | +| `tbIde_IncreaseFontSize` | Ctrl+Alt++ | +| `tbIdeWindow_Close` | Alt+F4 | +| `tbIntellisense_AcceptSelectedEntry` | Tab; Space, Enter, ,, ., +, -, &, (, ), !, *, \, /, :, =, on release | +| `tbIntellisense_AcceptSelectedOrFirstEntry` | Tab, Space | +| `tbIntellisense_Cancel` | Esc, Left, Right, Backspace | +| `tbIntellisense_SelectNext` | Down | +| `tbIntellisense_SelectPrevious` | Up | +| `tbIntellisense_ShowAfterKeyPress` | every key its `{ALLEXCEPT}` list does not name, on release | +| `tbIntellisense_ShowNow` | Ctrl+Space | +| `tbMenu_Cancel` | Esc | +| `tbMenu_ExecuteFocusedEntry` | Enter | +| `tbMenu_FocusAdjacentDown` | Down | +| `tbMenu_FocusAdjacentLeft` | Left | +| `tbMenu_FocusAdjacentRight` | Right | +| `tbMenu_FocusAdjacentUp` | Up | +| `tbMisc_AltKeyStateDown` | Alt | +| `tbMisc_AltKeyStateUp` | Alt, on release | +| `tbMisc_PreventDefaultKeyBehaviour` | Ctrl+A, Tab | +| `tbMisc_SaveProjectMetaStateInTwinproj` | Ctrl+Shift+Alt+M | +| `tbMisc_ShiftKeyStateDown` | Shift | +| `tbMisc_ShiftKeyStateUp` | Shift, on release | +| `tbPanels_SetActiveLayoutDefault` | Ctrl+# | +| `tbProject_Export` | Ctrl+E | +| `tbProject_FindInFiles` | Ctrl+Shift+F | +| `tbProject_New` | Ctrl+N | +| `tbProject_Open` | Ctrl+O | +| `tbProject_SaveAllChanges` | Ctrl+S | +| `tbProject_ShowReferences` | Ctrl+T | +| `tbProjectExplorer_ClipboardPaste` | Ctrl+V | +| `tbProjectExplorer_SelectedItemsClipboardCopy` | Ctrl+C, Ctrl+Insert | +| `tbProjectExplorer_SelectedItemsClipboardCut` | Ctrl+X, Shift+Delete | +| `tbProjectExplorer_SelectedItemsDeletePermanently` | Delete | +| `tbProjectExplorer_SelectedItemsRename` | F2 | +| `tbProjectExplorer_ToggleFileMode` | Ctrl+R | +| `tbShowObjectBrowser` | F2, on release | +| `tbSignatureHelpCancel` | Esc, Up, Down | +| `tbToolbox_SelectNextTool` | Down, Right | +| `tbToolbox_SelectPrevTool` | Up, Left | +| `tbWatches_Add` | Shift+F9 | + +## See Also + +- [Keyboard shortcuts](../Customising/Keyboard-Shortcuts) -- switching groups on and off, and writing your own +- [Window menu](Menu/Window#keyboard-shortcuts) -- the **Keyboard Shortcuts** entry diff --git a/docs/IDE/Reference/index.md b/docs/IDE/Reference/index.md index 063d2acbb..560a35f65 100644 --- a/docs/IDE/Reference/index.md +++ b/docs/IDE/Reference/index.md @@ -8,8 +8,10 @@ has_toc: false # IDE Reference -The parts of the IDE's window that are described one control at a time: the menus, the toolbar and the status bar. +The parts of the IDE that are described one control at a time: the menus, the toolbar, the status bar, the keyboard shortcuts and the Webpage pane. - [**Menu bar**](Reference/Menu) --- the eleven menus along the top of the window, with every command and its shortcut. - [**Toolbar**](Reference/Toolbar) --- the buttons under the menu bar, and the build configuration box. - [**Status Bar**](Reference/StatusBar) --- the strip along the bottom of the window, which shows the state of the compiler services and the licence. +- [**Keyboard Shortcuts**](Reference/Keyboard-Shortcuts) --- every command that has a key in the built-in group. +- [**Webpage**](Reference/Webpage) --- the pane that shows a web page inside the IDE. diff --git a/docs/IDE/Splash Screen.md b/docs/IDE/Splash Screen.md deleted file mode 100644 index 5b924454f..000000000 --- a/docs/IDE/Splash Screen.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Splash Screen -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/Splash ---- - -# Splash Screen - -![The twinBASIC splash window, its title bar reading Loading, please wait. The twinBASIC logo sits above a banner thanking the VIP Gold Supporters and a grid of six tiles: four sponsor logos and two placeholders inviting the reader to become a VIP Gold supporter. A twinBASIC Ltd copyright line closes the window.](Images/Splash_Screen.png){:width="470" height="377"} - -The Splash Screen appears when the IDE starts with no project to open, as it does from its icon. Its title bar reads **Loading, please wait...** for as long as it is on screen. It closes by itself after about two and a half seconds, and the **New / Open Project** dialog takes its place. An IDE started on a project file opens the project and shows no splash. - -Below the twinBASIC logo, a banner thanks the VIP Gold Supporters and a grid of tiles carries their logos. A tile with no sponsor reads **See your logo here!** and invites the reader to become one. A twinBASIC Ltd copyright line closes the window. - -> [!NOTE] -> The splash screen shows no version number and no build date. Those are on the **About twinBASIC** dialog --- see [Help](../Reference/Menu/Help), which also repeats the VIP Gold Supporters grid. diff --git a/docs/IDE/Starting/First-Launch.md b/docs/IDE/Starting/First-Launch.md new file mode 100644 index 000000000..ad48c2f05 --- /dev/null +++ b/docs/IDE/Starting/First-Launch.md @@ -0,0 +1,139 @@ +--- +title: First launch +parent: Starting a project +grand_parent: IDE +nav_order: 1 +permalink: /tB/IDE/Starting/First-Launch +redirect_from: + - /tB/IDE/Project/Splash +index: + - splash screen + - licence agreement + - licence key + - error reporting + - edition + - enter licence key + - purchase a licence +--- + +# First launch + +The IDE asks up to three questions before it shows anything else, each only until you have answered it once. After them it shows a splash and the **New / Open Project** dialog. This page describes them in order, then the empty IDE, the licence and the edition, and says what the IDE does not ask for. + +## What the first start shows + +While the IDE reads its saved settings, it shows a banner picture. On a first start, these appear in front of it, in this order: + +1. The [licence agreement](#the-licence-agreement), until you have accepted it once. +2. The [error reporting question](#the-error-reporting-question), until you have answered it once. +3. The [**IDE Options** dialog](#the-first-run-ide-options), on the very first start only, when the IDE has no saved options. +4. The [splash](#the-splash), then the [**New / Open Project**](New-Project) dialog. This pair is all that every later start shows, when no project is named on the command line. + +The IDE shows nothing else: no sign-in, no tip of the day and no list of what is new. + +## The licence agreement + +The dialog is titled **twinBASIC Licence Agreement**. It holds the agreement between you and TWINBASIC LTD as a scrolling text in English. It has two buttons, **I AGREE** and **QUIT**. **I AGREE** records the date of your agreement and goes on. **QUIT**, Esc, which is bound to **QUIT**, and closing the IDE's window all end the IDE. The dialog has no close box of its own. + +![The licence agreement dialog in front of the twinBASIC banner, the start of the agreement in a scrolling box, and the two buttons under it.](Images/Licence_Agreement.png){:width="617" height="683"} + +**Help → Licence Agreement...** shows the same text at any time, with one **Close** button, and under the text the line the IDE stored when you agreed: `AGREED_` and the date. A build whose agreement text changes asks the question again. + +## The error reporting question + +The dialog is titled **twinBASIC Automatic Error Reporting**. It asks whether the IDE may send anonymous error reports to twinBASIC Ltd, and says that no source code or personal data is included. Its buttons are **YES** and **NO**. Esc is **NO**, and the dialog has no close box. + +![The error reporting question in front of the twinBASIC banner, with its two buttons.](Images/Error_Reporting.png){:width="578" height="263"} + +The question turns reporting off the moment it opens. **YES** turns it on. **NO**, Esc and closing the IDE while the question is open all leave it off. **Help → Automatic IDE Error Reporting...** asks the same question again, so opening it switches reporting off until you press **YES**, whatever the setting was before. + +When reporting is on, the IDE sends a report each time its own code fails with an error. A report is one HTTPS request to `https://twinbasic.com/notify`. It holds the IDE's version, the text of the licence box in the status bar, the time, the text of the IDE's error, and, if a program is paused in the debugger at that moment, the name of the top procedure on the call stack. Compile errors are not reported. + +## The first-run IDE Options + +On the very first start the **IDE Options** dialog opens by itself, over the banner. It has a **Save Changes** button and no **Close** button. Two parts of it cannot work yet, because no compiler runs: its **Change Licence...** button is hidden, and the font lists are empty, with a grey line, ``, in their place. Choose the options you want, or none, and press **Save Changes**; the start continues. + +![The IDE Options dialog in front of the twinBASIC banner, with the grey line in place of each font list and one button at its bottom right.](Images/First_Run_Options.png){:width="1082" height="880"} + +Esc and the dialog's close box also end it, and the IDE goes on with the default options. The IDE has already saved the defaults when the dialog opens, so the next start is not a first start. [IDE Options](../Customising/IDE-Options) describes every option. + +## The splash + +Every start with no project to open shows the splash. Its title bar reads **Loading, please wait...**, and it closes by itself after two and a half seconds. The **New / Open Project** dialog takes its place. + +![The twinBASIC splash window: the twinBASIC logo above a banner thanking the VIP Gold Supporters and a grid of six boxes, four with sponsor logos and two placeholders.](../Images/Splash_Screen.png){:width="470" height="377"} + +Below the logo, a banner thanks the VIP Gold Supporters. A grid of six boxes follows: four hold the logos of supporters, and two read **See your logo here!** and invite you to become one. A twinBASIC Ltd copyright line closes the window. The splash shows no version number and no build date. They are in **Help → About twinBASIC...**, which repeats the logos. + +A logo that has a web address opens it in your browser when you click it. A logo without one shows the notification *Sorry, this VIP Gold supporter has not yet provided a website URL*. The logos answer while the splash is up. The splash is modal, so nothing else in the IDE answers meanwhile. + +No option, setting or command-line switch turns the splash off or shortens it. The IDE skips it only when it starts on a project: a double-click on a `.twinproj` file, or a project path on the command line. See [Opening projects](Opening-Projects#the-command-line). It shows the splash after a command line it cannot read, too; see the same section. + +## The empty IDE + +With no project loaded, every panel is empty, and no compiler is connected. + +![The IDE with no project open, in its default layout, with every panel empty.](../Images/IDE.png){:width="1920" height="1032"} + +The status bar at the bottom of the window says so. + +![The status bar with no project open: a red tB Services: UNAVAILABLE badge and a red tB Licence: NOT READY badge at the left, then the links to Ko-fi, Discord, Twitter and GitHub.](../Images/StatusBar.png){:width="661" height="27"} + +The services badge reads **tB Services: UNAVAILABLE**, and the licence badge reads **tB Licence: NOT READY**. Both change when a project has loaded and the compiler has answered. [Status Bar](../Reference/StatusBar) describes the badges and their other states. If they do not change once a project is open, see [Compiler services](../Troubleshooting/Compiler-Services#the-services-badge). + +Without a project, most of the **File** menu is greyed out. **New Project...**, **Open Project...**, **Open Recent...** and **Exit** work, and so do the licence commands of the **Help** menu. + +![The File menu with no project loaded: New Project, Open Project, Open Recent and Exit are available, and the other items are greyed out.](../Menu/Images/Menu_File.png){:width="190" height="337"} + +## The licence and the edition + +The licence box is the second badge of the status bar. When a project has loaded and the compiler has answered, the box names the edition: + +| The box reads | Meaning | +|---|---| +| **PERSONAL EDITION** | A Personal licence. | +| **PROFESSIONAL EDITION** | A Professional licence. | +| **ULTIMATE EDITION** | An Ultimate licence. | +| **COMMUNITY EDITION** | Any other answer, which includes no key and an invalid key. The badge is a warning, not a tick. | + +If the licence does not come out as you expect, see [The licence](../Troubleshooting/Missing-Files#the-licence). The box's tooltip repeats its text. Clicking the box opens [**Enter Licence Key**](#enter-licence-key), but only once a project is loaded. The edition belongs to the compiler's answer, not to the IDE, so it stays **tB Licence: NOT READY** until a project loads. + +When the compiler reports that a licence has expired or has been revoked, a box titled **Licence Problem!** says so, and gives the address `sales@twinbasic.com` to write to. The licence box keeps its earlier text. + +A Personal licence does not build from the command line. With `--buildAndExit32` or `--buildAndExit64`, the IDE shows the box *Sorry, twinBASIC Personal Edition does not support command line builds. Upgrade to Professional Edition to access this feature.* + +### Enter Licence Key + +The dialog is titled *twinBASIC*, and the line above its text box reads **Enter Licence Key:**. It has three ways in: + +- **Help → Enter Licence Key...**, which works with no project; +- the **Change Licence...** button of [IDE Options](../Customising/IDE-Options), which shows only while a project is loaded; +- a click on the licence box, while a project is loaded. + +The dialog advises you to copy and paste the key from your confirmation email, and warns that an invalid key applies the COMMUNITY licence. Its buttons are **Purchase a licence**, **OK** and **Cancel**. Enter in the text box does what **OK** does. + +![The Enter Licence Key dialog with its text box empty, its advice under the box, and its three buttons.](Images/Licence_Key.png){:width="500" height="214"} + +A key has 32 characters once the IDE has trimmed it. What happens next depends on whether a project is loaded: + +- **With no project**, a key of another length gives the box *Licence key invalid.* and changes nothing. A key of 32 characters is held. One second later a box says *Thanks. The licence key will verified and applied when you load a project.*, and the IDE sends the key to the compiler when the first project has loaded. +- **With a project**, the IDE sends the key to the compiler at once. A key of another length is replaced by 32 zeros, which is the Community key. + +The IDE then shows the compiler's answer in a box: *Licence key applied successfully. Thanks for your support.* or *Invalid licence key. Please try again.* It shows the box only when the answer differs from the key the IDE has saved. The IDE saves the key as plain text in the registry value `LicenceKey`; see [where the IDE keeps its settings](../Customising/#where-the-ide-keeps-its-settings). + +### Purchase A Licence + +**Help → Purchase A Licence...** and the **Purchase a licence** button of the Enter Licence Key dialog open the same address, `https://twinbasic.com/preorder.html`. + +## No account + +The IDE asks for no account and no sign-in at any start. The only sign-in in the IDE is the one for publishing a package, which is described under [Package Publishing](../Building/Package-Publishing). + +## See Also + +- [New Project](New-Project) -- the dialog that follows the splash +- [Opening projects](Opening-Projects) -- starting the IDE on a project, and the file association +- [Customising the IDE](../Customising/) -- the IDE Options, and where the IDE keeps its settings +- [Help menu](../Reference/Menu/Help) -- the licence and error reporting commands +- [Status Bar](../Reference/StatusBar) -- the services and licence badges +- [When something goes wrong](../Troubleshooting/) -- a box at start-up about missing files, the WebView2 runtime, the licence diff --git a/docs/IDE/Starting/Images/Damaged_Box.png b/docs/IDE/Starting/Images/Damaged_Box.png new file mode 100644 index 000000000..4992f4e25 Binary files /dev/null and b/docs/IDE/Starting/Images/Damaged_Box.png differ diff --git a/docs/IDE/Starting/Images/Error_Reporting.light.png b/docs/IDE/Starting/Images/Error_Reporting.light.png new file mode 100644 index 000000000..67de62890 Binary files /dev/null and b/docs/IDE/Starting/Images/Error_Reporting.light.png differ diff --git a/docs/IDE/Starting/Images/Error_Reporting.light.svg b/docs/IDE/Starting/Images/Error_Reporting.light.svg new file mode 100644 index 000000000..e219dd35d --- /dev/null +++ b/docs/IDE/Starting/Images/Error_Reporting.light.svg @@ -0,0 +1 @@ +twinBASIC Automatic Error ReportingYESNOAllow the twinBASIC IDE to automatically send anonymouserror reports to twinBASIC Ltd, to help identify and fix bugs?No source code or personal data is included. If you change your mind,use the 'Help' > 'Automatic Error Reporting' menu option. diff --git a/docs/IDE/Starting/Images/Error_Reporting.png b/docs/IDE/Starting/Images/Error_Reporting.png new file mode 100644 index 000000000..af4e2993a Binary files /dev/null and b/docs/IDE/Starting/Images/Error_Reporting.png differ diff --git a/docs/IDE/Starting/Images/Error_Reporting.svg b/docs/IDE/Starting/Images/Error_Reporting.svg new file mode 100644 index 000000000..af80ea87a --- /dev/null +++ b/docs/IDE/Starting/Images/Error_Reporting.svg @@ -0,0 +1 @@ +twinBASIC Automatic Error ReportingYESNOAllow the twinBASIC IDE to automatically send anonymouserror reports to twinBASIC Ltd, to help identify and fix bugs?No source code or personal data is included. If you change your mind,use the 'Help' > 'Automatic Error Reporting' menu option. diff --git a/docs/IDE/Starting/Images/First_Run_Options.light.png b/docs/IDE/Starting/Images/First_Run_Options.light.png new file mode 100644 index 000000000..407dd1e50 Binary files /dev/null and b/docs/IDE/Starting/Images/First_Run_Options.light.png differ diff --git a/docs/IDE/Starting/Images/First_Run_Options.light.svg b/docs/IDE/Starting/Images/First_Run_Options.light.svg new file mode 100644 index 000000000..88069b4ab --- /dev/null +++ b/docs/IDE/Starting/Images/First_Run_Options.light.svg @@ -0,0 +1 @@ +twinBASIC IDE OptionsSave ChangesAllAppearanceCode EditorCompilerProject ExplorerIntellisense/HoverDesigner/ToolboxGeneralAppearanceColour ThemeIDE Font SizeIDE Font<font list unavailable: load a project first>Show real OS titlebarSingle row of code panel tabsCode EditorTAB Spaces AmountCode Editor Font SizeCode Editor Font<font list unavailable: load a project first>Use Font LigaturesShow Code Editor Combo SelectorsRender WhitespaceLight13pxNoYesNoYes413pxNoYesabovenone(Filter)3251032534The general IDE font size. NOTE: this is *not* the font size used for the code editor.The general IDE font name. NOTE: this is *not* the font name used for the code editor.By default, the IDE uses a simulated titlebar so that it can collapse the Menu bar intothe titlebar area of the IDE, giving more usable vertical space in the IDE. Set to YES touse a real OS titlebar instead.Controls whether IDE code pane tabs display on a single line with scrolling, or splitover multiple lines.The number of space characters that each TAB key inserts.The code editor font size.The code editor font name. A monospace font is usually preferred, such as CourierNew.With font ligatures ON, operators such as >= might render as ≥, dependant on fontsupport. Fonts such as Cascadia Code support this feature.The combo selectors give you drop down lists of components and procedures,allowing you to quickly jump to them. Usually shown ABOVE the code editor.By default whitespace is not rendered in the code editor. Change this option to causewhitespace to be rendered as faint dots in the code editor. diff --git a/docs/IDE/Starting/Images/First_Run_Options.png b/docs/IDE/Starting/Images/First_Run_Options.png new file mode 100644 index 000000000..720ee3f23 Binary files /dev/null and b/docs/IDE/Starting/Images/First_Run_Options.png differ diff --git a/docs/IDE/Starting/Images/First_Run_Options.svg b/docs/IDE/Starting/Images/First_Run_Options.svg new file mode 100644 index 000000000..56d430d79 --- /dev/null +++ b/docs/IDE/Starting/Images/First_Run_Options.svg @@ -0,0 +1 @@ +twinBASIC IDE OptionsSave ChangesAllAppearanceCode EditorCompilerProject ExplorerIntellisense/HoverDesigner/ToolboxGeneralAppearanceColour ThemeIDE Font SizeIDE Font<font list unavailable: load a project first>Show real OS titlebarSingle row of code panel tabsCode EditorTAB Spaces AmountCode Editor Font SizeCode Editor Font<font list unavailable: load a project first>Use Font LigaturesShow Code Editor Combo SelectorsRender WhitespaceDark13pxNoYesNoYes413pxNoYesabovenone(Filter)3251032534The general IDE font size. NOTE: this is *not* the font size used for the code editor.The general IDE font name. NOTE: this is *not* the font name used for the code editor.By default, the IDE uses a simulated titlebar so that it can collapse the Menu bar intothe titlebar area of the IDE, giving more usable vertical space in the IDE. Set to YES touse a real OS titlebar instead.Controls whether IDE code pane tabs display on a single line with scrolling, or splitover multiple lines.The number of space characters that each TAB key inserts.The code editor font size.The code editor font name. A monospace font is usually preferred, such as CourierNew.With font ligatures ON, operators such as >= might render as ≥, dependant on fontsupport. Fonts such as Cascadia Code support this feature.The combo selectors give you drop down lists of components and procedures,allowing you to quickly jump to them. Usually shown ABOVE the code editor.By default whitespace is not rendered in the code editor. Change this option to causewhitespace to be rendered as faint dots in the code editor. diff --git a/docs/IDE/Starting/Images/Import_Log.light.png b/docs/IDE/Starting/Images/Import_Log.light.png new file mode 100644 index 000000000..084ea5456 Binary files /dev/null and b/docs/IDE/Starting/Images/Import_Log.light.png differ diff --git a/docs/IDE/Starting/Images/Import_Log.light.svg b/docs/IDE/Starting/Images/Import_Log.light.svg new file mode 100644 index 000000000..63ec606c2 --- /dev/null +++ b/docs/IDE/Starting/Images/Import_Log.light.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• Unsaved.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXEDITOR 1MD: '__IMPORT_LOG.md':Import VBP fileStarted: 02-11-26 09:30:00File: C:\tbshots\starting-new\Ledger\Ledger.vbpActions takenVBP line #1   set project.buildType to 'Standard EXE'VBP line #2   added project.reference 'OLE Automation'VBP line #4   imported module 'Module1.bas'VBP line #5   imported class 'Class1.cls'VBP line #6   set project.startupObject to 'Form1'VBP line #7   set project.iconForm to 'Form1'VBP line #8   set project.buildPath to '${SourcePath}\Ledger.exe'VBP line #9   set project.name to 'Ledger'VBP line #3   imported form 'Form1.frm'IMPORT FINISHED WITH 0 ERRORS/WARNINGSPlease check the DIAGNOSTICS PANEL for any potential compilation errors. You can delete this log file.DEBUG CONSOLE>DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034 LAAMAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERLedgerImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSources__IMPORT_LOG.mdClass1.clsForm1.frm.tbformForm1.frm.twinModule1.basPROPERTIES diff --git a/docs/IDE/Starting/Images/Import_Log.png b/docs/IDE/Starting/Images/Import_Log.png new file mode 100644 index 000000000..681754894 Binary files /dev/null and b/docs/IDE/Starting/Images/Import_Log.png differ diff --git a/docs/IDE/Starting/Images/Import_Log.svg b/docs/IDE/Starting/Images/Import_Log.svg new file mode 100644 index 000000000..118916501 --- /dev/null +++ b/docs/IDE/Starting/Images/Import_Log.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• Unsaved.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXEDITOR 1MD: '__IMPORT_LOG.md':Import VBP fileStarted: 02-11-26 09:30:00File: C:\tbshots\starting-new\Ledger\Ledger.vbpActions takenVBP line #1   set project.buildType to 'Standard EXE'VBP line #2   added project.reference 'OLE Automation'VBP line #4   imported module 'Module1.bas'VBP line #5   imported class 'Class1.cls'VBP line #6   set project.startupObject to 'Form1'VBP line #7   set project.iconForm to 'Form1'VBP line #8   set project.buildPath to '${SourcePath}\Ledger.exe'VBP line #9   set project.name to 'Ledger'VBP line #3   imported form 'Form1.frm'IMPORT FINISHED WITH 0 ERRORS/WARNINGSPlease check the DIAGNOSTICS PANEL for any potential compilation errors. You can delete this log file.DEBUG CONSOLE>DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034 LAAMAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERLedgerImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSources__IMPORT_LOG.mdClass1.clsForm1.frm.tbformForm1.frm.twinModule1.basPROPERTIES diff --git a/docs/IDE/Starting/Images/Licence_Agreement.light.png b/docs/IDE/Starting/Images/Licence_Agreement.light.png new file mode 100644 index 000000000..206cd0028 Binary files /dev/null and b/docs/IDE/Starting/Images/Licence_Agreement.light.png differ diff --git a/docs/IDE/Starting/Images/Licence_Agreement.light.svg b/docs/IDE/Starting/Images/Licence_Agreement.light.svg new file mode 100644 index 000000000..7d6d291ad --- /dev/null +++ b/docs/IDE/Starting/Images/Licence_Agreement.light.svg @@ -0,0 +1 @@ +twinBASIC Licence AgreementI AGREEQUITtwinBASIC (IDE and Compiler) Licence AgreementLast updated: 03-Dec-2025This Licence Agreement ("Agreement") is between you ("User") andTWINBASIC LTD, based in the United Kingdom ("Licensor"). Byinstalling, accessing, or using the twinBASIC IDE and Compiler("Software"), you agree to be bound by the terms of thisAgreement. If you do not agree, do not install or use theSoftware.1. Acceptance of TermsBy installing, accessing, or using the twinBASIC IDE andCompiler, you agree to comply with all terms of this Agreement.2. Licence GrantLicensor grants you a non-exclusive, non-transferable licence toinstall and use the twinBASIC solely for personal or commercialdevelopment purposes, subject to the restrictions outlined below.3. RestrictionsYou may not:Reverse engineer, decompile, or disassemble the Software, exceptto the extent that this restriction is expressly prohibited by UKlaw.Rent, lease, sublicense, or distribute the Software or any partof it to third parties.Modify or create derivative works of the Software without writtenpermission.4. OwnershipThe Software is licensed, not sold. TWINBASIC LTD retains allrights, title, and interest, including all intellectual propertyrights, in and to the Software.5. Disclaimer of Warranty diff --git a/docs/IDE/Starting/Images/Licence_Agreement.png b/docs/IDE/Starting/Images/Licence_Agreement.png new file mode 100644 index 000000000..b201e1c0f Binary files /dev/null and b/docs/IDE/Starting/Images/Licence_Agreement.png differ diff --git a/docs/IDE/Starting/Images/Licence_Agreement.svg b/docs/IDE/Starting/Images/Licence_Agreement.svg new file mode 100644 index 000000000..d6ab8090e --- /dev/null +++ b/docs/IDE/Starting/Images/Licence_Agreement.svg @@ -0,0 +1 @@ +twinBASIC Licence AgreementI AGREEQUITtwinBASIC (IDE and Compiler) Licence AgreementLast updated: 03-Dec-2025This Licence Agreement ("Agreement") is between you ("User") andTWINBASIC LTD, based in the United Kingdom ("Licensor"). Byinstalling, accessing, or using the twinBASIC IDE and Compiler("Software"), you agree to be bound by the terms of thisAgreement. If you do not agree, do not install or use theSoftware.1. Acceptance of TermsBy installing, accessing, or using the twinBASIC IDE andCompiler, you agree to comply with all terms of this Agreement.2. Licence GrantLicensor grants you a non-exclusive, non-transferable licence toinstall and use the twinBASIC solely for personal or commercialdevelopment purposes, subject to the restrictions outlined below.3. RestrictionsYou may not:Reverse engineer, decompile, or disassemble the Software, exceptto the extent that this restriction is expressly prohibited by UKlaw.Rent, lease, sublicense, or distribute the Software or any partof it to third parties.Modify or create derivative works of the Software without writtenpermission.4. OwnershipThe Software is licensed, not sold. TWINBASIC LTD retains allrights, title, and interest, including all intellectual propertyrights, in and to the Software.5. Disclaimer of Warranty diff --git a/docs/IDE/Starting/Images/Licence_Key.light.png b/docs/IDE/Starting/Images/Licence_Key.light.png new file mode 100644 index 000000000..5dbc8f690 Binary files /dev/null and b/docs/IDE/Starting/Images/Licence_Key.light.png differ diff --git a/docs/IDE/Starting/Images/Licence_Key.light.svg b/docs/IDE/Starting/Images/Licence_Key.light.svg new file mode 100644 index 000000000..4739070fa --- /dev/null +++ b/docs/IDE/Starting/Images/Licence_Key.light.svg @@ -0,0 +1 @@ +twinBASICPurchase a licenceOKCancelEnter Licence Key:Tip: copy and paste the key from your confirmation email. If you enteran invalid key, the COMMUNITY licence will be applied. diff --git a/docs/IDE/Starting/Images/Licence_Key.png b/docs/IDE/Starting/Images/Licence_Key.png new file mode 100644 index 000000000..ef7bc2970 Binary files /dev/null and b/docs/IDE/Starting/Images/Licence_Key.png differ diff --git a/docs/IDE/Starting/Images/Licence_Key.svg b/docs/IDE/Starting/Images/Licence_Key.svg new file mode 100644 index 000000000..94a84e8f8 --- /dev/null +++ b/docs/IDE/Starting/Images/Licence_Key.svg @@ -0,0 +1 @@ +twinBASICPurchase a licenceOKCancelEnter Licence Key:Tip: copy and paste the key from your confirmation email. If you enteran invalid key, the COMMUNITY licence will be applied. diff --git a/docs/IDE/Starting/Images/Missing_File_Box.png b/docs/IDE/Starting/Images/Missing_File_Box.png new file mode 100644 index 000000000..0331623a7 Binary files /dev/null and b/docs/IDE/Starting/Images/Missing_File_Box.png differ diff --git a/docs/IDE/Starting/Images/New_Standard_EXE.light.png b/docs/IDE/Starting/Images/New_Standard_EXE.light.png new file mode 100644 index 000000000..1a66abb5b Binary files /dev/null and b/docs/IDE/Starting/Images/New_Standard_EXE.light.png differ diff --git a/docs/IDE/Starting/Images/New_Standard_EXE.light.svg b/docs/IDE/Starting/Images/New_Standard_EXE.light.svg new file mode 100644 index 000000000..834e07141 --- /dev/null +++ b/docs/IDE/Starting/Images/New_Standard_EXE.light.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpUnsaved.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXCOMMONCONTROLSMorecomponents...EDITOR 1Form1.twinForm1.tbformDEBUG CONSOLE>DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034 LAAMAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERNewProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesForm1.tbformForm1.twinPROPERTIESPROPERTIESEVENTSDESIGNAPPEARANCENameForm1FormDesignerId{D0C5D0C5-3DAD-4000-8000-00000000D0C5}MDIChildFalseCaptionForm1Appearance1 - vbAppear3dBorderStyle2 - vbSizableBackColor&H(BGR)8000000FForeColor&H(BGR)000000FontNameSegoe UIFontSize8FontBoldFalseForm1    Form+ menu300 x 300 diff --git a/docs/IDE/Starting/Images/New_Standard_EXE.png b/docs/IDE/Starting/Images/New_Standard_EXE.png new file mode 100644 index 000000000..6f681648a Binary files /dev/null and b/docs/IDE/Starting/Images/New_Standard_EXE.png differ diff --git a/docs/IDE/Starting/Images/New_Standard_EXE.svg b/docs/IDE/Starting/Images/New_Standard_EXE.svg new file mode 100644 index 000000000..16c659d4e --- /dev/null +++ b/docs/IDE/Starting/Images/New_Standard_EXE.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpUnsaved.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWTOOLBOXCOMMONCONTROLSMorecomponents...EDITOR 1Form1.twinForm1.tbformDEBUG CONSOLE>DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034 LAAMAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERNewProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesForm1.tbformForm1.twinPROPERTIESPROPERTIESEVENTSDESIGNAPPEARANCENameForm1FormDesignerId{D0C5D0C5-3DAD-4000-8000-00000000D0C5}MDIChildFalseCaptionForm1Appearance1 - vbAppear3dBorderStyle2 - vbSizableBackColor&H(BGR)8000000FForeColor&H(BGR)000000FontNameSegoe UIFontSize8FontBoldFalseForm1    Form+ menu300 x 300 diff --git a/docs/IDE/Starting/Images/Options_ActiveX.light.png b/docs/IDE/Starting/Images/Options_ActiveX.light.png new file mode 100644 index 000000000..8c7b94d61 Binary files /dev/null and b/docs/IDE/Starting/Images/Options_ActiveX.light.png differ diff --git a/docs/IDE/Starting/Images/Options_ActiveX.light.svg b/docs/IDE/Starting/Images/Options_ActiveX.light.svg new file mode 100644 index 000000000..8468f1be0 --- /dev/null +++ b/docs/IDE/Starting/Images/Options_ActiveX.light.svg @@ -0,0 +1 @@ +Please wait...0.0%twinBASIC New Project OptionsContinueCancelProject NameOption Explicit ON(project scope)Register to HKEY_LOCAL_MACHINE(OFF will register to HKEY_CURRENT_USER)Include Common Controls(adds a reference to WinNativeCommonCtls)NewProject diff --git a/docs/IDE/Starting/Images/Options_ActiveX.png b/docs/IDE/Starting/Images/Options_ActiveX.png new file mode 100644 index 000000000..48eec7ee2 Binary files /dev/null and b/docs/IDE/Starting/Images/Options_ActiveX.png differ diff --git a/docs/IDE/Starting/Images/Options_ActiveX.svg b/docs/IDE/Starting/Images/Options_ActiveX.svg new file mode 100644 index 000000000..d872f295c --- /dev/null +++ b/docs/IDE/Starting/Images/Options_ActiveX.svg @@ -0,0 +1 @@ +Please wait...0.0%twinBASIC New Project OptionsContinueCancelProject NameOption Explicit ON(project scope)Register to HKEY_LOCAL_MACHINE(OFF will register to HKEY_CURRENT_USER)Include Common Controls(adds a reference to WinNativeCommonCtls)NewProject diff --git a/docs/IDE/Starting/Images/Options_Sample.light.png b/docs/IDE/Starting/Images/Options_Sample.light.png new file mode 100644 index 000000000..8878d18ad Binary files /dev/null and b/docs/IDE/Starting/Images/Options_Sample.light.png differ diff --git a/docs/IDE/Starting/Images/Options_Sample.light.svg b/docs/IDE/Starting/Images/Options_Sample.light.svg new file mode 100644 index 000000000..b1726543a --- /dev/null +++ b/docs/IDE/Starting/Images/Options_Sample.light.svg @@ -0,0 +1 @@ +Please wait...0.0%twinBASIC New Project OptionsContinueCancelProject NameOption Explicit ON(project scope)Visual Styles(via embedded manifest file)Include Common Controls(adds a reference to WinNativeCommonCtls)DPI AwarenessHelloWorldProjectSYSTEM_DPI_AWARE diff --git a/docs/IDE/Starting/Images/Options_Sample.png b/docs/IDE/Starting/Images/Options_Sample.png new file mode 100644 index 000000000..3d57c91f9 Binary files /dev/null and b/docs/IDE/Starting/Images/Options_Sample.png differ diff --git a/docs/IDE/Starting/Images/Options_Sample.svg b/docs/IDE/Starting/Images/Options_Sample.svg new file mode 100644 index 000000000..6ab49ec93 --- /dev/null +++ b/docs/IDE/Starting/Images/Options_Sample.svg @@ -0,0 +1 @@ +Please wait...0.0%twinBASIC New Project OptionsContinueCancelProject NameOption Explicit ON(project scope)Visual Styles(via embedded manifest file)Include Common Controls(adds a reference to WinNativeCommonCtls)DPI AwarenessHelloWorldProjectSYSTEM_DPI_AWARE diff --git a/docs/IDE/Starting/Images/Options_VBP.light.png b/docs/IDE/Starting/Images/Options_VBP.light.png new file mode 100644 index 000000000..df2c4fa10 Binary files /dev/null and b/docs/IDE/Starting/Images/Options_VBP.light.png differ diff --git a/docs/IDE/Starting/Images/Options_VBP.light.svg b/docs/IDE/Starting/Images/Options_VBP.light.svg new file mode 100644 index 000000000..ca7facfa3 --- /dev/null +++ b/docs/IDE/Starting/Images/Options_VBP.light.svg @@ -0,0 +1 @@ +Please wait...Finished parsing VBP. Preparing to open the project...twinBASIC New Project OptionsContinueCancelProject NameOption Explicit ON(project scope)Visual Styles(via embedded manifest file)DPI AwarenessUnicode Common Control Notifications(affects manual WM_NOTIFY handling)LedgerNONE diff --git a/docs/IDE/Starting/Images/Options_VBP.png b/docs/IDE/Starting/Images/Options_VBP.png new file mode 100644 index 000000000..46b132b20 Binary files /dev/null and b/docs/IDE/Starting/Images/Options_VBP.png differ diff --git a/docs/IDE/Starting/Images/Options_VBP.svg b/docs/IDE/Starting/Images/Options_VBP.svg new file mode 100644 index 000000000..f092872a5 --- /dev/null +++ b/docs/IDE/Starting/Images/Options_VBP.svg @@ -0,0 +1 @@ +Please wait...Finished parsing VBP. Preparing to open the project...twinBASIC New Project OptionsContinueCancelProject NameOption Explicit ON(project scope)Visual Styles(via embedded manifest file)DPI AwarenessUnicode Common Control Notifications(affects manual WM_NOTIFY handling)LedgerNONE diff --git a/docs/IDE/Starting/Images/Recent_VB6.light.png b/docs/IDE/Starting/Images/Recent_VB6.light.png new file mode 100644 index 000000000..ba7dc4ea9 Binary files /dev/null and b/docs/IDE/Starting/Images/Recent_VB6.light.png differ diff --git a/docs/IDE/Starting/Images/Recent_VB6.light.svg b/docs/IDE/Starting/Images/Recent_VB6.light.svg new file mode 100644 index 000000000..95161cf69 --- /dev/null +++ b/docs/IDE/Starting/Images/Recent_VB6.light.svg @@ -0,0 +1 @@ +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentRecent (VB6)C:\Old\Thing.vbpC:\Old\Other.VBPSearch projects, samples, recent...BETA 103482602Double-click an item or press Enterto open it.VB6 project files (VBP) you opened recently. diff --git a/docs/IDE/Starting/Images/Recent_VB6.png b/docs/IDE/Starting/Images/Recent_VB6.png new file mode 100644 index 000000000..a7f77f917 Binary files /dev/null and b/docs/IDE/Starting/Images/Recent_VB6.png differ diff --git a/docs/IDE/Starting/Images/Recent_VB6.svg b/docs/IDE/Starting/Images/Recent_VB6.svg new file mode 100644 index 000000000..0dff013c1 --- /dev/null +++ b/docs/IDE/Starting/Images/Recent_VB6.svg @@ -0,0 +1 @@ +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentRecent (VB6)C:\Old\Thing.vbpC:\Old\Other.VBPSearch projects, samples, recent...BETA 103482602Double-click an item or press Enterto open it.VB6 project files (VBP) you opened recently. diff --git a/docs/IDE/Starting/Images/Search_Results.light.png b/docs/IDE/Starting/Images/Search_Results.light.png new file mode 100644 index 000000000..1aa744f68 Binary files /dev/null and b/docs/IDE/Starting/Images/Search_Results.light.png differ diff --git a/docs/IDE/Starting/Images/Search_Results.light.svg b/docs/IDE/Starting/Images/Search_Results.light.svg new file mode 100644 index 000000000..d9f79265b --- /dev/null +++ b/docs/IDE/Starting/Images/Search_Results.light.svg @@ -0,0 +1 @@ +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEStandard DLLStandard EXE (+VBCCR)Standard EXEstandardBETA 10348260Double-click an item or press Enterto open it.NEW$\_Standard EXE(non-ActiveX)$\_Standard DLL#(non-ActiveX)(experimental)$\_Standard EXE (+VBCCR)#(experimental)(Console App)$\_Standard EXE#(Console App) diff --git a/docs/IDE/Starting/Images/Search_Results.png b/docs/IDE/Starting/Images/Search_Results.png new file mode 100644 index 000000000..2ca73fa51 Binary files /dev/null and b/docs/IDE/Starting/Images/Search_Results.png differ diff --git a/docs/IDE/Starting/Images/Search_Results.svg b/docs/IDE/Starting/Images/Search_Results.svg new file mode 100644 index 000000000..d58b619e9 --- /dev/null +++ b/docs/IDE/Starting/Images/Search_Results.svg @@ -0,0 +1 @@ +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEStandard DLLStandard EXE (+VBCCR)Standard EXEstandardBETA 10348260Double-click an item or press Enterto open it.NEW$\_Standard EXE(non-ActiveX)$\_Standard DLL#(non-ActiveX)(experimental)$\_Standard EXE (+VBCCR)#(experimental)(Console App)$\_Standard EXE#(Console App) diff --git a/docs/IDE/Starting/Images/Title_Unsaved.light.png b/docs/IDE/Starting/Images/Title_Unsaved.light.png new file mode 100644 index 000000000..37db75a08 Binary files /dev/null and b/docs/IDE/Starting/Images/Title_Unsaved.light.png differ diff --git a/docs/IDE/Starting/Images/Title_Unsaved.light.svg b/docs/IDE/Starting/Images/Title_Unsaved.light.svg new file mode 100644 index 000000000..db1322ebe --- /dev/null +++ b/docs/IDE/Starting/Images/Title_Unsaved.light.svg @@ -0,0 +1 @@ +FileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpwin32 diff --git a/docs/IDE/Starting/Images/Title_Unsaved.png b/docs/IDE/Starting/Images/Title_Unsaved.png new file mode 100644 index 000000000..3adbb6274 Binary files /dev/null and b/docs/IDE/Starting/Images/Title_Unsaved.png differ diff --git a/docs/IDE/Starting/Images/Title_Unsaved.svg b/docs/IDE/Starting/Images/Title_Unsaved.svg new file mode 100644 index 000000000..e241fd0a7 --- /dev/null +++ b/docs/IDE/Starting/Images/Title_Unsaved.svg @@ -0,0 +1 @@ +FileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpwin32 diff --git a/docs/IDE/Starting/Importing-VB6.md b/docs/IDE/Starting/Importing-VB6.md new file mode 100644 index 000000000..026904cf7 --- /dev/null +++ b/docs/IDE/Starting/Importing-VB6.md @@ -0,0 +1,144 @@ +--- +title: Importing a VB6 project +parent: Starting a project +grand_parent: IDE +nav_order: 4 +permalink: /tB/IDE/Starting/Importing-VB6 +index: + - import a VB6 project + - vbp + - import log + - migrate from VB6 +--- + +# Importing a VB6 project + +The IDE turns a VB6 project file, a `.vbp`, into a new twinBASIC project. The compiler does the conversion: it reads the `.vbp` and the files it names, and gives the IDE the settings and the files of a twinBASIC project. This page describes the routes, what the importer converts and what it leaves, the log it writes, and what to do next. The FAQ entries [What does '100% backwards compatible' mean](../../../FAQ#backwards-compatibility) and [So some of my projects won't work?](../../../FAQ#when-it-does-not-work) say what to expect of VB6 code. + +## The routes + +| Route | What it does | +|---|---| +| **New → Import from VBP...** in the [New / Open Project](New-Project) dialog, then **Open** or a double-click | Closes the open project, opens a Windows picker for the `.vbp`, then imports it. See the [note on the import cards](New-Project#import-from-folder). | +| **File → Open Project...** (Ctrl+O), or **Browse...** in the dialog, with a `.vbp` chosen | The same import. A name that ends `.vbp` goes to the importer. | +| The **Recent (VB6)** section of the dialog | Loads the `.vbp` by another route, described [below](#the-other-two-routes). | +| A `.vbp` on the command line | The same as **Recent (VB6)**. | +| **Project → Add → Import...** with a `.vbp` | Is not a new project. It copies files of the VB6 project into the open project. See [Single files and the Import tree](#single-files-and-the-import-tree). | + +![The New / Open Project dialog on its New section, with a red arrow pointing at the Import from VBP card.](../../Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png){:width="970" height="728"} + +The card **Import from folder...** is not a route for a VB6 project. It opens the folder of a twinBASIC project. See [New Project](New-Project#import-from-folder). + +### The first two routes + +The IDE sets the build configuration to **win32**, sets DEP off and large-address-aware on, and shows a progress indicator while the compiler converts the project. Then the **New Project Options** box opens, with the rows of an import: + +- **Project Name**, from the `.vbp`'s `Name`. +- **Option Explicit ON**, ticked. The importer itself leaves the setting off, but the box starts ticked, so a default import ends with it on. A project that relies on undeclared variables then shows errors; see [After the import](#after-the-import). +- **Unicode Common Control Notifications**, ticked. +- **Visual Styles**, when the project has no manifest, and **DPI Awareness**, for a Standard EXE. The DPI choice starts at `NONE`. +- **Register to HKEY_LOCAL_MACHINE**, ticked, for an ActiveX DLL or ActiveX Control. An IDE that is not running as administrator then shows a warning about the machine-wide registry once after **Continue**. See [ActiveX registration](../Building/ActiveX-Registration#the-warning-at-the-machine-wide-registry). + +There is no **Include Common Controls** row. [New Project Options](New-Project#new-project-options) describes all the rows. + +**Continue** opens the project. It is unsaved and marked as changed, it is named from the box, and the title bar reads `Unsaved.twinproj`. Nothing is written to disk until you save it: **File → Save Project** opens the **Save As** box. A failed import shows the box *ERROR: LoadProject failed. Error code ...* and nothing else. + +### The other two routes + +A `.vbp` loaded from **Recent (VB6)** or the command line shows no **New Project Options** box. It gets the importer's own settings. **Option Explicit** is off, and the project has no visual-styles manifest. The project is not marked as changed, so its title bar has no dot. The build configuration box is left as it is. **File → Save Project** still opens **Save As**, closing asks nothing, and the `.vbp` file is not touched. + +Use the first two routes when you want the box's choices. + +## What the importer converts + +### Project settings + +| In the `.vbp` | In the new project | +|---|---| +| `Name` | the project name and the application title | +| `Type=Exe`, `Type=OleDll`, `Type=Control` | a Standard EXE, an ActiveX DLL or an ActiveX Control | +| `Startup` | the [*Startup Object*](../Project-Settings#startup-object): `Sub Main` or a form. An ActiveX DLL or Control gets `(None)`. | +| `IconForm` | the [*Icon Form*](../Project-Settings#icon-form) | +| `LinkSwitches` with `/SUBSYSTEM:CONSOLE` | [*Is Console Application*](../Project-Settings#is-console-application) set to **Yes**. The switch must be in capitals. It may stand alone, under `[VBCompiler]`, or among other switches in quotes. In lower case, or as `/SUBSYSTEM:WINDOWS`, it sets nothing, and no other linker switch is carried over. | +| `Title`, `Description`, `MajorVer`, `MinorVer`, `RevisionVer`, `VersionCompanyName`, `VersionFileDescription`, `VersionLegalCopyright`, `VersionProductName` | the matching version settings | +| `AutoIncrementVer=1` | the version's auto-increment, set to *Revision* | +| `Reference=` | a reference to the type library, with its path | +| `Object=`, such as `MSCOMCTL.OCX` | a COM reference to the control file, and support for ActiveX controls turned on | + +See [Project Settings](../Project-Settings) for these settings. The importer also adds the WinNativeForms package as a reference, turns *Use Unicode Standard Library* on, and sets the *Build Output Path* to a file in the project's folder, such as `${SourcePath}\Project1.exe`, not to the `Build` folder of the templates. + +A reference to a type library the machine does not have fails to load, and [Project Settings opens by itself](Opening-Projects#what-happens-as-a-project-loads) on **Library References**. The version in the list can differ from the one the line names: a line for MSComctlLib that says 2.0 shows 2.1. To use the controls of an OCX, register it first; see the [FAQ](../../../FAQ#vb6-ocx). + +### Files + +- **A `.bas` or `.cls` file is copied unchanged**, with its `Attribute` lines. +- **A `.frm`, `.ctl` or `.pag` file becomes two files.** The designer file is `Form1.frm.tbform`, `Ctl1.ctl.tbcontrol` or `Pag1.pag.tbppage`. The code is in `Form1.frm.twin`, `Ctl1.ctl.twin` or `Pag1.pag.twin`, as a `Class` with the file's code. An MDI form is converted the same way, and the startup object is the MDI form. +- **A UserControl's list of property pages**, which VB6 keeps in the `.ctx` file, goes into its `.tbcontrol` file. +- **A `.frx` file is read with its form.** A picture or icon of the form's controls is kept as bytes inside the `.tbform`, and the project gets no `Resources` entry for it. A `.frx` record that VB6 itself cannot read loses its picture without a message. + +How the `Attribute` lines are kept, and which of them have an effect in a built ActiveX DLL, is in the [FAQ](../../../FAQ#vb6-import). + +### Resources + +A `.res` file named by `ResFile32=` is imported as the project's resources, and the log says *imported resource file*. Its manifest becomes `Resources\MANIFEST\#1.xml`. A string table becomes `Resources\STRING\Strings.json`. A resource of a numeric type keeps its type and its id: `RT_RCDATA` with id 201 is `Resources\RCDATA\#201`, and a name stays a name. The [FAQ](../../../FAQ#resources) lists the resource folders. + + +> [!WARNING] BETA 1034 +> A resource whose type is a name, such as `CUSTOM` or `MYDATA`, is imported with an empty name. The folder `Resources\` holds one blank row, however many resources of that type the `.res` file holds, and the log still reports `0 ERRORS/WARNINGS`. A saved project keeps the bytes of each resource under an empty name, and the IDE shows the row as empty. A build then ends with `NATIVE EXCEPTION: ACCESS_VIOLATION` and makes no executable. Resources whose type is a number are right, `RT_RCDATA` included. Take the resources of a named type out of the `.res` file before the import, and add them to the project afterwards, as [Resources](../Projects/Adding-Files#resources) describes. + +## What the importer leaves + +- **The code.** The importer does not change it. A `.bas` or `.cls` file is copied as it is, and the `.twin` file of a form holds the form's code as VB6 wrote it. twinBASIC compiles it as VB code. +- **The `Attribute` lines.** They stay as VB6 wrote them, and none becomes a twinBASIC attribute. +- **The linker switches** other than `/SUBSYSTEM:CONSOLE` in capitals. +- **A `.frm` chosen alone** with **Project → Add → Import...**, with no `.vbp`. It is copied unconverted. +- **The controls of an OCX.** The importer adds the reference, and the OCX has to be registered on the machine. + +## The import log + +The project gets a file `Sources\__IMPORT_LOG.md`. It lists what the importer did, one line for each file and setting. A few lines, as they read: + +```text +added project.reference 'OLE Automation' +imported module 'Module1.bas' +imported class 'Class1.cls' +imported usercontrol 'UserControl1.ctl' +imported propertypage 'PropertyPage1.pag' +imported resource file 'Project1.res' +imported form 'Form1.frm' +``` + +The lines for the settings come first, and the forms are logged last, whatever their line in the `.vbp`. The log ends with `IMPORT FINISHED WITH n ERRORS/WARNINGS` and the advice *Please check the DIAGNOSTICS PANEL*. After the import, the one tab that is open is a preview of this file. + +![The IDE after an import: the log in a preview tab, the imported files in the Project Explorer, and a dot in front of the name in the title bar.](Images/Import_Log.png){:width="1280" height="880"} + +## A file that is missing + +If the `.vbp` names a module, form, class, user control or resource file that is not there, the compiler opens a native Windows box. Its title is *Error reading file.*, its text is the name of the file, and it has one **OK**. Meanwhile the IDE shows *Migrating VBP to twinproj format...* and Esc does nothing, so the IDE seems to hang. If the box is not in front, look for it in the taskbar. After **OK** the import goes on. The log then holds a line such as `importing module 'Gone.bas' FAILED; unable to open or read the file`, or `import resource file 'Gone.res' FAILED`, and ends with `IMPORT FINISHED WITH 1 ERRORS/WARNINGS`. + +![The Windows box for a missing file, its title cut off by the width of the box, with the file's name and one button.](Images/Missing_File_Box.png){:width="134" height="127"} + +## After the import + +1. **Save the project.** It has no file yet: **File → Save Project** opens **Save As**. See [A project with no file](../Projects/Saving-and-Exporting#a-project-with-no-file). +2. **Read the DIAGNOSTICS panel.** The errors are usually *Unrecognized symbol* (TB5079) for variables that were never declared, because **Option Explicit ON** was left ticked. Either untick the row in the box, or set [*Option Explicit On*](../Project-Settings#option-explicit-on) to **No** in Project Settings. The warning TB0002 can be set to **IGNORE**. The [FAQ](../../../FAQ#unrecognized-variables) shows both. +3. **Run it.** **Start** (F5) runs the project in the compiler. See [Running](../Debugging/Running). + +## Single files and the Import tree + +**Project → Add → Import...** imports files into the open project, not a new project. See [Import](../Projects/Adding-Files#import). Three cases: + +- **A `.bas` or `.cls` file** is copied as it is. +- **A `.frm` chosen alone** is copied unconverted, as `Sources\Form1.frm`, with its text. Its `.frx` is not copied. No box opens, and the project is saved. +- **A `.vbp`** opens the **Import from twinproj...** tree, with the files already converted. The first row shows the project's name and path; below it come the **Resources**, **Sources** and **Miscellaneous** branches. Tick both files of a form, `Form1.frm.tbform` and `Form1.frm.twin`. A user control and a property page have two files each, in the same way, and the log is a file too. **Import** copies the ticked files into the open project. + +![The Import from twinproj dialog: a tick-box tree of everything found in a VB6 project, with the Resources and Sources branches open. The two files of a form are both ticked.](../../Miscellaneous/Images/16833fae-4bd7-418f-bb16-691a611a5b01.png){:width="469" height="401"} + +## See Also + +- [New Project](New-Project) -- the dialog and the **New Project Options** box +- [Opening projects](Opening-Projects) -- the other ways to open a project +- [Adding files](../Projects/Adding-Files) -- **Import...** and resources +- [Project Settings](../Project-Settings) -- the settings the importer fills +- [Project Types](../../../Features/Project-Configuration/Project-Types) -- the kinds of project twinBASIC builds +- [FAQ: How do I import my VB6 project](../../../FAQ#vb6-import) -- the `Attribute` lines diff --git a/docs/IDE/Starting/New-Project.md b/docs/IDE/Starting/New-Project.md new file mode 100644 index 000000000..1439c60d0 --- /dev/null +++ b/docs/IDE/Starting/New-Project.md @@ -0,0 +1,219 @@ +--- +title: New Project +parent: Starting a project +grand_parent: IDE +nav_order: 2 +permalink: /tB/IDE/Starting/New-Project +redirect_from: + - /tB/IDE/Project/New +index: + - new project dialog + - new / open project dialog + - open project dialog + - recent projects + - custom template +--- + +# New Project + +The **New / Open Project** dialog makes a project from a template or a sample, imports an existing one, or opens a project you used recently. **File → New Project...** (Ctrl+N) opens it, and so does the start of the IDE when no project is named. This page describes the dialog, what each template makes, the **New Project Options** box that follows, and what the new project is. + +![The New / Open Project dialog on its New section: a search box and a version badge across the top, the sections New, Samples and Recent down the left, each with a count, and eight project templates as cards in three rows. The Standard EXE card is selected. Browse, Open and Cancel run along the bottom.](../Images/New_Project.png){:width="970" height="728"} + +## Opening the dialog + +| How | Opens on | +|---|---| +| The start of the IDE, after the [splash](First-Launch#the-splash) | **New**, or **Recent** when [**Focus The 'Recent' Tab On IDE Startup**](../Customising/IDE-Options#project-explorer-tabs-and-start-up) is on; then the section the dialog was last closed on | +| **File → New Project...** (Ctrl+N) | **New** | +| **File → Open Recent...** | **Recent** | + +With a project open, the two **File** commands show the dialog over it. Nothing closes until you press **Open** or double-click an item. **File → Open Project...** (Ctrl+O) and the dialog's **Browse...** button do not use this dialog's lists: they open the Windows **Open** box. [Opening projects](Opening-Projects) describes them. + +### The section is remembered + +Every way of closing the dialog --- **Open**, **Cancel**, the close box of the title bar, **Browse...** and Esc --- saves the section it was on, and so does a click on another section. At the start of the IDE, the dialog first shows the section that **Focus The 'Recent' Tab On IDE Startup** names, and then moves to the saved section. The option therefore decides only before the dialog has been closed for the first time. After that, the saved section wins. + +**New Project...** and **Open Recent...** show **New** and **Recent** whatever is saved, and each saves the section it is closed on. A dialog opened from the menu and closed on **Recent** makes the next start open on **Recent**. + +If the saved section is **Recent (VB6)** and the machine no longer has a VB6 project to list, the dialog opens on an empty section that reads **Nothing here yet.**, with no section selected in the rail and **Open** greyed out. Click another section. + +## The parts of the dialog + +- **The title bar** reads **New / Open Project...**. You can drag it, and its close box does what **Cancel** does. +- **The search box**, across the top, has the focus when the dialog opens. At its right, a badge shows the IDE's version. +- **The rail**, down the left, lists the sections **New**, **Samples**, **Recent** and **Recent (VB6)**. Each shows the number of entries it holds; **Recent** shows an ellipsis until its list is read. **Recent (VB6)** is in the rail only when VB6's own list of recent projects holds a `.vbp` file. A hint under the rail reads *Double-click an item or press Enter to open it.* +- **The section**, beside the rail, starts with a line that says what it holds. **New** and **Samples** show **cards**: a name in bold and, for some, a grey line under it. **Recent** and **Recent (VB6)** show **rows**. A section with nothing in it reads **Nothing here yet.**, and while a list is being read it reads **Loading...**. +- **The buttons** are **Browse...**, **Open** and **Cancel**. **Open** is greyed out while nothing is selected, and does nothing then. No button is the default one. + +One click on a card or a row selects it, with an outline. A double-click opens it. The first item of the section is selected when the section is drawn, so **Open** has an item to act on unless the section is empty. A selected item that is still in the list after the section is drawn again stays selected. + +The text after the first `#` of a name is its **sub-text**, set on a second line. The card `Standard EXE#(Console App)` shows **Standard EXE** over *(Console App)*. Cards and rows follow the same rule. A sample's name starts with its number, and the card does not show it: `Sample 6. CustomControls` shows as **CustomControls**. [Samples](Samples) lists the numbers, by which the documentation names the samples. + +The lists of **New** and **Samples** are read once, when the IDE starts. A folder that you add to the IDE's `projects` folder while the IDE runs shows only after the next start. **Recent** and **Recent (VB6)** are read each time the dialog opens, and not again after you remove an entry. + +## Search + +The search box looks in every section at once. A word matches an item when it is in the item's name or its path, in either case. Several words must all match. A section's own name is not searched. + +- The path of a template is its folder in the IDE's `projects` folder, such as `$\_Standard EXE`. The path of a sample is `$\Sample 10. twinBASIC IDE Addin`, with its number, so `sample 10` finds a sample by the number its card does not show. The two import cards have the paths `$` and `$$`, so `vbp` finds **Import from VBP...**. +- The results replace the section, and the rail keeps its selection. They are rows, grouped under a small heading for each section that has a match: **New**, **Samples**, **Recent** and, when it is listed, **Recent (VB6)**. The matching text is marked. When two words match in the same line, only the last one is marked. A row of a template or a sample shows the item's path on a further line, and no row has an **X**. +- With no match, the dialog reads **No results found.** and **Open** is greyed out. +- Clearing the box brings the section back. A click on a section in the rail clears the box. + +![The dialog with a word typed in its search box: rows under the heading of one section, the matching text marked in each, and the path of each row on the line under its name.](Images/Search_Results.png){:width="970" height="728"} + +## Keys + +- In the search box, Enter opens the selected item. When nothing is selected, it opens the first one listed. With no results, it does nothing. +- Esc empties the search box when it holds text, and takes the focus out of the box. A second Esc presses **Cancel**. With the box empty, Esc presses **Cancel** at once. +- With the focus outside the search box, Up and Down move the selection through the items in the order they are drawn, and wrap round at both ends. In a grid of cards, Down is the next card. Enter opens the selected item. +- Tab from the search box moves the focus to the first card of **New** or **Samples**. Focusing a card does not select it. Space on a focused card selects it. +- With a card focused, Tab, Left and Right do not move the focus. They move a highlight through **Browse...**, **Open** and **Cancel**. + + +> [!NOTE] BETA 1034 +> Up and Down do nothing while the search box has the focus, which it has when the dialog opens, and no key reaches the sections of the rail. In **Recent**, the search box is the only element that can take the focus: Tab moves it to the page behind the dialog, a second Tab to a panel behind that, and then Esc, Enter and the arrow keys do nothing until you click. To use the arrow keys in **New** or **Samples**, press Tab once, or click blank space in the dialog. Choose a section with the mouse, and in **Recent** double-click the row. + + +> [!NOTE] BETA 1034 +> After Tab has moved the highlight to a button, Enter presses that button and also opens the selected item. **Open** opens the item twice. **Browse...** opens the Windows box and the item. **Cancel** closes the dialog and then opens the selected item: a saved project with no unsaved change is closed with no question, and **New Project Options** opens for the selected card. Enter on a card that has the focus opens the selected card, not the focused one. Select the card you want with a click or the arrow keys, and press the buttons with the mouse. To cancel from the keyboard, press Esc. + +## What each template makes +{: index="standard exe; create an ActiveX DLL; create ActiveX DLL" index_also="create a DLL project; create dll project; create a dll; create dll" } + +The cards of **New** are the template folders of the IDE's `projects` folder, in this order: **Standard EXE** first, the others in alphabetical order, then the two import cards. Each template is one project file. Choosing it makes a new project in memory from that file. + +| Card | Project type | Files it makes | References | Startup | +|---|---|---|---|---| +| **Standard EXE** | Standard EXE | `Form1.tbform` and `Form1.twin` (an empty `Form1` class), and the icon `Resources\ICON\twinBASIC.ico` | WinNativeForms, OLE Automation | `Form1` | +| **ActiveX Control** | ActiveX Control | `myActiveXControl.tbcontrol` and `myActiveXControl.twin` (a class with one property and the `ReadProperties` and `WriteProperties` events), and the bitmap `Resources\BITMAP\twinBASIC.bmp` | OLE Automation, Microsoft Add-In Designer, VB, Microsoft Visual Basic for Applications Extensibility 5.3 | none | +| **ActiveX DLL** | ActiveX DLL | `MyClass.twin` (an empty class) | OLE Automation, WinNativeForms | none | +| **Standard DLL** (*non-ActiveX*) | Standard DLL | `MainModule.twin`, with a `[DllExport]` function `MyCoolFunction` and comments on calling it from VB6 or VBA | OLE Automation, the App global object | none | +| **Standard EXE (+VBCCR)** (*experimental*) | Standard EXE | As **Standard EXE**, with a visual-styles manifest | VB, OLE Automation, VBCCR18 | `Form1` | +| **Standard EXE** (*Console App*) | Standard EXE, a console application | `Console.twin` (a `Console` class with `Cls`, `WriteLine` and `ReadLine`), `MainModule.twin` (a `Sub Main` that asks for a name), and the icon | OLE Automation, the App global object | `Sub Main` | + +The **VBCCR18** package is embedded in the project of the **+VBCCR** template, so the template file is 4.6 MB. + +Every template names the project `NewProject` (the ActiveX Control one, `myActiveXControlProject`), turns **Option Explicit** on for the project, and sets the [*Build Output Path*](../Project-Settings#build-output-path) to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`. + +The last two cards, **Import from VBP...** and **Import from folder...**, make no project from a template. See [Importing a VB6 project](Importing-VB6) and [Import from folder](#import-from-folder). + +### Custom templates + +A folder of the IDE's `projects` folder whose name does not start with `Sample ` is a card of **New**, if it holds a file named `projectName.twinproj`. A leading underscore is not shown, so `_My Template` is the card **My Template**. A `#` splits the name into a name and a sub-text, so `_My#Template (sub)` is **My** over *Template (sub)*. The new cards sort after **Standard EXE**, with the other templates. A folder that starts with `Sample ` and a number, such as `Sample 1c. Mine`, is a card of **Samples** instead, and sorts by its number. A custom card opens through [New Project Options](#new-project-options), like the others. + +To make a template, save a project, copy its file into a new folder of `projects`, and name it `projectName.twinproj`. The IDE reads the folder at the start, and the folder belongs to one IDE folder on disk, so copy it into the `projects` folder of a newer IDE as well. + +## New Project Options + +Choosing a template, a sample or an imported `.vbp` opens a small box, **twinBASIC New Project Options**, before the project is made. It has the buttons **Continue** and **Cancel**, and **Continue** is the default. The **Project Name** box is selected when the box opens. The rows that show depend on the project. + +![The New Project Options box for a Standard EXE: a Project Name box, ticked boxes for Option Explicit ON, Visual Styles and Include Common Controls, and a DPI Awareness list set to SYSTEM_DPI_AWARE.](../../Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png){:width="529" height="279"} + +| Row | Shown for | Default | What **Continue** does | +|---|---|---|---| +| **Project Name** | every project | the name the template sets, such as `NewProject`; for a sample, its own project name; for a `.vbp`, the project's `Name` | Sets the project's name. For anything but a `.vbp`, it also sets the description and the application title to the same text. | +| **Option Explicit ON** *(project scope)* | every project | ticked | Turns [**Option Explicit On**](../Project-Settings#option-explicit-on) on for every file of the project. | +| **Visual Styles** *(via embedded manifest file)* | a Standard EXE that has no manifest yet | ticked | Adds the visual-styles manifest as a resource. | +| **Register to HKEY_LOCAL_MACHINE** *(OFF will register to HKEY_CURRENT_USER)* | an ActiveX DLL or ActiveX Control | unticked for the templates, ticked for an import | Sets [*Register DLLs to HKLM*](../Project-Settings#register-dlls-to-hklm). | +| **Include Common Controls** *(adds a reference to WinNativeCommonCtls)* | a project that references WinNativeForms and not WinNativeCommonCtls, and is not a `.vbp` import | ticked | Adds the reference to the WinNativeCommonCtls package. | +| **DPI Awareness** | a Standard EXE | `SYSTEM_DPI_AWARE`; `NONE` for an import | Sets [*Force Dpi Awareness At Startup*](../Project-Settings#dpi-awareness) to `NONE`, `SYSTEM_DPI_AWARE` or `PER_MONITOR_DPI_AWARE`. | +| **Unicode Common Control Notifications** *(affects manual WM_NOTIFY handling)* | a `.vbp` import | ticked | Sets [*Unicode Control Notifications*](../Project-Settings#unicode-control-notifications). | + +The **Project Name** box takes letters, digits and the underscore, and ignores every other key you type. A leading underscore is removed. + + +> [!NOTE] BETA 1034 +> The box filters the keys you type, not its text. A name that you paste keeps its spaces, dots and other characters, and becomes the project's name, its description and its application title. With `My Project.x` pasted, the project's root folder has that name, and the build writes `Build\My Project.x_win32.exe`. Type the name, or paste only letters, digits and underscores. + +A few more things about the rows: + +- **Option Explicit ON** starts ticked for an import too, although the importer itself turns the setting off. A VB6 project that relies on undeclared variables then shows [TB5079 errors](../../../FAQ#unrecognized-variables). Untick the row to keep the VB6 behaviour. +- **Visual Styles** is not offered for a project that has a manifest already: the **Standard EXE (+VBCCR)** template, Sample 20, and a `.vbp` whose `.res` file holds a manifest. It is never offered for an ActiveX project. +- With **Register to HKEY_LOCAL_MACHINE** ticked, an IDE that is not running as administrator shows a warning box when the project's *Register DLL after build* is on. See [The warning at the machine-wide registry](../Building/ActiveX-Registration#the-warning-at-the-machine-wide-registry). +- **Include Common Controls** shows for a sample that references WinNativeForms and not yet WinNativeCommonCtls: Samples 0, 1, 1a, 2, 3, 4, 5, 9, 17, 19, 21 and 22. Samples 1b and 20 have the reference already, and the other samples do not reference WinNativeForms. +- **Continue** also turns [*Large Address Aware (LAA)*](../Project-Settings#large-address-aware-laa) on. + +**Cancel** closes the box, the progress indicator and the project. Nothing is opened, and the IDE is left with no project, because the one that was open was closed before the box appeared. + + +> [!NOTE] BETA 1034 +> After **Cancel** in this box for an imported `.vbp`, the next template or sample shows the box with an import's rows: **Unicode Common Control Notifications** is there, and **Include Common Controls** is not. **Continue** then makes a project with no reference to WinNativeCommonCtls, with [*Unicode Control Notifications*](../Project-Settings#unicode-control-notifications) on, and with the description and the application title left as they were, `New project` for a template, in place of the project's name. The box is right again after that **Continue**, and in a new IDE. Cancelling the Windows picker of **Import from VBP...** does not do this. In a project made this way, add the package under [Available Packages](../Projects/References-and-Packages#available-packages), and set the [*Project Description*](../Project-Settings#project-description) and the [*Application Title*](../Project-Settings#application-title). + +![The box for a .vbp import: the project's name from the .vbp, DPI Awareness set to NONE, the row for Unicode notifications, and no row for the common controls.](Images/Options_VBP.png){:width="529" height="279"} + +![The box for the ActiveX DLL template, with its row for HKEY_LOCAL_MACHINE unticked.](Images/Options_ActiveX.png){:width="503" height="245"} + +![The box for the HelloWorld sample, with the sample's own project name.](Images/Options_Sample.png){:width="529" height="279"} + +## What a new project is + +A project made from a card is new and **unsaved**. It exists in memory, and no file on disk holds it yet. + +- The title bar reads `Unsaved.twinproj - twinBASIC`, then the IDE's version in brackets. A dot in front of the name marks a project with unsaved changes. A project made from a template or a sample is not marked as changed, so you can close it at once without a question. A project made from an import is marked as changed. +- **File → Save Project** (Ctrl+S) opens the Windows **Save As** dialog first, with the project's name proposed. **Save Project As...** does the same at any time. The project goes into **Recent** when you save it. See [A project with no file](../Projects/Saving-and-Exporting#a-project-with-no-file). +- The build configuration box keeps the value it has. A `.vbp` import sets it to **win32**. + +![The title bar of a new project, and under it the same title bar after one edit, with a dot in front of the name.](Images/Title_Unsaved.png){:width="687" height="42"} + +What opens depends on the template, and on the IDE Option [**Restore Editors State When Opening Project**](../Customising/IDE-Options#project-explorer-tabs-and-start-up). The option is on by default. Each template carries a list of the files to open and the folders to expand. With the option off, no editor opens, and the folders expand as the template says. + +| Template | Opens | +|---|---| +| **Standard EXE**, **Standard EXE (+VBCCR)** | `Form1.twin` and the designer of `Form1`, which is in front | +| **ActiveX Control** | `myActiveXControl.twin` and `myActiveXControl.tbcontrol`, with the code in front | +| **ActiveX DLL** | `MyClass.twin` | +| **Standard DLL**, **Standard EXE** (*Console App*) | `MainModule.twin` | + +The [Hello World tutorial](../../../Tutorials/Hello-World) starts from this state: **Standard EXE**, **Continue**, and `Form1` in the designer. + +![The IDE with a project just made from the Standard EXE card: the designer of Form1 in front, Form1's two files in the Project Explorer, and the status bar along the bottom.](Images/New_Standard_EXE.png){:width="1280" height="880"} + +## Recent + +**Recent** lists the projects you opened last, the newest first. Each row shows the project's full path, and an **X** button at its right edge, titled **Remove from this list**. The **X** removes the entry at once, with no question, and the list is not read again after it. + +![The Recent section holding two entries, the first selected, each with a clock icon, the path of a project and a small X button at the right edge.](../Images/New_Project_Recent_2.png){:width="970" height="728"} + +The IDE adds a project to the list when it starts to load the project, and when you save a project under a name. A project that is opened again moves to the top and is listed once. The list holds 21 entries, and the oldest falls off the end. A project made from a template, a sample or an import is not listed until you save it. A project that cannot be opened stays on the list until you remove it; see [When a project will not open](Opening-Projects#when-a-project-will-not-open). + +With nothing in the list, the section reads **Nothing here yet.**, and **Open** is greyed out. + +![The Recent section with a count of 0: Nothing here yet. Open a project and it will appear here. The Open button is greyed out.](../Images/New_Project_Recent_1.png){:width="970" height="728"} + +A path that holds a `#` is shown cut in two, as a name with a sub-text: `C:\dev\C#\app.twinproj` shows `C:\dev\C` with `\app.twinproj` under it. The row opens the full path. + +### Recent (VB6) + +**Recent (VB6)** lists the `.vbp` files that VB6 itself opened recently. The IDE reads VB6's list of recent files, in the registry key `HKEY_CURRENT_USER\Software\Microsoft\Visual Basic\6.0\RecentFiles`, and never writes it. Only entries that end in `.vbp`, in either case, are listed. When the key holds none, the section and its rail item are not shown. The rows have no **X**. + +**Open** on a row loads the `.vbp` by the route described under [Importing a VB6 project](Importing-VB6#the-routes), which shows no **New Project Options** box. + +![The Recent (VB6) section listing two .vbp files, with no X on either row, and the section's count of 2 in the rail.](Images/Recent_VB6.png){:width="970" height="728"} + +## Samples + +The **Samples** section holds the 26 sample projects that ship with the IDE. A sample opens as a new, unsaved project through [New Project Options](#new-project-options), like a template. [Samples](Samples) says what each one is. + +## Import from folder + +Opens a project from a folder of source files, such as the folder that **File → Export Project** writes. It shows a *Browse For Folder* dialog titled *Select folder of the existing twinBASIC project...*: choose the folder that holds the project's `Settings` file. The card does not take a VB6 project's folder. A `.vbp` goes through **Import from VBP...**. + +The project opens with no `.twinproj` file behind it: it is unsaved, and marked as changed. Because it has no file, the first **Save Project** (Ctrl+S) opens the *Save As* dialog to ask for one. The folder it is saved in becomes `${SourcePath}`, which the project's [*Export Path*](../Project-Settings#export-path) may use. + +If the folder holds the compiler packages under `Packages`, as an export by the IDE does, delete their folders first: imported with them, the project gets a copy of them that the compiler does not use. See [Packing the export back into a project](../Projects/Saving-and-Exporting#packing-the-export-back-into-a-project). [Keeping a project in Git from the IDE](../../../Features/Packages/Import-Export-Tool#keeping-a-project-in-git-from-the-ide) uses this command to open a fresh clone. + + +> [!NOTE] BETA 1034 +> **Import from VBP...** and **Import from folder...** close the open project before the Windows picker opens. If you cancel the picker, the IDE is left with no project and no dialog. The project's file is as it was, and its path is in **Recent**. A project with unsaved changes is asked about first, and **Cancel** in that question keeps the project. **File → Open Project...** and **Browse...** close the project only after you have chosen a file, so cancelling them loses nothing. Open a `.vbp` with **File → Open Project...**, which imports it as the card does. For a folder there is no such command: save the project first, and open it again from **Recent** if you cancel. + +## See Also + +- [Opening projects](Opening-Projects) -- the other ways to open a project, and what happens as it loads +- [Importing a VB6 project](Importing-VB6) -- the **Import from VBP...** card and the other routes for a `.vbp` +- [Samples](Samples) -- the sample projects +- [First launch](First-Launch) -- the splash, which the dialog follows +- [Saving and exporting](../Projects/Saving-and-Exporting) -- saving a new project, and **Recent** +- [IDE Options](../Customising/IDE-Options) -- the options for the start-up dialog +- [File menu](../Reference/Menu/File) -- **New Project...**, **Open Project...** and **Open Recent...** diff --git a/docs/IDE/Starting/Opening-Projects.md b/docs/IDE/Starting/Opening-Projects.md new file mode 100644 index 000000000..911535ac6 --- /dev/null +++ b/docs/IDE/Starting/Opening-Projects.md @@ -0,0 +1,146 @@ +--- +title: Opening projects +parent: Starting a project +grand_parent: IDE +nav_order: 3 +permalink: /tB/IDE/Starting/Opening-Projects +index: + - open a project + - open a saved project + - file association + - twinproj file association + - damaged project + - recovered project + - failed to load the project +--- + +# Opening projects + +A saved project is one `.twinproj` file. This page describes the ways to open it, what the IDE does as the project loads, what it remembers about each project, and what you see when a project will not open. + +## The ways to open a project + +| Way | What it does | +|---|---| +| A double-click on a `.twinproj` file in Windows Explorer | Starts the IDE on the project. There is no splash and no **New / Open Project** dialog. See [The file association](#the-file-association). | +| `twinBASIC.exe ` | The same. See [The command line](#the-command-line). | +| **File → Open Project...** (Ctrl+O), or **Browse...** in the [New / Open Project](New-Project) dialog | Opens the Windows **Open** box. A name that ends `.vbp` is imported as a VB6 project; any other name is loaded as a twinBASIC project. | +| The **Recent** section of the dialog, or **File → Open Recent...** | Loads the project at the row's path. | +| The **Recent (VB6)** section | Loads the `.vbp` by a route of its own. See [Importing a VB6 project](Importing-VB6#the-routes). | +| A card of **New** or **Samples** | Makes a new, unsaved project from a template or a sample. See [New Project](New-Project). | +| The **Import from VBP...** and **Import from folder...** cards | Open a native picker, then import. See [Import from folder](New-Project#import-from-folder). | +| **Project → Add → Import...** | Is not an open. It copies files of another project into the open one. See [Import](../Projects/Adding-Files#import). | + +A `.twin` file, or a folder, is not a project: the IDE answers with the [failure box](#when-a-project-will-not-open). + +## The command line + +The IDE takes one project on its command line, and opens it at once. The arguments are split at spaces, and a double quote groups words. **A path with a space in it must be quoted.** An unquoted path makes two words, and the IDE then shows the alert *Bad command line syntax.* and starts as with no project: the splash, then the dialog. + +A word that starts with `--` is a switch, and the IDE ignores any it does not know. `--no-update-recent-files` keeps the project out of **Recent**. `--buildAndExit32` and `--buildAndExit64` are described under [Compiling from the command line](../../../Features/Packages/Import-Export-Tool#compiling-from-the-command-line). A project whose name ends `.vbp` is imported as a VB6 project, by the route described under [Importing a VB6 project](Importing-VB6#the-routes). + +## One project at a time + +The IDE holds one project. To open another, from any of the ways above, it closes the open one first: + +- It cancels every open dialog. +- If the project has unsaved changes, it asks **You've made changes to this project. Save now?** The buttons are **Save**, **Discard Changes** and **Cancel**. **Save** opens **Save As** first for a project with no file; cancelling that box leaves everything as it was. **Cancel** keeps the project. [Close Project and Exit](../Projects/Saving-and-Exporting#close-project-and-exit) shows the box. +- It ends the project's compiler, then loads the new project. + +**File → Close Project**, **File → Exit** (Alt+F4) and the close box of the IDE's window ask the same question. + +To have two projects open, start the IDE a second time. Running `twinBASIC.exe` again, or double-clicking a second `.twinproj` file while an IDE is running, starts a new process with a window of its own. The first IDE and its project stay as they were. + +## The file association + +Windows opens a `.twinproj` file in the IDE through an association that the IDE writes itself. It is per user, under `HKEY_CURRENT_USER\Software\Classes`, and not for the whole machine: + +- `.twinproj` belongs to the file type `twinBASIC.ProjectFile`, with the display name *twinBASIC Project*. Its icon is the IDE's own. +- The type's **open** command is `"\twinBASIC.exe" "%1"`. The path is quoted, so a folder with spaces in its name works. +- Explorer's **New** menu offers a twinBASIC project. The entry copies the **Standard EXE** template file of the IDE folder. + +Each start of the IDE writes the `.twinproj` entry and the **New** menu entry again. The **open** command is written when its path differs from the IDE that is running. An IDE started from a folder that the association does not name therefore points the association at itself, whether or not it opens a project. A double-click on a project file then opens the IDE that ran last. + +## What happens as a project loads + +1. **The build configuration box is set.** The IDE sets the box to the choice it remembers for the project's path, **win32** or **win64**, before the project loads. A path it has never seen leaves the box as it is. [Build configuration](../Building/Build-Configuration#where-the-choice-is-kept) describes it. +2. **The progress indicator shows.** The IDE adds the path to [**Recent**](New-Project#recent), at the start of the load, and asks the compiler to load the project. When the project has loaded, it connects to the compiler, clears the *changed* mark and starts the compiler's services. The status bar changes to **tB Services: OPERATIONAL**, and the licence box shows the edition. If it does not, see [Compiler services](../Troubleshooting/Compiler-Services#the-services-badge). +3. **The project may load a second time.** See [The DEP and large-address reload](#the-dep-and-large-address-reload). +4. **The IDE restores the project's state:** the open editors, the expanded folders and the other things listed under [What the IDE remembers](#what-the-ide-remembers-of-a-project). +5. **A reference that cannot load opens Project Settings.** About two seconds after the load, the IDE opens [**Project Settings**](../Project-Settings#library-references) by itself, on **Library References**, with the rows that did not load flagged. It shows no message and writes nothing to the Debug Console. +6. **The IDE checks the packages and loads the add-ins.** With the option [**Check referenced packages are up-to-date**](../Customising/IDE-Options#project-explorer-tabs-and-start-up) on, the Debug Console lists each package with a tick or a cross. The IDE then loads the [add-ins](../AddIns/). + +**Hold Shift while a project loads** to stop the add-ins loading. The package check does not run either. The Debug Console says `[IDE] SHIFT KEY DETECTED: DISABLED LOADING OF ADDINS`, once. + +### The DEP and large-address reload + +The IDE starts a compiler with DEP off and the large-address flag on. A project whose [*Large Address Aware (LAA)*](../Project-Settings#large-address-aware-laa) or [*Data Execution Prevention Aware (DEP)*](../Project-Settings#data-execution-prevention-aware-dep) setting does not match makes the IDE close the project and load it again, with the flags it asks for. You see only the progress indicator start again. There is no message and no line in the Debug Console. The flags then follow the project. + +A project from the **Standard EXE** template sets neither flag, so a saved default project opened first in an IDE loads twice. A new project made from a card does not: **Continue** turns *Large Address Aware* on, which matches. + + +> [!WARNING] BETA 1034 +> After the IDE has loaded a project twice for its flags, and you have then pressed **Continue** in **New Project Options**, the next project you open can take the whole *Settings* of the project before it, with no message. Its sources stay its own. This happens to a saved `.twinproj`, to a `.vbp` opened from **Recent (VB6)**, and to **Import from folder...**, but not to a `.vbp` chosen through the **Import from VBP...** card. +> +> For example, open a saved project with *Large Address Aware* off, import any `.vbp` with **Import from VBP...** and press **Continue**, then open another project and choose **Discard Changes** for the import. A template does the same in place of the import, if you first open a saved project with *Large Address Aware* on. +> +> The opened project is not marked as changed, and the title bar shows its own file name, but the top of the Project Explorer tree shows the earlier project's name. **File → Save Project** (Ctrl+S) then writes the earlier project's name, application title, project ID, startup object, build path and references into the file, and the file's own settings are lost. If the top of the tree shows a name that is not your project's, do not save: close the project without saving, which leaves the file as it was, and open it again. + + +> [!NOTE] BETA 1034 +> In an IDE that has not yet loaded a project twice, a saved project with *Large Address Aware* off fails to open if it is the first project opened after a **Continue** in **New Project Options** and a **File → Close Project**. The box reads *Failed to load the project (error code MISMATCHED LAA OR DEP AWARENESS)*. The file is not changed. Choose **Keep It**, and open the project again: the second open works. + +## What the IDE remembers of a project + +The IDE saves the state of the open project every five seconds, apart from its files: the expanded folders, the open editors with their state, the Debug Console's typed history, the expanded nodes of the Variables panel, the watches, the options of the Outline, debug and problems panels, the history panels, the position and size of the tool windows, the bookmarks, the options and history of Find / Replace and Find In Project, and the history of the Memory panes. For a project that has a file, the IDE keeps it in the registry, with a value for each path. A project with no file has none. [Saving and exporting](../Projects/Saving-and-Exporting#what-the-ide-remembers-about-a-project) says more. + +**Save Project As...** also writes the state into a hidden `.meta` entry inside the project, so that it travels with the `.twinproj` file. When the project opens, the registry's copy wins, and the `.meta` entry is used when the registry has none, for example on another PC. The [IDE Option](../Customising/IDE-Options#project-explorer-tabs-and-start-up) **Restore Editors State When Opening Project** decides whether the open editors come back. The templates and the samples carry a `.meta` entry too, which is why a new project opens with files already in tabs. + +The build configuration box is remembered for each project path as well. A project that was never saved has no path, so its choice lasts for the session. + +## When a project will not open + +When the IDE cannot load a project, it closes it and shows a box: + +**Failed to load the project (error code ...)**, then the project's path, and the question **Do you want to remove it from the list of recently opened projects?** The buttons are **Remove It**, which is the default, and **Keep It**. + +![The box Failed to load the project (error code ERROR_FILE_NOT_FOUND), with the path of a project, the question Do you want to remove it from the list of recently opened projects?, and the buttons Remove It and Keep It.](../Projects/Images/Failed_To_Load.png){:width="530" height="173"} + +The code names the cause: + +| Code | Opening | +|---|---| +| `ERROR_FILE_NOT_FOUND` | a missing file, or a folder | +| `ERROR_BAD_FILE_FORMAT` | a `.twin` file, an empty file, a text file, or a project file whose first bytes are wrong | +| `MISMATCHED LAA OR DEP AWARENESS` | a project whose settings still disagree with the compiler after the reload; see the [note above](#the-dep-and-large-address-reload) | + +Any other failure shows `ERROR_GENERIC`, or `ERROR_UNKNOWN` with a number. The box leaves no project open, and writes nothing to the Debug Console. [When something goes wrong](../Troubleshooting/) covers the failures that come after a project has loaded: the compiler's services, saving and the install. The path was added to **Recent** when the load began, so the question is accurate. **Remove It** takes the path off the list, and **Keep It** leaves it there. + +### A damaged project + +A file whose first bytes are right and whose body is damaged gets no failure box. The compiler shows a native Windows box instead, titled *bad file format*. It reads *WARNING: failed to deserialize file system. Some files might be lost or truncated.* and has one **OK**. The IDE's window shows *Please wait...* until you answer it, and the box shows twice when the project also reloads for its flags. + +![The Windows box titled bad file format, with its warning and one button.](Images/Damaged_Box.png){:width="406" height="141"} + +The project then opens with what the compiler could read. It is not marked as changed, and its title is the file's name. What is kept depends on where the damage is: + +- **Damage that breaks the structure inside the `Sources` tree loses every source.** A folder that fails to read is dropped whole, so a damaged project never keeps some of its sources and loses others. Damage in the file's header, before its `Settings` entry has been read, also loses every source, and the project is named `RECOVERED_PROJECT` and has default settings. +- **Damage after the `Sources` tree keeps every source.** Damage in the `Resources` part, which follows, loses the icon. +- **Damage inside the text of one source**, with the structure intact, raises no box at all. The project opens with that text damaged. +- **With the `Settings` entry read**, the project keeps its own name and settings, even when its sources are gone. + + +> [!WARNING] BETA 1034 +> A project that opens after the *bad file format* box is not marked as changed, and the next save writes only what was recovered over the damaged file, with no question and no backup. **File → Save Project** (Ctrl+S), **Project → Add → Import...** and **Apply Changes** in Project Settings, when the change restarts the compiler, all save it. Save a damaged project only with **File → Save Project As...** under a new name: the new file holds what was recovered, and the damaged file stays as it was. Closing without saving leaves it as it was too. Copy the damaged file before you open it. + + +> [!NOTE] BETA 1034 +> A `.twinproj` file damaged inside its `Settings` entry shows the *bad file format* box again after every **OK**, without end. The IDE's window stays on *Please wait...*, with no project and no close box, and the file is not changed. End the IDE, for example in the Task Manager, and restore the file from a copy. + +## See Also + +- [New Project](New-Project) -- the dialog, and the **Recent** sections +- [Importing a VB6 project](Importing-VB6) -- the routes for a `.vbp` +- [Saving and exporting](../Projects/Saving-and-Exporting) -- save, **Save As**, close and the recent list +- [Build configuration](../Building/Build-Configuration) -- the box that each project remembers +- [Project Settings](../Project-Settings) -- *Large Address Aware (LAA)*, *Data Execution Prevention Aware (DEP)* and the other settings of a project diff --git a/docs/IDE/Starting/Samples.md b/docs/IDE/Starting/Samples.md new file mode 100644 index 000000000..c3c3bb3fd --- /dev/null +++ b/docs/IDE/Starting/Samples.md @@ -0,0 +1,89 @@ +--- +title: Samples +parent: Starting a project +grand_parent: IDE +nav_order: 5 +permalink: /tB/IDE/Starting/Samples +index: + - sample projects + - samples + - example projects +--- + +# Samples + +The IDE ships with 26 sample projects, in the **Samples** section of the [**New / Open Project**](New-Project) dialog. This page says how a sample opens, what each one shows, and what it needs before it runs. + +![The New / Open Project dialog on its Samples section: two columns of sample cards named without their numbers, the first, Reports (Experimental), selected, then HelloWorld, WebView2 Examples, Chromium Embedded Framework Examples, GetIPAddresses and MyCodeLibrary.](../Images/New_Project_Samples.png){:width="970" height="728"} + +## How a sample opens + +A sample opens like a template. Double-click its card, or select it and press **Open**. The **New Project Options** box opens first. **Continue** makes the sample as a new project, unsaved, in memory. The first save asks for a file name, and the sample on disk stays as it is. See [What a new project is](New-Project#what-a-new-project-is). + +The box's rows apply to a sample too. The **Project Name** starts as the sample's own project name, such as `HelloWorldProject` for **HelloWorld**, and **Continue** sets the project's name, description and application title to the text of the box. The **Include Common Controls** row shows for the samples that reference WinNativeForms and not WinNativeCommonCtls yet, and **Visual Styles** does not show for a sample that has a manifest already, such as **TreeView + ListView + ImageList**. See [New Project Options](New-Project#new-project-options). + +A card shows the sample's name without its number, and the cards are in the order of the numbers. The numbering is the IDE's own: **1a** and **1b** follow **1**, and **10** follows **9**. The documentation names a sample by its number, as in *Sample 6*, so the table below gives both. + +## Where a sample builds + +**Samples 10 to 16 build into the IDE's own folder.** Their *Build Output Path* is `${IdePath}\addins\${Architecture}\${ProjectName}.${FileExtension}`, so a build of one installs the add-in in the folder of the running IDE, as `addins\win32` or `addins\win64`. It loads at the next start of the IDE. Holding Shift while a project loads stops add-ins loading; see [Opening projects](Opening-Projects#what-happens-as-a-project-loads). [Add Ins](../AddIns/) describes add-ins. + +The other samples build into a `Build` folder beside the project's file, once the project is saved. + +## The samples + +| Sample | Project type | What it shows | Where the documentation uses it | +|---|---|---|---| +| **0.** Reports (Experimental) | Standard EXE | A form that opens two [reports](../../Packages/VB/Report/). One takes its rows from an ADODB recordset made in memory, the other from a class that implements **ITbRecordset** | none | +| **1.** HelloWorld | Standard EXE | The smallest program: a `Sub Main` that shows a message box | none | +| **1a.** WebView2 Examples | Standard EXE | Four forms on the WebView2 control: a browser with a log of its events, a local web page that exchanges calls with twinBASIC, the Monaco editor with a live preview, and a Markdown preview. Uses the WebView2 package | the [WebView2 tutorials](../../../Tutorials/WebView2/) | +| **1b.** Chromium Embedded Framework Examples | Standard EXE | The same four forms on the **CefBrowser** control. Uses the CEF package | the [CEF tutorials](../../../Tutorials/CEF/) | +| **2.** GetIPAddresses | Standard EXE | Calls the Windows API through **Declare**, and shows the machine's IPv4 addresses in a message box | none | +| **3.** MyCodeLibrary | ActiveX DLL | A COM class with one method, for a host such as VBA to call | none | +| **4.** MyVBEAddin (NEW with ToolWindow) | ActiveX DLL | An add-in for the VBA editor: a tool window made from a user control, and an item on the editor's **Add-Ins** menu that shows or hides it. References the Microsoft Add-In Designer and the VBA Extensibility 5.3 type libraries | the [FAQ on add-ins](../../../FAQ#addins) | +| **5.** MyCOMAddin | ActiveX DLL | A COM add-in for Excel and Access: a ribbon tab, *twinBASIC Test*, with a button that shows a message box. References the Microsoft Add-In Designer and the Office 16.0 object library | the [FAQ on add-ins](../../../FAQ#addins) | +| **6.** CustomControls | Standard EXE | Ten forms, each opening the next, on the controls of the CustomControls package: anchoring, borders and corners, buttons, a data grid, docking, fill patterns, a slider, text boxes and text rendering | the [CustomControls tutorial](../../../Tutorials/CustomControls/) | +| **7.** Package | Package TWINPACK | The smallest package: a class and a module, each with a `Show` method | [Creating a TWINPACK Package](../../../Features/Packages/Creating-TWINPACK) | +| **8.** FilePropertyViewer (CustomControls) | Standard EXE | A form that asks for a file and lists its properties in a grid. Uses the CustomControls package and the [FileProps](../../Packages/Contributed/FileProps/) package | none | +| **9.** ActiveX Control WebView2 + Monaco | ActiveX Control | A control for other programs to host. It holds the Monaco editor in a WebView2, with two events and two methods that read and set the text. Uses the WebView2 package | the WebView2 tutorial on the user data folder | +| **10.** twinBASIC IDE Addin | Standard DLL | Two toolbar buttons and a tool window of buttons that call the **Host** object: the project, the DEBUG CONSOLE and the active editor | [Add Ins](../AddIns/), the [FAQ on add-ins](../../../FAQ#addins) and the [tbIDE reference](../../Packages/tbIDE/) | +| **11.** twinBASIC IDE Addin (Chart) | Standard DLL | A line chart in a tool window, which an **AddinTimer** updates every half second with random numbers | the tbIDE reference | +| **12.** twinBASIC IDE Addin (Monaco) | Standard DLL | A Monaco editor in a tool window. Each change prints the text to the DEBUG CONSOLE | the tbIDE reference | +| **13.** twinBASIC IDE Addin (ListView) | Standard DLL | A list in a tool window, with rows of HTML, icons from a style sheet in the project, and a text box in one row. Clicks and key presses reach twinBASIC as events | the tbIDE reference | +| **14.** twinBASIC IDE Addin (VirtualListView) | Standard DLL | A list of 5 million rows in a tool window, which asks twinBASIC for the HTML of a row only when it needs the row | the tbIDE reference | +| **15.** twinBASIC IDE Addin (GlobalSearch) | Standard DLL | The project of the [Global Search](../AddIns/GlobalSearch) add-in that ships with the IDE: a search of the project's files in a tool window, where a click opens a match | the tbIDE reference | +| **16.** twinBASIC IDE Addin (TODO Widgets demo) | Standard DLL | Two toolbar buttons that add a TODO note as a widget inside the code editor and list the notes in a tool window. The notes are saved in the project | none | +| **17.** Static Library Example (SQLITE3) | Standard EXE | Links SQLite into the EXE from `.obj` files, so that the EXE needs no DLL. It makes a database in memory, adds three rows and shows each in a message box | [Static Linking](../../../Features/Advanced/Static-Linking) | +| **18.** Static Library Example (libdeflate) | Standard EXE | Links libdeflate into the EXE from `.a` files in the same way. It compresses a string and expands it again, and prints the bytes to the DEBUG CONSOLE | [Static Linking](../../../Features/Advanced/Static-Linking) | +| **19.** MDI Forms | Standard EXE | An MDI form with red and blue child forms. Its **File** menu adds a child, and its **Window** menu arranges and lists them | none | +| **20.** TreeView + ListView + ImageList | Standard EXE | A form of buttons that fill and clear a TreeView and a ListView, change their icons from an ImageList and switch the ListView's views. Uses the WinNativeCommonCtls package | none | +| **21.** Windows Service Simple Example | Standard EXE | A Windows service that runs for 15 seconds, in an EXE that installs itself. Uses the WinServicesLib package | none | +| **22.** Windows Service Complex Example (inc Event Logging and IPC) | Standard EXE | Two services in one process, which write to the event log and answer requests over named pipes, and a form that installs, starts, stops and queries them. Uses the WinServicesLib, WinEventLogLib and WinNamedPipesLib packages | none | +| **23.** OOP Inheritance Example (Animals) | Standard EXE | A small class hierarchy that prints to the DEBUG CONSOLE, with **Inherits**, **Protected**, **Overridable**, **Overrides** and an inherited event | [Inheritance](../../../Features/Language/Inheritance) | + +Samples 10 to 16 use the [tbIDE](../../Packages/tbIDE/) package, and its reference uses Samples 10 to 15 as its examples. + +## Before you run a sample + +Most samples run with **Start** (F5) as they are. These need something more. + +**Samples 3, 4, 5, 7 and 9 to 16 are not programs.** Each makes a DLL, a control or a package, so **Start** has nothing to run and ends with a compile error in the console. Build them instead: + +- A build of Sample 3, 4, 5 or 9 [registers the file](../Building/ActiveX-Registration) for the current user, and **Clean** removes the registration. The file must match the program that loads it: for 64-bit Office, choose **win64** in the [build box](../Building/Build-Configuration). +- The registration of Sample 4 adds it to the VBA editor, and the registration of Sample 5 adds it to Excel and to Access. Each then loads when that program starts. +- Samples 10 to 16 install themselves in the IDE, as [Where a sample builds](#where-a-sample-builds) describes. + +**Sample 23 has no `Main`,** so **Start** ends with a compile error there too. Open `Main.twin`, put the caret inside `Sub DemoAnimals` and press F6. See [Run Or Preview](../Debugging/Running#run-or-preview). + +**Samples 1a, 1b and 9 use a browser engine.** Samples 1a and 9 need the WebView2 Runtime. Sample 1b needs the CEF runtime, which is a separate download: see [Runtime files](../../Packages/CEF/#runtime-files). The first form of Samples 1a and 1b opens a web site, so it needs a network connection. Samples 1a and 1b make the folders `tbVirtualHostDemo` and `tbMonacoDemo` in your Documents folder, and Sample 9 makes `tbMonacoActiveXDemo` in your local application data folder. These folders stay after the sample ends. + +**Samples 21 and 22 install Windows services,** which needs administrator rights, so they do it only from a built EXE that you start with **Run as administrator**: + +- Sample 21 only shows a message box in the IDE. The built EXE offers to install the service, `TBSERVICE000`, which then starts on demand. The sample has no command that removes it. To remove it, run `sc delete TBSERVICE000` from a command prompt started as administrator. +- Sample 22 opens its form in the IDE too, but **Install** refuses there. Its **Uninstall** buttons remove the services. **Launch in-process named pipe server** needs no service, so you can try the form's named pipes without installing anything. + +## See Also + +- [New Project](New-Project) -- the dialog, and **New Project Options** +- [What Start runs](../Debugging/Running#what-start-runs) -- the startup object, and what **Start** does without one +- [Add Ins](../AddIns/) -- the add-in samples, 10 to 16 diff --git a/docs/IDE/Starting/index.md b/docs/IDE/Starting/index.md new file mode 100644 index 000000000..922a0e532 --- /dev/null +++ b/docs/IDE/Starting/index.md @@ -0,0 +1,40 @@ +--- +title: Starting a project +parent: IDE +nav_order: 2 +permalink: /tB/IDE/Starting/ +has_toc: false +index: + - start a project + - first start of the IDE +--- + +# Starting a project + +This chapter follows the IDE from the moment it starts to a project that is open: what the first start asks, the dialog that makes or opens a project, every way of opening one, importing a VB6 project, and the sample projects that ship with the IDE. + +- [**First launch**](First-Launch) --- the questions the first start asks, the splash, the empty IDE, the licence and the edition. +- [**New Project**](New-Project) --- the **New / Open Project** dialog: its sections, its search and keys, what each template makes, the **New Project Options** box, and the recent lists. +- [**Opening projects**](Opening-Projects) --- the ways to open a project, one project at a time, the file association, what happens as a project loads, and what to do when one will not open. +- [**Importing a VB6 project**](Importing-VB6) --- the routes for a `.vbp` file, what the importer converts and what it leaves, and its log. +- [**Samples**](Samples) --- the 26 sample projects: what each is, and where the documentation uses it. + +## From a double-click to a project + +When the IDE starts, it shows a banner picture while it reads its saved settings. Three questions can come before anything else, each only until you have answered it once: the licence agreement, whether the IDE may send error reports, and, on the very first start, the **IDE Options** dialog. [First launch](First-Launch) describes them. + +What happens next depends on how the IDE was started: + +- **With a project on the command line**, for example by a double-click on a `.twinproj` file, the IDE opens that project at once. It shows no splash and no **New / Open Project** dialog. See [Opening projects](Opening-Projects). +- **With no project**, for example from the IDE's own icon, the IDE shows the [splash](First-Launch#the-splash) for two and a half seconds, and then the [**New / Open Project**](New-Project) dialog. + +The dialog makes a project from a template or a sample, imports one, or opens a recent project. A project made from a template or a sample, or imported, is new and has no file on disk until you save it. See [What a new project is](New-Project#what-a-new-project-is). + +The compiler starts only when a project loads. Until then the [status bar](../Reference/StatusBar) reads **tB Services: UNAVAILABLE** and **tB Licence: NOT READY**, and the editor's language features have nothing to work on. [The empty IDE](First-Launch#the-empty-ide) says what works without a project. + +## See Also + +- [Projects and files](../Projects/) -- the Project Explorer, adding files, saving and exporting +- [Project Settings](../Project-Settings) -- the settings of a project +- [Customising the IDE](../Customising/) -- the options for the start-up dialog and the other IDE settings +- [When something goes wrong](../Troubleshooting/) -- the compiler, a save or the install failing diff --git a/docs/IDE/Status Bar.md b/docs/IDE/Status Bar.md index 75e10453a..3288b54b9 100644 --- a/docs/IDE/Status Bar.md +++ b/docs/IDE/Status Bar.md @@ -2,7 +2,7 @@ title: Status Bar parent: IDE Reference grand_parent: IDE -# nav_order: 2 +nav_order: 13 permalink: /tB/IDE/Reference/StatusBar redirect_from: - /tB/IDE/Project/StatusBar @@ -14,7 +14,7 @@ redirect_from: With no project open, no compiler is connected: the services badge reads **UNAVAILABLE** and the licence badge reads **tB Licence: NOT READY**. Both change once a project has loaded and the compiler has answered. -The Status Bar runs along the bottom of the IDE window. It has four regions, left to right: the health of the backend services, the active licence tier, links to community resources, and the name of the command currently under the mouse cursor. +The Status Bar runs along the bottom of the IDE window. From left to right it holds the [services badge](#services), the [licence badge](#licence), the [links](#links) to community resources, a [**PROCESS** box](#other-boxes) for each program that your program started, the [progress bar](#other-boxes), the [command name](#status), the [memory box](#other-boxes) and the [line and column box](#other-boxes). The progress bar, the memory box and the line and column box show only in some states. ## Services @@ -30,37 +30,63 @@ The Status Bar runs along the bottom of the IDE window. It has four regions, lef The badge's tooltip has one line for each of the four connections between the IDE and the compiler: **COMPILER**, **FS** (the project's files), **LSP** (the editor's language features, over the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/)) and **DEBUGGER**. Each line gives that connection's state: -- **Not initialized** --- the connection has not been made, as with no project open; +- **Not initialized** --- the connection has not been made, because no project has loaded since the IDE started; - **Connecting** or **Disconnecting** --- the connection is being opened or closed; - **OPERATIONAL** --- the connection is open; -- **Disconnected** --- the connection has closed; +- **Disconnected** --- the connection has closed, as it has after **Close Project**; - **Error occured** --- the connection failed. The IDE spells it this way. -The badge reads **LIMITED** while some of the four are connected and some are not, as it does for a moment while a compiler starts. +The badge reads **OPERATIONAL** when all four are open, **LIMITED** when one to three are open, as they are for a moment while a compiler starts, and **UNAVAILABLE** when none is. A running program does not change it. The badge cannot be clicked. [Compiler services](../Troubleshooting/Compiler-Services#the-services-badge) says what to do when it does not return to OPERATIONAL, and what a crash looks like. ## Licence -- [Pre Order](https://twinbasic.com/preorder.html) +The licence badge reads **tB Licence: NOT READY** until the compiler answers. After that it reads the edition alone, without the prefix: + +| The badge reads | Look | Meaning | +|---|---|---| +| **PERSONAL EDITION** | green, with a tick | A Personal licence. | +| **PROFESSIONAL EDITION** | green, with a tick | A Professional licence. | +| **ULTIMATE EDITION** | green, with a tick | An Ultimate licence. | +| **COMMUNITY EDITION** | orange or olive, with a warning triangle | No key, an invalid key, or any other answer. | ![An olive status bar badge, orange in the light theme, with a warning triangle, reading COMMUNITY EDITION.](Images/Licence_CommunityEdition.png){:width="151" height="25"} -- Community Edition -- Professional Edition -- Ultimate Edition +Pointing at the badge shows the edition again. Clicking it opens the **Enter Licence Key** dialog, once a project is loaded; **Help → Enter Licence Key...** opens the same dialog at any time, and **Help → Purchase A Licence...** opens [the pre-order page](https://twinbasic.com/preorder.html). [First launch](../Starting/First-Launch#the-licence-and-the-edition) describes the dialog and the editions. + +When the compiler reports that a licence has expired or has been revoked, the badge keeps its text, and a box titled **Licence Problem!** says so. [The licence](../Troubleshooting/Missing-Files#the-licence) lists what else can go wrong. ## Links ![Four icons in the status bar, after its badges: a Ko-fi coffee cup holding a heart, the Discord face, the Twitter bird and the GitHub cat.](Images/Links.png){:width="128" height="27"} -- https://ko-fi.com/twinbasic -- https://discord.com/invite/UaW9GgKKuE -- http://x.com/waynephillipsea -- https://github.com/twinbasic/twinbasic +A click on an icon opens a page in your default browser. Nothing opens inside the IDE. + +- Ko-fi: https://ko-fi.com/twinbasic +- Discord: https://discord.gg/UaW9GgKKuE +- Twitter: https://twitter.com/waynephillipsea +- GitHub: https://github.com/twinbasic/twinbasic --- the repository's front page, not the form for a new issue + +The same four are items of the [Help menu](Menu/Help). See [Reporting a problem](../Troubleshooting/#reporting-a-problem). + +## Other boxes + +- **PROCESS** boxes --- one green box, `PROCESS : RUNNING`, for each program that your program started. See [Programs that your program starts](../Debugging/Running#programs-that-your-program-starts). +- **The progress bar** --- a bar with the compiler's own text on it. It shows only while the compiler reports progress, for example while LLVM compiles. +- **The memory box** --- `MB in use | ui objects | gdi objects`, the figures of the running program. It shows only when the IDE Option **IDE: Show memory and resource usage** is on and a project is loaded. See [IDE Options](../Customising/IDE-Options). +- **The line and column box** --- `Ln , Col `, shown only while a code editor is the active tab. A click opens the editor's own box for going to a line. It is hidden for a form designer, an image's preview and a Settings editor. + +The four counters of errors, warnings, hints and infos are not in the status bar. They are in the header of the [DIAGNOSTICS panel](../Code/Diagnostics). ## Status -The rightmost region names the command a click would trigger --- the command under the mouse cursor. It gives the IDE's internal command identifier rather than the menu caption, so pointing at **Close Project** on the File menu shows **tbProject_Close**, as in the screenshot at the top of this page. +The region left of the memory box names the command a click would trigger --- the command under the mouse cursor. It gives the IDE's internal command identifier rather than the menu caption, so pointing at **Close Project** on the File menu shows **tbProject_Close**, as in the screenshot at the top of this page. -The name stays on screen after that command has run. It is replaced only when the pointer moves over another control that has a command of its own, and the region is blanked when the pointer moves into an area with no such control. +The name goes when the pointer leaves the icon or the menu item. It stays only when the item goes away under the pointer: a menu that closes on a click or on Esc leaves the name of the last item it showed, until the pointer enters another item that has a command. These are the same identifiers the keyboard shortcut map is keyed by --- see [Window](Menu/Window) for the full list and the keys bound to each one. + +## See Also + +- [When something goes wrong](../Troubleshooting/) -- the badges' failures, and what to do +- [Compiler services](../Troubleshooting/Compiler-Services) -- the services badge, restarts and Safe Mode +- [Customising the IDE](../Customising/) -- the IDE Options and where the IDE keeps its settings diff --git a/docs/IDE/Toolbar.md b/docs/IDE/Toolbar.md index ab6f4b704..a31c2a56f 100644 --- a/docs/IDE/Toolbar.md +++ b/docs/IDE/Toolbar.md @@ -2,7 +2,7 @@ title: Toolbar parent: IDE Reference grand_parent: IDE -# nav_order: 3 +nav_order: 12 permalink: /tB/IDE/Reference/Toolbar redirect_from: - /tB/IDE/Project/Toolbar @@ -10,48 +10,97 @@ redirect_from: # Toolbar -![The IDE toolbar with no project open. Every button is greyed out, from save and find through the start, break, stop and stepping controls to the build, alignment, resize and z-order groups; the zoom box beside the PREVIEW button reads 100% in grey, and there is no Global Search button at the right. Only the build configuration box, reading win32, is active.](Images/Toolbar_1.png){:width="1128" height="35"} - -![The same toolbar with a project open but not running, and no file open. Save and find are active, Start is a green triangle, Restart the compiler a blue circular arrow, and Clean and Build are active, while Break, Stop and the stepping controls stay greyed out. A Global Search button now sits at the right, and the zoom box still reads 100% in grey.](Images/Toolbar_2.png){:width="1228" height="35"} - -![The same toolbar with a form open in the designer. Switch Between Form And Code has become a red circle holding a white lightning bolt and the zoom box reads 100%; Start is still green, the debugging buttons are still grey, and the Global Search button is still at the right.](Images/Toolbar_3.png){:width="1228" height="35"} - -![The same toolbar while the project runs. Start has gone grey, Break is blue and Stop is a red square; the red form and code button and the 100% zoom box are unchanged.](Images/Toolbar_4.png){:width="1228" height="35"} - -- Save All Changes (Ctrl+S) -- Find In Project... (Ctrl+Shift+F) -- Switch Between Form And Code -- Undo -- Redo -- Start / Continue (F5) -- [Start, Break and End](../Debugging/Running#start-break-and-end) -- Break Into Code (Ctrl+Break) -- [Break](../Debugging/Running#break) -- Stop -- [Start, Break and End](../Debugging/Running#start-break-and-end) -- Step Over (Shift+F8 / F10) -- [Step commands](../Debugging/Breakpoints-and-Stepping#step-commands) -- Step Into (F8 / F11) -- [Step commands](../Debugging/Breakpoints-and-Stepping#step-commands) -- Step Out (Ctrl+Shift+F8 / Shift+F11) -- [Step commands](../Debugging/Breakpoints-and-Stepping#step-commands) -- [Choose a build configuration](#build-configuration) -- [Build configuration](../Building/Build-Configuration) -- Restart the compiler -- [Restart the compiler](../Building/Build-Configuration#restart-the-compiler) -- Clean (deregister & delete build) -- [Clean](../Building/Build-and-Clean#clean) -- Build -- [Build](../Building/Build-and-Clean#build) -- Comment Selection (Ctrl+K) -- Uncomment Selection (Ctrl+Shift+K) -- Indent Block (Ctrl+]) -- Outdent Block (Ctrl+[) -- Align controls to the LEFT (Alt+Left) -- Align controls to the TOP (Alt+Up) -- Align controls to the RIGHT (Alt+Right) -- Align controls to the DOWN (Alt+Down) -- Resize controls to the WIDEST (Ctrl+Shift+Right) -- Resize controls to the TALLEST (Ctrl+Shift+Down) -- Resize controls to the NARROWEST (Ctrl+Shift+Left) -- Resize controls to the SHORTEST (Ctrl+Shift+Up) -- Toggle the grid indicators ON/OFF -- Bring the selected control to the FRONT of the z-order -- Send the selected control to the BACK of the z-order -- Form designer zoom ratio -- Launch the form in isolation to test the functionality -- [Run Or Preview](../Debugging/Running#run-or-preview) -- Change IDE Theme -- Global Search +The toolbar is the row of buttons under the menu bar. Most buttons run a command that a menu holds too, and a button's tooltip gives its name and its key. A button is greyed out while its command cannot run, so the row changes with what you are doing. + +With no project open, every button is greyed out. + +![The toolbar with no project open, every button greyed out.](Images/Toolbar_1.png){:width="1128" height="35"} + +With a project open, the buttons for saving, finding, starting and building are available. + +![The toolbar with a project open and no program running.](Images/Toolbar_2.png){:width="1228" height="35"} + +With a form open in the designer, **Switch Between Form And Code** turns red, and the zoom box shows the form's zoom. The buttons that arrange controls wait for a selection. + +![The toolbar with a form open in the designer.](Images/Toolbar_3.png){:width="1228" height="35"} + +While a program runs, **Start / Continue** is greyed out, and **Break Into Code** and **Stop** are available. + +![The toolbar while a program runs.](Images/Toolbar_4.png){:width="1228" height="35"} + +## Saving and finding + +**Save All Changes** (Ctrl+S) +: Saves the whole project, as **File → Save Project** does. See [Save Project](../Projects/Saving-and-Exporting#save-project). + +**Find In Project...** (Ctrl+Shift+F) +: Opens the **FIND / REPLACE** panel with **Current Project** ticked. See [Search the whole project](../Code/Finding#search-the-whole-project). + +**Switch Between Form And Code** +: Opens the code file from the designer, and the designer from the code file. It has no key. See [Switch between design and code](../Forms/Designer#switch-between-design-and-code). + +**Undo** and **Redo** +: Step back and forward through the changes to the file or the form. See [Undo and redo](../Code/Editor#undo-and-redo). + +## Running and stepping + +**Start / Continue** (F5), **Break Into Code** (Ctrl+Break) and **Stop** +: Start the program or continue it, pause it, and end it. See [Start, Break and End](../Debugging/Running#start-break-and-end). + +**Step Over** (Shift+F8, F10), **Step Into** (F8, F11) and **Step Out** (Ctrl+Shift+F8, Shift+F11) +: Run a paused program one statement at a time. **Step Out** is on no menu. See [Step commands](../Debugging/Breakpoints-and-Stepping#step-commands). + +## Building + +**Choose a build configuration** +: The box that chooses 32-bit or 64-bit. See [Build configuration](#build-configuration), below. + +**Restart the compiler** +: Ends the compiler and starts a new one. With Alt held, it also restarts the process that caches LLVM code. See [Restart the compiler](../Building/Build-Configuration#restart-the-compiler). + +**Clean (deregister & delete build)** +: Deregisters the previously built DLL, if there is one, and deletes the previous build file. See [Clean](../Building/Build-and-Clean#clean). + +**Build** +: Compiles the project into a file. See [Build](../Building/Build-and-Clean#build). + +## Editing code + +**Comment Selection** (Ctrl+K) and **Uncomment Selection** (Ctrl+Shift+K) +: Put `' ` in front of each selected line, or take it away. See [Edit the text](../Code/Editor#edit-the-text). + +**Indent Block** (Ctrl+]) and **Outdent Block** (Ctrl+[) +: Move the selected lines one level in or out. See [Indent and outdent](../Code/Editor#indent-and-outdent). + +## Arranging controls + +These buttons act on the form designer. The [Format menu](Menu/Format) holds most of the same commands, and [Arranging controls](../Forms/Arranging) explains them. + +**Align controls to the LEFT**, **TOP**, **RIGHT** and **BOTTOM** (Alt+Left, Alt+Up, Alt+Right and Alt+Down) +: Move every selected control to that edge of the selection. See [Align](../Forms/Arranging#align). + +**Resize controls to the WIDEST**, **TALLEST**, **NARROWEST** and **SHORTEST** (Ctrl+Shift+Right, Ctrl+Shift+Down, Ctrl+Shift+Left and Ctrl+Shift+Up) +: Give every selected control the size of the widest, tallest, narrowest or shortest one. See [Make the controls the same size](../Forms/Arranging#make-the-controls-the-same-size). + +**Toggle the grid indicators ON/OFF** +: Shows or hides the form's grid. Pointing at the button opens the grid options. See [Grid and outlines](../Forms/Designer#grid-and-outlines). + +**Bring the selected control to the FRONT of the z-order** and **Send the selected control to the BACK of the z-order** +: Change the order in which controls are drawn. See [Bring to front and send to back](../Forms/Arranging#bring-to-front-and-send-to-back). + +**Form designer zoom ratio** +: The box that scales the form designer. See [Zoom](../Forms/Designer#zoom). + +**PREVIEW** +: Titled **Launch the form in isolation to test its functionality**, it runs the form on its own, in a window of its own. See [Previewing a form](../Forms/Previewing). + +## Other buttons + +**Change IDE Theme** +: Opens the list of themes. See [Themes](../Customising/Themes). + +**Global Search** +: Opens the window of the [Global Search](../AddIns/GlobalSearch) add-in, which adds this button. Other add-ins can add buttons of their own here. ## Build configuration diff --git a/docs/IDE/Troubleshooting/Compiler-Services.md b/docs/IDE/Troubleshooting/Compiler-Services.md new file mode 100644 index 000000000..01fc20606 --- /dev/null +++ b/docs/IDE/Troubleshooting/Compiler-Services.md @@ -0,0 +1,209 @@ +--- +title: Compiler services +parent: When something goes wrong +grand_parent: IDE +nav_order: 1 +permalink: /tB/IDE/Troubleshooting/Compiler-Services +index: + - compiler crash + - restart the compiler + - crash loop + - safe mode + - services badge + - tB Services +--- + +# Compiler services + +The IDE does not compile anything. When a project loads, it starts a compiler process and opens four connections to it, each named on the status bar: **COMPILER** (the control channel: load, save, build, restart), **FS** (the project's files), **LSP** (the editor's language features: completion, hover, the DIAGNOSTICS rows) and **DEBUGGER** (running, pausing, the console and the debugging panels). This page describes how the status bar shows those connections, what a crash of the compiler looks like, what **Restart the compiler** does, and the states the IDE falls into when the compiler cannot be used. + +## The services badge + +The first box of the [status bar](../Reference/StatusBar) shows how many of the four connections are open. + +| Open connections | The badge reads | Look | +|---|---|---| +| 4 | **tB Services: OPERATIONAL** | green, with a tick | +| 1 to 3 | **tB Services: LIMITED** | orange or olive, with a warning triangle | +| 0 | **tB Services: UNAVAILABLE** | red, with a warning triangle | + +![A green status bar badge with a tick, reading tB Services: OPERATIONAL.](../Images/Services_Operational.png){:width="175" height="24"} + +Pointing at the badge shows one line for each connection, in the order **COMPILER**, **FS**, **LSP**, **DEBUGGER**. Each line gives the state of the connection: + +| The line reads | The connection is | +|---|---| +| **Not initialized** | not made yet, because no project has loaded since the IDE started | +| **Connecting** | being opened | +| **OPERATIONAL** | open | +| **Disconnecting** | being closed | +| **Disconnected** | closed. This is what the lines read after **Close Project**, and for a moment while a compiler ends | +| **Error occured** | broken, or it could not be made. The IDE spells it so | + +![The tooltip of the badge with a project open, four lines each giving a connection as OPERATIONAL.](../Images/Services_Operational_Tooltip.png){:width="158" height="73"} + +The words OPERATIONAL, LIMITED and UNAVAILABLE come from the IDE's language pack, and the rest are in English whatever the language. + +When a compiler starts, the badge goes through UNAVAILABLE and LIMITED to OPERATIONAL in about one to two seconds. The **COMPILER** connection opens first, and then **FS**, **LSP** and **DEBUGGER**, in that order. A badge that stays on UNAVAILABLE or LIMITED, with the lines to tell which connection is missing, is the sign that something is wrong. A program that is running, paused or stopped does not change the badge. + +The badge cannot be clicked. + +![The services badge on LIMITED with its tooltip open above it: COMPILER and FS read OPERATIONAL, and LSP and DEBUGGER read Disconnected.](Images/Services_Limited_Tooltip.png){:width="350" height="104"} + +## A crash + +When the compiler process ends without being asked to, whether it crashed or was killed, the IDE notices that all four connections have closed. It does not show a box or a message. The badge goes to UNAVAILABLE, the four lines read **Disconnected**, and the IDE starts a new compiler at once. The only trace is one line in the **DEBUG CONSOLE**: + +```text +restarting from MEMORY [] +``` + +The line reads `restarting from FILE` instead when the IDE holds no copy of the project in memory. About one to two seconds after the crash, the badge is OPERATIONAL again. + +If a program was running or paused, it has ended, and the console shows no `aborted` line of its own: it goes from the program's last output straight to `restarting from MEMORY`. The Call Stack, the Variables panel, the list of **PROCESS** boxes and the Memory panel are empty. Your unsaved edits are still there; see [What a restart keeps and loses](#what-a-restart-keeps-and-loses). + +A crash of the compiler is not the same as a native exception in your program. A native exception prints a red `NATIVE EXCEPTION` line, ends the program, and leaves the compiler running, so the badge does not move. See [Native exceptions](Diagnostic-Tools#native-exceptions). + +## Restart the compiler + +**Restart the compiler** ends the compiler process and starts a new one, which loads the project from the IDE's memory. It is the repair for most states on this page. + +The IDE asks no question before it restarts the compiler, whichever way you cause it: + +| What you do | Notes | +|---|---| +| Press the toolbar button **Restart the compiler** | It has no key. With Alt held, it also ends the process that caches LLVM code, and then restarts. | +| **Tools → Restart the compiler** | Greyed out with no project open. | +| **Tools → Flush the LLVM compiler cache** | Ends the cache process, then restarts. | +| **Help → Compiler services TRACE mode** | Switches the mode and restarts. See [TRACE mode](Diagnostic-Tools#trace-mode). | +| Change the build configuration box, between **win32**, **win64** and **safeMode** | See [Build configuration](../Building/Build-Configuration). | +| Change an IDE Option that needs it | [IDE Options](../Customising/IDE-Options) marks these. | +| Change a Project Settings value that needs it | The console writes `[COMPILER] Project settings changes require compiler-restart`, the IDE saves the project without asking, and the compiler restarts after the save. A setting that does not need it writes `[COMPILER] Project settings updated`. | +| Rename a project, or add a package that forms need | See [Projects and files](../Projects/). | +| Choose **OK** in the box *The IDE timed out while trying to save the project.* | See [When the compiler does not answer](#when-the-compiler-does-not-answer). | +| A crash | Automatic. | + +The IDE ends the old compiler by force, and does not ask it to close. A box titled **Please wait...**, with a percentage bar, covers the IDE until the new compiler has the project. It lasts about a second for a small project, and about eleven seconds after a switch of the build configuration box. + +![The Please wait... box with its percentage bar, in front of the editor, during a restart.](Images/Please_Wait.png){:width="878" height="454"} + +The licence is asked again after a restart, so the licence box reads **tB Licence: NOT READY** for a moment and then the edition. + +### What a restart keeps and loses +{: #what-a-restart-keeps-and-loses } + +The IDE replays everything you did in this session into the new compiler, so the new compiler holds the project as the IDE shows it. + +| Kept | Notes | +|---|---| +| Unsaved edits, and the changed mark in the title bar | The IDE sends the text of the open editors, and replays each file you created, moved, deleted or wrote. | +| Open editor tabs, the tab that is selected, the caret | The restart does not close tabs. | +| Breakpoints | Including those you set since the last save. | +| Watches, bookmarks, the history of the Debug Console, of **FIND / REPLACE** and of the **HISTORY** panel, the Outline filters | The watches show **not available** until the next run. | +| Open form designers | The IDE presses **RESYNC** itself when the project has loaded. | +| The project, its path, the IDE Options, the layouts | Untouched. | + +| Lost | Notes | +|---|---| +| A running or paused program | It dies at once. `Form_Unload` and `Class_Terminate` do not run. | +| The contents of the Call Stack, Variables and Memory panels | They fill again at the next stop. | +| The **PROCESS** boxes, and the processes that your program started | The restart ends them. | +| The contents of the DIAGNOSTICS panel | They come back when the compiler has compiled the project again. | +| The add-ins' toolbar buttons, menu items and key bindings | The IDE removes them and loads the add-ins again when the project has loaded. A docked add-in window stays where it is and reads **(currently unavailable)** until its add-in fills it again. A detached window gets a message to close. The add-in's own `Class_Terminate` does not run. | + + +> [!NOTE] BETA 1034 +> Each restart reverses the order of the project's files inside the compiler, and so does each switch of the build configuration box. **Save** writes that order into the project's file. The code of a built executable follows it, so two builds of an unchanged project can place their code differently. A second restart puts the order back. To compare two builds, make them with no restart, switch or save between them. + +A restart of a project with a linked package whose file is gone behaves differently; see [A linked package's missing file](#a-linked-packages-missing-file). + +The text of the DEBUG CONSOLE is not cleared by a restart. Only closing the project clears it. + +## The crash loop + +If the compiler keeps ending, restarting it again and again does nothing useful. The IDE counts its automatic restarts. When it has made a fourth within 60 seconds, it switches to Safe Mode, and the floating message box says: + +> Compiler crash loop detected. Restarting in SAFE mode. + +The build configuration box then reads **safeMode**. A **Please wait...** box follows for a moment, and then the Safe Mode box opens. All of this takes about half a second. + +The count starts with one entry whenever a project opens, whenever you press **Restart the compiler**, and whenever you change the build configuration box. So **three crashes within 60 seconds of opening a project, of a restart or of a switch of the build box** are enough; after a longer calm, four are needed. Pressing the button over and over does not cause the loop by itself. + +![The IDE in a crash loop: the Safe Mode box in front, the floating message above the status bar, the build configuration box on safeMode, and one restart line for each crash in the DEBUG CONSOLE.](Images/Crash_Loop.png){:width="1280" height="880"} + +The floating message stays for a minute. Safe Mode gives you access to the project's files, so that you can copy them out or fix them. + +## Safe Mode + +Safe Mode is the third entry of the build configuration box, **safeMode**. The IDE chooses it by itself in a crash loop, and you can choose it. The compiler still starts, but it does not compile. The IDE still makes the four connections. + +When the project has loaded in Safe Mode, a box opens with these words: + +![The box the IDE shows once a project is open in Safe Mode, with an OK button.](../Building/Images/Safe_Mode.png){:width="556" height="192"} + +> SafeMode is designed to give you access to the file system of your project, without starting all of the associated compiler services. Use this mode to investigate / fix / recover your code before saving and restarting in normal mode. + +It has one **OK** button and does not close by itself. Another dialog that opens closes it. + +**The badge reads OPERATIONAL in Safe Mode, with four lines that read OPERATIONAL, although the language services do not work.** It is the one state in which OPERATIONAL does not mean that. Here is what works and what does not: + +| Works | Does not | +|---|---| +| Opening and editing the project's files | **Build** and **Clean** look available and do nothing: no line, no box. | +| Ctrl+S and **Save All Changes**, which write the project | **Start** does not run the program (see the note below). | +| Unsaved edits, which survive the switch into Safe Mode and out of it | Hover shows nothing, and completion lists nothing. | +| **Export Project** and **Restart the compiler** | The form designer's **RESYNC** answers **Can't resync the form designer in Safe Mode**. | +| The rows that DIAGNOSTICS held before | The DIAGNOSTICS counters read 0 while the rows stay. | + +To leave Safe Mode, choose **win32** or **win64** in the build configuration box. That restarts the compiler, and the counters come back when the project has compiled again. The choice of **safeMode** is not remembered for the project. [Build configuration](../Building/Build-Configuration#safe-mode) describes the box. + + +> [!NOTE] BETA 1034 +> **Start** in Safe Mode never runs the program and never ends. The console prints `Executing 'Main'...` and `WARNING: execution delayed; waiting for background compilation to finish`, then nothing more; the IDE stays in the running state and **Stop** does nothing. Restart the compiler, or choose **win32** or **win64** in the build configuration box, to end it. To run the program, leave Safe Mode first. + +![The left end of the toolbar with the build configuration box on safeMode, above the left end of the status bar with the services badge on OPERATIONAL.](Images/Safe_Mode_Bar.png){:width="474" height="70"} + +## When the compiler does not answer + +The IDE has no watch on the compiler and no state for a compiler that is not responding. Its only protection is a time limit around some operations. Each time-out opens a box with one **OK** button. + +| When | Waits | The box says | +|---|---|---| +| Sending the open documents to a new compiler | 10 s | *The IDE timed out while trying to synchronize the currently open documents.*, then that the load will try to continue and that you should make backups of recently edited documents by hand | +| Replaying your file operations into a new compiler | 10 s, for each operation | *The IDE timed out while trying to reload the project from memory.*, and the same advice | +| Saving the open documents | 10 s | *The IDE timed out while trying to save the currently open documents.*, and the same advice | +| Writing the project | 20 s | *The IDE timed out while trying to save the project.*, and that the background compiler process will now restart automatically and you should then try to save again | +| Any of the first three, when a document raises an error | at once | *<n> errors occured when trying to synchronize changes of the open documents in the IDE with the compiler process.* (or *to save changes of the open documents in the IDE.*), the same advice, and *Check the DEBUG CONSOLE for details.* The console names each document, as `[ERROR] synchronizing open document ` | + +The first three boxes let the operation go on. **OK** in the fourth restarts the compiler. When you see any of these, copy the text of the files you changed into another program before you do anything else. + +Two shorter messages mean the same thing. A designer or Properties action while a request is outstanding says **Backend compiler seems to be busy. Check there isn't a modal dialog box open behind the IDE window.** A debugger command with no DEBUGGER connection writes `ERROR: DEBUGGER IS NOT CONNECTED.` in red to the console. + +## A linked package's missing file +{: #a-linked-packages-missing-file } + +A project can reference a [linked package](../../../Features/Packages/Linked), a package file that stays in `%APPDATA%\twinBASIC\packages`. If that file is gone, the project still opens. The References part of Project Settings opens by itself, and the package's row reads **ERROR unable to load this reference**. DIAGNOSTICS counts the names the package would have given. + +**Restart the compiler** with the file gone shows a native Windows box about a second after you press the button. It is titled **shutdown** and reads `Run-time error '-2147467259'` and `Unspecified error`, with one **OK**. Behind it the badge reads LIMITED, and **Please wait...** reads 5.0%. + + +> [!NOTE] BETA 1034 +> The box that a restart shows when a linked package's file is missing names neither the package nor the file, and the restart takes about 80 seconds after **OK**. If you leave the box open, the badge reads UNAVAILABLE after about 80 seconds and **Please wait...** stays at 0.0%. Choose **OK** and wait: the compiler comes back with the project loaded and your unsaved edits kept. Put the package's file back, or remove the reference, before the next restart. + +While you wait, you can still get at the text of the editors: close the IDE's window, which removes **Please wait...**, and choose **Cancel** in the question about the unsaved changes. The IDE is usable again and you can copy the text out. Saving does not work in that state; the time-out boxes above come after 10 and 20 seconds, and the file is not changed. + +## A connection that cannot be made +{: #a-connection-that-cannot-be-made } + +If the IDE starts a compiler and cannot connect to it, the **COMPILER** line of the badge's tooltip reads **Error occured**, and the badge reads UNAVAILABLE. + + +> [!NOTE] BETA 1034 +> A compiler whose **COMPILER** connection cannot be made is started again every 5 seconds, and each start writes `restarting from FILE` to the console. The crash loop never counts these restarts, so the IDE does not switch to Safe Mode and does not say anything. Close the IDE's window and start it again. If the project does not load the second time either, report the problem. + +## See Also + +- [Build configuration](../Building/Build-Configuration) -- the box, its three entries, and where the choice is kept +- [Running](../Debugging/Running#restarting-the-compiler) -- what a restart does to a running program +- [Diagnostic tools](Diagnostic-Tools) -- the console lines, TRACE mode and DevTools +- [Saving](Saving) -- a save that fails, and the edits that nothing recovers diff --git a/docs/IDE/Troubleshooting/Diagnostic-Tools.md b/docs/IDE/Troubleshooting/Diagnostic-Tools.md new file mode 100644 index 000000000..e96753fea --- /dev/null +++ b/docs/IDE/Troubleshooting/Diagnostic-Tools.md @@ -0,0 +1,180 @@ +--- +title: Diagnostic tools +parent: When something goes wrong +grand_parent: IDE +nav_order: 3 +permalink: /tB/IDE/Troubleshooting/Diagnostic-Tools +index: + - native exception + - TRACE mode + - DevTools + - floating message + - Copy to clipboard + - IDE internal error + - IDE startup failure + - command failed +index_also: error reporting +--- + +# Diagnostic tools + +The IDE writes no log file. What it has to say about a failure goes to the **DEBUG CONSOLE**, to the floating message boxes above the status bar, or to a dialog. This page lists those, the lines that mean that something went wrong, TRACE mode, DevTools, and what automatic error reporting sends. + +## The lines that mean something went wrong + +The [Debug Console](../Debugging/Debug-Console) holds program output and the IDE's own lines. It lives in memory: **Clear Debug Console**, closing a project and `Debug.Cls` empty it, and nothing writes it to a file. Its right-click menu has **Copy All** and **Copy All (With Timestamps)**. These lines are the IDE's: + +| The line | It means | +|---|---| +| `restarting from MEMORY []`, `restarting from FILE` | The compiler was restarted, by you or by a crash. See [A crash](Compiler-Services#a-crash). | +| `[IDE] auto-activated TRACE-MODE in this session ...` (yellow) | A native exception turned TRACE mode on. See [TRACE mode](#trace-mode). | +| `GDI HANDLE LIMIT EXCEEDED ()`, followed by a warning in capitals that the debugger's stability is compromised | The running program holds too many GDI handles, and the line tells you to restart the compiler. It appears once for each compiler. | +| `[ERROR] synchronizing open document `, `[ERROR] saving open document ` | A time-out box named the failure; this line names the document. See [When the compiler does not answer](Compiler-Services#when-the-compiler-does-not-answer). | +| `[COMPILER] Project settings changes require compiler-restart` | A change to the project's settings is saved and the compiler restarts. | +| `[BUILD] failed due to licence error. Please try again.` | See [The licence](Missing-Files#the-licence). | +| `[PACKAGES] ... failed ...`, `[PACKAGES] version checks failed ...` | See [The package server](Missing-Files#the-package-server). | +| `[IDE] SHIFT KEY DETECTED: DISABLED LOADING OF ADDINS` | You held Shift while the project loaded. See [Add-ins that fail to load](#add-ins-that-fail-to-load). | +| `IDE internal error: ...` | See [IDE internal errors](#ide-internal-errors). | +| `command failed: ""` | See [A command that fails](#a-command-that-fails). | +| `ERROR: DEBUGGER IS NOT CONNECTED.` (red) | A command needed the **DEBUGGER** connection and it is not open. See [The services badge](Compiler-Services#the-services-badge). | +| `IDE: failed to restore project state from host data.` | The project's remembered state could not be read. The project itself is not touched. See [Saving](Saving#a-projects-state-and-where-it-lives). | +| `Failed to create imported file ''`, `Failed to read import file ''` | An import could not write or read the file it names. | + +The compiler writes lines of its own beside these: `[LINKER] ...`, `[SHELL] ...`, `[DEBUGGER] ...` and `NATIVE EXCEPTION ...`. + +## Native exceptions + +A *native exception* is a crash inside the compiler process, for example an access violation in a call to a Windows function made with a bad address. It ends the program, not the compiler. The console shows one red line, with a running number, the time, and where it happened: + +```text +0001 11:42:08.716 NATIVE EXCEPTION: ACCESS_VIOLATION /MainMod.twin; MainMod.Crash LINE 000010 [$773D8C3B:ntdll.dll+00088C3B] +``` + +The program ends with no panel and no box. The compiler stays up, so the badge does not move, and the next run needs no restart. The line names the module and the procedure. The first native exception while TRACE mode is off also writes two yellow lines that begin `[IDE]`; see [TRACE mode](#trace-mode). When [error reporting](#automatic-error-reporting) is on, the IDE sends this line to twinBASIC Ltd. + +![The DEBUG CONSOLE after a native exception: the NATIVE EXCEPTION line in red, and two yellow lines under it.](Images/Native_Exception.png){:width="1060" height="124"} + +## TRACE mode + +TRACE mode makes the compiler write more about what it is doing when something fails. It is a diagnostic for the developers of twinBASIC. It is not the same as the project's **Trace Flags** and **Trace Output** settings, which switch the logger of your own program; see [The trace logger](../Debugging/Debug-Console#the-trace-logger). + +**Help → Compiler services TRACE mode: Disabled** (the item reads **Enabled**, with a tick, when the mode is on) switches the mode and restarts the compiler, because the compiler reads the setting only when it starts. The item has no key. The mode is not saved, so the IDE starts with it off. The compiler is slower while it is on. + +A plain program prints the same lines with the mode on. What it adds is a dump after a native exception, one line for each thread of the compiler, such as: + +```text +thread 000A: CompilerOrganizationParsingEnd +>>> thread 0008: RuntimeExpressionEvaluationExecutionStart, Crash +``` + +The running number of the exception lines starts again with each compiler. + +The first native exception of a session switches the mode on by itself. The console then writes, in yellow: + +```text +[IDE] auto-activated TRACE-MODE in this session for extra debugging info on further native exceptions +[IDE] NOTE: compiler performance will be affected until TRACE-MODE is disabled via the Help menu +``` + + +> [!NOTE] BETA 1034 +> After a native exception turns TRACE mode on by itself, the **Help** item reads **Enabled**, but the compiler writes the dump only after its next restart; a second native exception in the same compiler prints the single line as before. Press **Restart the compiler** to get the dump for the next exception. + +![The Help menu with the TRACE mode item reading Disabled at its foot.](../Menu/Images/Menu_Help.png){:width="269" height="294"} + +![The Help menu with the TRACE mode item reading Enabled, with a tick, at its foot.](Images/Menu_Help_Trace.png){:width="265" height="294"} + +## DevTools + +DevTools is the browser's inspector for the IDE's own page, with its Console, Elements and Network. The IDE gives it no menu item, no toolbar button and no key. The command is `tbIdeDevTools_Show`, and you reach it by binding a key in a [shortcut group](../Customising/Keyboard-Shortcuts#manage-keyboard-shortcuts): choose **Window → Keyboard Shortcuts → Manage Keyboard Shortcuts...**, add a group, and write + +```json +{"tbIdeDevTools_Show": ["{KEYDOWN}{CTRL}{SHIFT}F12"]} +``` + +The key opens a window titled `DevTools - localhost:...`. Use it to read the full stack of an [IDE internal error](#ide-internal-errors). + + +> [!NOTE] BETA 1034 +> While DevTools is open, a restart of the compiler (by the button, by a crash or by a change of the build configuration box) stops the page at a `debugger;` statement in the IDE's own code. The compiler has been ended, the badge reads UNAVAILABLE with four **Disconnected** lines, and nothing moves. Press **Resume** in DevTools, or close DevTools, and the restart goes on. + +## Floating message boxes + +The IDE says that something went wrong, and that nothing is blocked, in a message box that floats above the status bar. Up to three show, stacked at the right, one line each, the newest at the bottom. A fourth makes the oldest go. Each closes by itself after about a minute. + +- **Click** a box, and its whole text shows. A **Copy to clipboard** button appears; it puts the text on the clipboard and leaves the box open. +- The **X** of a box closes it. + +![Three floating message boxes stacked above the status bar, the middle one clicked, so that its whole text and Copy to clipboard show.](Images/Floating_Boxes.png){:width="542" height="276"} + +The messages that mean a failure include **Compiler crash loop detected. Restarting in SAFE mode.**, **Invalid licence key. Please try again.**, **Licence key invalid.** and `ERROR: LoadProject failed. Error code ERROR_...`, which a project import raises. + +A box that blocks the IDE is a dialog. The one dialog that offers **Copy to clipboard** is the one that reports `webSocketJson: bad json received`, a message from the compiler that the IDE could not read. + +## A command that fails +{: #a-command-that-fails } + +A command that has a key can fail. The console then writes: + +```text +command failed: "" +``` + +and nothing else. The id is the name that the status bar shows when the pointer is over the command, such as `tbProject_Close`. [Keyboard shortcuts](../Customising/Keyboard-Shortcuts) lists them. + +## Add-ins that fail to load + +An add-in that cannot load writes a line to the console, prefixed by the name of its file, and nothing else. No box opens and the project opens without it: + +```text +[broken.dll] Failed to load addin. LoadLibrary() failed. +[noentry.dll] Failed to load addin. Entry point not found. Addin may have been compiled for a newer version of the twinBASIC IDE. +``` + +A third line, `Failed to load addin. Entry point 'tbCreateCompilerAddin' call failed.`, means the add-in's start-up function raised an error. [Add-Ins](../AddIns/) describes how add-ins are built and loaded. + +To open a project without its add-ins, hold Shift while the project loads. This also works at a restart of the compiler. The console writes `[IDE] SHIFT KEY DETECTED: DISABLED LOADING OF ADDINS`, and the load skips the add-ins, the check of the referenced packages and the automatic **RESYNC** of the form designers. Use it when an add-in stops the IDE. + +## IDE internal errors + +A fault in the IDE's own code writes this line in red to the console: + +```text +IDE internal error: , , , +``` + +The IDE goes on. Copy the line when you report it, and read the full stack in [DevTools](#devtools) if you can. + +A fault of one kind ends the IDE. It is a promise that the page's code did not handle. When the IDE was started with a project on its command line, for example by a double-click on a `.twinproj` file, it shows an alert and then closes its window: + +> IDE startup failure (BETA twinBASIC IDE BETA 1034): +> +> JS Promise Rejection(1): *the message and the stack* + +The first line repeats the word *BETA*, and the build number is the IDE's own. The IDE asks **Save now?** before it closes if the project has changes. When the IDE was started with no project, the same text goes to the console and the IDE stays open. Save often, and report the text. + +## Automatic error reporting + +[First launch](../Starting/First-Launch#the-error-reporting-question) describes the question that decides whether the IDE sends reports. When you have agreed, the IDE sends one HTTPS request to `https://twinbasic.com/notify` for each of these: + +- a native exception in the compiler, with the console line; +- an error in the IDE's own page, with the `IDE internal error:` text; +- a promise that the page's code did not handle, after the start page has shown. + +The request holds the IDE's version, the text of the licence box in the status bar, the time, the message, and the first row of the Call Stack (for example `MAIN_THREAD [IDLE]`, or ``). It holds no source file. Compile errors are not reported. The IDE writes nothing about a report, and the reply is not shown. + +**Help → Automatic IDE Error Reporting...** asks the question again. Opening it switches reporting off until you choose **YES**, even if you agreed before and open it only to read it. **NO** and Esc leave it off. + +## The compiler's own messages + +Two messages come from the compiler and not from the IDE. + +- `COMPILATION (CODEGEN) ERROR AT LINE #. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S` is the description of the run-time error panel for a syntax error in a procedure that **Main** calls. A compile error in your code is not a defect to report. [When the project does not compile](../Debugging/Running#when-the-project-does-not-compile) says which messages go with which errors. +- `COMPILATION ERROR DETECTED. CHECK THE DIAGNOSTICS AT LINE #` is the description for an error such as an unknown name. Read the DIAGNOSTICS panel. + +## See Also + +- [Debug Console](../Debugging/Debug-Console) -- the console, its buttons and the trace logger +- [Compiler services](Compiler-Services) -- crashes, restarts and Safe Mode +- [Breakpoints and stepping](../Debugging/Breakpoints-and-Stepping) -- the run-time error panel +- [Keyboard shortcuts](../Customising/Keyboard-Shortcuts) -- binding a key to a command diff --git a/docs/IDE/Troubleshooting/Images/Crash_Loop.light.png b/docs/IDE/Troubleshooting/Images/Crash_Loop.light.png new file mode 100644 index 000000000..30bb7c096 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Crash_Loop.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Crash_Loop.light.svg b/docs/IDE/Troubleshooting/Images/Crash_Loop.light.svg new file mode 100644 index 000000000..8950dd367 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Crash_Loop.light.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)safeMode100%PREVIEWTOOLBOXEDITOR 1MainMod(general)(declaration)MainMod.twin12345678910111213ModuleMainModPrivateDeclarePtrSafeSubCopyMemoryLib"kernel32"Alias"RtlMoveMemory"(ByValdAsLongPtr,ByValsAsLongPtr,ByValnAsLongPtr)PublicSubMain()Debug.Print"ran to the end"EndSub' An access violation in the compiler's process. RtlMoveMemory does nothing when the two addresses are the same.PublicSubCrash()CopyMemory16,32,4EndSubEndModule▶   run MainMod.Main▶   run MainMod.CrashDEBUG CONSOLE>16:41:58.204restarting from MEMORY[C:\tbshots\Troubleshooting\project.twinproj]16:41:58.204restarting from MEMORY[C:\tbshots\Troubleshooting\project.twinproj]16:41:58.204restarting from MEMORY[C:\tbshots\Troubleshooting\project.twinproj]DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034 LAAVARIABLESPROJECT EXPLORERTroubleImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesMainMod.twinPROPERTIESCompiler crash loop detected. Restarting in SAFE mode.twinBASICOKSafeMode is designed to give you access to the file systemof your project, without starting all of the associated compiler services.Use this mode to investigate / fix / recover your code before savingand restarting in normal mode. diff --git a/docs/IDE/Troubleshooting/Images/Crash_Loop.png b/docs/IDE/Troubleshooting/Images/Crash_Loop.png new file mode 100644 index 000000000..ece1262c7 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Crash_Loop.png differ diff --git a/docs/IDE/Troubleshooting/Images/Crash_Loop.svg b/docs/IDE/Troubleshooting/Images/Crash_Loop.svg new file mode 100644 index 000000000..6e5c6f995 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Crash_Loop.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALCOMMUNITY EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)safeMode100%PREVIEWTOOLBOXEDITOR 1MainMod(general)(declaration)MainMod.twin12345678910111213ModuleMainModPrivateDeclarePtrSafeSubCopyMemoryLib"kernel32"Alias"RtlMoveMemory"(ByValdAsLongPtr,ByValsAsLongPtr,ByValnAsLongPtr)PublicSubMain()Debug.Print"ran to the end"EndSub' An access violation in the compiler's process. RtlMoveMemory does nothing when the two addresses are the same.PublicSubCrash()CopyMemory16,32,4EndSubEndModule▶   run MainMod.Main▶   run MainMod.CrashDEBUG CONSOLE>16:41:58.204restarting from MEMORY[C:\tbshots\Troubleshooting\project.twinproj]16:41:58.204restarting from MEMORY[C:\tbshots\Troubleshooting\project.twinproj]16:41:58.204restarting from MEMORY[C:\tbshots\Troubleshooting\project.twinproj]DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034 LAAVARIABLESPROJECT EXPLORERTroubleImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesMainMod.twinPROPERTIESCompiler crash loop detected. Restarting in SAFE mode.twinBASICOKSafeMode is designed to give you access to the file systemof your project, without starting all of the associated compiler services.Use this mode to investigate / fix / recover your code before savingand restarting in normal mode. diff --git a/docs/IDE/Troubleshooting/Images/Floating_Boxes.light.png b/docs/IDE/Troubleshooting/Images/Floating_Boxes.light.png new file mode 100644 index 000000000..212189a31 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Floating_Boxes.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Floating_Boxes.light.svg b/docs/IDE/Troubleshooting/Images/Floating_Boxes.light.svg new file mode 100644 index 000000000..dc3307dcf --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Floating_Boxes.light.svg @@ -0,0 +1 @@ +CALL STACKCOMPILER [Win32] v0.15.1034 LAAVARIABLESCompiler crash loop detected. Restarting in SAFE mode.Copy to clipboardBackend compiler seems to be busy. Check there isn't a modal dialogbox open behind the IDE window.Invalid licence key. Please try again. diff --git a/docs/IDE/Troubleshooting/Images/Floating_Boxes.png b/docs/IDE/Troubleshooting/Images/Floating_Boxes.png new file mode 100644 index 000000000..b57376941 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Floating_Boxes.png differ diff --git a/docs/IDE/Troubleshooting/Images/Floating_Boxes.svg b/docs/IDE/Troubleshooting/Images/Floating_Boxes.svg new file mode 100644 index 000000000..218dcbef5 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Floating_Boxes.svg @@ -0,0 +1 @@ +CALL STACKCOMPILER [Win32] v0.15.1034 LAAVARIABLESCompiler crash loop detected. Restarting in SAFE mode.Copy to clipboardBackend compiler seems to be busy. Check there isn't a modal dialogbox open behind the IDE window.Invalid licence key. Please try again. diff --git a/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.light.png b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.light.png new file mode 100644 index 000000000..9a75c2bfb Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.light.svg b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.light.svg new file mode 100644 index 000000000..406dfd795 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.light.svg @@ -0,0 +1 @@ +HelpAbout twinBASIC...Licence Agreement...Automatic IDE Error Reporting...Help & Support (Discord Server)...Help & Support (GitHub repository)...Twitter (News Feed)...Purchase A Licence...Enter Licence Key...Buy us a Coffee! (Ko-Fi)...Compiler services TRACE mode: Enabled diff --git a/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.png b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.png new file mode 100644 index 000000000..3a9d2aabc Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.png differ diff --git a/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.svg b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.svg new file mode 100644 index 000000000..4259c5706 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Menu_Help_Trace.svg @@ -0,0 +1 @@ +HelpAbout twinBASIC...Licence Agreement...Automatic IDE Error Reporting...Help & Support (Discord Server)...Help & Support (GitHub repository)...Twitter (News Feed)...Purchase A Licence...Enter Licence Key...Buy us a Coffee! (Ko-Fi)...Compiler services TRACE mode: Enabled diff --git a/docs/IDE/Troubleshooting/Images/Missing_DLL_Box.png b/docs/IDE/Troubleshooting/Images/Missing_DLL_Box.png new file mode 100644 index 000000000..8f6dcf823 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Missing_DLL_Box.png differ diff --git a/docs/IDE/Troubleshooting/Images/Native_Exception.light.png b/docs/IDE/Troubleshooting/Images/Native_Exception.light.png new file mode 100644 index 000000000..62663dd0b Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Native_Exception.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Native_Exception.light.svg b/docs/IDE/Troubleshooting/Images/Native_Exception.light.svg new file mode 100644 index 000000000..ea7921930 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Native_Exception.light.svg @@ -0,0 +1 @@ +DEBUG CONSOLE>11:42:08.8000001 11:42:08.716 NATIVE EXCEPTION: ACCESS_VIOLATION /MainMod.twin; MainMod.Crash LINE 000010 [$773D8C3B:ntdll.dll+00088C3B]11:42:08.814[IDE] auto-activated TRACE-MODE in this session for extra debugging info on further native exceptions11:42:08.814[IDE] NOTE: compiler performance will be affected until TRACE-MODE is disabled via the Help menu diff --git a/docs/IDE/Troubleshooting/Images/Native_Exception.png b/docs/IDE/Troubleshooting/Images/Native_Exception.png new file mode 100644 index 000000000..1328f9d09 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Native_Exception.png differ diff --git a/docs/IDE/Troubleshooting/Images/Native_Exception.svg b/docs/IDE/Troubleshooting/Images/Native_Exception.svg new file mode 100644 index 000000000..34b4412b5 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Native_Exception.svg @@ -0,0 +1 @@ +DEBUG CONSOLE>11:42:08.8000001 11:42:08.716 NATIVE EXCEPTION: ACCESS_VIOLATION /MainMod.twin; MainMod.Crash LINE 000010 [$773D8C3B:ntdll.dll+00088C3B]11:42:08.814[IDE] auto-activated TRACE-MODE in this session for extra debugging info on further native exceptions11:42:08.814[IDE] NOTE: compiler performance will be affected until TRACE-MODE is disabled via the Help menu diff --git a/docs/IDE/Troubleshooting/Images/Please_Wait.light.png b/docs/IDE/Troubleshooting/Images/Please_Wait.light.png new file mode 100644 index 000000000..3132959bc Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Please_Wait.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Please_Wait.light.svg b/docs/IDE/Troubleshooting/Images/Please_Wait.light.svg new file mode 100644 index 000000000..33f3b60ec --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Please_Wait.light.svg @@ -0,0 +1 @@ +45678910111213PublicSubMain()Debug.Print"ran to the end"EndSub' An access violation in the compiler's process. RtlMoveMemory does nothing when the two addresses are the same.PublicSubCrash()CopyMemory16,32,4EndSubEndModule▶   run MainMod.Main▶   run MainMod.CrashDIAGNOSTICS0000CALL STACKVARIABLESPlease wait...10.0% diff --git a/docs/IDE/Troubleshooting/Images/Please_Wait.png b/docs/IDE/Troubleshooting/Images/Please_Wait.png new file mode 100644 index 000000000..0f078927d Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Please_Wait.png differ diff --git a/docs/IDE/Troubleshooting/Images/Please_Wait.svg b/docs/IDE/Troubleshooting/Images/Please_Wait.svg new file mode 100644 index 000000000..c5f5d5c0b --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Please_Wait.svg @@ -0,0 +1 @@ +45678910111213PublicSubMain()Debug.Print"ran to the end"EndSub' An access violation in the compiler's process. RtlMoveMemory does nothing when the two addresses are the same.PublicSubCrash()CopyMemory16,32,4EndSubEndModule▶   run MainMod.Main▶   run MainMod.CrashDIAGNOSTICS0000CALL STACKVARIABLESPlease wait...10.0% diff --git a/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.light.png b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.light.png new file mode 100644 index 000000000..5fe327fac Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.light.svg b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.light.svg new file mode 100644 index 000000000..1af06c117 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.light.svg @@ -0,0 +1 @@ +FileEditViewProjectFormatDebugRunToolsAdd-InsWindowsafeMode diff --git a/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.png b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.png new file mode 100644 index 000000000..7986c4998 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.png differ diff --git a/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.svg b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.svg new file mode 100644 index 000000000..489d6cb26 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Safe_Mode_Bar.svg @@ -0,0 +1 @@ +FileEditViewProjectFormatDebugRunToolsAdd-InsWindowsafeModeTOOLBOXEDITOR 1 diff --git a/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.png b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.png new file mode 100644 index 000000000..59cfbc5c3 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.png differ diff --git a/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.svg b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.svg new file mode 100644 index 000000000..e83d17998 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.svg @@ -0,0 +1 @@ +tB Services: LIMITEDCOMMUNITY EDITION>COMPILER: OPERATIONALFS: OPERATIONALLSP: DisconnectedDEBUGGER: Disconnected diff --git a/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.png b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.png new file mode 100644 index 000000000..b682fca52 Binary files /dev/null and b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.png differ diff --git a/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.svg b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.svg new file mode 100644 index 000000000..fafce9061 --- /dev/null +++ b/docs/IDE/Troubleshooting/Images/Services_Limited_Tooltip.svg @@ -0,0 +1 @@ +tB Services: LIMITEDCOMMUNITY EDITION>COMPILER: OPERATIONALFS: OPERATIONALLSP: DisconnectedDEBUGGER: Disconnected diff --git a/docs/IDE/Troubleshooting/Missing-Files.md b/docs/IDE/Troubleshooting/Missing-Files.md new file mode 100644 index 000000000..45feef4ff --- /dev/null +++ b/docs/IDE/Troubleshooting/Missing-Files.md @@ -0,0 +1,119 @@ +--- +title: Missing files and the install +parent: When something goes wrong +grand_parent: IDE +nav_order: 4 +permalink: /tB/IDE/Troubleshooting/Missing-Files +index: + - missing files + - failed to load the twinBASIC DLL + - WebView2 runtime + - licence problem + - package server + - reset IDE options + - reset-ide-settings +--- + +# Missing files and the install + +This page describes the boxes that a damaged install gives, the licence and the package server when they fail, the folders the IDE uses, and how to put the IDE's options back to their defaults. + +## A damaged install +{: #a-damaged-install } + +The IDE is a folder of files: `twinBASIC.exe`, a `bin` folder with the IDE's host and the compiler, an `ide` folder with the IDE's page, and others. When a file the IDE needs is gone, the IDE does not start, or it starts without a compiler. Anti-virus software that quarantines a file, and an unzip that stopped halfway, can both do this. The [FAQ](../../../FAQ#missing-files) says how to restore the files and how to tell the anti-virus software that they are safe, and [Is twinBASIC safe?](../../../FAQ#false-scanner-alerts) explains the false alerts. Extract a new IDE into an empty folder, not over an older one: [How do I install twinBASIC?](../../../FAQ#installation) says why. + +Each box below is a native Windows box with one **OK** button. The IDE ends when you choose it, unless the table says otherwise. + +| The file that is gone | The box | +|---|---| +| `bin\twinBASIC_ide_win32.dll` | Titled **twinBASIC Compiler**: `ERROR: failed to load the twinBASIC DLL:`, then a file name, then `The file could not be found. See https://twinbasic.com/missing`, then `The application will now exit.` | +| `bin\twinBASIC_win32.dll` | Titled **shutdown**: `Run-time error '-353703418'`, then `Unable to load DLL`. | +| `bin\twinBASIC_win32.exe` and all its variants | Titled **twinBASIC IDE**: `ERROR: failed to load the twinBASIC EXE:`, then `\bin\twinBASIC_win32.exe`, then the same address and `The application will now exit.` The IDE checks for this file at its start. | +| The compiler variant that the IDE starts, and no other | The IDE opens. The box reads as the one above, names the variant's file, and ends `Please close the IDE, and restore any missing files.` | + +A box that begins `failed to call TWINBASIC_INIT dll entry in the twinBASIC DLL:` means the same: a file of the install is damaged or is the wrong one. + +The compiler is not one file. It is a set of executables, `twinBASIC_win32.exe` and its variants with `_noDEP` and `_LAA` in the name, and the 64-bit ones. Which the IDE starts depends on the build configuration and on the DEP and large-address settings of the project. + + +> [!NOTE] BETA 1034 +> When `bin\twinBASIC_ide_win32.dll` is missing, the box names `bin\twinBASIC_win64.dll`, a file that is there. The launcher `twinBASIC.exe` names that file for every file it cannot load. Compare the whole `bin` folder with the IDE's download, or extract the download again, rather than looking for the file the box names. + + +> [!NOTE] BETA 1034 +> When `bin\twinBASIC_win32.dll` is missing, the box is titled **shutdown** and names no file and no address. It means that the compiler's file is missing or cannot be loaded. Restore the `bin` folder from the download. + + +> [!NOTE] BETA 1034 +> When the compiler variant that the IDE starts is missing, the IDE shows the box once and then goes quiet. After **OK**, the IDE has no project, tries nothing more, and writes nothing to the console. The badge reads UNAVAILABLE, with four **Not initialized** lines, for good. Restore the file, and start the IDE again. + +![The native box titled twinBASIC Compiler that a missing DLL brings, with its one OK button.](Images/Missing_DLL_Box.png){:width="304" height="211"} + +## The WebView2 runtime + +The IDE's page runs in the Microsoft Edge WebView2 control. If the Evergreen runtime is not installed, no page loads, and the host shows a box: + +> Failed to initialize the WebView2 control. Please check that the WebView2 runtime (Evergreen) is installed on this machine. +> +> Open the WebView2 download page now? + +If you agree, the host opens the download page of Microsoft's runtime in your browser. Install the runtime, and start the IDE again. The [FAQ](../../../FAQ#system-requirements) lists the requirements, and [How do I install twinBASIC on Windows 7?](../../../FAQ#invalid-entry-point) says which runtime to take on that system. The host's other text, `WebView2: Failed to find an installed WebView2 runtime or non-stable Microsoft Edge installation.`, means the same. + +## The licence +{: #the-licence } + +The licence box of the status bar reads **tB Licence: NOT READY** until the compiler has answered with the edition. It goes back to NOT READY at each restart of the compiler. [First launch](../Starting/First-Launch#the-licence-and-the-edition) describes the editions and [**Enter Licence Key**](../Starting/First-Launch#enter-licence-key). When something fails: + +| What you see | Meaning | +|---|---| +| The floating message **Invalid licence key. Please try again.**, and the box reads **COMMUNITY EDITION** | The compiler did not accept the key. The change lasts for the session. | +| **Licence key invalid.**, with no project loaded | The key does not have 32 characters. | +| A box titled **Licence Problem!**: *This twinBASIC licence appears to have expired or may have been marked as revoked.* | The compiler's answer. The box tells you to write to `sales@twinbasic.com`. The IDE then behaves as for a Community licence. | +| The console line `[BUILD] failed due to licence error. Please try again.`, about six seconds after you pressed **Build** | **Build** waits up to five seconds for the compiler's answer and then stops. Press it again when the licence box shows an edition. | +| A banner **COMMUNITY LICENCE RESTIRCTION** or **PERSONAL LICENCE RESTIRCTION** above the two **Compiler Options** rows of Project Settings | On a Personal edition the optimisations set there apply only to the built-in packages, and on a Community edition they are not applied. The IDE spells the word *RESTIRCTION* so. | +| The box *Sorry, twinBASIC Personal Edition does not support command line builds.* | `--buildAndExit32` and `--buildAndExit64` need a Professional licence. | + +## The package server + +The IDE asks the online package server when you open the Packages tab, add a reference, and, with **Check referenced packages are up-to-date** on, when a project loads. If the server cannot be reached: + +- A notification says **Failed to connect to the online package server. Error code <n>. Check your internet connection, or try again later.** The code can be absent. Choose **OK**. +- The console writes a `[PACKAGES]` line: `[PACKAGES] failed. HTTPS request to server (www.everythingaccess.com) failed. Try again later.`, `[PACKAGES] failed. Server returned HTTPS code `, or, for the check at load, `[PACKAGES] version checks failed - server response invalid - try again later.` or a line that says `Check your internet connection, or try again later.` + +An error code of 552 does not mean an outage. It means that the session is not signed in, and the IDE opens the **twinBASIC Publisher Login / Signup** box instead. [Package Publishing](../Building/Package-Publishing) describes it. + +## WebView2's folders + +The IDE keeps the browser's cache and storage in four folders under `%LOCALAPPDATA%`: `twinBASIC`, `twinBASIC_Admin`, `twinBASIC_WebPanel` and `twinBASIC_WebPanel_Admin`, each with a subfolder `v0`. Some may not exist. They are not a log. The IDE keeps no project and no setting in them; those are in the registry and in the project's file. You can delete them with the IDE closed, and the IDE makes them again. The [FAQ](../../../FAQ#ide-data-storage) lists them with the IDE's other folders. + +## Where the IDE keeps its settings + +[Customising the IDE](../Customising/#where-the-ide-keeps-its-settings) lists the registry values and the folders in `%APPDATA%\twinBASIC`. Deleting a value puts back what it held: + +| Delete | The IDE | +|---|---| +| `GENERAL` | Puts every IDE Option back to its default, and starts again as for the very first start, with the **IDE Options** dialog and no **Close** button. | +| `LAYOUTv2` and the other layout values | Shows the default layout. | +| `ALLOWERRORREPORTINGV3` | Asks the [error reporting question](../Starting/First-Launch#the-error-reporting-question) again. | +| `LICENCEAGREEMENTV3` | Asks for the [licence agreement](../Starting/First-Launch#the-licence-agreement) again. | +| `LicenceKey` | Leaves the licence at Community. | +| `NEWFRONTPAGE` | Opens the New / Open Project dialog on its default section. | + +Close the IDE before you edit the registry. + +The IDE has no command or menu item that resets all its settings. It has these resets: + +- **Window → Panel Layouts → Default Built-in Layout**, and **Window → Keyboard Shortcuts → Default Built-in Keyboard Shortcuts**, which change the layout and the keys. +- The **IDE Options** dialog, in which each option is changed on its own. +- **`--reset-ide-settings`** on the IDE's command line. It writes empty options to the registry and loads them again, so every IDE Option returns to its default. It resets the IDE Options and nothing else: the panel layouts, the keyboard shortcut groups, the licence values, the recent projects and the window's position stay as they are. + +A folder in `%APPDATA%\twinBASIC` (`themes`, `locale`, `packages`, `addins`) holds what you added. Delete the file that is wrong, and the IDE makes the folder again if it is gone. A theme whose `inherits:` chain returns to itself gives the alert **Invalid theme file detected: <file>**, then **(recursive inheritance)** on the next line, and the IDE drops the theme from the list; [Themes](../Customising/Themes) describes it. + +## See Also + +- [Compiler services](Compiler-Services) -- the services badge, restarts and Safe Mode +- [Diagnostic tools](Diagnostic-Tools) -- the console lines and error reporting +- [Customising the IDE](../Customising/) -- the settings and the language packs +- [FAQ](../../../FAQ#installation) -- installing, system requirements and false alerts +- [Opening projects](../Starting/Opening-Projects#when-a-project-will-not-open) -- a project that will not open diff --git a/docs/IDE/Troubleshooting/Saving.md b/docs/IDE/Troubleshooting/Saving.md new file mode 100644 index 000000000..b03d8012d --- /dev/null +++ b/docs/IDE/Troubleshooting/Saving.md @@ -0,0 +1,86 @@ +--- +title: Saving +parent: When something goes wrong +grand_parent: IDE +nav_order: 2 +permalink: /tB/IDE/Troubleshooting/Saving +index: + - save fails + - save project failed + - recover unsaved work + - project state +--- + +# Saving + +This page describes what the IDE does when a save cannot write, what it keeps of your work outside the project's file, and what it does not keep. [Saving and exporting](../Projects/Saving-and-Exporting) describes the commands. + +## A save that fails +{: #a-save-that-fails } + +When the compiler cannot write the project file, the IDE shows an alert, a plain dialog with one **OK** button. It reads: + +> save project failed, result code 2, GetLastErr: ERROR_ACCESS_DENIED +> +> Check that the file path is accessible and writable, and that you have the necessary permissions to write to it. + +The result code and the Windows error change with the cause: + +| Cause | The alert gives | +|---|---| +| The file is read-only, for **Save** or for **Save As** over it | `ERROR_ACCESS_DENIED` | +| Another program holds the file open with no sharing | `ERROR_SHARING_VIOLATION` | +| **Save As** into a folder that does not exist | `ERROR_PATH_NOT_FOUND` | +| The disk is full | `ERROR_DISK_FULL` | +| Any other error | its number | + +The alert is the only sign. The IDE writes no line to the **DEBUG CONSOLE**, and shows no other box. The project stays open and stays marked as changed, with its dot in the title bar, and the file on disk is as it was. Nothing is lost. Choose **OK**, remove the cause (clear the read-only flag, close the other program, make the folder or free space), and save again. If you cannot, use **File → Save Project As...** with a path that works. + +The same alert comes from **Save Project As** and from the saves that other commands make. [Other commands that save](../Projects/Saving-and-Exporting#other-commands-that-save) lists them. + +### A failed Close + + +> [!NOTE] BETA 1034 +> When you choose **Save** in the question that **Close Project** asks, and the save fails, the alert opens and the close is dropped. The project stays open and changed. Fix the cause, and choose **Close Project** again. The same holds for **Exit** and for the close box of the window. + +### A failed Save As +{: #a-failed-save-as } + + +> [!NOTE] BETA 1034 +> A **Save Project As** whose save fails still makes the new path the project's path. After the alert, the title bar names the new file, which does not exist, and a plain **Save** goes to that path. While the folder is missing, **Save** fails again; once the folder exists, **Save** writes the project there and leaves the original file as it was. To save to the original again, use **Save Project As...** with its path. Check the title bar after any alert about a save. + +### When the compiler does not answer + +A save that gets no answer from the compiler ends in a time-out box instead of the alert, after 10 or after 20 seconds. [When the compiler does not answer](Compiler-Services#when-the-compiler-does-not-answer) lists the boxes. + +## No autosave and no recovery +{: #no-autosave-and-no-recovery } + +The IDE does not save by itself, and it keeps no backup of your edits and no recovery file. If the IDE ends, whether by a crash, by a power failure or because you ended it in the Task Manager, the edits since your last save are gone. The IDE offers nothing when you open the project again. [Save often](../Code/Editor#save-often) says it for the editor. + +These protect your work: + +- The question that **Close Project**, **Exit** and the window's close box ask when the project has changes: **You've made changes to this project. Save now?** +- **Restart the compiler**, which starts from the IDE's memory and keeps unsaved edits. See [What a restart keeps and loses](Compiler-Services#what-a-restart-keeps-and-loses). +- **Safe Mode**, which lets you read and edit the project's files and save them when the compiler's services are not working. See [Safe Mode](Compiler-Services#safe-mode). + +When the compiler will not come back, the editors stay open. Copy the text you need into another program before you end the IDE. [A linked package's missing file](Compiler-Services#a-linked-packages-missing-file) describes the one way into the editors when the IDE is stuck on **Please wait...**. + +## A project's state, and where it lives +{: #a-projects-state-and-where-it-lives } + +Apart from its files, the IDE remembers how you left the project: the open editors with their carets and folds, the selected tab, the bookmarks, the watches, the history of the console and of the find boxes, and the layout of some panels. This is the project's *state*. The IDE saves it every five seconds, and not with the project. For a project that has a file, it is a value in the registry, one for each project path, under `ProjectState` beside the IDE's settings. [Where the IDE keeps its settings](../Customising/#where-the-ide-keeps-its-settings) lists the keys. + +- **Delete the project's value**, and the project opens with no tabs, no bookmarks and the caret at the first line. The project itself is not touched. +- **Ctrl+Shift+Alt+M** writes the state into the project too. The floating message says **Saved project meta state to file.** A hidden entry, `.meta`, joins the project's root. The Project Explorer does not draw it, and the project becomes changed. After the next save, the `.meta` entry is in the `.twinproj` file, and a project that opens with no state in the registry, for example on another PC, gets its tabs, bookmark and caret from it. The command is on no menu. +- **A project with no file** has no value in the registry, and its state is kept in the project as `.meta`. +- **`.meta` does not survive the [Import / Export tool](../../../Features/Packages/Import-Export-Tool)**: the export leaves it out, so a project that is exported and packed again loses it. + +## See Also + +- [Saving and exporting](../Projects/Saving-and-Exporting) -- Save, Save As, Close Project and Export Project +- [Compiler services](Compiler-Services) -- Restart the compiler and Safe Mode +- [Editor](../Code/Editor#save-often) -- save often +- [Customising the IDE](../Customising/) -- where the IDE keeps its settings diff --git a/docs/IDE/Troubleshooting/index.md b/docs/IDE/Troubleshooting/index.md new file mode 100644 index 000000000..4a82f8a70 --- /dev/null +++ b/docs/IDE/Troubleshooting/index.md @@ -0,0 +1,76 @@ +--- +title: When something goes wrong +parent: IDE +nav_order: 10 +permalink: /tB/IDE/Troubleshooting/ +has_toc: false +index: + - troubleshooting + - crash + - something goes wrong +index_also: + - problems +--- + +# When something goes wrong + +The IDE is a web page that runs inside a native window, and it does no compiling itself. It starts a separate **compiler** process and talks to it over four connections. Most of what goes wrong is one of those connections failing, a file the IDE needs being gone, or a save that cannot write. This chapter says what each kind of failure looks like, what the IDE does about it, and what to do. + +The IDE keeps no log file of its own, and it shows no crash dialog. What it has to say goes to three places: a line in the **DEBUG CONSOLE**, a floating message box above the status bar, or a dialog. Many failures leave only one console line. + +## What you see + +| What you see | Where to look | +|---|---| +| **tB Services: UNAVAILABLE** or **LIMITED** with a project open, and it does not change back | [Compiler services](Compiler-Services#the-services-badge) | +| A console line `restarting from MEMORY [...]` that you did not ask for | [A crash](Compiler-Services#a-crash) | +| The floating message **Compiler crash loop detected. Restarting in SAFE mode.**, or a box about **SafeMode** | [The crash loop](Compiler-Services#the-crash-loop) and [Safe Mode](Compiler-Services#safe-mode) | +| The build box reads **safeMode**, **Build** does nothing, or **Start** never ends | [Safe Mode](Compiler-Services#safe-mode) | +| **Please wait...** covers the IDE and does not go away, or a box titled **shutdown** | [A linked package's missing file](Compiler-Services#a-linked-packages-missing-file) | +| A box says the IDE **timed out** | [When the compiler does not answer](Compiler-Services#when-the-compiler-does-not-answer) | +| An alert says **save project failed** | [A save that fails](Saving#a-save-that-fails) | +| The title bar names a file you did not open, after a failed **Save As** | [A failed Save As](Saving#a-failed-save-as) | +| The IDE ended, and the edits since your last save are gone | [No autosave and no recovery](Saving#no-autosave-and-no-recovery) | +| A red `NATIVE EXCEPTION` line in the **DEBUG CONSOLE** | [Native exceptions](Diagnostic-Tools#native-exceptions) | +| `IDE internal error:` in the console, or an alert titled **IDE startup failure** | [IDE internal errors](Diagnostic-Tools#ide-internal-errors) | +| `command failed:` in the console | [A command that fails](Diagnostic-Tools#a-command-that-fails) | +| An add-in did not load, or you need the project to load without its add-ins | [Add-ins that fail to load](Diagnostic-Tools#add-ins-that-fail-to-load) | +| A box at start-up: **ERROR: failed to load the twinBASIC DLL:**, **failed to load the twinBASIC EXE:**, or **shutdown** | [A damaged install](Missing-Files#a-damaged-install) | +| A box about the **WebView2 control** | [The WebView2 runtime](Missing-Files#the-webview2-runtime) | +| **Licence Problem!**, **Invalid licence key. Please try again.**, or **[BUILD] failed due to licence error. Please try again.** | [The licence](Missing-Files#the-licence) | +| **Failed to connect to the online package server.** | [The package server](Missing-Files#the-package-server) | +| You want the IDE's options back to their defaults | [Where the IDE keeps its settings](Missing-Files#where-the-ide-keeps-its-settings) | +| A project will not open: **Failed to load the project**, or the box titled **bad file format** | [When a project will not open](../Starting/Opening-Projects#when-a-project-will-not-open) | +| A compile error that says **PLEASE REPORT THIS ERROR** | [When the project does not compile](../Debugging/Running#when-the-project-does-not-compile) | +| A paused program ends in a native access violation after **Break** | [Break](../Debugging/Running#break) | +| A run-time error panel under a line of code | [Breakpoints and stepping](../Debugging/Breakpoints-and-Stepping) | + +## The pages of this chapter + +- [**Compiler services**](Compiler-Services) --- the services badge and every state it reads, what a compiler crash looks like, **Restart the compiler** and what it keeps, the crash loop, Safe Mode, and the compiler that does not answer or cannot be reached. +- [**Saving**](Saving) --- a save that fails, the failed **Save As**, what the IDE keeps of a project outside its file, and why nothing recovers unsaved edits. +- [**Diagnostic tools**](Diagnostic-Tools) --- the lines the **DEBUG CONSOLE** writes when something fails, TRACE mode, DevTools, the floating message boxes, errors in the IDE's own code, and automatic error reporting. +- [**Missing files and the install**](Missing-Files) --- the boxes of a damaged install, the licence and the package server when they fail, the folders the IDE uses, and how to put the IDE's options back. + +## Reporting a problem + +The IDE has no **Report this** button and no report file. To report a problem with twinBASIC, open an issue on [the twinBASIC repository on GitHub](https://github.com/twinbasic/twinbasic/issues), or write in the `#bugs` channel of the Discord server. The [FAQ](../../../FAQ#bug-reporting) says what to include. The GitHub icon in the status bar and **Help → Help & Support (GitHub repository)...** open the repository's front page, not the form for a new issue. + +## What the IDE does not provide + +So that you do not look for them: + +- A log file, a crash dump or a recovery file. +- An automatic save or a backup. See [No autosave and no recovery](Saving#no-autosave-and-no-recovery). +- A command or a menu item that resets every setting. The one reset is a switch on the IDE's command line, and it resets the IDE Options only. See [Where the IDE keeps its settings](Missing-Files#where-the-ide-keeps-its-settings). +- A way to start the IDE in a safe state. Safe Mode is an entry of the build configuration box, and it applies to a project that is open. +- A watchdog on the compiler. The IDE has no state for a compiler that is not responding, and no way to cancel a long operation other than the compiler's own **Stop** and a restart. +- A key for **Restart the compiler**, for TRACE mode or for DevTools, and a menu item or a button for DevTools. +- A click on the services badge. Only the licence badge, the links, the **PROCESS** boxes and the line and column box respond to a click. + +## See Also + +- [Status Bar](../Reference/StatusBar) -- the boxes along the bottom of the window +- [Opening projects](../Starting/Opening-Projects) -- what happens as a project loads, and when one will not open +- [Running and debugging](../Debugging/) -- the run-time error panel, **Break**, and what ends a run +- [Customising the IDE](../Customising/) -- the IDE Options and where the IDE keeps its settings diff --git a/docs/IDE/Webpage.md b/docs/IDE/Webpage.md index a752945e4..8859f3c47 100644 --- a/docs/IDE/Webpage.md +++ b/docs/IDE/Webpage.md @@ -1,14 +1,31 @@ --- title: Webpage -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/Webpage +parent: IDE Reference +grand_parent: IDE +nav_order: 15 +permalink: /tB/IDE/Reference/Webpage +redirect_from: + - /tB/IDE/Project/Webpage --- # Webpage -![The Webpage pane hosting a browser view: back, forward and reload buttons, an address bar holding https://docs.twinbasic.com/, and a zoom dropdown set to 70 per cent, above the home page of the twinBASIC documentation rendered inside the pane.](Images/Webpage.png){:width="340" height="500"} +The **WEBPAGE** pane shows a web page inside the IDE, so that you can read online content without opening a browser. **View → WEBPAGE** opens it. It is not in either built-in panel layout. -The Webpage pane embeds a web browser view inside the IDE, so online content can be read without opening an external browser. +![The Webpage pane showing the home page of the twinBASIC documentation at 70 per cent.](Images/Webpage.png){:width="340" height="500"} -Back, forward and reload buttons sit to the left of an address bar that accepts any URL, and a zoom dropdown at the right scales the rendered page --- 70 per cent, the default, in the screenshot above. The page itself fills the rest of the pane. +**Back**, **Forward** and **Refresh** +: The three buttons at the left. They have no default keys; the commands are `tbWebpage_GoBack`, `tbWebpage_GoForward` and `tbWebpage_Reload`. + +The address box +: Shows the address of the page, and follows the page as it changes. Type an address and press Enter to go there. An address with no colon in it gets `https://` in front of it. + +**Zoom** +: Scales the page, from 50% to 150% in steps of ten. It starts at 70%. + +**Use Mobile Mode** +: The one item of the **...** menu at the right of the pane's title bar. While it is ticked, the pane presents itself to websites as a browser on an Android phone, so a site that has a layout for phones sends that one. + +The pane keeps its address, zoom and mobile mode from one session to the next. The first address it shows is `https://www.google.com`. + +While a menu, a dialog, a floating message or another floating panel overlaps the pane, the pane shows a still picture of the page. The live page comes back when nothing overlaps it. diff --git a/docs/IDE/index.md b/docs/IDE/index.md index a3cc931cb..ec6e70098 100644 --- a/docs/IDE/index.md +++ b/docs/IDE/index.md @@ -6,12 +6,47 @@ permalink: /tB/IDE # The twinBASIC IDE -![The twinBASIC IDE with no project loaded. A menu bar and toolbar run across the top, the Toolbox is docked on the left, an empty Editor 1 fills the centre, and Project Explorer sits above Properties on the right. Debug Console, Diagnostics, Call Stack and Variables share a row along the bottom. Every pane is empty, and the status bar reports tB Services unavailable and tB Licence not ready.](Images/IDE.png){:width="1920" height="1032"} +The twinBASIC IDE is one window that holds everything you need to write a program, run it, debug it and build it. This page goes round the window, then follows a project from start to finish, and says at each step which chapter explains it. -The IDE consists of several fixed panes and tool windows. The [**Project Explorer**](IDE/Projects/Project-Explorer) shows the file tree of the open project, and [**Projects and files**](IDE/Projects/) describes how to add, change and save its files; the [**Editor**](IDE/Code/Editor) is the main code and designer surface; the [**Properties**](IDE/Forms/Properties) pane shows and edits properties for the selected item; the [**Toolbox**](IDE/Forms/Toolbox) lists the controls available to drop onto a form. +![The IDE in its default layout with a form open in the designer, each part of the window labelled.](Images/IDE_Tour.png){:width="1280" height="880"} -The debugging panels --- [**Call Stack**](IDE/Debugging/Inspecting#call-stack), [**Variables**](IDE/Debugging/Inspecting#variables), [**Debug Console**](IDE/Debugging/Debug-Console), [**Watches**](IDE/Debugging/Inspecting#watches) and [**Memory**](IDE/Debugging/Memory) --- are described in [**Running and debugging**](IDE/Debugging/). Nothing opens them when a program stops. The Call Stack, Variables and Debug Console are in the default layout. Watches and Memory are not: open them from the **View** menu. +The picture shows the IDE in its default layout, with a project open and one of its forms in the designer. With no project open, there is no compiler, so the panels are empty and the two badges in the status bar are red. They change once a project is open. -The panels you use while writing code --- [**Diagnostics**](IDE/Code/Diagnostics), [**Outline**](IDE/Code/Navigating#outline), [**Open Editors**](IDE/Code/Navigating#open-editors), [**History**](IDE/Code/Navigating#history) and [**Find / Replace**](IDE/Code/Finding) --- are described, with the editor, in [**Writing code**](IDE/Code/). +## Around the window -The [**form designer**](IDE/Forms/) and the [**report designer**](IDE/Reports/) open when a form or report file is opened from the Project Explorer. Third-party and community [**addins**](IDE/AddIns/) extend the IDE with additional commands and tool windows. +**The menu bar** runs along the top, in the title bar. The [menu reference](IDE/Reference/Menu) lists every command with its key. + +**The toolbar** is under it. It has the buttons for saving and finding, the buttons that start, pause, stop and step a program, the build configuration box that chooses 32-bit or 64-bit, and the buttons that arrange the controls on a form. See [Toolbar](IDE/Reference/Toolbar). + +**The Editor** fills the middle. A file opens in it, in a tab: a code file in the [code editor](IDE/Code/Editor), a form in the [form designer](IDE/Forms/), and a report in the [report designer](IDE/Reports/). The code editor gives you completion lists, hints and live diagnostics while you type; [Writing code](IDE/Code/) describes them. + +**The Toolbox**, at the left, lists the controls that you can drop onto a form. See [Toolbox](IDE/Forms/Toolbox). + +**The Project Explorer**, at the top right, shows the files of the open project, or the objects in its code. See [Project Explorer](IDE/Projects/Project-Explorer). + +**Properties**, under it, shows and edits the properties of the control or form that is selected. See [Properties](IDE/Forms/Properties). + +**The row along the bottom** holds four panels: + +- [**DEBUG CONSOLE**](IDE/Debugging/Debug-Console) shows what a program prints and what the compiler reports, and runs a line of code that you type. +- [**DIAGNOSTICS**](IDE/Code/Diagnostics) lists the errors and warnings that the compiler finds as you type. +- [**CALL STACK**](IDE/Debugging/Inspecting#call-stack) and [**VARIABLES**](IDE/Debugging/Inspecting#variables) show where a paused program is, and what its variables hold. + +**The status bar**, at the foot of the window, shows the state of the compiler's services and your licence, and, on the right, the name of the command under the pointer. See [Status Bar](IDE/Reference/StatusBar). + +More panels open from the **View** menu: [**Outline**](IDE/Code/Navigating#outline), [**Open Editors**](IDE/Code/Navigating#open-editors), [**History**](IDE/Code/Navigating#history), [**Find / Replace**](IDE/Code/Finding), [**Watches**](IDE/Debugging/Inspecting#watches), [**Memory**](IDE/Debugging/Memory) and the [**Webpage**](IDE/Reference/Webpage) pane. Every panel can be moved, floated or closed, and [Panel layouts](IDE/Customising/Panel-Layouts) says how to save an arrangement. + +## From a project to a program + +1. **Start a project.** The IDE opens on the **New / Open Project** dialog, which makes a project from a template or opens one you have, a `.vbp` file from VB6 included. [Starting a project](IDE/Starting/) describes the first start, the dialog and the samples. +2. **Add to it.** The **Project** menu and the Project Explorer add forms, modules, classes and other files. [Projects and files](IDE/Projects/) describes how to add, change and save them, and [Project Settings](IDE/Project-Settings) describes each setting of a project. +3. **Write the code and draw the forms.** See [Writing code](IDE/Code/), [Designing forms](IDE/Forms/) and [Designing reports](IDE/Reports/). +4. **Run it.** F5 runs the program inside the IDE, with no build. You can stop it, look at its variables and step through its code. See [Running and debugging](IDE/Debugging/). +5. **Build it.** **File → Build** writes the executable, the DLL or the package. See [Building](IDE/Building/). + +## Everything else + +- [**Customising the IDE**](IDE/Customising/) --- options, themes, panel layouts, keyboard shortcuts and the language. +- [**Add Ins**](IDE/AddIns/) --- add-ins that extend the IDE with commands and tool windows, and how to write your own. +- [**When something goes wrong**](IDE/Troubleshooting/) --- what the IDE shows when the compiler's services fail, a save cannot write or a file of the install is missing, and the tools for finding out why. +- [**IDE Reference**](IDE/Reference) --- the menus, the toolbar, the status bar and the keyboard shortcuts, one control at a time. diff --git a/docs/Miscellaneous/FAQs.md b/docs/Miscellaneous/FAQs.md index b3b0ef31b..b313d13fa 100644 --- a/docs/Miscellaneous/FAQs.md +++ b/docs/Miscellaneous/FAQs.md @@ -330,22 +330,19 @@ Every executable and DLL of the IDE is signed with TWINBASIC LTD's code-signing
How do I import my VB6 project into twinBASIC? -The easiest way is through the import wizard. When you first start the twinBASIC IDE, you're presented with the New / Open Project dialog --- its **New** section has an **Import from VBP...** card: +The easiest way is through the import wizard. When the twinBASIC IDE starts with no project, it shows the New / Open Project dialog after its splash --- its **New** section has an **Import from VBP...** card. **File → Open Project...** on the `.vbp` file imports it the same way, and a `.vbp` from the dialog's **Recent (VB6)** section is imported as well, without the options box: ![The New / Open Project dialog on its New section, showing a grid of project template cards from Standard EXE to Import from folder, with a red arrow pointing up at the icon of the Import from VBP card.](Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png){:width="970" height="728"} -You can import individual files, from VB projects of any type, through the Import option on the Add menu, under Project or by right clicking the desired folder in the Project Explorer pane: +You can import individual files, from VB projects of any type, through the **Import...** item of the Add menu, under Project or by right clicking the desired folder in the Project Explorer pane: ![The Project Explorer context menu opened on a project's Sources folder, with the Add submenu expanded beside it. A red arrow points at Add, and a second arrow points at Import, which is ringed among the Add Folder, Add Windows Form and Add Resource entries.](Images/2b32ab8c-fabc-4f42-9e6b-06e85574eaf4.png){:width="754" height="626"} -> [!NOTE] -> You can select .bas/.cls files individually, but to import Forms, UserControls, Property Pages, and Resource Files you must currently select the .vbp file they're associated with. You'll then be shown a list of files you can import (with their new twinBASIC extensions .tbform/.twin and so on --- make sure to import both, e.g. for Form1.frm you'll see Form1.frm.tbform and Form1.frm.twin: - -![The Import from twinproj dialog: a tick-box tree of everything found in ImportDemo.vbp, with the Resources branch expanded over its ICON and MANIFEST folders and a Sources branch below, where a module from a folder beside the project's sits in a PARENT-FOLDER branch. Form1.frm.tbform and Form1.frm.twin are both ticked.](Images/16833fae-4bd7-418f-bb16-691a611a5b01.png){:width="469" height="401"} +You can select `.bas` and `.cls` files individually, but to import forms, user controls, property pages and resource files you must select the `.vbp` file they belong to. The IDE then lists the files it can import, with their new twinBASIC extensions, and you tick both files of a form, `Form1.frm.tbform` and `Form1.frm.twin`. -Importing a .vbp also carries over its [startup object](tB/IDE/Project-Settings#startup-object) and its [icon form](tB/IDE/Project-Settings#icon-form). A VB6 project that was linked as a console program --- its `LinkSwitches` line contains `/SUBSYSTEM:CONSOLE`, written in capitals --- becomes a [console application](tB/IDE/Project-Settings#is-console-application). No other linker switch is carried over. +[Importing a VB6 project](tB/IDE/Starting/Importing-VB6) describes the routes, the settings and files that the import converts and those it leaves, the import log, a missing file's box, and the tree of single files. A `.vbp` imported from **Recent (VB6)** or the command line gets the importer's own settings, with **Option Explicit** off. -**What happens to the `Attribute` lines.** The import keeps every `Attribute` line as VB6 wrote it, and turns none of them into a twinBASIC attribute such as `[Description]`. twinBASIC reads the lines itself when it compiles. **Import from VBP...** and **File → Open Project...** both import a project this way: +**What happens to the `Attribute` lines.** The import keeps every `Attribute` line as VB6 wrote it, and turns none of them into a twinBASIC attribute such as `[Description]`. twinBASIC reads the lines itself when it compiles. An imported project's files are treated like this: - A `.bas` or `.cls` file is imported unchanged, because twinBASIC compiles these files as they are. - A `.frm`, `.ctl` or `.pag` file becomes two files. The designer file (`.tbform`, `.tbcontrol` or `.tbppage`) holds the properties from the file's `Begin` block. The `.twin` file holds a **Class** with the module's code, its `Attribute` lines included. The import puts the attributes `[FormDesignerId]`, `[ClassId]`, `[InterfaceId]` and `[EventInterfaceId]` in front of the class, with new GUIDs, and `[COMControl]` in front of a UserControl. None of them comes from an `Attribute` line: a form keeps its `Attribute VB_PredeclaredId = True` line and gets no `[PredeclaredId]`. @@ -378,7 +375,7 @@ In a built ActiveX DLL, these lines have the same effect as the twinBASIC attrib ![The DIAGNOSTICS panel reporting five errors under frmMain.twin, each reading TB5079 Unrecognized symbol for hr or hMenu with its line and column. The first is expanded to show its Jump to issue and Help links.](Images/e409ea37-96ad-44c5-8017-3699ef04b53d.png){:width="347" height="193"} -While use is strongly recommended and considered a best practice, twinBASIC **does not** require `Option Explicit`. If you're seeing these errors, you may have overlooked a new feature of twinBASIC: automatically enabling `Option Explicit` project-wide. When you import a VB6 project, or create a new one, a small dialog pops up: +While use is strongly recommended and considered a best practice, twinBASIC **does not** require `Option Explicit`. If you're seeing these errors, you may have overlooked a new feature of twinBASIC: automatically enabling `Option Explicit` project-wide. When you import a VB6 project with **Import from VBP...** or **File → Open Project...**, or create a new project from a template or a sample, a small dialog pops up (see [New Project Options](tB/IDE/Starting/New-Project#new-project-options)). A `.vbp` that opens from **Recent (VB6)** shows no dialog, and its project has **Option Explicit** off. ![The twinBASIC New Project Options dialog: a Project Name box above ticked boxes for Option Explicit ON at project scope, Visual Styles and Include Common Controls, and a DPI Awareness dropdown set to SYSTEM_DPI_AWARE.](Images/05306a72-4ff6-427d-8970-969ef0c582e6.png){:width="529" height="279"} diff --git a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.png b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.png index 0761891e3..3028489b9 100644 Binary files a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.png and b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.png differ diff --git a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.svg b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.svg index e1029b963..6af70f387 100644 --- a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.svg +++ b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsTreeView + ListView + ImageListWindows Service Simple ExampleWindows Service Complex Example (inc Eve…OOP Inheritance Example (Animals)Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsTreeView + ListView + ImageListWindows Service Simple ExampleWindows Service Complex Example (inc Eve…OOP Inheritance Example (Animals)Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it. diff --git a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png index 00035aabc..b53f5b3e8 100644 Binary files a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png and b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png differ diff --git a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.svg b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.svg index 7cc9f612c..8a7c9a490 100644 --- a/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.svg +++ b/docs/Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsTreeView + ListView + ImageListWindows Service Simple ExampleWindows Service Complex Example (inc Eve…OOP Inheritance Example (Animals)Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsTreeView + ListView + ImageListWindows Service Simple ExampleWindows Service Complex Example (inc Eve…OOP Inheritance Example (Animals)Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it. diff --git a/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png b/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png index 77c582ae5..eb377757c 100644 Binary files a/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png and b/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png differ diff --git a/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.svg b/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.svg index 1fe060681..4d70e25e0 100644 --- a/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.svg +++ b/docs/Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.svg @@ -1 +1 @@ -DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1033MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESCompiler WarningsCompiler WarningsFilter warnings...TB0001 Use of legacy DefType statements is strongly discouragedTB0002 This variable has been auto-declared by the compiler due to Option Explicit being OFFTB0003 Datatype has not been explicitly declared so will default to VariantTB0004 [Exit Sub/Function] inside a Property-Get procedure is only supported for backwards compatibility. Use [Exit Property] instead.TB0005 #Warning directive was encounteredTB0006 Record length is ignored in binary modeTB0007 Mismatched datatype hintTB0008 Suspicious callTB0009 Member is unimplementedTB0010 Duplicate DLL declaration detectedTB0011 Use of legacy date/time property-let assignments are strongly discouragedTB0012 Possible misuse of hexadecimal literal notationTB0013 It is strongly recommended to specify an [ InterfaceId() ] attribute for this classTB0014 The inline ByVal attribute is ignored on User Defined TypesTB0015 Use of legacy DLL {LEGACY.DLL} has been redirected to {MODERN.DLL}WARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGHINTDefine how the compiler is to treat warnings inside this project. These settings can be overriden with Class/Module/procedure attributes: [ EnforceWarnings(TBxxxx) ][ EnforceErrors(TBxxxx) ][ IgnoreWarnings(TBxxxx) ]WARNINGHINTINFOIGNOREERROR +DIAGNOSTICS0000CALL STACKCOMPILER [Win32] v0.15.1034MAIN_THREAD [IDLE]VARIABLESPROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesSourcesIShape.twinMainModule.twinTypes.twinCHANGELOG.mdLICENCE.mdPROPERTIESCompiler WarningsCompiler WarningsFilter warnings...TB0001 Use of legacy DefType statements is strongly discouragedTB0002 This variable has been auto-declared by the compiler due to Option Explicit being OFFTB0003 Datatype has not been explicitly declared so will default to VariantTB0004 [Exit Sub/Function] inside a Property-Get procedure is only supported for backwards compatibility. Use [Exit Property] instead.TB0005 #Warning directive was encounteredTB0006 Record length is ignored in binary modeTB0007 Mismatched datatype hintTB0008 Suspicious callTB0009 Member is unimplementedTB0010 Duplicate DLL declaration detectedTB0011 Use of legacy date/time property-let assignments are strongly discouragedTB0012 Possible misuse of hexadecimal literal notationTB0013 It is strongly recommended to specify an [ InterfaceId() ] attribute for this classTB0014 The inline ByVal attribute is ignored on User Defined TypesTB0015 This Declare statement has been redirected to an internal twinBASIC implementation, supporting both 32-bit and 64-bitWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGWARNINGHINTDefine how the compiler is to treat warnings inside this project. These settings can be overriden with Class/Module/procedure attributes: [ EnforceWarnings(TBxxxx) ][ EnforceErrors(TBxxxx) ][ IgnoreWarnings(TBxxxx) ]WARNINGHINTINFOIGNOREERROR diff --git a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.png b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.png index 486c2de77..ceb38d2b6 100644 Binary files a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.png and b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.png differ diff --git a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.svg b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.svg index 6c9be85c2..d3fa94bed 100644 --- a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.svg +++ b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.svg @@ -1 +1 @@ -ToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWMainModule(general)(declaration)Main()Show▶   run MainModule.MainPROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesICONtwinBASIC.icoMANIFEST#1.xmlMESSAGETABLESTRINGSourcesfrmMain.tbformfrmMain.twinIShape.twinMainModule.twin +ToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWMainModule(general)(declaration)Main()Show▶   run MainModule.MainPROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesICONtwinBASIC.icoMANIFEST#1.xmlMESSAGETABLESTRINGSourcesfrmMain.tbformfrmMain.twinIShape.twinMainModule.twin diff --git a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png index 6af0855bd..d1cca2ac7 100644 Binary files a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png and b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png differ diff --git a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.svg b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.svg index 9fde2ff69..c8ac05eac 100644 --- a/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.svg +++ b/docs/Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.svg @@ -1 +1 @@ -ToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1033)win32100%PREVIEWMainModule(general)(declaration)Main()Show▶   run MainModule.MainPROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesICONtwinBASIC.icoMANIFEST#1.xmlMESSAGETABLESTRINGSourcesfrmMain.tbformfrmMain.twinIShape.twin +ToolsAdd-InsWindowHelpproject.twinproj - twinBASIC (twinBASIC IDE BETA 1034)win32100%PREVIEWMainModule(general)(declaration)Main()Show▶   run MainModule.MainPROJECT EXPLORERSampleProjectImportedTypeLibrariesMiscellaneousPackagesReferencesResourcesICONtwinBASIC.icoMANIFEST#1.xmlMESSAGETABLESTRINGSourcesfrmMain.tbformfrmMain.twinIShape.twin diff --git a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.png b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.png index 60970bc17..2191f3151 100644 Binary files a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.png and b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.png differ diff --git a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.svg b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.svg index d72937969..139942ccb 100644 --- a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.svg +++ b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) diff --git a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png index 54cbe6c48..cff1ea25c 100644 Binary files a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png and b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png differ diff --git a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.svg b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.svg index 298b056d4..380361fc8 100644 --- a/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.svg +++ b/docs/Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentStandard EXEActiveX ControlActiveX DLLStandard DLLStandard EXE (+VBCCR)Standard EXEImport from VBP...Import from folder...Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Create a new twinBASIC project from a template, or import an existing project.(non-ActiveX)(experimental)(Console App) diff --git a/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.png b/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.png index cb363a832..6b2caec0d 100644 Binary files a/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.png and b/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.png differ diff --git a/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.svg b/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.svg index 9fd6b15f7..109615ce9 100644 --- a/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.svg +++ b/docs/Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.svg @@ -1 +1 @@ -Assets3twinBASIC_IDE_BETA_1033.zip39.3 MB3 days agoSource code (zip)Jun 29Source code (tar.gz)Jun 29sha256:c1ed737e16129e1f… +Assets3twinBASIC_IDE_BETA_1034.zip39.3 MB5 days agoSource code (zip)Jun 29Source code (tar.gz)Jun 29sha256:db5b88cf07e81bd… diff --git a/docs/Reference/Core/Delegate.md b/docs/Reference/Core/Delegate.md index a96788dcd..f6376a6ae 100644 --- a/docs/Reference/Core/Delegate.md +++ b/docs/Reference/Core/Delegate.md @@ -38,6 +38,10 @@ After the declaration, *name* may be used wherever a type is allowed: to declare A delegate value is normally produced by **AddressOf**, which yields a delegate-typed reference to a regular procedure with a matching signature. A procedure that does not match causes only a warning, TB0026 *Mismatched delegate type*, and the program still builds; its arguments then arrive in a form it does not expect. See [Passing a function as an argument](../../Features/Language/Delegates#callbacks) for what that looks like and how to make it an error. For backwards compatibility, a delegate variable can also be assigned a plain **LongPtr** address obtained by other means --- the value passes through unchecked. A delegate variable is called like a function: `result = myDelegate(arg1, arg2)`. + +> [!NOTE] BETA 1034 +> After a **Delegate** statement, the next [**Friend**](Friend), **ReadOnly**, **WithEvents**, [**Implements**](Implements) or **Inherits** in the same class or module is refused with TB5182, *Syntax error. No handler for this symbol*. A procedure, a [**Declare**](Declare) or an [**Event**](Event) ends the effect; a blank line, a variable, a **Const**, a **Type**, an **Enum**, an attribute or another **Delegate** does not. Put the **Delegate** after those statements, at the end of the class if necessary: a delegate type can be used before its declaration. A **Delegate** in a **Module** does not affect a class that follows it in the same file. + ### Example A basic delegate, declared, assigned, and called: diff --git a/docs/Reference/Core/Friend.md b/docs/Reference/Core/Friend.md new file mode 100644 index 000000000..40651d7f9 --- /dev/null +++ b/docs/Reference/Core/Friend.md @@ -0,0 +1,131 @@ +--- +title: Friend +parent: Statements +permalink: /tB/Core/Friend +--- +# Friend +{: .no_toc } + +Makes a member of a class available to the whole project, and keeps it out of the class's public interface. + +Syntax: + +- > **Friend** [ **Static** ] [ **Overridable** ] **Sub** \| **Function** \| **Property Get** \| **Property Let** \| **Property Set** *name* ... +- > **Friend** [ **ReadOnly** \| **WithEvents** ] *varname* [ **As** [ **New** ] *type* ] [ **=** *value* ] [ **,** *varname* ... ] + +In a procedure declaration, **Friend** takes the place of **Public**, **Private** or **Protected**. The rest of the declaration is described on the [**Sub**](Sub), [**Function**](Function) and [**Property**](Property) pages. + +Every module and class of the project can use a **Friend** member, through a variable of the class's own type. The class does not publish it: the type library of an ActiveX DLL lists no **Friend** procedure or variable. Use **Friend** for what the classes of a project need from one another, and that a client of the class should not call. + + +> [!NOTE] BETA 1034 +> A **Friend** member also answers late-bound calls, through an **Object** or **Variant** variable and through [**CallByName**](../Modules/Interaction/CallByName), because the compiler's [**IDispatch**](../../Reference/COM-Interfaces/IDispatch#classes-written-in-twinbasic) finds it. This holds for an object passed to another project too, so that project can call its **Friend** procedures and read and write its **Friend** variables. VB6 raises error [438](tb:Errors#438), as twinBASIC does for a **Private** member. Do not rely on **Friend** to keep a member away from code outside the project. + + +> [!NOTE] BETA 1034 +> A project that references a package can use the **Friend** members of the package's classes, as if they were **Public**. + + +> [!NOTE] BETA 1034 +> The first **Friend** after a [**Delegate**](Delegate) statement is refused with TB5182, *Syntax error. No handler for this symbol*, even though nothing is wrong with it. Move the **Delegate** to the end of the class. The [**Delegate**](Delegate) page says what else this affects. + +## Where it can be used + +**Friend** applies to the members of a class, and a form is a class too. + +| Member of a class | Result | +|:---|:---| +| **Sub**, **Function**, **Property Get**, **Property Let**, **Property Set**, **Sub New** | Allowed. | +| Variable | Allowed, under the rules for a **Public** variable. It can have an initial value, or be **ReadOnly** or **WithEvents**. A fixed-size array is refused, with TB5131. | +| [**Event**](Event), [**Enum**](Enum), [**Type**](Type) | Allowed, and the same as **Public**: the type library lists them. | +| [**Const**](Const) | Refused, with TB5250: *Constants in a class cannot be Public*. | +| [**Declare**](Declare), [**Delegate**](Delegate) | Refused, with TB5227: these must be **Private** in a class. | + +Anywhere else **Friend** is a syntax error, TB5182: on a member of a [**Module**](Module), an [**Interface**](Interface) or a [**Type**](Type), and on a **Class**, **Module** or **Interface** itself. It is refused inside a procedure as well. + +A declaration has one access modifier, so `Public Friend Sub` and `Protected Friend Sub` are syntax errors. **Static** and **Overridable** can stand on either side of **Friend**. + +> [!NOTE] +> VB6 allows **Friend** only on a **Sub**, **Function** or **Property** procedure of a class. On anything else in a class it reports *Expected: Sub or Function or Property*, and in a standard module *Only valid in object module*. **Friend** on a variable, **Event**, **Enum** or **Type** is a twinBASIC extension. + +## Private types in a signature + +In VB6, a **Private** **Type** or **Enum** can be a parameter or the return type of a **Friend** procedure, but not of a **Public** one. That is a common reason to use **Friend** there. twinBASIC allows these types in both, in an ActiveX DLL too. The difference is in the type library: a **Public** member that uses a **Private** type adds that type to it, and a **Friend** member does not. + +## Implements, Inherits and Via + +A class that [**Implements**](Implements) another class does not have to provide its **Friend** procedures. Calling one through a variable of the implemented class, on an object of the implementing class, raises error [97](tb:Errors#97), *Can not call friend function on object which is not an instance of defining class*, as it does in VB6. A **Friend** variable is different: the implementing class must provide it, as a **Property Get** and a **Property Let**. + + +> [!WARNING] BETA 1034 +> In a 32-bit build, that call damages the stack unless the procedure's arguments and return value take exactly one stack slot besides **Me**. It raises error 97 correctly from a **Sub** with one argument of 4 bytes or less, from a **Function** or **Property Get** with no arguments, and from a **Property Let** with one such value. Any other signature, such as `Function F(ByVal X As Long) As Long`, a **Sub** with two arguments or a **Sub** with a **ByVal Currency** argument, ends an exe with an access violation before a handler runs, and a run in the IDE does not return. A **Sub** with no arguments raises an error 97 that **On Error** does not keep: in an exe the handler runs, and then the program stops with an error box when the procedure returns. A 64-bit build is not affected. Do not call a **Friend** procedure on an object that may be of an implementing class. + +A class that [**Inherits**](../../Features/Language/Inheritance#inherits-for-complete-oop) another class inherits its **Friend** members, and they stay available to the whole project. A **Friend Overridable** procedure is overridden in the usual way. + +`Implements ... Via` forwards the **Friend** members of the delegate as well as the **Public** ones, so they can be called on the delegating class from anywhere in the project. The [**ImplementsViaPrivateFriendlies**](Attributes#implementsviaprivatefriendlies) attribute keeps them inside that class. + +## Example + +`Post` changes the balance. `Bank`, in the same project, can call it, and a client of the class sees only `Balance`. + +```tb check_build projname=core-friend slot=file +Class Account + Private mBalance As Long + + Public Property Get Balance() As Long + Balance = mBalance + End Property + + ' For the rest of the project, not for clients of the class. + Friend Sub Post(ByVal Amount As Long) + mBalance = mBalance + Amount + End Sub +End Class + +Class Bank + Public Function OpenAccount(ByVal Deposit As Long) As Account + Set OpenAccount = New Account + OpenAccount.Post Deposit + End Function +End Class +``` + +```tb check_run projname=core-friend +Dim b As New Bank +Dim a As Account +Set a = b.OpenAccount(100) +a.Post 25 +Debug.Print a.Balance ' 125 +``` + +A class that implements `Account` provides `Balance`, and has no `Post` to call: + +```tb check_build projname=core-friend slot=file +Class TestAccount + Implements Account + + Private Property Get Account_Balance() As Long + Account_Balance = 1 + End Property +End Class +``` + +```tb check_run projname=core-friend +Dim a As Account +Set a = New TestAccount +Debug.Print a.Balance ' 1 +On Error Resume Next +a.Post 5 +Debug.Print Err.Number ' 97 +``` + +## See Also + +- [**Public** statement](Public) +- [**Private** statement](Private) +- [**Protected** statement](Protected) +- [**Sub** statement](Sub) +- [**Function** statement](Function) +- [**Property** statement](Property) +- [**Implements** statement](Implements) +- [Inheritance](../../Features/Language/Inheritance) diff --git a/docs/Reference/Core/Function.md b/docs/Reference/Core/Function.md index aa0631ddb..b436cb772 100644 --- a/docs/Reference/Core/Function.md +++ b/docs/Reference/Core/Function.md @@ -30,7 +30,7 @@ Syntax: **Private** : *optional* Indicates that the **Function** procedure is accessible only to other procedures in the module where it is declared. -**Friend** +**[Friend](Friend)** : *optional* Used only in a class module. Indicates that the **Function** procedure is visible throughout the project, but not visible to a controller of an instance of an object. **[Protected](Protected)** @@ -113,7 +113,7 @@ If not explicitly specified by using **Public**, **Private**, or **Friend**, **F If **Static** isn't used, the value of local variables is not preserved between calls. -The **Friend** keyword can only be used in class modules. However, **Friend** procedures can be accessed by procedures in any module of a project. A **Friend** procedure does not appear in the type library of its parent class, nor can a **Friend** procedure be late bound. +The **Friend** keyword can only be used in class modules. However, **Friend** procedures can be accessed by procedures in any module of a project. A **Friend** procedure does not appear in the type library of its parent class. **Function** procedures can be recursive; that is, they can call themselves to perform a given task. However, recursion can lead to stack overflow. The **Static** keyword usually isn't used with recursive **Function** procedures. diff --git a/docs/Reference/Core/Implements.md b/docs/Reference/Core/Implements.md index 89c2d5bf8..e92e2e84f 100644 --- a/docs/Reference/Core/Implements.md +++ b/docs/Reference/Core/Implements.md @@ -40,6 +40,10 @@ twinBASIC extends classic VBA's **Implements** in several ways. See [Inheritance > [!NOTE] > Use `Private` (or `Friend`) on the implementing procedures so that the interface methods don't also become part of the implementing class's *default* interface. The conventional naming pattern is `_`. + +> [!WARNING] BETA 1034 +> A class that implements another class need not provide its [**Friend**](Friend) procedures, and a call to one through a variable of the implemented class raises error [97](tb:Errors#97). In a 32-bit build, most signatures end the program with an access violation instead. See [**Friend**](Friend#implements-inherits-and-via). + ### Example The following example shows how to use the **Implements** statement to make a set of declarations available to multiple classes. By sharing the declarations through the **Implements** statement, neither class has to make any declarations itself. The example also shows how use of an interface supports abstraction: a strongly-typed variable can be declared by using the interface type. It can then be assigned objects of different class types that implement the interface. diff --git a/docs/Reference/Core/Private.md b/docs/Reference/Core/Private.md index 695039bf1..b3760197b 100644 --- a/docs/Reference/Core/Private.md +++ b/docs/Reference/Core/Private.md @@ -69,5 +69,6 @@ Private MyVar, YourVar, ThisVar As Integer - [**Dim** statement](Dim) - [**Public** statement](Public) +- [**Friend** statement](Friend) - [**Static** statement](Static) - [**ReDim** statement](ReDim) diff --git a/docs/Reference/Core/Property.md b/docs/Reference/Core/Property.md index 66e829e97..5c218a7f7 100644 --- a/docs/Reference/Core/Property.md +++ b/docs/Reference/Core/Property.md @@ -50,7 +50,7 @@ Syntax: **Private** : *optional* Indicates that the **Property** procedure is accessible only to other procedures in the module where it is declared. -**Friend** +**[Friend](Friend)** : *optional* Used only in a class module. Indicates that the **Property** procedure is visible throughout the project, but not visible to a controller of an instance of an object. **[Protected](Protected)** @@ -128,7 +128,7 @@ If the name of a generic type parameter is used, it becomes bound to the concret If not explicitly specified by using **Public**, **Private**, or **Friend**, **Property** procedures are public by default. If **Static** isn't used, the value of local variables is not preserved between calls. -The **Friend** keyword can only be used in class modules. However, **Friend** procedures can be accessed by procedures in any module of a project. A **Friend** procedure doesn't appear in the type library of its parent class, nor can a **Friend** procedure be late bound. +The **Friend** keyword can only be used in class modules. However, **Friend** procedures can be accessed by procedures in any module of a project. A **Friend** procedure doesn't appear in the type library of its parent class. All executable code must be in procedures. A **Property** procedure cannot be defined inside another **[Property](Property)**, **[Sub](Sub)**, or **[Function](Function)** procedure. diff --git a/docs/Reference/Core/Protected.md b/docs/Reference/Core/Protected.md index 1c1ffa1b7..7990f19ab 100644 --- a/docs/Reference/Core/Protected.md +++ b/docs/Reference/Core/Protected.md @@ -9,7 +9,7 @@ permalink: /tB/Core/Protected Declares a class member (variable, procedure, or property) that is accessible from inside the declaring class and from classes that derive from it via [**Inherits**](../../Features/Language/Inheritance#inherits-for-complete-oop), but not from outside callers. > [!NOTE] -> The **Protected** access modifier is a twinBASIC extension over classic VBA, which has only **Public**, **Private**, and **Friend**. **Protected** is meaningful only inside an [**Inherits**](../../Features/Language/Inheritance#inherits-for-complete-oop) hierarchy; in a class without inheritance, it behaves like [**Private**](Private). +> The **Protected** access modifier is a twinBASIC extension over classic VBA, which has only **Public**, **Private**, and [**Friend**](Friend). **Protected** is meaningful only inside an [**Inherits**](../../Features/Language/Inheritance#inherits-for-complete-oop) hierarchy; in a class without inheritance, it behaves like [**Private**](Private). Syntax: @@ -76,5 +76,6 @@ End Class - [**Public** statement](Public) - [**Private** statement](Private) +- [**Friend** statement](Friend) - [**Class** statement](Class) - [Inheritance](../../Features/Language/Inheritance) diff --git a/docs/Reference/Core/Public.md b/docs/Reference/Core/Public.md index ba07c10dd..29e1fc9d9 100644 --- a/docs/Reference/Core/Public.md +++ b/docs/Reference/Core/Public.md @@ -68,6 +68,7 @@ Public MyVar, YourVar, ThisVar As Integer - [**Dim** statement](Dim) - [**Private** statement](Private) +- [**Friend** statement](Friend) - [**Static** statement](Static) - [**Option** statement](Option) - [**ReDim** statement](ReDim) diff --git a/docs/Reference/Core/Sub.md b/docs/Reference/Core/Sub.md index d6b7ac5af..02cbdce6f 100644 --- a/docs/Reference/Core/Sub.md +++ b/docs/Reference/Core/Sub.md @@ -26,7 +26,7 @@ Syntax: **Private** : *optional* Indicates that the **Sub** procedure is accessible only to other procedures in the module where it is declared. -**Friend** +**[Friend](Friend)** : *optional* Used only in a class module. Indicates that the **Sub** procedure is visible throughout the project, but not visible to a controller of an instance of an object. **[Protected](Protected)** @@ -94,7 +94,7 @@ If not explicitly specified by using **Public**, **Private**, or **Friend**, **S If **Static** isn't used, the value of local variables is not preserved between calls. -The **Friend** keyword can only be used in class modules. However, **Friend** procedures can be accessed by procedures in any module of a project. A **Friend** procedure does not appear in the type library of its parent class, nor can a **Friend** procedure be late bound. +The **Friend** keyword can only be used in class modules. However, **Friend** procedures can be accessed by procedures in any module of a project. A **Friend** procedure does not appear in the type library of its parent class. **Sub** procedures can be recursive; that is, they can call themselves to perform a given task. However, recursion can lead to stack overflow. The **Static** keyword usually is not used with recursive **Sub** procedures. diff --git a/docs/Reference/Core/Type.md b/docs/Reference/Core/Type.md index 865f6f1cc..aa7172e50 100644 --- a/docs/Reference/Core/Type.md +++ b/docs/Reference/Core/Type.md @@ -104,7 +104,7 @@ twinBASIC extends classic VBA's **Type** in several ways. UDTs remain stack-allo > [!WARNING] BETA 1034 -> A member procedure called on an element of an array of the UDT, such as `a(1).Sum` or `a(1).Diff(a(2))`, ends the program with an access violation, in 32-bit and 64-bit builds. This holds for local, static, module-level, dynamic and parameter arrays, and in a **With** block. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected. Copy the element to a variable first. +> A member procedure called on an element of an array of the UDT, such as `a(1).Sum` or `a(1).Diff(a(2))`, ends the program with an access violation, in 32-bit and 64-bit builds. So does a **Type_Conversion** of an element, explicit or implicit, as in `CStr(a(1))`, `s = a(1)` or `Debug.Print a(1)`. This holds for local, static, module-level, dynamic and parameter arrays, and in a **With** block. A variable of the type, a UDT member of another UDT, and an element of an array declared inside a UDT are not affected, nor is **Type_Assignment** on an element, as in `a(1) = 5`. Copy the element to a variable first. > [!NOTE] BETA 1034 @@ -128,6 +128,14 @@ twinBASIC extends classic VBA's **Type** in several ways. UDTs remain stack-allo | `Type_Conversion` | Conversion operator --- produces a value of another type from the UDT. The signature is `Function Type_Conversion() As ...`, and several overloads with different return types may coexist. | | `Type_DebugView` | Debugger display --- returns a **String** shown in the IDE's variable inspector. | + +> [!WARNING] BETA 1034 +> **Type_Initialize** does not run for an element of an array of the UDT, or for a UDT that is a member of another UDT. **Type_Terminate** still runs for each of them, on a value that was never initialized, and no error is raised. A variable of the type and a class field of the type are not affected. **ReDim** and **ReDim Preserve** discard elements without running **Type_Terminate**, and an array declared inside a UDT runs neither procedure. Write **Type_Terminate** to do nothing when the members are all zero. + + +> [!NOTE] BETA 1034 +> A UDT with two or more **Type_Conversion** overloads, none of them `As Variant`, cannot be converted to a **Variant**. `Debug.Print c`, `v = c` and `CVar(c)` are a code generation error that fails the build, and an argument for a `ByVal` **Variant** parameter is error TB5001. A conversion to **String** or **Long**, as in `s = c`, is not affected. Name the target type, as in `CStr(c)`, or add an overload `As Variant`, which a conversion to **Variant** then uses. + **API declarations inside a UDT.** A [**Declare**](Declare) statement inside a **Type** body works as a regular module-level declaration, except that when its first parameter is named `Me` and is the same type as the UDT, calls on a variable of that type pass the variable as the first argument implicitly: ```tb check_build slot=module diff --git a/docs/Reference/Statements.md b/docs/Reference/Statements.md index 494421500..a14440f4a 100644 --- a/docs/Reference/Statements.md +++ b/docs/Reference/Statements.md @@ -50,6 +50,8 @@ These statements are built into the language itself. They are understood by the * [For Each...Next](../tB/Core/For-Each-Next) -- repeats a group of statements for each element in an array or collection +* [Friend](../tB/Core/Friend) -- makes a member of a class available to the whole project, and keeps it out of the class's public interface + * [Function](../tB/Core/Function) -- declares the name, arguments, and code that form the body of a **Function** procedure * [Get](../tB/Core/Get) -- reads data from an open disk file into a variable diff --git a/docs/Tutorials/CustomControls/Images/ccSampleProject.light.png b/docs/Tutorials/CustomControls/Images/ccSampleProject.light.png index aa89c96de..61657b9d2 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccSampleProject.light.png and b/docs/Tutorials/CustomControls/Images/ccSampleProject.light.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccSampleProject.light.svg b/docs/Tutorials/CustomControls/Images/ccSampleProject.light.svg index 081a86029..5e26d1b6d 100644 --- a/docs/Tutorials/CustomControls/Images/ccSampleProject.light.svg +++ b/docs/Tutorials/CustomControls/Images/ccSampleProject.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.12 +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.12 diff --git a/docs/Tutorials/CustomControls/Images/ccSampleProject.png b/docs/Tutorials/CustomControls/Images/ccSampleProject.png index bb0cca078..e383ee8cc 100644 Binary files a/docs/Tutorials/CustomControls/Images/ccSampleProject.png and b/docs/Tutorials/CustomControls/Images/ccSampleProject.png differ diff --git a/docs/Tutorials/CustomControls/Images/ccSampleProject.svg b/docs/Tutorials/CustomControls/Images/ccSampleProject.svg index e245cd798..4e0883a60 100644 --- a/docs/Tutorials/CustomControls/Images/ccSampleProject.svg +++ b/docs/Tutorials/CustomControls/Images/ccSampleProject.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.12 +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)twinBASIC IDE Addin (VirtualListView)twinBASIC IDE Addin (GlobalSearch)twinBASIC IDE Addin (TODO Widgets demo)Static Library Example (SQLITE3)Static Library Example (libdeflate)MDI FormsSearch projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.12 diff --git a/docs/Tutorials/Hello-World.md b/docs/Tutorials/Hello-World.md index df70ecacb..c65cce866 100644 --- a/docs/Tutorials/Hello-World.md +++ b/docs/Tutorials/Hello-World.md @@ -14,7 +14,7 @@ In this tutorial you will create a Standard EXE project, place a button on a for ## Create the project -Open twinBASIC and choose **File → New Project → Standard EXE**. The IDE creates a new project with one form, `Form1`, already open in the designer. +Open twinBASIC. When it starts with no project, the **New / Open Project** dialog opens by itself after the splash. If a project is open, choose **File → New Project...** (Ctrl+N) to open it. Select the **Standard EXE** card and press **Open**, or double-click the card, and then press **Continue** in the **New Project Options** box that follows. The IDE creates a new project with one form, `Form1`, already open in the designer. The project is not saved yet: its title bar reads `Unsaved.twinproj`, and the first save asks for a file name. [New Project](../tB/IDE/Starting/New-Project) describes the dialog. diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.png b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.png index 3612471df..43be85182 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.png and b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.png differ diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.svg b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.svg index 5b4f6e537..e51526d7e 100644 --- a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.svg +++ b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.light.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png index bd88d563f..767191721 100644 Binary files a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png and b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.png differ diff --git a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.svg b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.svg index 74efe0638..6883bb1dc 100644 --- a/docs/Tutorials/WebView2/Images/tbWebView2Sample0.svg +++ b/docs/Tutorials/WebView2/Images/tbWebView2Sample0.svg @@ -1 +1 @@ -EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10338260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. +EDITOR 1DIAGNOSTICS0000CALL STACKVARIABLESPROJECT EXPLORERPROPERTIESNew / Open Project...Browse...OpenCancelNewSamplesRecentReports (Experimental)HelloWorldWebView2 ExamplesChromium Embedded Framework ExamplesGetIPAddressesMyCodeLibraryMyVBEAddin (NEW with ToolWindow)MyCOMAddinCustomControlsPackageFilePropertyViewer (CustomControls)ActiveX Control WebView2 + MonacotwinBASIC IDE AddintwinBASIC IDE Addin (Chart)twinBASIC IDE Addin (Monaco)twinBASIC IDE Addin (ListView)Search projects, samples, recent...BETA 10348260Double-click an item or press Enterto open it.Ready-made sample projects to explore the twinBASIC language and IDE. diff --git a/docs/_book.yml b/docs/_book.yml index 1565156a7..f335b5595 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -255,17 +255,13 @@ parts: - title: The twinBASIC IDE subtitle: Starting, configuring, building, and debugging a project - # Only the IDE pages with prose of their own. The rest of the section - # is in left_out at the end of this file; move a page from there to - # here once it explains something. Links to the pages left out open - # the website. landing_page: /tB/IDE outline_closed: true chapters: - - title: New Project - page: /tB/IDE/Project/New - no_outline_entry: true - no_heading_shift: true + - title: Starting a project + landing_page: /tB/IDE/Starting/ + page: /tB/IDE/Starting/ + landing_is_target: true outline_closed: true - title: Projects and files landing_page: /tB/IDE/Projects/ @@ -307,8 +303,16 @@ parts: page: /tB/IDE/Customising/ landing_is_target: true outline_closed: true + - title: When something goes wrong + landing_page: /tB/IDE/Troubleshooting/ + page: /tB/IDE/Troubleshooting/ + landing_is_target: true + outline_closed: true - title: Add Ins - page: /tB/IDE/AddIns/ + pages: + - /tB/IDE/AddIns/ + - /tB/IDE/AddIns/GlobalSearch + - /tB/IDE/AddIns/Community no_descent: true no_outline_entry: true no_heading_shift: true @@ -319,8 +323,9 @@ parts: no_heading_shift: true outline_closed: true - title: IDE Reference - pages: - - /tB/IDE/Reference/Toolbar + landing_page: /tB/IDE/Reference + page: /tB/IDE/Reference + landing_is_target: true outline_closed: true - title: The Core Language @@ -687,29 +692,3 @@ left_out: - /tB/Packages/Contributed/TBMANLIB_ListBoxHScroll/ - /tB/Packages/Contributed/TBMANLIB_Xml/ - /tB/Packages/Contributed/WinReg/ - - - reason: IDE pages that are still screenshots and the labels they show, or headings with nothing under them - no_descent: true - pages: - - /tB/IDE/AddIns/Community - - /tB/IDE/Reference/Menu - - /tB/IDE/Reference/Menu/AddIns - - /tB/IDE/Reference/Menu/Help - - - reason: IDE pages that are complete but too short, or too much a list, to earn space in print yet - no_descent: true - pages: - - /tB/IDE/Reference/StatusBar - - /tB/IDE/Reference - - /tB/IDE/Project/Webpage - - /tB/IDE/Project/Splash - - /tB/IDE/AddIns/GlobalSearch - - /tB/IDE/Reference/Menu/File - - /tB/IDE/Reference/Menu/Edit - - /tB/IDE/Reference/Menu/Project - - /tB/IDE/Reference/Menu/Format - - /tB/IDE/Reference/Menu/Run - - /tB/IDE/Reference/Menu/Debug - - /tB/IDE/Reference/Menu/View - - /tB/IDE/Reference/Menu/Tools - - /tB/IDE/Reference/Menu/Window # ~800 lines of shortcut JSON that would print unfolded diff --git a/scripts/check_tb_registry.mjs b/scripts/check_tb_registry.mjs index 1080dbc80..8ea2d7baa 100644 --- a/scripts/check_tb_registry.mjs +++ b/scripts/check_tb_registry.mjs @@ -53,6 +53,7 @@ import { tmpdir } from "node:os"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../lib/cli.mjs"; +import { TOOL_ROOT } from "./lib/shot-root.mjs"; import * as R from "./lib/tb-registry.mjs"; // A crash exits 2; the catch below passes on everything but a failed assertion. @@ -360,6 +361,28 @@ try { /outside/, "the temp folder itself is not a sweepable prefix", ); + + // One folder outside the temp folder is swept, shoot_docs's, and no other: not one beside it, + // not its parent, and not a folder inside it. + const TOOL_PROJECT = `${TOOL_ROOT}\\Building\\project.twinproj`; + const TOOL_NEIGHBOUR = `${TOOL_ROOT}2\\Building\\project.twinproj`; + setValues(`${ROOT}\\ProjectState`, { [TOOL_PROJECT]: "{}", [TOOL_NEIGHBOUR]: "{}" }); + R.restoreProjects({ root: ROOT, entries: [] }, { prefixes: [TOOL_ROOT] }); + const toolState = R.ideLists({ root: ROOT }).projectState; + assert.ok(!toolState.includes(TOOL_PROJECT), "the allowed folder's entry is swept"); + assert.ok(toolState.includes(TOOL_NEIGHBOUR), "and the neighbour's is not"); + deleteValues(`${ROOT}\\ProjectState`, [TOOL_NEIGHBOUR]); + const memoryKey = `${ROOT}\\IDESettings`; + const targets = JSON.stringify({ [TOOL_PROJECT]: "win64", [TOOL_NEIGHBOUR]: "win32" }); + setValues(memoryKey, { targetArchitectureMemory: targets }); + assert.deepEqual(R.rememberedTargets([TOOL_ROOT], { root: ROOT }), [TOOL_PROJECT]); + assert.deepEqual(R.rememberedTargets([TOOL_ROOT.toUpperCase()], { root: ROOT }), [TOOL_PROJECT], "in any case"); + deleteValues(memoryKey, ["targetArchitectureMemory"]); + for (const near of [`${TOOL_ROOT}2`, path.dirname(TOOL_ROOT), path.join(TOOL_ROOT, "Building")]) { + assert.throws(() => R.restoreProjects({ root: ROOT, entries: [] }, { prefixes: [near] }), /outside/, near); + assert.throws(() => R.rememberedTargets([near], { root: ROOT }), /outside/, near); + } + assert.throws( () => R.restoreKeys([ diff --git a/scripts/ide-js/lib.mjs b/scripts/ide-js/lib.mjs index 078462d37..5e2664c11 100644 --- a/scripts/ide-js/lib.mjs +++ b/scripts/ide-js/lib.mjs @@ -578,6 +578,111 @@ export function patchUnits(parsed, fix, map) { return { text, edits, skipped }; } +// ---- the built-in keyboard shortcuts ---- + +const KEY_NAMES = { + ARROWUP: "Up", + ARROWDOWN: "Down", + ARROWLEFT: "Left", + ARROWRIGHT: "Right", + ESCAPE: "Esc", + ENTER: "Enter", + SPACE: "Space", + TAB: "Tab", + BACKSPACE: "Backspace", + DELETE: "Delete", + INSERT: "Insert", + HOME: "Home", + END: "End", + PAGEUP: "Page Up", + PAGEDOWN: "Page Down", + CANCEL: "Break", + CONTROL: "Ctrl", + SHIFT: "Shift", + ALT: "Alt", +}; +// Written as entities, because in a page's markdown these would otherwise mean something. +const KEY_ENTITIES = { "\\": "\", "*": "*", "&": "&", "|": "|" }; +const kbd = (name) => `${KEY_ENTITIES[name] ?? name}`; + +/** + * The built-in group of keyboard shortcuts, `{ command: [key string, ...] }` in the build's + * order, read from the syntax tree: `var builtInKeyboardShortcutGroups={"":{data:{...}}}`. + */ +export function builtInKeymap(parsed) { + const prop = (object, name) => + object?.type === "ObjectExpression" + ? object.properties.find((p) => (p.key.name ?? p.key.value) === name)?.value + : undefined; + for (const node of parsed.ast.body) { + if (node.type !== "VariableDeclaration") continue; + const decl = node.declarations.find((d) => d.id.name === "builtInKeyboardShortcutGroups"); + const data = prop(prop(decl?.init, ""), "data"); + if (!data) continue; + return Object.fromEntries( + data.properties.map((p) => [p.key.name ?? p.key.value, p.value.elements.map((e) => e.value)]), + ); + } + throw new Error("main.js has no built-in keyboard shortcut group"); +} + +/** + * A key string as a page writes it: `{ keys, release }`, where `keys` is the key with its + * modifiers in , and `release` says the string has no {KEYDOWN}, so that it runs when the + * key is released. `{ALLEXCEPT}...` has no such form, and the page says what it is in words. + */ +export function keyLabel(string) { + if (string.startsWith("{ALLEXCEPT}")) + return { keys: "every key its `{ALLEXCEPT}` list does not name", release: true }; + let rest = string; + const has = (token) => { + if (!rest.startsWith(token)) return false; + rest = rest.slice(token.length); + return true; + }; + const down = has("{KEYDOWN}"); + has("{KEYDOWNREPEAT}"); + const modifiers = []; + if (has("{CTRL}")) modifiers.push("Ctrl"); + if (has("{SHIFT}")) modifiers.push("Shift"); + if (has("{ALT}")) modifiers.push("Alt"); + return { keys: [...modifiers, KEY_NAMES[rest] ?? rest].map(kbd).join("+"), release: !down }; +} + +/** The table of the Keyboard Shortcuts reference page, one row for each command, by name. */ +export function keymapTable(map) { + const rows = Object.keys(map) + .sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })) + .map((command) => { + const labels = map[command].map(keyLabel); + const list = (release) => + labels + .filter((l) => l.release === release) + .map((l) => l.keys) + .join(", "); + const cell = [list(false), list(true) && `${list(true)}, on release`].filter(Boolean).join("; "); + return `| \`${command}\` | ${cell || "none"} |`; + }); + return ["| Command | Keys |", "|---|---|", ...rows].join("\n"); +} + +/** + * `page` with its keymap table, from the `| Command | Keys |` line to the last row, replaced by + * `table`, in the page's own line endings. The table of a page is `.current`. + */ +export function pageKeymap(page, table) { + const eol = page.includes("\r\n") ? "\r\n" : "\n"; + const lines = page.split(eol); + const start = lines.indexOf("| Command | Keys |"); + if (start < 0) throw new Error("the page has no | Command | Keys | table"); + let end = start; + while (lines[end + 1]?.startsWith("|")) end++; + return { + current: lines.slice(start, end + 1).join("\n"), + text: [...lines.slice(0, start), ...table.split("\n"), ...lines.slice(end + 1)].join(eol), + }; +} + // ---- the fix shape ---- /** A unified diff of two texts' lines, `context` lines around each change; "" when they are equal. */ diff --git a/scripts/ide_js.mjs b/scripts/ide_js.mjs index 027e7e911..f8db0261b 100644 --- a/scripts/ide_js.mjs +++ b/scripts/ide_js.mjs @@ -21,16 +21,20 @@ import { die, exitOnCrash, parseCli, printHelpAndExit, withUsageError } from ".. import { REPO_ROOT } from "../lib/repo-paths.mjs"; import { buildOf, + builtInKeymap, checkUnit, coverage, emptyMap, FixError, fileView, fixShapes, + keymapTable, + lineDiff, load, mainJsOf, NameError, namedUnits, + pageKeymap, parse, patchUnits, scopes, @@ -44,8 +48,9 @@ import { findIde } from "./lib/tb-install.mjs"; exitOnCrash(); const DEFAULT_MAP = path.join(REPO_ROOT, "scripts", "ide-js", "names.json"); +const KEYMAP_PAGE = path.join(REPO_ROOT, "docs", "IDE", "Reference", "Keyboard-Shortcuts.md"); -const USAGE = `usage: node scripts/ide_js.mjs [arguments] [--ide ] [--map ] [--out ] [--copy-to ] [--all] [--json] [-h, --help] +const USAGE = `usage: node scripts/ide_js.mjs [arguments] [--ide ] [--map ] [--out ] [--copy-to ] [--all] [--json] [--check] [--update] [-h, --help] Reads, names and patches the twinBASIC IDE's minified ide/main.js. A unit is a top-level function ("debugOutputPartial") or a method of a top-level class @@ -75,6 +80,9 @@ Commands: its twinBASIC.exe; --out writes main.js only shape the fix shape: each unit of the fix as the build has it and as fixed, a diff in our names, as markdown + keymap --ide's built-in keyboard shortcuts, as the table of + docs/IDE/Reference/Keyboard-Shortcuts.md; --check + compares the page with it, --update writes it there Options: --ide the install: its twinBASIC.exe, its folder or a main.js @@ -86,6 +94,8 @@ Options: --copy-to patch: the folder for the patched copy, under the temp folder --all view: the whole file --json survey, units, check: one JSON object + --check keymap: compare the page's table with the build + --update keymap: write the build's table into the page -h, --help print this text and exit Exit codes: @@ -93,7 +103,7 @@ Exit codes: 1 a finding: check, a name is lost; survey, a byte the token list does not account for; patch, shape, a fix that does not apply to the build (a unit it names is missing, a name in it is one of the build's minified names, or - the patched unit is not the fix) + the patched unit is not the fix); keymap --check, the page differs 2 a refused command line, an install or a file that cannot be read, a main.js or fix that does not parse, a name the map may not take, a refused --out or --copy-to, or a crash`; @@ -111,6 +121,7 @@ const COMMANDS = { view: [0, Infinity, ["ide", "map", "out", "all"]], patch: [1, 1, ["ide", "map", "out", "copyTo"]], shape: [1, 1, ["ide", "map"]], + keymap: [0, 0, ["ide", "check", "update"]], }; const COMMAND_ARGS = { units: "another install", @@ -132,6 +143,8 @@ const { values, positionals } = withUsageError( "copy-to": { type: "string" }, all: { type: "boolean", default: false }, json: { type: "boolean", default: false }, + check: { type: "boolean", default: false }, + update: { type: "boolean", default: false }, help: { type: "boolean", short: "h", default: false }, }, positionals: { min: 0, max: Infinity }, @@ -150,7 +163,7 @@ const [command, ...args] = positionals; if (!command) die(2, USAGE); if (!(command in COMMANDS)) refuse(`unknown command: ${command}`); const [min, max, applies] = COMMANDS[command]; -for (const key of ["ide", "map", "out", "copyTo", "all", "json"]) { +for (const key of ["ide", "map", "out", "copyTo", "all", "json", "check", "update"]) { const given = values[key] !== undefined && values[key] !== false; if (given && !applies.includes(key)) refuse(`${FLAG(key)} does not apply to ${command}`); } @@ -160,6 +173,7 @@ const most = command === "view" && values.all ? 0 : max; if (args.length < fewest) refuse(`${command} needs ${COMMAND_ARGS[command]}`); if (args.length > most) refuse(`unexpected argument: ${args[most]}`); if (command === "view" && values.all && !values.out) refuse("view --all needs --out "); +if (values.check && values.update) refuse("keymap takes one of --check and --update"); if (command === "patch" && !values.out === !values.copyTo) refuse("patch needs one of --out and --copy-to "); if (command === "name") { @@ -367,6 +381,27 @@ if (command === "view") { process.exit(0); } +if (command === "keymap") { + const { parsed, build } = loadInstall(values.ide); + const table = keymapTable(builtInKeymap(parsed)); + if (!values.check && !values.update) { + console.log(table); + process.exit(0); + } + const page = readFileSync(KEYMAP_PAGE, "utf8"); + const { current, text } = pageKeymap(page, table); + const name = path.relative(REPO_ROOT, KEYMAP_PAGE); + if (values.update) { + writeFileSync(KEYMAP_PAGE, text); + console.log(`${name}: ${current === table ? "unchanged" : `written from ${build ?? "the build"}`}`); + process.exit(0); + } + const diff = lineDiff(current, table, { context: 0 }); + if (diff) console.log(diff); + console.log(`${name}: ${diff ? `differs from ${build ?? "the build"}` : `the same as ${build ?? "the build"}`}`); + process.exit(diff ? 1 : 0); +} + // patch and shape const { file, parsed, build } = loadInstall(values.ide); const fix = loadFix(args[0]); diff --git a/scripts/lib/cli-cases.mjs b/scripts/lib/cli-cases.mjs index c587831b5..d22a880fb 100644 --- a/scripts/lib/cli-cases.mjs +++ b/scripts/lib/cli-cases.mjs @@ -813,6 +813,8 @@ bad( bad(tool, ["survey", "extra"], thenUsage("unexpected argument: extra", tool)); bad(tool, ["survey", "--map", "x.json"], thenUsage("--map does not apply to survey", tool)); bad(tool, ["check", "--out", "x.js"], thenUsage("--out does not apply to check", tool)); + bad(tool, ["survey", "--check"], thenUsage("--check does not apply to survey", tool)); + bad(tool, ["keymap", "--check", "--update"], thenUsage("keymap takes one of --check and --update", tool)); bad(tool, ["shape", "fix.js", "--copy-to", "x"], thenUsage("--copy-to does not apply to shape", tool)); bad(tool, ["patch", "fix.js"], thenUsage("patch needs one of --out and --copy-to ", tool)); bad( diff --git a/scripts/lib/shot-first-run.mjs b/scripts/lib/shot-first-run.mjs new file mode 100644 index 000000000..6acc94eb9 --- /dev/null +++ b/scripts/lib/shot-first-run.mjs @@ -0,0 +1,127 @@ +// What keeps a first start's dialogs from the user's registry, for shoot_docs.mjs. +// +// The IDE reads --no-save-ide-settings in parseCommandLine, after the first-run questions, so +// while the licence agreement, the error-reporting question and the first-run IDE Options are +// showing the flag is not set, and each writes the user's IDESettings: LICENCEAGREEMENTV3 on I +// AGREE, ALLOWERRORREPORTINGV3 the moment the question opens, GENERAL the moment the Options open. +// The defaults hook (tb-defaults.mjs) drops the saves of the keys it answers, which are not those +// of the licence. So a page that shows those dialogs runs over a guard of its own, put in at the +// document's start (Page.addScriptToEvaluateOnNewDocument) and the page reloaded: +// +// * It stands in front of the hook, on window.hostAppObject, by defineProperty: an assignment +// would go to the hook's setter. The hook's script runs first, because it was added first; a +// guard that ran before it would be replaced, `inFront` stays false, and no first-run key is +// answered, so the page starts as any other and shoot_docs stops. +// * It answers the first-run keys, and records and blocks every SaveIDESetting by its key (a +// value is never kept) and every other call that writes or that ends the IDE. +// * A second proxy stands under the hook, on the host object itself, and records any of those +// calls that reached it in `through`, and drops it too. The record of a run is that list and +// the counts of what was blocked; shoot_docs fails on any entry in `through`. +// * The Recent lists are answered empty, so no path of the user's comes into the page. +// +// An answer comes after a real call to the host. The page's theme files are read by host calls +// made before GENERAL is asked for, so the host answers them first, and a first start draws its +// dialogs in the theme. An answer made in the page alone overtakes them: buildThemeData then finds +// no theme, and the dialogs are drawn with no frame and no background, which no first start shows. +// LICENCEAGREEMENTV3 is the call made: the hook leaves it to the host, and its value is dropped. + +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { IDE_SETTINGS_KEY } from "./tb-registry.mjs"; + +// What the page reads for a first start: the three keys a new machine does not have. GENERAL +// is "", not the hook's "{}": "" is what makes it a first start. +export const FIRST_RUN = { GENERAL: "", LICENCEAGREEMENTV3: "", ALLOWERRORREPORTINGV3: "" }; + +// What the page reads for a start past those dialogs: agreed, and reporting off, so that a page +// left between the shots reports nothing. Made-up dates; neither is saved. +export const SETTLED = { LICENCEAGREEMENTV3: "AGREED_01-JAN-2026", ALLOWERRORREPORTINGV3: "DISAGREED_01-JAN-2026" }; + +// Runs in the page, in every document of the IDE's browser; only main.htm does anything. +function pageGuard(cfg) { + if (!/main\.htm$/i.test(location.pathname)) return; + if (window.__shotGuard) return; + const G = (window.__shotGuard = { + gen: cfg.gen, + hook: !!window.__tbDefaults, + inFront: false, + blocked: {}, + through: [], + }); + // The calls of the host object that write something or end the IDE. + const STOPPED = new Set([ + "SaveIDESetting", + "SaveProjectMetaState", + "AddRecentsList", + "RemoveRecentsList", + "ChangeWindowSetting", + "Shell", + "Exit", + "CloseWindow", + ]); + const stopped = + (name) => + (...a) => { + const what = name === "SaveIDESetting" ? `SaveIDESetting ${String(a[0])}` : name; + G.blocked[what] = (G.blocked[what] ?? 0) + 1; + return Promise.resolve(undefined); + }; + const under = (host) => + new Proxy(host, { + get(target, name) { + const member = target[name]; + if (typeof member !== "function") return member; + if (STOPPED.has(name)) { + return (...a) => { + G.through.push(name === "SaveIDESetting" ? `SaveIDESetting ${String(a[0])}` : name); + return Promise.resolve(undefined); + }; + } + return (...a) => member.apply(target, a); + }, + }); + const front = (host) => { + G.inFront = true; + return new Proxy(host, { + get(target, name) { + if (name === "GetIDESetting") { + return (...a) => { + const key = String(a[0]); + if (!Object.hasOwn(cfg.settings, key)) return target.GetIDESetting(...a); + return target.GetIDESetting("LICENCEAGREEMENTV3").then(() => cfg.settings[key]); + }; + } + if (name === "GetRecentsList" || name === "GetVB6RecentProjects") return () => Promise.resolve(""); + if (STOPPED.has(name)) return stopped(name); + return target[name]; + }, + }); + }; + const hook = Object.getOwnPropertyDescriptor(window, "hostAppObject"); + let real; + Object.defineProperty(window, "hostAppObject", { + configurable: true, + enumerable: true, + get: () => real, + set: (v) => { + if (hook?.set) { + hook.set(v ? under(v) : v); + real = hook.get(); + } else { + real = v; + } + real = real ? front(real) : real; + }, + }); +} + +export const guardSource = (settings, gen) => `(${pageGuard})(${JSON.stringify({ settings, gen })})`; + +// A hash of the IDE's settings key as `reg query` lists it, in memory: nothing is written, and +// the values are never kept or shown. What a first start could have written is in this key. +export function settingsHash() { + const key = `HKCU\\${IDE_SETTINGS_KEY}\\IDESettings`; + const r = spawnSync("reg", ["query", key], { windowsHide: true }); + if (r.status !== 0) throw new Error(`reg query could not read ${key}`); + return createHash("sha256").update(r.stdout).digest("hex").slice(0, 12); +} diff --git a/scripts/lib/shot-policy.mjs b/scripts/lib/shot-policy.mjs index 8c24a8b4c..17415285a 100644 --- a/scripts/lib/shot-policy.mjs +++ b/scripts/lib/shot-policy.mjs @@ -18,6 +18,46 @@ import { pictureFate } from "./shot-revision.mjs"; // was not in the state the dark picture shows. export const SIZE_SLACK = 32; +const noSpace = (texts) => texts.join("").replace(/\s+/g, ""); + +// The words of `a` that `b` does not hold, each as often as it is over. +function wordsOver(a, b) { + const left = new Map(); + for (const w of b) left.set(w, (left.get(w) ?? 0) + 1); + return a.filter((w) => { + const n = left.get(w) ?? 0; + if (n) left.set(w, n - 1); + return !n; + }); +} + +/** + * What the two themes of one picture say differently. The two passes of a shot show one state + * in two colours, so the text the page shows inside the picture's clip is the same; a difference + * is a wrong state, not the theme (Options_ActiveX's light picture once showed an import's + * rows, at the dark one's size). White space does not count, since the same text wraps + * differently in each theme. Each theme also lays the window out a few pixels differently, so + * a line at the edge of a clip shows more in one than in the other: only text one side shows + * whole and the other shows none of is a difference, and text a side shows cut by an edge + * answers for the other's but is never asked for. + * + * @param {{whole: string[], cut: string[]}} dark the text the dark pass read, block by block: + * `whole` the blocks with a line that shows in full, `cut` those an edge cuts + * @param {{whole: string[], cut: string[]}} light the text the light pass read + * @param {{dark?: string[], light?: string[]} | null} [allow] the words a shot says must differ + * (the name of the theme in a list that shows it), each on its own side, and no others + * @returns {{dark: string[], light: string[]} | null} the words only each side shows, or null + * when they say the same + */ +export function themeTextDifference(dark, light, allow = null) { + const all = (side) => [...side.whole, ...side.cut]; + if (noSpace(all(dark)) === noSpace(all(light))) return null; + const words = (texts) => texts.join(" ").split(/\s+/).filter(Boolean); + const left = wordsOver(words(dark.whole), words(all(light))).filter((w) => !allow?.dark?.includes(w)); + const right = wordsOver(words(light.whole), words(all(dark))).filter((w) => !allow?.light?.includes(w)); + return left.length || right.length ? { dark: left, light: right } : null; +} + // The most an SVG may differ from its PNG and still be written: the share of // its pixels (percent) more than 96 grey levels off the PNG's, a shift of one // pixel allowed. Past it something is drawn wrongly or not at all -- a part diff --git a/scripts/lib/shot-root.mjs b/scripts/lib/shot-root.mjs new file mode 100644 index 000000000..8336702dc --- /dev/null +++ b/scripts/lib/shot-root.mjs @@ -0,0 +1,167 @@ +// The folder shoot_docs.mjs keeps its projects, builds and packages in, and the name the pictures +// show for it. Nothing writes at a drive's root (WIP.md, "Nothing writes outside the cloned +// repository and the temp folder"), so the real folder is in the checkout, where git ignores it, +// and a path a picture shows is a virtual one: the page's text is rewritten at the capture. +// +// * Text: the setters of the DOM's text properties and the page's TEXTtoHTML rewrite what they are +// given, at once, since the IDE measures a console row in the task that writes it, and a path +// 40 characters too long leaves the row a line too tall. A MutationObserver on the document +// and on each shadow root takes what comes by any other way, and what was there before. +// rootTextJs installs them. +// * Text boxes: a value is rewritten just before a capture and just before an SVG is read, and put +// back after the capture (rootBoxesJs, rootBoxesBackJs), since the page can read a box back +// and act on what it finds: a path it read from a picture's rewrite would name the folder +// nothing may write to. +// * A guard (holdsRoot), as for the Windows user name: a picture whose page, SVG or window +// still holds the real folder fails. + +import path from "node:path"; +import { REPO_ROOT } from "../../lib/repo-paths.mjs"; + +/** The real folder. It stands for a folder at a drive's root, and it is short for that reason. */ +export const TOOL_ROOT = path.join(REPO_ROOT, ".claude", "tbshots"); + +/** The name a picture shows for it. No such folder exists. */ +export const SHOWN_ROOT = "C:\\tbshots"; + +const escaped = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + +/** + * What finds `root` in text: in any case, with either slash or a doubled one (a path in JSON) + * between its parts, and not a longer name that starts with it. A part that starts with a dot may + * have no slash before it: Markdown reads a backslash before a dot as an escape, and the import + * log is Markdown. Global, so use it with `replace` or `search`, which ignore its lastIndex. + * + * @param {string} root + * @returns {RegExp} + */ +export const rootPattern = (root) => { + const parts = root.split(/[\\/]+/).filter(Boolean); + const body = parts.map((p, i) => (i === 0 ? "" : p.startsWith(".") ? "[\\\\/]*" : "[\\\\/]+") + escaped(p)); + return new RegExp(`${body.join("")}(?![\\w-])`, "gi"); +}; + +/** + * `text` with each place `pattern` finds shown as `shown`. A function of its own because the page + * runs this same source. + * + * @param {string} text + * @param {RegExp} pattern from rootPattern + * @param {string} shown + * @returns {string} + */ +export const showRoot = (text, pattern, shown) => text.replace(pattern, () => shown); + +/** + * Whether `text` holds what `pattern` finds, an embedded image's bytes left out. + * + * @param {string} text + * @param {RegExp} pattern from rootPattern + * @returns {boolean} + */ +export const holdsRoot = (text, pattern) => text.replace(/;base64,[A-Za-z0-9+/=]+/g, ";base64,").search(pattern) >= 0; + +const PAGE_HEAD = (root, shown) => `const pattern = new RegExp(${JSON.stringify(rootPattern(root).source)}, "gi"); + const shown = ${JSON.stringify(shown)}; + const show = ${showRoot};`; + +/** + * Runs in the page: rewrites what the DOM's text setters are given from here on, every text node + * there is, and any that comes by another way, in the document and each shadow root that has + * appeared since. Safe to run again. + * + * @param {string} root + * @param {string} shown + * @returns {string} the expression + */ +export const rootTextJs = (root, shown) => `(() => { + ${PAGE_HEAD(root, shown)} + if (!window.__shotRootSetters) { + window.__shotRootSetters = true; + const text = (v) => (typeof v === "string" ? show(v, pattern, shown) : v); + const setter = (proto, name) => { + const d = Object.getOwnPropertyDescriptor(proto, name); + Object.defineProperty(proto, name, { + ...d, + set(v) { + d.set.call(this, text(v)); + }, + }); + }; + setter(Node.prototype, "textContent"); + setter(Node.prototype, "nodeValue"); + setter(CharacterData.prototype, "data"); + setter(HTMLElement.prototype, "innerText"); + setter(Element.prototype, "innerHTML"); + setter(ShadowRoot.prototype, "innerHTML"); + const createTextNode = document.createTextNode; + document.createTextNode = function (t) { + return createTextNode.call(this, text(t)); + }; + } + // The DEBUG CONSOLE's lines go through this on their way to HTML, whose escapes no pattern finds. + if (typeof window.TEXTtoHTML === "function" && !window.__shotRootHtml) { + window.__shotRootHtml = true; + const toHtml = window.TEXTtoHTML; + window.TEXTtoHTML = function (t, ...rest) { + return toHtml.call(this, typeof t === "string" ? show(t, pattern, shown) : t, ...rest); + }; + } + const fix = (n) => { + const text = show(n.data, pattern, shown); + if (text !== n.data) n.data = text; + }; + const sweep = (top) => { + const walk = document.createTreeWalker(top, NodeFilter.SHOW_TEXT); + for (let n = walk.nextNode(); n; n = walk.nextNode()) fix(n); + }; + const watched = (window.__shotRootWatched ??= new WeakSet()); + const watch = (top) => { + if (!watched.has(top)) { + watched.add(top); + new MutationObserver((records) => { + for (const r of records) { + if (r.type === "characterData") fix(r.target); + for (const n of r.addedNodes) { + if (n.nodeType === Node.TEXT_NODE) fix(n); + else if (n.nodeType === Node.ELEMENT_NODE) sweep(n); + } + } + }).observe(top, { subtree: true, childList: true, characterData: true }); + } + sweep(top); + for (const e of top.querySelectorAll("*")) if (e.shadowRoot) watch(e.shadowRoot); + }; + watch(document); +})()`; + +/** + * Runs in the page: rewrites each text box's value, across the shadow roots, and remembers what it + * held for rootBoxesBackJs. + * + * @param {string} root + * @param {string} shown + * @returns {string} the expression + */ +export const rootBoxesJs = (root, shown) => `(() => { + ${PAGE_HEAD(root, shown)} + const held = (window.__shotRootHeld ??= new Map()); + const pass = (top) => { + for (const e of top.querySelectorAll("input, textarea")) { + const text = show(e.value, pattern, shown); + if (text === e.value) continue; + if (!held.has(e)) held.set(e, e.value); + e.value = text; + } + for (const e of top.querySelectorAll("*")) if (e.shadowRoot) pass(e.shadowRoot); + }; + pass(document); +})()`; + +/** Runs in the page: puts back what rootBoxesJs rewrote. */ +export const rootBoxesBackJs = `(() => { + const held = window.__shotRootHeld; + if (!held) return; + for (const [e, value] of held) e.value = value; + held.clear(); +})()`; diff --git a/scripts/lib/tb-defaults.mjs b/scripts/lib/tb-defaults.mjs index 5bcc8fcd1..b9b9280fe 100644 --- a/scripts/lib/tb-defaults.mjs +++ b/scripts/lib/tb-defaults.mjs @@ -1,10 +1,11 @@ // The IDE with its default settings, whatever the user's own IDE saved. // // The IDE reads its per-user settings (options, panel layouts, keyboard groups, ...) -// through `hostAppObject.GetIDESetting(key)`. IDE_FLAGS (tb-ide.mjs) stop it writing them, -// and nothing stops it reading them: a font size the owner changed in their own IDE showed -// in every IDE the tools started. So each IDE a tool starts is answered "no saved value" for -// those keys, in the page only. The registry is never written. try-help-addin, which is the +// through `hostAppObject.GetIDESetting(key)`. IDE_FLAGS (tb-ide.mjs) stop it writing most of +// them, and nothing stops it reading them: a font size the owner changed in their own IDE +// showed in every IDE the tools started. So each IDE a tool starts is answered "no saved value" +// for those keys and has its saves of them dropped, in the page only. The registry is never +// written. try-help-addin, which is the // user's own IDE in all but its project, keeps its settings (`userSettings` of launchIde). // WIP.Harness.md, "Default settings, whatever the user saved", says why it is built this way. // @@ -28,9 +29,10 @@ import { connect } from "./tb-cdp.mjs"; // answer: JSON.parse fails inside a try and the variable keeps its default, parseJson gives // undefined, an empty targetArchitectureMemory reads as "unknown", and the Object Browser // (OBJECT_BROWSER, BETA 1023) and the New / Open Project dialog's section (NEWFRONTPAGE, BETA -// 1033) test the length first and keep their defaults. Not here, and -// answered from the registry: the licence's keys, LICENCEAGREEMENTV3, ALLOWERRORREPORTINGV3, -// PUBLISHER and NEWYEAR_*, which are not settings of how the IDE looks. +// 1033) test the length first and keep their defaults. ALLOWERRORREPORTINGV3 is answered no: with +// the user's yes, each native exception of a tool's IDE, which many a reproducer provokes, would post +// a report to the developers (postError). Not here, and answered from the registry: the licence's +// keys, LICENCEAGREEMENTV3, PUBLISHER and NEWYEAR_*, which are not settings of how the IDE looks. export const BLANKED_KEYS = { GENERAL: "{}", GENERAL2: "{}", @@ -45,22 +47,39 @@ export const BLANKED_KEYS = { targetArchitectureMemory: "", OBJECT_BROWSER: "", NEWFRONTPAGE: "", + ALLOWERRORREPORTINGV3: "DISAGREED_TOOLS", }; // Runs in the page, in every document of the IDE's browser; only main.htm does anything. // `window.__tbDefaults` is the marker that it ran, and its `keys` the keys it answered -// (never their values). Every function of the host object is bound to the real object: the -// proxy of a WebView2 host object refuses to be called with another `this`. +// (never their values). A key it answers is never saved either: IDE_FLAGS stop every save of +// these keys but two, OBJECT_BROWSER's and NEWFRONTPAGE's, which test no flag (BETA 1034), so +// that merely closing the New / Open Project dialog would write the user's registry. Every +// function of the host object is bound to the real object: the proxy of a WebView2 host object +// refuses to be called with another `this`. A request for the error-report address is answered +// here and counted in `reports`, never sent, whatever g_ErrorReporting says: a page that turns +// reporting on (the question's YES) stays a page of a tool. export function pageHook(answers) { if (!/main\.htm$/i.test(location.pathname)) return; - const marker = { keys: [] }; + const marker = { keys: [], reports: 0 }; window.__tbDefaults = marker; + const send = window.fetch; + window.fetch = function (resource, ...rest) { + if (!/\/\/(www\.)?twinbasic\.com\/notify\b/i.test(String(resource?.url ?? resource))) { + return send.call(this, resource, ...rest); + } + marker.reports++; + return Promise.resolve(new Response("{}", { status: 200 })); + }; let real; const wrap = (host) => new Proxy(host, { get(target, name) { const member = target[name]; if (typeof member !== "function") return member; + if (name === "SaveIDESetting") { + return (...a) => (Object.hasOwn(answers, String(a[0])) ? Promise.resolve() : member.apply(target, a)); + } if (name !== "GetIDESetting") return (...a) => member.apply(target, a); return (...a) => { const key = String(a[0]); diff --git a/scripts/lib/tb-lane.mjs b/scripts/lib/tb-lane.mjs index d6fd0ccbb..b6af19bf5 100644 --- a/scripts/lib/tb-lane.mjs +++ b/scripts/lib/tb-lane.mjs @@ -101,6 +101,8 @@ export class Lane { * @param {string} [o.projectId] the project.id every project the lane opens is given; * by default laneProjectId's for its slot, which differs from run to run. shoot_docs * gives one id for all, since a picture can show it + * @param {((c: object) => Promise) | null} [o.onAttach] called with each IDE's + * connection as the lane attaches to it, before the project has loaded */ constructor({ name, @@ -113,8 +115,9 @@ export class Lane { page = null, userSettings = false, projectId = null, + onAttach = null, }) { - Object.assign(this, { name, slot, desktop, work, ide, show, browserArgs, page, userSettings, projectId }); + Object.assign(this, { name, slot, desktop, work, ide, show, browserArgs, page, userSettings, projectId, onAttach }); this.port = null; // the open IDE's DevTools port this.lease = null; // the slot, as launchIde takes it (leased()) this.exe = null; // the lane's copy of the install, made on first use @@ -313,6 +316,7 @@ export class Lane { Object.assign(this, { src, project, port: this.run.port }); this.c = await attachIde(this.port); if (!this.c) throw new Error(`lane ${this.name}: the IDE never exposed a debug port`); + await this.onAttach?.(this.c); if (page) await fixPageSize(this.c, page); this.checkCompile(await waitForCompile(this.c, { project, timeout }), src); // Checked against whatever APPDATA the IDE was given: `env` can name another. @@ -349,6 +353,7 @@ export class Lane { Object.assign(this, { src: null, project: null, port: this.run.port }); this.c = await attachIde(this.port); if (!this.c) throw new Error(`lane ${this.name}: the IDE never exposed a debug port`); + await this.onAttach?.(this.c); if (page) await fixPageSize(this.c, page); return this.c; } diff --git a/scripts/lib/tb-registry.mjs b/scripts/lib/tb-registry.mjs index 67d1297ec..364c344d0 100644 --- a/scripts/lib/tb-registry.mjs +++ b/scripts/lib/tb-registry.mjs @@ -88,6 +88,7 @@ import { createHash } from "node:crypto"; import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import path from "node:path"; +import { TOOL_ROOT } from "./shot-root.mjs"; import { findIde } from "./tb-install.mjs"; export const IDE_SETTINGS_KEY = "Software\\VB and VBA Program Settings\\twinBASIC_IDE"; @@ -475,11 +476,14 @@ function deepEnough(key) { // A folder prefix has to end in a separator, or tbrun's %TEMP%\tbrun\9346 would // also claim %TEMP%\tbrun\93460. And it has to be inside the temp folder, which // is the only place the harness creates projects. +// One folder outside it is allowed, exactly that one: shoot_docs.mjs keeps projects a picture +// names in TOOL_ROOT (shot-root.mjs), in the checkout. function asTempFolder(p) { const r = path.resolve(p); const tmp = path.resolve(tmpdir()); const rel = path.relative(tmp, r); - if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) { + const allowed = r.toLowerCase() === path.resolve(TOOL_ROOT).toLowerCase(); + if (!allowed && (!rel || rel.startsWith("..") || path.isAbsolute(rel))) { throw new Error(`refusing to sweep project entries outside ${tmp}: "${r}"`); } return r.endsWith(path.sep) ? r : r + path.sep; diff --git a/scripts/shoot_docs.mjs b/scripts/shoot_docs.mjs index 52911952b..6ab402727 100644 --- a/scripts/shoot_docs.mjs +++ b/scripts/shoot_docs.mjs @@ -59,6 +59,8 @@ // sample9 Sample 9 open: its WebView_Create handler, for the WebView2 tutorial // featuremap test/shots/featuremap, FeatureTour: the whole window with twelve of the IDE's // features named, in a layout and with editor options set in the page only +// tour test/shots/forms, FormsDemo: the whole window as the IDE's landing page shows +// it, a form in its designer and each part of the window named // code-chapter the same project with test/shots/codechapter staged onto it: the // "Writing code" chapter's pictures of the editor (tabs, combo boxes, menus, // IntelliSense, finding, bookmarks, Outline, DIAGNOSTICS) and the Edit and @@ -68,7 +70,13 @@ // no-project no project, as from the IDE's icon: every menu in its // no-project state, the dialogs that need no project, and the window, // its bars and its panels (each shown as a floating window on its own) -// programs test/shots/programs, ShotPrograms, built in the IDE: each picture is the +// starting-first-run the three dialogs of a first start, in a page over a guard of its own +// (lib/shot-first-run.mjs); starting, starting-new and starting-boxes no project, +// then the "Starting a project" chapter's other pictures, with test/shots/starting +// troubleshooting test/shots/troubleshooting, the "When something goes wrong" chapter's pictures: +// a compiler restarted, killed and in Safe Mode, a native exception and TRACE mode; +// troubleshooting-box no page: a copy of the install with a file gone, and its box +// programs test/shots/programs, ShotPrograms, built in the IDE: each picture is the // window of the exe, started on a private desktop with a command line naming // its form, taken with PrintWindow (lib/tb-ide.mjs's captureWindow), once, // in no theme; the ListView's is of the project run from the IDE (F5) @@ -114,7 +122,9 @@ // its light theme (builder/theme-pictures.mjs). A light picture with the dark one's // pixels is not kept. Light lays the window out a few pixels differently, so a light // picture can be a few pixels off the dark one's size; the page shows each at its own -// (SIZE_SLACK). A label's pill follows the theme (lib/shot-annotate.mjs). +// (SIZE_SLACK). A label's pill follows the theme (lib/shot-annotate.mjs). The two pictures say +// the same: text one shows whole and the other shows none of fails the picture +// (TEXT_IN_CLIP, themeTextDifference). // * What the user's machine would put into a picture is kept out of the IDE's // page, in the page only (the no-project and project setups): the IDE's // options are set to their defaults, the saved panel layouts and keyboard @@ -140,7 +150,17 @@ // not a page: the texts of the window and its child windows are searched instead, // and what the program draws itself is the fixture's own text (see programShot). -import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, writeSync } from "node:fs"; +import { + cpSync, + existsSync, + mkdirSync, + readdirSync, + readFileSync, + renameSync, + rmSync, + writeFileSync, + writeSync, +} from "node:fs"; import { tmpdir, userInfo } from "node:os"; import path from "node:path"; import puppeteer from "puppeteer"; @@ -160,13 +180,23 @@ import { hoverText, mouseAway, pointOf, restMouse } from "../test/addin/hover.mj import { frameEval, frameOf, serveLoopback } from "../test/addin/pages.mjs"; import { launchBrowser } from "./lib/browser.mjs"; import { annotate as annotateOver, LAYER_ID, resolveAnchors, unannotate } from "./lib/shot-annotate.mjs"; -import { composite, uncomposite } from "./lib/shot-composite.mjs"; +import { COMPOSITE_ID, composite, uncomposite } from "./lib/shot-composite.mjs"; import { decodePng, encodePng, pngSize } from "./lib/png.mjs"; import { iconGroup, iconImage, manifestResource, resFile } from "./lib/res-file.mjs"; import { inRects, pngsNear, withinNoise } from "./lib/shot-change.mjs"; import { changedPictures, openRevision, planCleanup } from "./lib/shot-revision.mjs"; import { applyPicture, applySvg, dropPicture, restorePictures, svgOf } from "./lib/shot-apply.mjs"; import { freezePage, showFrozen } from "./lib/shot-freeze.mjs"; +import { FIRST_RUN, guardSource, SETTLED, settingsHash } from "./lib/shot-first-run.mjs"; +import { + holdsRoot, + rootBoxesBackJs, + rootBoxesJs, + rootPattern, + rootTextJs, + SHOWN_ROOT, + TOOL_ROOT, +} from "./lib/shot-root.mjs"; import { bundleFollowsSvg, cleanupResets, @@ -174,6 +204,7 @@ import { pngRule, SVG_FAITHFUL, svgFollowsPng, + themeTextDifference, } from "./lib/shot-policy.mjs"; import { MANIFEST, openManifest, svgDecision } from "./lib/shot-manifest.mjs"; import { reviewPicture, reviewSvg } from "./lib/shot-queue.mjs"; @@ -200,10 +231,12 @@ import { captureWindow, compilerPid, desktopWindows, + IDE_FLAGS, killTree, launchOnDesktop, leaseSlot, postWindowMessage, + readCrash, shutdownIde, sleep, waitForCompile, @@ -215,6 +248,7 @@ import { holdsUserName, judgeSvg } from "./svgshot/keep.mjs"; import { stageProject, unpackProject } from "./lib/tb-project.mjs"; import { holdLlvmCache } from "./lib/tb-llvm-cache.mjs"; import { guard, presentKeys, press, serverRequests } from "../test/ide/building-kit.mjs"; +import { layout } from "../test/ide/starting-kit.mjs"; import { checkHarness } from "./lib/harness-settings.mjs"; import { buildNumber, findIde, unsupportedIde } from "./lib/tb-install.mjs"; import { Lane } from "./lib/tb-lane.mjs"; @@ -695,6 +729,81 @@ const VISIBLE_TEXT = `(() => { return out.join("\\n"); })()`; +// The text a picture of `clip` shows, read from the page as it is captured, for the check that +// its two themes say the same (themeTextDifference): each block of text, in the document's order, +// across every shadow root, the text boxes' values and each closed select's choice among them. A +// line of text shows where part of it is inside the clip and inside every box of its ancestors +// that clips it, and is on top there: a dialog hides what is behind it, a list's rows that scrolled +// away are clipped, and the copies of rows a list keeps out of sight lie under the ones it shows. +// A block counts all its text once any line of it shows, so a line a picture's edge cuts off, and +// text an ellipsis shortens, read the same in both themes. It is `whole` when a line of it shows +// its full height and half its width, or 40 px of it, and else `cut`: a row at the clip's edge, +// which a theme that lays it out a few pixels further on shows less of. Null for a composite's +// layer, whose parts were read as they were captured. The annotation layer is the tool's own, and +// takes no hits, so it is not read. +const TEXT_IN_CLIP = (clip) => `(() => { + if (document.getElementById(${JSON.stringify(COMPOSITE_ID)})) return null; + const c = ${JSON.stringify(clip ?? null)} ?? { x: scrollX, y: scrollY, width: innerWidth, height: innerHeight }; + const x0 = c.x - scrollX, y0 = c.y - scrollY, x1 = x0 + c.width, y1 = y0 + c.height; + const boxes = new Map(); + const clipBox = (e) => { + if (!e) return { left: -Infinity, top: -Infinity, right: Infinity, bottom: Infinity }; + if (boxes.has(e)) return boxes.get(e); + let box = clipBox(e.parentElement ?? e.getRootNode().host ?? null); + const s = getComputedStyle(e); + if (s.overflowX !== "visible" || s.overflowY !== "visible") { + const r = e.getBoundingClientRect(); + box = { left: Math.max(box.left, r.left), top: Math.max(box.top, r.top), right: Math.min(box.right, r.right), bottom: Math.min(box.bottom, r.bottom) }; + } + boxes.set(e, box); + return box; + }; + const shown = (e, r) => { + const b = clipBox(e); + const left = Math.max(r.left, x0, b.left), right = Math.min(r.right, x1, b.right); + const top = Math.max(r.top, y0, b.top), bottom = Math.min(r.bottom, y1, b.bottom); + if (right <= left || bottom <= top) return 0; + const hit = e.getRootNode().elementFromPoint((left + right) / 2, (top + bottom) / 2); + if (!hit || !(hit === e || e.contains(hit) || hit.contains(e))) return 0; + return bottom - top >= r.height - 1 && right - left >= Math.min(r.width / 2, 40) ? 2 : 1; + }; + const blockOf = (e) => { + for (let a = e; a; a = a.parentElement) if (!/^(inline$|contents)/.test(getComputedStyle(a).display)) return a; + return e; + }; + const blocks = new Map(); + const entry = (key) => { + if (!blocks.has(key)) blocks.set(key, { text: "", shown: 0 }); + return blocks.get(key); + }; + const range = document.createRange(); + const SKIP = /^(STYLE|SCRIPT|NOSCRIPT|OPTION|TITLE|TEMPLATE|TEXTAREA)$/; + const read = (root) => { + const walk = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); + for (let n = walk.nextNode(); n; n = walk.nextNode()) { + const e = n.parentElement; + if (!e || !/\\S/.test(n.data) || SKIP.test(e.tagName) || getComputedStyle(e).visibility !== "visible") continue; + const b = entry(blockOf(e)); + b.text += n.data; + if (b.shown === 2) continue; + range.selectNodeContents(n); + for (const r of range.getClientRects()) b.shown = Math.max(b.shown, shown(e, r)); + } + for (const e of root.querySelectorAll("input, textarea, select")) { + const r = e.getBoundingClientRect(); + if (/^(checkbox|radio|hidden|button|submit)$/.test(e.type) || r.width < 4 || r.height < 4) continue; + if (getComputedStyle(e).visibility !== "visible") continue; + const b = entry(e); + b.text = e.tagName === "SELECT" ? (e.options[e.selectedIndex]?.text ?? "") : e.value; + b.shown = shown(e, r); + } + for (const e of root.querySelectorAll("*")) if (e.shadowRoot) read(e.shadowRoot); + }; + read(document); + const all = [...blocks.values()].filter((b) => b.text); + return { whole: all.filter((b) => b.shown === 2).map((b) => b.text), cut: all.filter((b) => b.shown === 1).map((b) => b.text) }; +})()`; + // `expression` evaluated in the document of a frame: `frame` is the origin of a child frame // of `conn`'s page, or the connection to a frame of another site, which is a target of its // own (the detached help window's page is a string, and its frame is on another site). @@ -761,6 +870,7 @@ const newShotState = (name) => ({ ignore: null, editedOriginal: null, svg: null, + text: { whole: [], cut: [] }, }); // A line of output, prefixed with the setup it is from (they run at once). @@ -851,6 +961,21 @@ function refuseUserName(text, name, what) { throw new Error(`${what} for ${name} shows the Windows user name: ${JSON.stringify(around)}`); } +// The tool's real folder, which every picture shows as SHOWN_ROOT (lib/shot-root.mjs). +const ROOT_PATTERN = rootPattern(TOOL_ROOT); +const showRootText = (conn) => conn.evaluate(rootTextJs(TOOL_ROOT, SHOWN_ROOT)); +const showRootBoxes = (conn) => conn.evaluate(rootBoxesJs(TOOL_ROOT, SHOWN_ROOT)); + +// Fails the picture `name` when `text` holds the real folder: what could not be rewritten is +// fixed where it is, and this is never relaxed. +function refuseRoot(text, name, what) { + if (!holdsRoot(text, ROOT_PATTERN)) return; + const at = text.search(ROOT_PATTERN); + throw new Error( + `${what} for ${name} shows ${TOOL_ROOT}, not ${SHOWN_ROOT}: ${JSON.stringify(text.slice(Math.max(0, at - 40), at + TOOL_ROOT.length + 40))}`, + ); +} + // A capture of `clip` (CSS pixels, on whole device pixels) from `conn`, refused // when the page's visible text holds the user name: before the first capture, and // again, with the SVG when there is one, when a capture is chosen. The page can draw @@ -865,6 +990,7 @@ async function capture(conn, name, clip, { frame, away = null, keep = null, soli refuseUserName(await textOf(conn, frame), name, "the page"); if (away) await away(); await quiet(conn); + await showRootText(conn); try { if (keep) await cutoutOn(conn, keep, solid); else await frames(conn); @@ -897,12 +1023,20 @@ async function capture(conn, name, clip, { frame, away = null, keep = null, soli // wide) is captured a device pixel short of it (168), and an SVG of the whole clip // would show the half of an edge the PNG cuts off. const picked = async (png) => { - refuseUserName(await textOf(conn, frame), name, "the page as captured"); + const text = await textOf(conn, frame); + refuseUserName(text, name, "the page as captured"); + refuseRoot(text, name, "the page as captured"); + const read = await conn.evaluate(TEXT_IN_CLIP(clip)); + conn.shot.text.whole.push(...(read?.whole ?? [])); + conn.shot.text.cut.push(...(read?.cut ?? [])); + if (frame) conn.shot.text.whole.push(await inFrame(conn, frame, "document.body.innerText")); if (values.svg) { const [width, height] = pngSize(png); const size = { width: width / SCALE, height: height / SCALE }; + await showRootBoxes(conn); conn.shot.svg = await svgOfPage(conn, clip && { ...clip, ...size }, { background: !keep }); if (holdsUserName(conn.shot.svg.svg, USER)) throw new Error(`the SVG of ${name} holds the Windows user name`); + refuseRoot(conn.shot.svg.svg, name, "the SVG"); } return png; }; @@ -915,6 +1049,7 @@ async function capture(conn, name, clip, { frame, away = null, keep = null, soli let agreed = null; let gap = 0; for (let i = 0; ; i++) { + await showRootBoxes(conn); const { data } = await conn.send("Page.captureScreenshot", params); const png = Buffer.from(data, "base64"); const equal = wants.find((w) => same(w, png, masks)); @@ -938,6 +1073,7 @@ async function capture(conn, name, clip, { frame, away = null, keep = null, soli if (agreed) return await picked(agreed); throw new Error(`the page for ${name} kept changing: no two captures in a row were equal`); } finally { + await conn.evaluate(rootBoxesBackJs); if (keep) await cutoutOff(conn); } } @@ -1097,6 +1233,9 @@ async function resetUi(c) { const THEMES = ["dark", "light"]; const THEME_COMMAND = { dark: "tbTheme_SwitchToDarkMode", light: "tbTheme_SwitchToLightMode" }; const THEME_NAME = { dark: "dark", light: "Light" }; +// A shot's `themeWords`: the words its two themes must differ by, for themeTextDifference. A shot +// that shows the list of colour themes shows the theme it is taken in. +const SHOWS_ITS_THEME = { dark: ["Dark"], light: ["Light"] }; // The path of a picture in a theme: the dark one is the path the shot table gives. const themedOut = (out, theme) => (theme === "dark" ? out : out.replace(/\.png$/, ".light.png")); @@ -1156,16 +1295,20 @@ async function fixPageSize(c) { // IDE save its place in the registry). The language is left as the IDE runs it, and so are // the options the schema does not list. Returns what differed from the defaults. const PAGE_DEFAULTS = `(() => { - const blocked = new Set(["RemoveRecentsList"]); + const blocked = (window.__shotBlocked = new Set(["RemoveRecentsList"])); // The New / Open Project dialog (BETA 1033's) reads the recent list from the host object // itself, and remembers the section it was left on as the setting NEWFRONTPAGE: the list is // the tool's own (window.__shotRecents, setRecents), the section is never read or saved. + // VB6's list is __shotVb6Recents, and a native picker a shot goes through is answered from __shotAnswers. window.__shotRecents = []; + window.__shotVb6Recents = ""; + window.__shotAnswers = {}; const FRONT_PAGE = "NEWFRONTPAGE"; hostAppObject = new Proxy(hostAppObject, { get(target, key) { if (blocked.has(key)) return async () => undefined; if (key === "GetRecentsList") return async () => window.__shotRecents.join("?"); + if (Object.hasOwn(window.__shotAnswers, key)) return async () => window.__shotAnswers[key]; const member = Reflect.get(target, key); if (key === "GetIDESetting" || key === "SaveIDESetting") { return (...a) => (String(a[0]) === FRONT_PAGE ? Promise.resolve("") : member(...a)); @@ -1191,7 +1334,7 @@ const PAGE_DEFAULTS = `(() => { activePanelLayoutName = ""; customKeyboardShortcutGroups = {}; activeKeyboardShortcutGroupNames = ""; - window.HostGetVB6RecentProjects = (done) => done(""); + window.HostGetVB6RecentProjects = (done) => done(window.__shotVb6Recents); window.HostGetRecentsList = (e) => { e.classList.add("frontPageListViewLIST"); listviewFromArray(e, [], () => {}); @@ -1216,10 +1359,12 @@ window.HostGetRecentsList = (e) => { // A setup is one IDE: `start` which opens it and returns the connection, // `prepare` which brings it to a state the shots start from and returns their // context, and `port` when it needs a port chosen before it is bound (the help -// add-in's detached window), which its slot then carries; `end`, when given, runs -// once the IDE has ended, whatever happened, to check or give back what the setup -// holds (a lease, the registry the Building setups' ActiveX builds wrote and -// cleaned). `run` holds the lane, +// add-in's detached window), which its slot then carries; `closing`, when given, runs +// before the IDE ends, whatever happened, to check what only its page can say; `end`, +// when given, runs once the IDE has ended, whatever happened, to check or give back +// what the setup holds (a lease, the registry the Building setups' ActiveX builds wrote +// and cleaned). `noPage` says that the setup has no page: its `start` returns a stand-in +// for the connection, and its shots are `once`. `run` holds the lane, // its slot (tbbroker: the IDE's private desktop), the work folder and what the // setup has to put back. @@ -1599,6 +1744,14 @@ async function prepareFormsChapter(run) { return ctx; } +// ---- tour: FormsDemo again, for the one picture of the IDE's landing page. Its status bar is in +// the picture, so the compiler starts on no licence key, as in community. +async function prepareTour(run) { + const ctx = await prepareFormsChapter(run); + await communityCompiler(run); + return ctx; +} + // ---- projects: test/shots/projects, MyProject, for the pictures of the "Projects and files" // chapter (projectsShots): a form, a .bas module, a module and a class in a Helpers folder, a // manifest, and Resources/ICON/AppIcon.ico, the install's template icon under another name (a @@ -1670,6 +1823,11 @@ async function prepareProjectsRefs(run) { async function prepareCommunity(run) { const ctx = await prepareProject(run); + await communityCompiler(run); + return ctx; +} + +async function communityCompiler(run) { const { c } = run; if ( !(await waitFor(c, () => c.evaluate("licenceIsSet === true && !!compilerLicence.innerText"), { @@ -1688,7 +1846,6 @@ async function prepareCommunity(run) { }); if (!community) throw new Error("the compiler started with no key, and the licence badge is not COMMUNITY EDITION"); await clearUserFromConsole(c); - return ctx; } // A compiler's restart writes a line to the DEBUG CONSOLE that names the project's folder, @@ -1713,6 +1870,7 @@ const SETUPS = { designer: { start: startDesigner, prepare: prepareDesigner }, forms: { start: (run) => startDesigner(run, { addin: false }), prepare: prepareWithDesigners }, "forms-chapter": { start: startFormsChapter, prepare: prepareFormsChapter }, + tour: { start: startFormsChapter, prepare: prepareTour }, projects: { start: startProjects(), prepare: prepareSample }, "projects-refs": { start: startProjects({ refs: true }), prepare: prepareProjectsRefs }, "projects-start": { start: startNoProject, prepare: prepareNoProject }, @@ -1735,6 +1893,17 @@ const SETUPS = { prepare: prepareBuildingPkg, end: endBuilding, }, + "starting-first-run": { start: startFirstRun, prepare: prepareFirstRun, end: endFirstRun }, + starting: { start: startStarting, prepare: prepareStarting, end: endBuilding }, + "starting-new": { start: startStarting, prepare: prepareStarting, end: endBuilding }, + "starting-boxes": { start: startStarting, prepare: prepareStarting, end: endBuilding }, + troubleshooting: { + start: startTroubleshooting, + prepare: prepareTroubleshooting, + closing: closingTroubleshooting, + end: endBuilding, + }, + "troubleshooting-box": { noPage: true, start: startMissingDll, prepare: prepareMissingDll, end: endMissingDll }, customcontrols: { start: startCustomControls, prepare: prepareWithDesigners }, code: { start: startCode, prepare: prepareSample }, sample9: { start: startSample9, prepare: prepareSample }, @@ -1775,13 +1944,14 @@ const SETUPS = { // corners: the outline's corner is what fills the pixel the border's curve leaves. const OUTLINE = 1; // `before(c)` brings the IDE to the state the menu is shown in, after every menu is closed, -// and `after(c)`, when given, takes it out of that state once the menu is closed. `out` is -// the picture's path when it is not the Menu pages' folder. +// and `after(c)`, when given, takes it out of that state once the menu is closed. `check(c)`, +// when given, runs with the menu open and fails the picture by throwing. `out` is the picture's +// path when it is not the Menu pages' folder. function menuShot( name, top, item = null, - { subOnly = false, setup = "no-project", before = null, after = null, out = null } = {}, + { subOnly = false, setup = "no-project", before = null, after = null, check = null, out = null } = {}, ) { return { out: out ?? `IDE/Menu/Images/${name}.png`, @@ -1793,6 +1963,7 @@ function menuShot( const drop = await openMenu(c, top, { close: !before }); const title = await rectOf(c, `#${TOP[top]}`); const sub = item ? await hoverItem(c, item) : null; + if (check) await check(c); let area; let keep; if (subOnly) { @@ -2009,6 +2180,7 @@ const dialogShots = [ // unavailable: load a project first>" in place of their lists. out: dialogOut("IDE/Menu", "Menu_Tools_IDEOptions"), setup: "project", + themeWords: SHOWS_ITS_THEME, take: ({ c }) => inDialog(c, { command: "tbIde_ShowIDEOptions", title: "IDE Options", close: "Close" }, () => dialogShot(c, "Menu_Tools_IDEOptions"), @@ -2179,17 +2351,89 @@ async function nameCloseProject(c) { if (named !== "tbProject_Close") throw new Error(`the status bar names "${named}", not tbProject_Close`); } -// The IDE's whole window at the size of a full-HD screen, as it opens with no project. +// The IDE's whole window at the size of a full-HD screen, as it opens with no project. The +// panels' pictures after it float each panel on its own, so a pass that follows theirs would show +// the window without them: the shot puts the default layout back itself. const ideWindowShot = { out: "IDE/Images/IDE.png", setup: "no-project", take: ({ c }) => atSize(c, 1920, 1032, async () => { + await resetUi(c); + await command(c, "tbPanels_SetActiveLayoutDefault"); + await frames(c); await nameCloseProject(c); return capture(c, "IDE", snapOut({ x: 0, y: 0, width: 1920, height: 1032 }, 1920, 1032)); }), }; +// The box of the docked panel headed `title`. +const dockedBox = (c, title) => + c.evaluate(`(() => { + const h = [...document.querySelectorAll(".sectionHeaderInner")].find((e) => e.textContent === ${JSON.stringify(title)}); + const r = h?.closest(".toolWindowContainer").getBoundingClientRect(); + return r ? { x: r.x, y: r.y, width: r.width, height: r.height } : null; +})()`); + +// The tour's picture: the window at work, in the default layout, with a form in its designer, so +// that the Toolbox and Properties have something to show. A bar's label stands on the bar, and a +// panel's in its own empty part; the Toolbox and Properties are full, so theirs stand on the +// ground round the form, with an arrow. +const tourShot = { + out: "IDE/Images/IDE_Tour.png", + setup: "tour", + async take({ c }) { + await formsDesigner(c); + try { + await communityBadge(c); + const titles = ["TOOLBOX", "EDITOR 1", "PROJECT EXPLORER", "PROPERTIES", "DEBUG CONSOLE", "DIAGNOSTICS"]; + const [toolbox, editor, explorer, properties, console_, diagnostics] = await Promise.all( + titles.map((t) => dockedBox(c, t)), + ); + const [stack, variables] = await Promise.all(["CALL STACK", "VARIABLES"].map((t) => dockedBox(c, t))); + const missing = [toolbox, editor, explorer, properties, console_, diagnostics, stack, variables].some((b) => !b); + if (missing) throw new Error("the default layout does not hold every panel"); + const { width: W, height: H } = await c.evaluate(`({ width: innerWidth, height: innerHeight })`); + const bar = await c.evaluate(`(() => { + const r = rootStatusBar.getBoundingClientRect(); + return { y: r.y, height: r.height }; +})()`); + const tools = await toolbarRect(c); + const menus = await c.evaluate(`(() => { + const r = rootMenuHelp.getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; +})()`); + const mid = (r) => r.y + r.height / 2; + const pill = (text, x, y, side = "right") => ({ type: "label", text, on: POINT(x, y), side, gap: 0 }); + const arrow = (from, to) => ({ type: "arrow", from, to }); + // the ground under the form, level with Properties' upper part + const low = editor.y + editor.height - 44; + const inPanel = (text, box) => pill(text, box.x + 16, box.y + box.height - 40); + const annotate = [ + pill("Menu bar", menus.x + menus.width + 24, mid(menus)), + pill("Toolbar", W - 110, mid(tools)), + pill("Toolbox", editor.x + 34, low), + arrow(POINT(editor.x + 30, low), POINT(toolbox.x + toolbox.width - 6, low)), + pill("Editor", editor.x + editor.width / 2 - 30, low), + pill("Properties", editor.x + editor.width - 34, low, "left"), + arrow(POINT(editor.x + editor.width - 30, low), POINT(properties.x + 6, low)), + inPanel("Project Explorer", explorer), + inPanel("Debug Console", console_), + inPanel("Diagnostics", diagnostics), + inPanel("Call Stack", stack), + inPanel("Variables", variables), + pill("Status bar", W / 2, bar.y + bar.height / 2), + ]; + await annotateOver(c, annotate); + const clip = snapOut({ x: 0, y: 0, width: W, height: H }, W, H); + return await capture(c, "IDE_Tour", clip, { unsteady: await groundUnsteady(c, clip, await layerBoxes(c)) }); + } finally { + await unannotate(c); + await formsReset(c); + } + }, +}; + // The status bar at the width of its own contents, with the command named at its right. const statusBarShot = { out: "IDE/Images/StatusBar.png", @@ -3655,6 +3899,10 @@ const settingsShot = ( await noFlash(c); await nameUserAsUser(c); if (prepare) await prepare(c); + // the count's box has no height until the dialog has counted, which can be after it is still + if (unsteady?.includes(CHANGED_COUNT)) { + await waitFor(c, () => rectOf(c, CHANGED_COUNT.on.css), { timeout: 5000, interval: 100 }); + } return dialogShot(c, out, (m) => clipOf(m, c), annotate, { unsteady }); }), }); @@ -7061,11 +7309,11 @@ async function fixConsoleThumb(c) { } // The floated console grown to show its `lines` lines, its times and scroll bar set, captured; -// `fix` sets what else changes from run to run, in the page. -async function consolePicture(c, name, lines, fix = null) { +// `fix` sets what else changes from run to run, in the page; `times: false` leaves the times to it. +async function consolePicture(c, name, lines, fix = null, { times = true } = {}) { await fitConsole(c, lines); if (fix) await fix(); - await fixConsoleTimes(c); + if (times) await fixConsoleTimes(c); await fixConsoleThumb(c); await parkMouse(c); await frames(c); @@ -10903,63 +11151,76 @@ const communityShot = { // restarted with the toolbar's button, and the page's first call of the three, fs.connect, is // held until the picture is taken: the IDE stays where its start-up is, the compiler up and the // other three not yet connected, and draws LIMITED itself. The call is then made as the IDE -// made it, the other two follow, and the badge goes back to OPERATIONAL. -const HOLD_FS_CONNECT = `(() => { - if (window.tbFsConnect) return; - const own = Object.prototype.hasOwnProperty.call(fs, "connect"); - const connect = fs.connect; - window.tbFsConnect = { own, connect, held: null }; - fs.connect = function (...args) { - window.tbFsConnect.held = () => connect.apply(fs, args); +// made it, the other two follow, and the badge goes back to OPERATIONAL. Holding the second +// call, lspSocket.connect, stops the start-up with FS connected (Services_Limited_Tooltip). +const holdConnect = (socket) => `(() => { + if (window.tbHeldConnect) return; + const socket = ${socket}; + const own = Object.prototype.hasOwnProperty.call(socket, "connect"); + const connect = socket.connect; + window.tbHeldConnect = { socket, own, connect, held: null }; + socket.connect = function (...args) { + window.tbHeldConnect.held = () => connect.apply(socket, args); }; })()`; -const RELEASE_FS_CONNECT = `(() => { - const h = window.tbFsConnect; +const RELEASE_CONNECT = `(() => { + const h = window.tbHeldConnect; if (!h) return false; - delete window.tbFsConnect; - if (h.own) fs.connect = h.connect; - else delete fs.connect; + delete window.tbHeldConnect; + if (h.own) h.socket.connect = h.connect; + else delete h.socket.connect; if (h.held) h.held(); return !!h.held; })()`; const servicesAre = (c, cls) => c.evaluate(`compilerStatusOuter.classList.contains(${JSON.stringify(cls)})`); + +// A restart with the toolbar's button, the page's call of `socket`.connect held: `shot()` once +// that call has come and the page's expression `state` holds, then the call made and the badge +// back on OPERATIONAL. `missing` says what never came. +async function whileConnectHeld(c, socket, state, missing, shot) { + await resetUi(c); + if (!(await waitFor(c, () => servicesAre(c, "servicesOK"), { timeout: 60000, interval: 250 }))) { + throw new Error("the services badge never said OPERATIONAL before the restart"); + } + await c.evaluate(holdConnect(socket)); + try { + await click(c, "restartIcon"); + const held = await waitFor(c, () => c.evaluate(`!!window.tbHeldConnect?.held && (${state})`), { + timeout: 60000, + interval: 100, + }); + if (!held) throw new Error(missing); + // The restart's line names the project's folder, under the user's, and the console's + // Clear does nothing until the compiler is connected: the name is replaced in the + // console's text, in the page only, as nameUserAsUser does in a dialog. The picture + // holds no name. + await nameUserAsUserIn(c, ".debugConsoleOuter"); + return await shot(); + } finally { + await c.evaluate(RELEASE_CONNECT); + if (!(await waitFor(c, () => servicesAre(c, "servicesOK"), { timeout: 60000, interval: 250 }))) { + complain(c.shot.name, "the services badge did not go back to OPERATIONAL after the restart"); + } + await clearUserFromConsole(c); + } +} + const limitedShot = { out: "IDE/Images/Services_Limited.png", setup: "community", - async take({ c }) { - await resetUi(c); - if (!(await waitFor(c, () => servicesAre(c, "servicesOK"), { timeout: 60000, interval: 250 }))) { - throw new Error("the services badge never said OPERATIONAL before the restart"); - } - await c.evaluate(HOLD_FS_CONNECT); - try { - await click(c, "restartIcon"); - const held = await waitFor( - c, - () => - c.evaluate( - `!!window.tbFsConnect?.held && compilerStatusOuter.classList.contains("servicesPARTIAL") && /LIMITED/.test(compilerStatus.innerText)`, - ), - { timeout: 60000, interval: 100 }, - ); - if (!held) throw new Error("the restarted compiler never asked for its FS connection, with the badge LIMITED"); - // The restart's line names the project's folder, under the user's, and the console's - // Clear does nothing until the compiler is connected: the name is replaced in the - // console's text, in the page only, as nameUserAsUser does in a dialog. The picture - // is of the badge, which holds no name. - await nameUserAsUserIn(c, ".debugConsoleOuter"); - say(c.shot.name, ` the badge's title: ${JSON.stringify(await c.evaluate("compilerStatusOuter.title"))}`); - return await capture(c, "Services_Limited", snapOut(await rectOf(c, "#compilerStatusOuter")), { - away: () => parkMouse(c), - }); - } finally { - await c.evaluate(RELEASE_FS_CONNECT); - if (!(await waitFor(c, () => servicesAre(c, "servicesOK"), { timeout: 60000, interval: 250 }))) { - complain(c.shot.name, "the services badge did not go back to OPERATIONAL after the restart"); - } - await clearUserFromConsole(c); - } - }, + take: ({ c }) => + whileConnectHeld( + c, + "fs", + `compilerStatusOuter.classList.contains("servicesPARTIAL") && /LIMITED/.test(compilerStatus.innerText)`, + "the restarted compiler never asked for its FS connection, with the badge LIMITED", + async () => { + say(c.shot.name, ` the badge's title: ${JSON.stringify(await c.evaluate("compilerStatusOuter.title"))}`); + return capture(c, "Services_Limited", snapOut(await rectOf(c, "#compilerStatusOuter")), { + away: () => parkMouse(c), + }); + }, + ), }; // Resolves once every CSS background image in the top dialog is decoded: the VIP Gold logos @@ -11147,6 +11408,7 @@ async function stableWindow(desktop, hwnd, name, file, flags = 2, { active = fal if (active) await activated(desktop, hwnd, name); for (let i = 0; i < 8; i++) { const info = await captureWindow(desktop, hwnd, file, { flags }); + refuseRoot(info.texts.join("\n"), name, "the window"); const named = info.texts.find((t) => t.toLowerCase().includes(USER.toLowerCase())); if (named !== undefined) throw new Error(`the window of ${name} shows the Windows user name in ${JSON.stringify(named)}`); @@ -11524,13 +11786,13 @@ const memoryShot = panelShot("Memory_String", "MEMORY 1", [696, 120], { }); // ---- package: the sample, a TWINPACK package, built (setup package), with its build path on a -// folder of the tool's own at the root of the temp folder's drive, PACKAGE_ROOT: the DEBUG -// CONSOLE names the file the build creates, and the lane's own build path is under the +// folder of the tool's own in the checkout, PACKAGE_ROOT, which the page shows as SHOWN_ROOT: the +// DEBUG CONSOLE names the file the build creates, and the lane's own build path is under the // user's profile. The folder is made by the IDE and removed at the end of the run, unless it // was there before it. The console's times are set in the page once the real ones are shown, // as the CodeLens pictures' are. -const PACKAGE_ROOT = path.join(path.parse(tmpdir()).root, "tbshots"); +const PACKAGE_ROOT = TOOL_ROOT; const PACKAGE_BUILD_PATH = path.join(PACKAGE_ROOT, "Build", "${ProjectName}_${Architecture}.${FileExtension}"); // Sets every line time of the DEBUG CONSOLE to CODELENS_TIME, and the time a CodeLens run took @@ -12125,9 +12387,11 @@ const packageServerShots = [ was = { text: await editorText(c), counts: await diagCounts(c) }; await setCursor(c, 4, 1); await typeText(c, " fmt"); - // The list shows each member's type as "...", or a variable's or a function's kind with - // no type after it, until the compiler has given it, which it now and then never does - // for a list: the dot is typed again then. + // The list shows each member's type as "..." until the compiler has given it. It asks for + // the rows it draws by their lazy index, and its test of that index is false for 0, so + // the row with index 0 is never asked for and keeps a variable's or a function's kind with + // no type after it, in every list of this compile (bugs/completion-row-zero-no-type). + // `askForRowZero` asks, and keeps the compiler's answer as the list keeps one. const listed = (resolved) => c.evaluate(`(() => { const b = [...document.querySelectorAll(${JSON.stringify(COMPLETIONS)})].find((b) => b.getBoundingClientRect().width); @@ -12149,20 +12413,36 @@ const packageServerShots = [ await waitFor(c, async () => !(await boxShown()), { timeout: 2000, interval: 100 }); } }; - let shown = false; - for (let i = 0; i < 3 && !shown; i++) { - if (i) { - await pressKey(c, "Escape"); - await pressKey(c, "Backspace"); - } - await noList(); - await typeText(c, "."); - if (!(await waitFor(c, () => listed(false), { timeout: 30000, interval: 200 }))) { - throw new Error("typing fmt. opened no completion list of the package's members"); - } - shown = await waitFor(c, () => listed(true), { timeout: 10000, interval: 200 }); + const askForRowZero = () => + c.evaluate( + `new Promise((done) => { + const list = debugConsoleEntryIntellisenseList; + const x = list.dataNodesDATA.findIndex((o) => o.i === 0 && !o.gotData); + if (x < 0) return done(false); + lspSocket.request("textDocument/lazyCompletion", { lazyRequestId: list.lazyRequestId, requests: [{ x, i: 0 }] }, (e) => { + for (const { x: row, ...answer } of e.results ?? []) { + Object.assign(list.dataNodesDATA[row], answer, { gotData: true }); + list.notifyChangedItem(row); + } + list.repaint(); + done(true); + }); +})`, + { awaitPromise: true }, + ); + await noList(); + await typeText(c, "."); + if (!(await waitFor(c, () => listed(false), { timeout: 30000, interval: 200 }))) { + throw new Error("typing fmt. opened no completion list of the package's members"); + } + await askForRowZero(); + if (!(await waitFor(c, () => listed(true), { timeout: 10000, interval: 200 }))) { + const rows = await c.evaluate(`[...document.querySelectorAll(${JSON.stringify(COMPLETIONS)})] + .filter((b) => b.getBoundingClientRect().width) + .flatMap((b) => [...b.querySelectorAll(".listitem")].map((r) => r.innerText.replace(/\\s+/g, " ").trim())) + .filter((t) => /(\\.\\.\\.|variable|returns)$/.test(t))`); + throw new Error(`the completion list never showed its members' types: ${JSON.stringify(rows)}`); } - if (!shown) throw new Error("the completion list never showed its members' types"); await sleep(500); // The IDE places the list at its text cursor's box as drawn when the list is asked for, // and that is at the dot or, now and then, already after it, 7 pixels to the right @@ -12382,8 +12662,8 @@ async function startPackageCheck(run) { // Add menu's Import imports a .vbp. The IDE's own Open dialog is a native window, so the take // calls what the IDE calls with the file chosen in it (importFileByPath); the IDE then loads the // project in a compiler of its own and shows the Import from twinproj dialog, its tree of what -// it found. The project is staged on PACKAGE_ROOT, a folder of the tool's own at the root of -// the temp folder's drive, since the dialog names the .vbp's path: with CRLF line ends, as VB6 +// it found. The project is staged on PACKAGE_ROOT, a folder of the tool's own, since the dialog +// names the .vbp's path, which the page shows under SHOWN_ROOT: with CRLF line ends, as VB6 // writes its files, a module in a folder beside the project's (Shared), which the IDE puts // under {PARENT-FOLDER}, and a resource file the tool writes (lib/res-file.mjs): three icons // and the manifest test/shots/vbp/manifest.xml. @@ -13280,6 +13560,1096 @@ function helpPlaces() { }; } +// ---------------------------------------------------------------- the "Starting a project" chapter + +// The chapter's pictures are under docs/IDE/Starting/Images. Its fixtures are test/shots/starting: +// Ledger (a .vbp with a form, a module and a class, and no manifest) and Missing (a .vbp naming a +// file that is not there) are staged with CRLF line ends, as VB6 writes its files, and Throwaway +// (the Starting lanes' project) is packed and cut; each in a folder of the tool's own on +// PACKAGE_ROOT, so that no path names the user. Nothing there is ever saved over: the page's call +// that keeps a project's state in the registry is one these setups make do nothing, as they do +// the call that adds to the recent list, and the lists are the tool's own. +const ST_IMAGE = (name) => `IDE/Starting/Images/${name}.png`; +const STARTING = path.join(REPO_ROOT, "test", "shots", "starting"); + +// ---- starting-first-run: the three dialogs of a first start. Their page runs over the guard of +// lib/shot-first-run.mjs, which says why the flags and the hook do not protect the registry here. +// Each take makes a page of a first start, and leaves it for a page past those dialogs (SETTLED) +// however it goes, so that no such page runs on. The setup's `end` fails on any call that got past +// the guard, and on any change to the IDE's settings key. + +async function startFirstRun(run) { + run.settingsBefore = settingsHash(); + run.guard = { blocked: {}, through: [], script: null, gen: 0 }; + return startNoProject(run); +} + +// What the page's guard has recorded, added to the setup's record and emptied in the page. +async function readGuard(c, guard) { + const read = `(() => { + const g = window.__shotGuard; + if (!g) return "null"; + const seen = JSON.stringify({ blocked: g.blocked, through: g.through }); + g.blocked = {}; + g.through = []; + return seen; +})()`; + const seen = JSON.parse(await c.evaluate(read)); + if (!seen) return; + for (const [k, n] of Object.entries(seen.blocked)) guard.blocked[k] = (guard.blocked[k] ?? 0) + n; + guard.through.push(...seen.through); + if (seen.through.length) throw new Error(`a call got past the guard: ${seen.through.join(", ")}`); +} + +// The page again, from its document's start, over the guard, reading `settings`: the guard in +// front of the hook before anything else happens, or the page is left alone. +async function guardedPage(c, guard, settings) { + await readGuard(c, guard); + await c.send("Page.enable"); + if (guard.script) await c.send("Page.removeScriptToEvaluateOnNewDocument", { identifier: guard.script }); + const gen = ++guard.gen; + const { identifier } = await c.send("Page.addScriptToEvaluateOnNewDocument", { source: guardSource(settings, gen) }); + guard.script = identifier; + await c.send("Page.reload", {}); + const up = () => + c + .evaluate(`window.__shotGuard?.gen === ${gen} && window.__shotGuard.hook && window.__shotGuard.inFront`) + .catch(() => false); + if (!(await waitFor(c, up, { timeout: 30000, interval: 50 }))) { + throw new Error("the page has no guard in front of the defaults hook"); + } +} + +// A page past the first start's dialogs, reporting off, no dialog open. +async function settledPage(c, guard) { + await guardedPage(c, guard, SETTLED); + if (!(await waitModal(c, "New / Open", { timeout: 60000 }))) throw new Error("the IDE showed no New / Open dialog"); + if (await c.evaluate("g_ErrorReporting")) throw new Error("error reporting is on"); + await closeModal(c, "Cancel"); + await readGuard(c, guard); +} + +async function prepareFirstRun(run) { + const { c, guard } = run; + run.step = "shoot"; + await fixPageSize(c); + if (!(await waitModal(c, "New / Open", { timeout: 60000 }))) throw new Error("the IDE showed no New / Open dialog"); + await settledPage(c, guard); + await ensureTheme(c, "dark"); + await parkMouse(c); + return { c, guard }; +} + +// The record of the guard, once the IDE has ended, and the key it protects. +async function endFirstRun(run) { + const { blocked, through } = run.guard; + const stopped = Object.entries(blocked).map(([k, n]) => `${k} x${n}`); + say(run.name, `the guard blocked ${stopped.join(", ") || "nothing"}; through it: ${through.join(", ") || "nothing"}`); + const after = settingsHash(); + if (after !== run.settingsBefore) throw new Error("the IDE's settings key changed while the first-run pages ran"); + say(run.name, `the IDE's settings key is as it was found (${after})`); + if (through.length) throw new Error(`a call got past the guard: ${through.join(", ")}`); +} + +// The dialog titled `title`, over the banner the page shows until its start is over. +async function firstRunDialog(c, title) { + const shown = await waitModal(c, title, { timeout: 30000 }); + if (!shown) throw new Error(`the first start showed no dialog titled ${title}`); + if (!(await c.evaluate(`document.getElementById("root").classList.contains("invisible")`))) { + throw new Error(`the dialog ${title} is not over the banner`); + } +} + +// How much of the banner shows round a first start's dialog. +const BANNER_MARGIN = 40; + +// A first start up to the dialog `title`: `answers` the buttons pressed to get there, each with +// the title of the dialog it leads to. NO, not YES, on the reporting question: it leads to the +// same IDE Options, and reporting is never on, not for a moment. The theme is set at the first +// dialog, as the IDE Options read it when they open. The options a first start has are the dark +// theme's, so the Light pictures are that dialog with the theme switched by its own command. +const firstRunShot = (name, answers, themeWords = null) => ({ + out: ST_IMAGE(name), + setup: "starting-first-run", + themeWords, + async take({ c, guard }) { + const theme = c.shot.out.endsWith(".light.png") ? "light" : "dark"; + await guardedPage(c, guard, FIRST_RUN); + try { + await firstRunDialog(c, "Licence Agreement"); + const themed = `!!getComputedStyle(document.documentElement).getPropertyValue("--themeGeneralPanelBackColor")`; + if (!(await c.evaluate(themed))) throw new Error("the first start's page has no theme"); + await ensureTheme(c, theme); + for (const [caption, next] of answers) { + await clickInModal(c, caption, ".msgBoxButton"); + await firstRunDialog(c, next); + } + await dialogStill(c); + if (await c.evaluate("g_ErrorReporting")) throw new Error("error reporting is on"); + return await dialogShot(c, name, (m) => grow(m, BANNER_MARGIN)); + } finally { + await settledPage(c, guard); + } + }, +}); + +// ---- starting, starting-new, starting-boxes: an IDE with no project, its Recent lists the tool's +// own. The licence key is emptied in the page only, as in the community setup, so that a compiler +// these setups start answers COMMUNITY EDITION and the dialog that takes a key has nothing near it. + +async function startStarting(run) { + mkdirSync(runFolder(run, run.name), { recursive: true }); + return startNoProject(run); +} + +async function prepareStarting(run) { + const ctx = await prepareNoProject(run); + await run.c.evaluate(`licenceKey = ""; +currentDPI = window.devicePixelRatio; +window.__shotBlocked.add("AddRecentsList"); +window.__shotBlocked.add("SaveProjectMetaState")`); + return { ...ctx, run }; +} + +// A .vbp fixture in the setup's folder, its files with VB6's line ends: the .vbp's path. +function stageVb6(run, name) { + const dir = path.join(run.buildingDir, name); + mkdirSync(dir, { recursive: true }); + for (const f of readdirSync(path.join(STARTING, name))) { + const text = readFileSync(path.join(STARTING, name, f), "utf8"); + writeFileSync(path.join(dir, f), text.replace(/\r?\n/g, "\r\n")); + } + return path.join(dir, `${name}.vbp`); +} + +// A real double-click at a point of the page. +async function doubleClickAt(c, { x, y }) { + await mouseMove(c, x, y); + for (const clickCount of [1, 2]) { + for (const type of ["mousePressed", "mouseReleased"]) { + await c.send("Input.dispatchMouseEvent", { + type, + x, + y, + button: "left", + buttons: type === "mousePressed" ? 1 : 0, + clickCount, + }); + } + } +} + +// The middle of the card of the New / Open Project dialog whose item's name starts with `name`. +const cardCentre = (c, name) => + c.evaluate(`(() => { + const e = [...document.querySelectorAll("#fp2root .fp2-card")].find((x) => x.getBoundingClientRect().width && x.fp2Item?.name.startsWith(${JSON.stringify(name)})); + if (!e) return null; + e.scrollIntoView({ block: "nearest" }); + const r = e.getBoundingClientRect(); + return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; +})()`); + +// File > New Project..., and a double-click on the card `name`. `section`: the rail's section it +// is in, when that is not New. +async function openCard(c, name, section = null) { + await command(c, "tbProject_New"); + if (!(await waitModal(c, "New / Open", { timeout: 10000 }))) throw new Error("New Project opened no dialog"); + await dialogStill(c); + if (section) await dialogSection(c, section); + const card = await cardCentre(c, name); + if (!card) throw new Error(`the dialog has no card ${name}`); + await doubleClickAt(c, card); +} + +async function waitOptions(c) { + if (!(await waitModal(c, "New Project Options", { timeout: 60000 }))) { + throw new Error("the card did not show the New Project Options dialog"); + } + await dialogStill(c); +} + +// The card Import from VBP..., the native picker answered in the page with the fixture `name`. +async function importCard(c, run, name) { + await c.evaluate(`window.__shotAnswers.OpenProjectVBP = ${JSON.stringify(stageVb6(run, name))}`); + await openCard(c, "Import from VBP"); +} + +// The log's first line says when the import started: a time of the tool's own, set in the page +// once the real one shows, or no two pictures would be alike. +async function fixImportTime(c) { + const set = `(() => { + const time = /\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d/; + const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); + for (let n = walk.nextNode(); n; n = walk.nextNode()) { + if (n.data.includes("Started:") && time.test(n.data)) { + n.data = n.data.replace(time, "02-11-26 09:30:00"); + return true; + } + } + return false; +})()`; + if (!(await waitFor(c, () => c.evaluate(set), { timeout: 10000, interval: 100 }))) { + throw new Error("the import log's first line did not show"); + } +} + +// PROPERTIES shows the form's FormDesignerId, which is a new GUID in each new project: an id of +// the tool's own, set in the page once the real one shows, or no two pictures would be alike. +async function fixDesignerId(c) { + const set = `(() => { + const guid = /\\{[0-9A-F]{8}(-[0-9A-F]{4}){3}-[0-9A-F]{12}\\}/i; + const own = "{D0C5D0C5-3DAD-4000-8000-00000000D0C5}"; + let done = 0; + const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); + for (let n = walk.nextNode(); n; n = walk.nextNode()) { + if (guid.test(n.data)) { + n.data = n.data.replace(guid, own); + done++; + } + } + for (const i of document.querySelectorAll("input")) { + if (guid.test(i.value)) { + i.value = i.value.replace(guid, own); + done++; + } + } + return done; +})()`; + if (!(await waitFor(c, () => c.evaluate(set), { timeout: 10000, interval: 100 }))) { + throw new Error("PROPERTIES shows no FormDesignerId"); + } +} + +// The title bar's text, a little of the bar round it, as a part of a composite. +async function titlePart(c, name) { + await parkMouse(c); + await frames(c); + const text = await rectOf(c, "#rootMenuText"); + if (!text) throw new Error("the page has no title bar text"); + const clip = snapOut({ x: text.x - 8, y: text.y, width: text.width + 16, height: text.height }); + return { png: await capture(c, name, clip, { away: () => parkMouse(c) }), clip }; +} + +// An import's Cancel leaves the page's g_IsLoadingVBP set (BETA 1034: only the box's Continue +// clears it), and every later New Project Options is then drawn as an import's until a Continue. +// A shot sets its own state and relies on no other: the card shows what it shows in a fresh page. +const forgetImport = (c) => c.evaluate("g_IsLoadingVBP = false"); + +// New Project Options reached by `open`, and Cancel, which leaves the IDE with no project: no +// project is made, and an ActiveX one is never built or registered. +const optionsShot = (name, open) => ({ + out: ST_IMAGE(name), + setup: "starting", + async take({ c, run }) { + await resetUi(c); + try { + await open(c, run); + await waitOptions(c); + // the Project Name is selected: its caret at the end of the name + await c.evaluate(`(() => { + const i = [...document.querySelectorAll(".modalDialogContainer")].pop().querySelector("input[type=text], input:not([type])"); + if (i) i.setSelectionRange(i.value.length, i.value.length); +})()`); + // the box is centred, so it can start half a CSS pixel into the progress box behind it + const left = (m) => ({ ...m, x: Math.ceil(m.x), width: m.x + m.width - Math.ceil(m.x) }); + return await dialogShot(c, name, left); + } finally { + await cancelOptions(c); + } + }, +}); + +// Cancel on New Project Options, and what that leaves put right. +async function cancelOptions(c) { + await closeModal(c, "Cancel"); + await c.evaluate(`delete window.__shotAnswers.OpenProjectVBP`); + await forgetImport(c); + const gone = async () => !(await modals(c)).length && !(await c.evaluate("context.projectLoaded.value")); + if (!(await waitFor(c, gone, { timeout: 15000, interval: 100 }))) throw new Error("Cancel left a project open"); + await sleep(1000); +} + +// The project open and the IDE idle, and still so a second later. +async function projectReady(c) { + const ready = `(() => { try { + return Object.keys(fs.tree.rootFolder.entries).length > 0 && context.projectLoaded.value && licenceIsSet === true + && !(lockUICount > 0) && /OPERATIONAL/.test(compilerStatusOuter.innerText); +} catch { return false; } })()`; + let since = null; + for (const until = Date.now() + 90000; Date.now() < until; await sleep(100)) { + if ((await c.evaluate(ready)) && !(await modals(c)).length) { + since ??= Date.now(); + if (Date.now() - since >= 1000) return; + } else since = null; + } + throw new Error("no project became ready"); +} + +// Back to no project: Close Project, and Discard Changes if it asks. Nothing is saved. +async function closeUnsaved(c) { + await resetUi(c); + if (!(await c.evaluate("context.projectLoaded.value"))) return; + await command(c, "tbProject_Close"); + if (await waitFor(c, () => modalHolding(c, "Save now"), { timeout: 1500, interval: 100 })) { + await clickInModal(c, "Discard Changes", ".msgBoxButton"); + } + const gone = async () => !(await modals(c)).length && !(await c.evaluate("context.projectLoaded.value")); + if (!(await waitFor(c, gone, { timeout: 15000, interval: 100 }))) throw new Error("the project did not close"); + await sleep(1000); +} + +// A new project from the Standard EXE card, as a person makes one: no file, and nothing changed. +async function newStandardExe(c) { + await closeUnsaved(c); + await openCard(c, "Standard EXE"); + await waitOptions(c); + await closeModal(c, "Continue"); + await projectReady(c); + if ((await c.evaluate("compilerLicence.innerText")) !== "COMMUNITY EDITION") { + throw new Error("the new project's compiler does not say COMMUNITY EDITION"); + } + await clearUserFromConsole(c); + await parkMouse(c); + await frames(c); +} + +// The whole window. +const WINDOW = () => snapOut({ x: 0, y: 0, ...IDE_SIZE }); + +const VB6_RECENTS = ["C:\\Old\\Thing.vbp", "C:\\Old\\Other.VBP"]; + +// The compiler's own box titled `title` on `desktop`, with its child windows, or null. The +// compiler's MessageBox is no part of the page. +async function compilerBox(desktop, title) { + const list = await desktopWindows(desktop, { children: true }); + const at = list.findIndex((w) => w.class === "#32770" && w.visible && w.title === title); + if (at < 0) return null; + const kids = []; + for (let j = at + 1; j < list.length && list[j].depth === 1; j++) kids.push(list[j]); + return { ...list[at], kids }; +} + +const BM_CLICK = 0xf5; +// WM_UPDATEUISTATE, UIS_SET with UISF_HIDEFOCUS: the box's button drew its dotted focus rectangle +// in some runs and not in others, and a box opened with the mouse draws none. +const WM_UPDATEUISTATE = 0x128; +const HIDE_FOCUS_CUES = 0x10001; + +// The part of a capture of a DPI-unaware window that it drew. The compiler's boxes are unaware of +// the display's scaling: PrintWindow draws one at 96 DPI in the top left corner of a bitmap the +// size it has on the screen, and black where its invisible resize borders are, so the capture's +// frame, which is in the display's pixels, does not fit it. Its first row of pixels is its border. +function drawnPart(picture) { + const { width, height, rgba } = picture; + const lit = (x, y) => { + const i = (y * width + x) * 4; + return rgba[i + 3] && rgba[i] + rgba[i + 1] + rgba[i + 2]; + }; + let left = 0; + while (left < width && !lit(left, 1)) left++; + let right = left; + while (right < width && lit(right, 1)) right++; + let bottom = 0; + while (bottom < height && lit(left, bottom)) bottom++; + return crop(picture, [left, 0, right, bottom]); +} + +// The window of the box titled `title` alone, as Windows draws it, once the box shows; then OK, by +// the message a button takes on a desktop that has no input. Not over the IDE: on a window the +// size of the screen the box stands on the text of the IDE's progress box, and a picture of the +// two would need this 96 DPI window stretched to the IDE's scale. `check(box)`, when given, fails +// the picture by throwing; the box then stays for the caller to close. +async function boxPicture(run, title, check = null) { + const desktop = run.lane.desktop; + const box = await waitFor(run.c, () => compilerBox(desktop, title), { timeout: 40000, interval: 200 }); + if (!box) throw new Error(`the compiler showed no box titled ${title}`); + check?.(box); + const file = path.join(run.work, "box.png"); + await activated(desktop, box.hwnd, title); + await postWindowMessage(desktop, box.hwnd, WM_UPDATEUISTATE, HIDE_FOCUS_CUES, 0); + const { info, picture } = await stableWindow(desktop, box.hwnd, title, file, 2, { active: true }); + await postWindowMessage(desktop, box.kids.find((k) => k.class === "Button").hwnd, BM_CLICK, 0, 0); + return besideEachOther([{ picture: drawnPart(picture), dpi: info.dpi }]); +} + +// Throwaway, packed into the setup's folder and cut in the middle of its last source: past the +// Settings entry, so that the box shows once and goes. Large Address Aware on, as a new project +// has it, so that it does not load twice. The bytes it holds, for the check that it is never saved. +function damagedProject(run) { + const dir = path.join(run.buildingDir, "Throwaway"); + mkdirSync(dir, { recursive: true }); + const file = path.join(dir, "Throwaway.twinproj"); + stageProject({ + src: path.join(STARTING, "Throwaway"), + stage: path.join(run.work, "throwaway"), + project: file, + settings: { + "project.name": "Throwaway", + "project.appTitle": "Throwaway", + "project.description": "Throwaway", + "project.largeAddressAware": true, + "project.id": DOCS_PROJECT_ID, + }, + }); + // impexp writes Settings last and the IDE first: first, so that the cut leaves it whole + const bytes = readFileSync(file); + const packed = layout(bytes); + const settings = packed.find((e) => e.full === "Settings"); + const body = packed[1].start; + const moved = Buffer.concat([ + bytes.subarray(0, body), + bytes.subarray(settings.start, settings.end), + bytes.subarray(body, settings.start), + bytes.subarray(settings.end), + ]); + const source = layout(moved).findLast((e) => e.file && e.full.startsWith("Sources/")); + const cut = moved.subarray(0, source.contentAt + Math.floor(source.len / 2)); + writeFileSync(file, cut); + return { file, cut }; +} + +// Close Project on what the compiler recovered: nothing changed, so nothing is saved, and the +// cut copy holds the bytes it was given. +async function leaveDamaged(c, file, cut) { + await projectReady(c); + await closeUnsaved(c); + if (!readFileSync(file).equals(cut)) throw new Error("the cut copy of Throwaway changed"); +} + +const startingShots = [ + firstRunShot("Licence_Agreement", []), + firstRunShot("Error_Reporting", [["I AGREE", "Automatic Error Reporting"]]), + firstRunShot( + "First_Run_Options", + [ + ["I AGREE", "Automatic Error Reporting"], + ["NO", "IDE Options"], + ], + SHOWS_ITS_THEME, + ), + { + // Help > Enter Licence Key... with no project: the box empty, and Cancel + out: ST_IMAGE("Licence_Key"), + setup: "starting", + take: ({ c }) => + inDialog(c, { command: "tbHelp_EnterLicenceKey", title: "twinBASIC", close: "Cancel" }, async () => { + const typed = `[...document.querySelectorAll(".modalDialogContainer input")].map((i) => i.value).join("")`; + if (await c.evaluate(typed)) throw new Error("the licence key's box is not empty"); + return dialogShot(c, "Licence_Key"); + }), + }, + { + // the dialog on New, "standard" typed into its search box with real keys + out: ST_IMAGE("Search_Results"), + setup: "starting", + take: ({ c }) => + inNewProject(c, async () => { + const box = await rectOf(c, "#fp2root .fp2-search"); + await clickAt(c, box.x + box.width / 2, box.y + box.height / 2); + await typeText(c, "standard"); + const shown = () => c.evaluate(`document.querySelectorAll("#fp2root .fp2-group .fp2-rowPath").length`); + if (!(await waitFor(c, shown, { timeout: 5000, interval: 50 }))) + throw new Error("the search listed no results"); + await dialogStill(c); + return dialogShot(c, "Search_Results"); + }), + }, + { + // the rail's Recent (VB6), two made-up paths answered in the page; VB6's key is not read + out: ST_IMAGE("Recent_VB6"), + setup: "starting", + async take({ c }) { + await c.evaluate(`window.__shotVb6Recents = ${JSON.stringify(VB6_RECENTS.join("?"))}`); + try { + return await inNewProject(c, async () => { + await dialogSection(c, "Recent (VB6)"); + return dialogShot(c, "Recent_VB6"); + }); + } finally { + await c.evaluate(`window.__shotVb6Recents = ""`); + } + }, + }, + optionsShot("Options_ActiveX", (c) => openCard(c, "ActiveX DLL")), + optionsShot("Options_Sample", (c) => openCard(c, "Sample 1.", "Samples")), + optionsShot("Options_VBP", (c, run) => importCard(c, run, "Ledger")), + { + // a project just made from the Standard EXE card: the designer of Form1 in front + out: ST_IMAGE("New_Standard_EXE"), + setup: "starting-new", + async take({ c }) { + await newStandardExe(c); + await fixDesignerId(c); + return capture(c, "New_Standard_EXE", WINDOW(), { away: () => parkMouse(c) }); + }, + }, + { + // the title bar of a new project, and of the same project after one edit, one over the other + out: ST_IMAGE("Title_Unsaved"), + setup: "starting-new", + async take({ c }) { + await newStandardExe(c); + // the parts are not the picture: capture keeps the first two captures that agree + const { reference, prefer, ignore } = c.shot; + Object.assign(c.shot, { reference: null, prefer: null, ignore: null }); + const made = []; + try { + made.push(await titlePart(c, "Title_Unsaved-new")); + const root = await c.evaluate("Object.keys(fs.tree.rootFolder.entries)[0]"); + await openFile(c, `/${root}/Sources/Form1.twin`, { line: 1, column: 1 }); + await typeText(c, "' x\n"); + if (!(await waitFor(c, () => c.evaluate("projectIsDirty"), { timeout: 5000, interval: 50 }))) { + throw new Error("the edit did not mark the project as changed"); + } + made.push(await titlePart(c, "Title_Unsaved-changed")); + } finally { + Object.assign(c.shot, { reference, prefer, ignore }); + } + try { + const { box } = await composite(c, made, [], { gap: 8, margin: 0 }); + return await capture(c, "Title_Unsaved", snapOut(box), { away: () => parkMouse(c) }); + } finally { + await uncomposite(c); + } + }, + }, + { + // Continue after an import: the log in its preview tab, and the title bar's dot + out: ST_IMAGE("Import_Log"), + setup: "starting-new", + async take({ c, run }) { + await closeUnsaved(c); + await importCard(c, run, "Ledger"); + await waitOptions(c); + await closeModal(c, "Continue"); + await c.evaluate(`delete window.__shotAnswers.OpenProjectVBP`); + await projectReady(c); + if (!(await c.evaluate("projectIsDirty"))) throw new Error("the import is not marked as changed"); + await clearUserFromConsole(c); + await fixImportTime(c); + await parkMouse(c); + return capture(c, "Import_Log", WINDOW(), { away: () => parkMouse(c) }); + }, + }, + { + // the compiler's box for a file the .vbp names and the disk lacks; OK, and Cancel + out: ST_IMAGE("Missing_File_Box"), + setup: "starting-boxes", + once: true, + window: true, + async take({ c, run }) { + await resetUi(c); + await importCard(c, run, "Missing"); + try { + return await boxPicture(run, "Error reading file."); + } finally { + await waitOptions(c); + await cancelOptions(c); + } + }, + }, + { + // the compiler's box for a project cut in two, opened from Recent; OK, and Close Project: + // the cut copy is never saved + out: ST_IMAGE("Damaged_Box"), + setup: "starting-boxes", + once: true, + window: true, + async take({ c, run }) { + await closeUnsaved(c); + const { file, cut } = damagedProject(run); + await setRecents(c, [file]); + try { + await command(c, "tbProject_OpenRecent"); + if (!(await waitModal(c, "New / Open", { timeout: 10000 }))) throw new Error("Open Recent opened no dialog"); + await dialogStill(c); + const row = await recentRow(c, file); + if (!row) throw new Error("the Recent tab does not list the project"); + await doubleClickAt(c, row); + return await boxPicture(run, "bad file format"); + } finally { + await setRecents(c, []); + await leaveDamaged(c, file, cut); + } + }, + }, +]; + +// ---- troubleshooting, troubleshooting-box: the "When something goes wrong" chapter. +// test/shots/troubleshooting/Trouble, the lanes' test/ide/probes/troubleshooting under a name for a +// picture: Sub Crash calls RtlMoveMemory with two bad addresses at line 10 of MainMod, the line the +// page quotes. A crash, a kill and Safe Mode each leave the IDE where a later take would see them, so +// each take that goes there comes back, and each theme's take reaches its state by itself. +// +// What these takes do on purpose and a tool's IDE never else does: +// * The compiler is killed by the pid the page names, its own lane's, and no other process. A kill +// leaves no NATIVE EXCEPTION line, so the lane's crash check does not count it. +// * Native_Exception makes one native exception. Lane.close counts the console's NATIVE EXCEPTION +// lines and throws, so the take asserts that there is exactly its own one and clears the console. +// That is the allowance, and all of it: any other exception stays in the console, and `closing` +// fails the setup on it. +// * A JavaScript alert cannot be captured and no picture wants one, so a take fails on one. +// * No report leaves: the defaults hook answers ALLOWERRORREPORTINGV3 no, so postError posts +// nothing, and CDP stops any request for the report's address, which fails the setup. + +const TS_IMAGE = (name) => `IDE/Troubleshooting/Images/${name}.png`; +const TROUBLESHOOTING = path.join(REPO_ROOT, "test", "shots", "troubleshooting"); +const TROUBLE_FILE = "/Trouble/Sources/MainMod.twin"; +const SAFE_MODE_TEXT = "SafeMode is designed"; +const CRASH_LOOP_TEXT = "Compiler crash loop detected."; +// how much of the toolbar and the status bar Safe_Mode_Bar shows: to the end of the build box's +// group of buttons, and of the status bar's badges and links +const SAFE_BAR_WIDTH = 474; + +async function startTroubleshooting(run) { + run.step = "open"; + run.lane.includeProjects(); + const dir = runFolder(run, "Troubleshooting"); + say(run.name, `opening ${path.relative(REPO_ROOT, TROUBLESHOOTING)} in ${dir}`); + return run.lane.open(path.join(TROUBLESHOOTING, "Trouble"), { folder: dir, buildPath: BD_BUILD_PATH }); +} + +async function prepareTroubleshooting(run) { + const ctx = await prepareSample(run); + const { c } = run; + await c.evaluate(`window.__shotBlocked.add("AddRecentsList"); +window.__shotBlocked.add("SaveProjectMetaState")`); + run.reportsStopped = 0; + c.on((m) => { + if (m.method !== "Fetch.requestPaused") return; + run.reportsStopped++; + c.send("Fetch.failRequest", { requestId: m.params.requestId, errorReason: "BlockedByClient" }).catch(() => {}); + }); + await c.send("Fetch.enable", { patterns: [{ urlPattern: "*twinbasic.com/notify*" }] }); + await openFile(c, TROUBLE_FILE, { line: 1, column: 1 }); + await communityCompiler(run); + // what Native_Exception's float ends with, which moves Light's panels a device pixel: from the + // start, so that a picture does not depend on whether that take came before it + await defaultLayout(c); + await parkMouse(c); + return { ...ctx, run }; +} + +// With its IDE still up: what only the page can say about what the takes left behind. +async function closingTroubleshooting(run) { + const { c } = run; + const crash = await readCrash(c); + const { reports, reporting } = await c.evaluate("({ reports: __tbDefaults.reports, reporting: g_ErrorReporting })"); + say( + run.name, + `__tbDefaults.reports ${reports}; error reporting ${reporting ? "on" : "off"}; requests for the report's address stopped: ${run.reportsStopped}`, + ); + if (run.reportsStopped) throw new Error(`${run.reportsStopped} requests for the error report's address went out`); + if (reporting || reports) throw new Error(`with reporting off no exception posts, and the hook answered ${reports}`); + if (crash) throw new Error(`the console holds ${crash.n} NATIVE EXCEPTION lines no take expected`); + if (c.dialogs.length) throw new Error(`a JavaScript dialog opened: ${JSON.stringify(c.dialogs)}`); +} + +// A take of these setups fails on a JavaScript dialog: tb-ide.mjs accepts each and keeps it. +const noAlert = (shot) => ({ + ...shot, + async take(ctx) { + const taken = await shot.take(ctx); + if (ctx.c.dialogs.length) throw new Error(`a JavaScript dialog opened: ${JSON.stringify(ctx.c.dialogs)}`); + return taken; + }, +}); + +// The licence badge, which every restart asks again: COMMUNITY EDITION, or the take fails. +async function communityBadge(c) { + const shows = () => c.evaluate(`compilerLicence.innerText === "COMMUNITY EDITION"`); + if (!(await waitFor(c, shows, { timeout: 30000, interval: 100 }))) { + throw new Error(`the licence badge says ${JSON.stringify(await c.evaluate("compilerLicence.innerText"))}`); + } +} + +// Ends the lane's compiler from outside, as a crash does; its pid. +async function killCompiler(c) { + const pid = await compilerPid(c); + if (!(pid > 0)) throw new Error("the page names no compiler"); + killTree(pid); + return pid; +} + +// A compiler other than `pid` has the project, and nothing covers the IDE. +async function compilerBack(c, pid) { + const replaced = async () => { + const now = await compilerPid(c); + return now && now !== pid; + }; + if (!(await waitFor(c, replaced, { timeout: 30000, interval: 50 }))) throw new Error("no new compiler started"); + await projectReady(c); +} + +// The floating boxes, as their X closes them. +const closeFloats = (c) => + c.evaluate(`for (const n of [3, 2, 1]) { + if (document.getElementById("msgBoxInner" + n).style.display !== "none") window["msgBox" + n + "Close"](); +}`); +const floatTexts = (c) => + c.evaluate(`[1, 2, 3].map((n) => document.getElementById("msgBoxInner" + n)) + .filter((e) => e.style.display !== "none" && e.getBoundingClientRect().height) + .map((e) => e.innerText.trim())`); + +// Out of Safe Mode, with the box's OK and Ctrl+F2, about eleven seconds: win32, the project +// compiled, an empty console, no floating box. What each take that goes there ends with. +async function leaveSafeMode(c) { + if (await modalHolding(c, SAFE_MODE_TEXT)) await closeModal(c, "OK"); + await closeFloats(c); + if ((await configValue(c)) === "nocompile") { + await pressKey(c, "F2", { ctrl: true }); + await configSettled(c, "win32"); + } + await projectReady(c); + await resetUi(c); + await communityBadge(c); + await clearConsole(c); +} + +// TRACE mode on or off by Help's command, which restarts the compiler. +async function setTrace(c, on) { + if ((await c.evaluate("g_TraceMode")) === on) return; + const pid = await compilerPid(c); + await command(c, "tbHelp_ToggleCompilerTraceMode"); + await compilerBack(c, pid); + if ((await c.evaluate("g_TraceMode")) !== on) throw new Error(`TRACE mode did not go ${on ? "on" : "off"}`); + await communityBadge(c); + await clearConsole(c); +} + +// The console's lines and what a native exception writes, as the page quotes them. +const NATIVE_TIME = "11:42:08.716"; +const NATIVE_RED_TIME = "11:42:08.800"; +const NATIVE_YELLOW_TIME = "11:42:08.814"; +const NATIVE_ADDRESS = "[$773D8C3B:ntdll.dll+00088C3B]"; +// wide enough for the red line, on one row with its time, and high enough for its three rows: a +// console grown row by row goes on growing until its list has drawn, a different height each time +const NATIVE_CONSOLE = { width: 1060, height: 124 }; +const NATIVE_LINE = + /^0001 \d\d:\d\d:\d\d\.\d{3} NATIVE EXCEPTION: ACCESS_VIOLATION \/MainMod\.twin; MainMod\.Crash LINE 000010 \[\$[0-9A-F]{8}:ntdll\.dll\+[0-9A-F]{8}\]$/m; +// from the console's list, which draws only the rows that show +const consoleLinesOf = (c, cls) => + c.evaluate( + `debugConsoleContent.dataNodes.filter((l) => l.includes(${JSON.stringify(cls)})).map((l) => l.replace(/<[^>]*>/g, ""))`, + ); + +// The time and the address of the line, which differ from run to run and from boot to boot, set to +// the page's once the real ones show, in the page only, and the console's own times of the three +// lines to as far after it as the IDE writes them. +const fixNativeLine = (c) => + c.evaluate(`(() => { + const outer = document.querySelector(".debugConsoleOuter"); + const walk = document.createTreeWalker(outer, NodeFilter.SHOW_TEXT); + let done = 0; + for (let n = walk.nextNode(); n; n = walk.nextNode()) { + if (!n.nodeValue.includes("NATIVE EXCEPTION")) continue; + n.nodeValue = n.nodeValue + .replace(/^(\\d{4} )\\d\\d:\\d\\d:\\d\\d\\.\\d{3}/, "$1" + ${JSON.stringify(NATIVE_TIME)}) + .replace(/\\[\\$[0-9A-F]{8}:ntdll\\.dll\\+[0-9A-F]{8}\\]/, () => ${JSON.stringify(NATIVE_ADDRESS)}); + done++; + } + for (const e of outer.querySelectorAll(".debugConsoleOutputTextALERT .ts")) e.textContent = ${JSON.stringify(NATIVE_RED_TIME)}; + for (const e of outer.querySelectorAll(".debugConsoleOutputTextYELLOW .ts")) e.textContent = ${JSON.stringify(NATIVE_YELLOW_TIME)}; + return done; +})()`); + +// Three messages the IDE gives, the middle one too long for a floating box's one line. +const FLOAT_MESSAGES = [ + "Invalid licence key. Please try again.", + "Backend compiler seems to be busy. Check there isn't a modal dialog box open behind the IDE window.", + "Compiler crash loop detected. Restarting in SAFE mode.", +]; + +const troubleshootingShots = [ + noAlert({ + // a restart held at the page's LSP connection: FS connected, the two after it not, the + // tooltip the badge's own title in a replica, as Services_Limited holds the call before it + out: TS_IMAGE("Services_Limited_Tooltip"), + setup: "troubleshooting", + take: ({ c }) => + whileConnectHeld( + c, + "lspSocket", + `compilerStatusOuter.classList.contains("servicesPARTIAL") && /^FS: OPERATIONAL$/m.test(compilerStatusOuter.title)`, + "the restarted compiler never asked for its LSP connection, with the badge LIMITED and FS OPERATIONAL", + async () => { + const title = await c.evaluate("compilerStatusOuter.title"); + say(c.shot.name, ` the badge's title: ${JSON.stringify(title)}`); + if (/^(LSP|DEBUGGER): OPERATIONAL$/m.test(title)) throw new Error(`the title is ${JSON.stringify(title)}`); + await communityBadge(c); + const { rect } = await tooltipOn(c, "#compilerStatusOuter"); + try { + // the licence badge whole, which the tooltip stands over part of; the line of the + // console above the tooltip left out + const bar = union(await rectOf(c, "#compilerStatusOuter"), await rectOf(c, "#compilerLicenceOuter")); + const all = grow(union(bar, rect), 8); + const top = rect.y - 3; + const clip = snapOut({ ...all, y: top, height: all.y + all.height - top }); + return await capture(c, "Services_Limited_Tooltip", clip, { away: () => parkMouse(c) }); + } finally { + await tooltipOff(c); + } + }, + ), + }), + noAlert({ + // a restart held at the page's FS connection, the first of the three, which holds the box; + // with enough of the editor and the panels round it to show that it covers them + out: TS_IMAGE("Please_Wait"), + setup: "troubleshooting", + take: ({ c }) => + whileConnectHeld( + c, + "fs", + `[...document.querySelectorAll(".modalDialogContainer")].some((m) => m.getBoundingClientRect().width && m.innerText.includes("Please wait"))`, + "the restart showed no Please wait box", + async () => { + await dialogStill(c); + const box = await modalHolding(c, "Please wait"); + say(c.shot.name, ` the box reads ${JSON.stringify(await c.evaluate(`${TOP_MODAL}.innerText`))}`); + const editor = await dockedRect(c, "EDITOR 1"); + if (!editor) throw new Error("the page has no EDITOR 1 panel"); + const clip = snapOut({ ...grow(box, 190), x: editor.x, width: editor.width }); + return capture(c, "Please_Wait", clip, { away: () => parkMouse(c) }); + }, + ), + }), + noAlert({ + // three kills after a restart by the button, whose own close is the count's first entry: the + // floating message and the Safe Mode box together; OK, and back to win32 + out: TS_IMAGE("Crash_Loop"), + setup: "troubleshooting", + async take({ c, run }) { + await resetUi(c); + await closeFloats(c); + await run.lane.restartCompiler(); + await projectReady(c); + await clearConsole(c); + try { + for (let n = 0; n < 2; n++) await compilerBack(c, await killCompiler(c)); + await killCompiler(c); + const shown = async () => + (await modalHolding(c, SAFE_MODE_TEXT)) && (await floatTexts(c)).some((t) => t.startsWith(CRASH_LOOP_TEXT)); + if (!(await waitFor(c, shown, { timeout: 60000, interval: 100 }))) { + throw new Error(`no crash loop: ${JSON.stringify(await floatTexts(c))}`); + } + await dialogStill(c, { ms: 500 }); + await communityBadge(c); + await fixConsoleTimes(c); + return await capture(c, "Crash_Loop", WINDOW(), { away: () => parkMouse(c) }); + } finally { + await leaveSafeMode(c); + } + }, + }), + noAlert({ + // the box set to safeMode, OK, and the badge OPERATIONAL: the left of the toolbar over the left + // of the status bar, since a whole window would shrink both on the page; back to win32 + out: TS_IMAGE("Safe_Mode_Bar"), + setup: "troubleshooting", + async take({ c }) { + await resetUi(c); + await clearConsole(c); + try { + await c.evaluate(`buildConfigSelector.value = "nocompile"; buildConfigSelector.onchange(); 0`); + if (!(await waitFor(c, () => modalHolding(c, SAFE_MODE_TEXT), { timeout: 90000, interval: 250 }))) { + throw new Error("safeMode showed no box"); + } + await dialogStill(c, { ms: 500 }); + await closeModal(c, "OK"); + await configSettled(c, "nocompile"); + if (!(await waitFor(c, () => servicesAre(c, "servicesOK"), { timeout: 30000, interval: 100 }))) { + throw new Error("the services badge does not say OPERATIONAL in Safe Mode"); + } + await communityBadge(c); + // the parts are not the picture: capture keeps the first two captures that agree + const { reference, prefer, ignore } = c.shot; + Object.assign(c.shot, { reference: null, prefer: null, ignore: null }); + const made = []; + try { + for (const [part, bar] of [ + ["toolbar", await toolbarRect(c)], + ["status", await rectOf(c, "#rootStatusBar")], + ]) { + const clip = snapOut({ x: 0, y: bar.y, width: SAFE_BAR_WIDTH, height: bar.height }); + const png = await capture(c, `Safe_Mode_Bar-${part}`, clip, { away: () => parkMouse(c) }); + made.push({ png, clip }); + } + } finally { + Object.assign(c.shot, { reference, prefer, ignore }); + } + try { + const { box } = await composite(c, made, [], { gap: 8, margin: 0 }); + return await capture(c, "Safe_Mode_Bar", snapOut(box), { away: () => parkMouse(c) }); + } finally { + await uncomposite(c); + } + } finally { + await leaveSafeMode(c); + } + }, + }), + noAlert({ + // Sub Crash run as a line of the console runs it, TRACE mode off: the red line and the two + // yellow ones, which only the first exception with it off writes; then TRACE mode off again + out: TS_IMAGE("Native_Exception"), + setup: "troubleshooting", + async take({ c }) { + await resetUi(c); + await setTrace(c, false); + await floatPanel(c, "DEBUG CONSOLE", NATIVE_CONSOLE); + try { + await clearConsole(c); + await c.evaluate(`debugSocket.request("evaluate", { expression: "Crash", context: "repl" }, () => {}); 0`); + const written = async () => (await consoleLinesOf(c, "debugConsoleOutputTextYELLOW")).length >= 2; + if (!(await waitFor(c, written, { timeout: 15000, interval: 100 }))) { + throw new Error(`the yellow lines did not show: ${JSON.stringify(await consoleText(c))}`); + } + await waitFor(c, async () => !(await c.evaluate("!!context.codeExecuting.value")), { timeout: 10000 }); + const [red, yellow] = [ + await consoleLinesOf(c, "debugConsoleOutputTextALERT"), + await consoleLinesOf(c, "debugConsoleOutputTextYELLOW"), + ]; + say(c.shot.name, ` the console's lines: ${JSON.stringify({ red, yellow })}`); + if (red.length !== 1 || !NATIVE_LINE.test(red[0].replace(/^\d\d:\d\d:\d\d\.\d{3}/, ""))) { + throw new Error(`the red line is ${JSON.stringify(red)}`); + } + if (yellow.length !== 2) throw new Error(`the yellow lines are ${JSON.stringify(yellow)}`); + if ((await readCrash(c))?.n !== 1) throw new Error("the console has another NATIVE EXCEPTION"); + // The list keeps each row's height from when it first measured it, in whatever width the + // console had then, and fitConsole would grow a console whose rows are too tall by an + // amount that differs from run to run. A resize makes the page call this: the rows + // measured in this width, one line each. The list's last row, of X's, is its measure. + await c.evaluate("debugConsoleOutput.contentDiv.recalcCachedItemHeights(); debugConsoleContent.repaint()"); + const rowsShow = () => + c.evaluate(`(() => { + const p = document.querySelector(${JSON.stringify(panelSel("DEBUG CONSOLE"))}); + const box = p.querySelector(".listItems").getBoundingClientRect(); + const rows = [...p.querySelectorAll(".listItems .listitem")].filter((e) => !e.textContent.includes("XXXXXXXX")).map((e) => e.getBoundingClientRect()).filter((r) => r.height); + return rows.length === 3 && rows.every((r) => r.height < 24 && r.top >= box.top && r.bottom <= box.bottom); +})()`); + if (!(await waitFor(c, rowsShow, { timeout: 5000, interval: 50 }))) { + throw new Error("the console did not draw its three rows, one line each"); + } + const fix = async () => { + if (!(await fixNativeLine(c))) throw new Error("the line was not found to set its time"); + }; + return await consolePicture(c, "Native_Exception", 3, fix, { times: false }); + } finally { + await clearConsole(c); + await unfloatPanel(c, "DEBUG CONSOLE"); + await defaultLayout(c); + await setTrace(c, false); + } + }, + }), + noAlert( + menuShot("Menu_Help_Trace", "Help", null, { + setup: "troubleshooting", + out: TS_IMAGE("Menu_Help_Trace"), + before: (c) => setTrace(c, true), + after: (c) => setTrace(c, false), + async check(c) { + const item = await c.evaluate(`(() => { + const e = [...document.querySelectorAll("#contextMenu .contextMenuItem")].find((x) => x.textContent.includes("TRACE mode")); + return e ? { text: e.innerText.trim(), tick: !!e.querySelector(".codicon-check") } : null; +})()`); + if (!item?.tick || item.text !== "Compiler services TRACE mode: Enabled") { + throw new Error(`the TRACE item is ${JSON.stringify(item)}`); + } + }, + }), + ), + noAlert({ + // three boxes the IDE gives, and a real click on the middle one, with the status bar under them + out: TS_IMAGE("Floating_Boxes"), + setup: "troubleshooting", + async take({ c }) { + await resetUi(c); + await closeFloats(c); + try { + await c.evaluate(`${FLOAT_MESSAGES.map((m) => `msgBox(${JSON.stringify(m)});`).join(" ")} 0`); + const boxes = await Promise.all([3, 2, 1].map((n) => rectOf(c, `#msgBoxInner${n}`))); + if (!(boxes[0].y < boxes[1].y && boxes[1].y < boxes[2].y)) + throw new Error("the boxes are not stacked in order"); + if (!(await c.evaluate("msgBox2text.innerText")).startsWith("Backend")) { + throw new Error("the middle box is not the long message"); + } + const text = await rectOf(c, "#msgBox2text"); + await clickAt(c, text.x + 20, text.y + text.height / 2); + const open = () => c.evaluate(`msgBox2Extra.style.display === "block"`); + if (!(await waitFor(c, open, { timeout: 5000, interval: 50 }))) throw new Error("the click opened no box"); + await communityBadge(c); + await parkMouse(c); + await frames(c); + const top = await rectOf(c, "#msgBoxInner3"); + const left = Math.min(top.x, (await rectOf(c, "#msgBoxInner2")).x); + const clip = snapOut({ + x: left - 12, + y: top.y - 12, + width: IDE_SIZE.width - left + 12, + height: IDE_SIZE.height - top.y + 12, + }); + return await capture(c, "Floating_Boxes", clip, { away: () => parkMouse(c) }); + } finally { + await closeFloats(c); + } + }, + }), + { + // the box of a copy of the install with its IDE DLL renamed, alone, once: there is no page. + // It names another file, which is there; OK ends the process. + out: TS_IMAGE("Missing_DLL_Box"), + setup: "troubleshooting-box", + once: true, + window: true, + async take({ run }) { + try { + return await boxPicture(run, "twinBASIC Compiler", (box) => { + const text = box.kids.map((k) => k.title ?? "").join("\n"); + if (!text.includes(MISSING_DLL_TEXT)) throw new Error(`the box says ${JSON.stringify(text)}`); + }); + } finally { + await waitFor(run.c, () => run.exited, { timeout: 10000, interval: 100 }); + if (!run.exited) killTree(run.process.pid); + } + }, + }, +]; + +const MISSING_DLL_TEXT = + "ERROR: failed to load the twinBASIC DLL:\r\n bin\\twinBASIC_win64.dll\r\n\r\nThe file could not be found. See https://twinbasic.com/missing \r\n\r\nThe application will now exit."; + +// The lane's own copy of the install with bin\twinBASIC_ide_win32.dll renamed, started as from its +// icon. There is no page to attach to: launchIde would wait a minute for one. +async function startMissingDll(run) { + run.step = "open"; + const exe = run.lane.copy(); + const dll = path.join(path.dirname(exe), "bin", "twinBASIC_ide_win32.dll"); + renameSync(dll, `${dll}.gone`); + say(run.name, "starting a copy of the install with bin\\twinBASIC_ide_win32.dll renamed"); + run.process = await launchOnDesktop({ + exe, + desktop: run.lane.desktop, + env: { ...process.env, APPDATA: run.lane.appdataDir(), TB_ADDIN_TEST: "1" }, + args: IDE_FLAGS, + }); + run.process.exited.then(() => { + run.exited = true; + }); + return { dialogs: [] }; +} + +async function prepareMissingDll(run) { + run.step = "shoot"; + return { c: run.c, run }; +} + +// The process, if it outlived the box, ended by its pid. +async function endMissingDll(run) { + if (run.process && !run.exited) killTree(run.process.pid); +} + // ---------------------------------------------------------------- the table of shots // Each shot is a picture: `out` its path under the output folder, `setup` the IDE @@ -13335,11 +14705,14 @@ const SHOTS = [ ...formsChapterShots, ...projectsShots, ...buildingShots, + ...startingShots, + ...troubleshootingShots, ...customControlsShots, ...customControlsCodeShots, ...codeShots, ...sample9Shots, featureMapShot, + tourShot, ...codeChapterShots, ...debuggingShots, newProjectOptionsShot, @@ -13421,12 +14794,19 @@ const JOB_SECONDS = { "building-hklm": 40, "building-llvm": 20, "building-pkg": 30, + "starting-first-run": 40, + starting: 60, + "starting-new": 60, + "starting-boxes": 40, + troubleshooting: 150, + "troubleshooting-box": 15, customcontrols: 54, "customcontrols-2": 53, "customcontrols-3": 35, code: 33, sample9: 11, featuremap: 39, + tour: 25, "code-chapter": 95, "code-chapter-2": 85, "code-chapter-3": 100, @@ -13550,7 +14930,7 @@ for (const name of existsSync(runs) ? readdirSync(runs) : []) { } const root = path.join(runs, String(process.pid)); mkdirSync(root, { recursive: true }); -const tidy = startTidy({ prefixes: [root] }); +const tidy = startTidy({ prefixes: [root, PACKAGE_ROOT] }); if (!tidy) die(2, "could not record the registry, so it could not be put back afterwards"); // The SaveSetting application names of the add-ins a setup loads. What they have saved is // the user's (an installed copy of the add-in reads the same key): a run starts from none, @@ -13811,6 +15191,7 @@ async function keepSvg(name, out, png, picture, kept, holds, window = false) { } const { svg, stats, misses, bundle } = picture; if (holdsUserName(svg, USER)) throw new Error(`the SVG of ${out} holds the Windows user name`); + refuseRoot(svg, out, "the SVG"); // The bundle names the PNG as the file holds it: a capture within the noise of the file // leaves the file as it was, and that file is what replay.mjs compares with. if (bundlesRoot) writeBundle(bundlesRoot, out, bundle, holds ?? png, misses); @@ -13925,6 +15306,12 @@ async function runJob(job) { show: false, browserArgs: BROWSER_ARGS, projectId: DOCS_PROJECT_ID, + // before the project loads, not at the first capture: the IDE and the shots measure what it + // draws, and the console's first lines are the load's + onAttach: async (c) => { + await waitFor(c, () => c.evaluate(`typeof TEXTtoHTML === "function"`), { timeout: 30000, interval: 20 }); + await showRootText(c); + }, }); const state = newShotState(job.name); running.add(run); @@ -13947,7 +15334,8 @@ async function runJob(job) { // taken in the dark pass only; a job of them has no light pass. // A run stopped (--events) takes no further picture, and switches no theme. const darks = new Map(); - const prepared = await setupState(run.c, ctx); + const darkTexts = new Map(); + const prepared = setup.noPage ? null : await setupState(run.c, ctx); for (const [theme, shots] of passes(job)) { if (!shots.length) continue; if (theme !== "dark" && !stopping) { @@ -13970,6 +15358,7 @@ async function runJob(job) { state.ignore = pngRule(entryOf(out)).rects; state.out = out; state.svg = null; + state.text = { whole: [], cut: [] }; try { // a take returns the PNG's bytes, or { png, scale } for a picture not at SCALE const taken = await shot.take(ctx); @@ -13980,7 +15369,19 @@ async function runJob(job) { `the shot ${window ? "took a program's window" : "is marked window: true"} and ${window ? "is not marked window: true" : "took no program's window"} in the table`, ); const png = Buffer.isBuffer(taken) ? taken : taken.png; - if (theme === "dark") darks.set(shot.out, png); + if (theme === "dark") { + darks.set(shot.out, png); + darkTexts.set(shot.out, state.text); + } else if (darkTexts.has(shot.out)) { + // a shot's two themes show one state: other text is a wrong state, as another size is + const other = themeTextDifference(darkTexts.get(shot.out), state.text, shot.themeWords); + if (other) { + writeDiff(job.name, out, dark, png, { suffix: ".text", labels: { before: "DARK", after: "LIGHT" } }); + throw new Error( + `the two themes say different things: only the dark picture has ${JSON.stringify(other.dark)}, only the light one ${JSON.stringify(other.light)}`, + ); + } + } const { kept, holds } = keep( job.name, shot.out, @@ -14020,6 +15421,15 @@ async function runJob(job) { failed = Math.max(failed, own ? 1 : 2); event("job", { job: job.name, state: "failed", error: e.message }); } + // what a setup reads from its IDE before that ends + if (setup.closing && run.c) { + try { + await setup.closing(run); + } catch (e) { + complain(job.name, e.message); + failed = Math.max(failed, 1); + } + } await endRun(run); // what a setup checks or gives back once its IDE has ended if (setup.end) { diff --git a/test/ide-folders.test.mjs b/test/ide-folders.test.mjs index a48c60653..59c85d279 100644 --- a/test/ide-folders.test.mjs +++ b/test/ide-folders.test.mjs @@ -6,7 +6,8 @@ // the IDE is already gone when shutdownIde looks at its folders, as it is after waitForExit. // // It also tests checkDefaults (scripts/lib/tb-defaults.mjs), which an IDE's start-up runs, on a -// fake page. +// fake page, and pageHook, which runs in that page: what it answers, what it drops, and that no +// error report leaves a tool's IDE. Those use a stand-in fetch, so that a failure here sends nothing. // // Runs with a bare `node --test test/ide-folders.test.mjs`: no tree, no IDE. @@ -17,7 +18,69 @@ import path from "node:path"; import { after, before, describe, test } from "node:test"; import { shutdownIde, shutdownIdeAsync } from "../scripts/lib/tb-ide.mjs"; import { removeTreeAsync } from "../scripts/lib/tb-ide-copy.mjs"; -import { checkDefaults } from "../scripts/lib/tb-defaults.mjs"; +import { BLANKED_KEYS, checkDefaults, pageHook } from "../scripts/lib/tb-defaults.mjs"; + +describe("the defaults hook in a page", () => { + const saved = { location: globalThis.location, window: globalThis.window, fetch: globalThis.fetch }; + let sent; + let stored; + + before(() => { + sent = []; + stored = []; + Object.defineProperty(globalThis, "location", { configurable: true, value: { pathname: "/ide/main.htm" } }); + globalThis.window = globalThis; + globalThis.fetch = async (url) => { + sent.push(String(url?.url ?? url)); + return new Response("sent"); + }; + pageHook(BLANKED_KEYS); + globalThis.hostAppObject = { + GetIDESetting: async (key) => `real ${key}`, + SaveIDESetting: (key) => stored.push(key), + }; + }); + + after(() => { + delete globalThis.hostAppObject; + delete globalThis.__tbDefaults; + Object.defineProperty(globalThis, "location", { configurable: true, value: saved.location }); + globalThis.window = saved.window; + globalThis.fetch = saved.fetch; + }); + + test("a tool's IDE answers no to error reporting, not the user's yes", async () => { + assert.match(await globalThis.hostAppObject.GetIDESetting("ALLOWERRORREPORTINGV3"), /^DISAGREED_/); + }); + + test("an error report is counted and never sent, in any spelling of the address", async () => { + for (const url of [ + "https://twinbasic.com/notify", + "https://www.twinbasic.com/notify", + "HTTPS://TWINBASIC.COM/NOTIFY", + new Request("https://twinbasic.com/notify", { method: "POST" }), + ]) { + assert.equal((await fetch(url, { method: "POST", body: "{}" })).status, 200); + } + assert.deepEqual(sent, []); + assert.equal(window.__tbDefaults.reports, 4); + }); + + test("control: any other request goes through, another page of the same site included", async () => { + await fetch("https://twinbasic.com/preorder.html"); + await fetch("https://example.com/notify"); + assert.deepEqual(sent, ["https://twinbasic.com/preorder.html", "https://example.com/notify"]); + assert.equal(window.__tbDefaults.reports, 4); + }); + + test("a key it answers is not saved, and any other is", () => { + const host = globalThis.hostAppObject; + host.SaveIDESetting("ALLOWERRORREPORTINGV3", "AGREED_01-JAN-2026"); + host.SaveIDESetting("NEWFRONTPAGE", "{}"); + host.SaveIDESetting("PUBLISHER", "x"); + assert.deepEqual(stored, ["PUBLISHER"]); + }); +}); describe("the folders an IDE leaves", () => { // Windows process ids are multiples of four, so this is never a process, and diff --git a/test/ide-js.test.mjs b/test/ide-js.test.mjs index 47b89f5fc..07d1f3cd6 100644 --- a/test/ide-js.test.mjs +++ b/test/ide-js.test.mjs @@ -18,13 +18,17 @@ import { after, before, describe, test } from "node:test"; import { REPO_ROOT } from "../lib/repo-paths.mjs"; import { bindings, + builtInKeymap, checkUnit, coverage, FixError, fixShapes, + keyLabel, + keymapTable, lineDiff, NameError, namedUnits, + pageKeymap, parse, patchUnits, renamedUnitText, @@ -292,6 +296,56 @@ describe("the fix shape", () => { }); }); +describe("the built-in keyboard shortcuts", () => { + const MAIN = + 'var other={};var builtInKeyboardShortcutGroups={"":{caption:"{%Default}",data:{' + + 'tbB_Two:["{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}ARROWLEFT","{KEYDOWN}{SHIFT}{ALT}F8"],' + + 'tbA_One:["F2","{KEYDOWN}{CTRL}{"],tbD_Text:["&","\\\\","*"],tbC_None:[],' + + 'tbE_All:["{ALLEXCEPT}ARROWUP|&"]}}};var ideCommands={};\n'; + + test("the group is read in the build's order, with no key too", () => { + const map = builtInKeymap(parse(MAIN)); + assert.deepEqual(Object.keys(map), ["tbB_Two", "tbA_One", "tbD_Text", "tbC_None", "tbE_All"]); + assert.deepEqual(map.tbA_One, ["F2", "{KEYDOWN}{CTRL}{"]); + assert.deepEqual(map.tbD_Text, ["&", "\\", "*"]); + assert.deepEqual(map.tbC_None, []); + assert.throws(() => builtInKeymap(parse("var other={};\n")), /no built-in keyboard shortcut group/); + }); + + test("a key string is written by name, its modifiers in the pages' order", () => { + assert.deepEqual(keyLabel("{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}ARROWLEFT"), { + keys: "Ctrl+Shift+Left", + release: false, + }); + assert.deepEqual(keyLabel("{KEYDOWN}{CTRL}{"), { keys: "Ctrl+{", release: false }); + assert.deepEqual(keyLabel("CONTROL"), { keys: "Ctrl", release: true }); + assert.deepEqual(keyLabel("{KEYDOWN}CANCEL").keys, "Break"); + assert.deepEqual(keyLabel("{KEYDOWN}\\").keys, "\"); + }); + + test("the table has one row for each command, by name, and says what is odd in words", () => { + assert.deepEqual(keymapTable(builtInKeymap(parse(MAIN))).split("\n"), [ + "| Command | Keys |", + "|---|---|", + "| `tbA_One` | Ctrl+{; F2, on release |", + "| `tbB_Two` | Ctrl+Shift+Left, Shift+Alt+F8 |", + "| `tbC_None` | none |", + "| `tbD_Text` | &, \, *, on release |", + "| `tbE_All` | every key its `{ALLEXCEPT}` list does not name, on release |", + ]); + }); + + test("a page keeps everything round its table, in its own line endings", () => { + for (const eol of ["\n", "\r\n"]) { + const page = ["# Keys", "", "| Command | Keys |", "|---|---|", "| `old` | x |", "", "## See Also", ""].join(eol); + const { current, text } = pageKeymap(page, "| Command | Keys |\n|---|---|\n| `new` | y |"); + assert.equal(current, "| Command | Keys |\n|---|---|\n| `old` | x |"); + assert.equal(text, page.replace("`old` | x", "`new` | y")); + } + assert.throws(() => pageKeymap("# no table\n", ""), /has no \| Command \| Keys \| table/); + }); +}); + describe("the command line", () => { const TOOL = path.join(REPO_ROOT, "scripts", "ide_js.mjs"); let root; diff --git a/test/ide/completion-row-zero.test.mjs b/test/ide/completion-row-zero.test.mjs new file mode 100644 index 000000000..c3760e3c2 --- /dev/null +++ b/test/ide/completion-row-zero.test.mjs @@ -0,0 +1,86 @@ +// The completion list asks the compiler for the type of each row it draws, by the row's lazy +// index, and its test of that index is false for 0: the row with index 0 is never asked for, and +// shows no type. Which row that is depends on the compile, so the list here is short enough that +// every row is drawn. +// +// The test not named `control` asserts bugs/completion-row-zero-no-type. If it fails after an IDE +// update, the IDE has changed: remove the NOTE under The completion list in +// docs/IDE/Code/IntelliSense.md and the entry in BUGS-TO-REPORT.md, the row's request in +// scripts/shoot_docs.mjs (`askForRowZero`), and then this file. +// +// Run it with ide-test.bat, which gives it a lane; on its own it is skipped. + +import assert from "node:assert/strict"; +import path from "node:path"; +import { before, test } from "node:test"; +import { fileURLToPath } from "node:url"; +import { sleep } from "../../scripts/lib/tb-ide.mjs"; +import { openFile, pressKey, setCursor, typeText, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { scenario } from "../addin/scenario.mjs"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const PROJECT = path.join(HERE, "probes", "completion-lazy"); +const FILE = "/CompletionLazyProbe/Sources/Startup.twin"; +const LINE = 14; +const LIST = "debugConsoleEntryIntellisenseList"; +const NAMES = ["Alpha", "Bravo", "Charlie", "Delta", "Echo", "Foxtrot"]; + +// Each row: its name, its lazy index, whether it was asked for or answered, its type. +const ROWS_JS = `(${LIST}.dataNodesDATA || []).map((o) => ({ name: o.l, i: o.i, asked: !!o.requestedData, answered: !!o.gotData, type: o.type }))`; +// The text drawn on the row of this name. +const drawn = (name) => `[...document.querySelectorAll(".debugConsoleEntryIntellisenseBox .listitem")] + .find((r) => r.innerText.trim().startsWith(${JSON.stringify(name)}))?.innerText.replace(/\\s+/g, " ").trim()`; + +scenario("the completion list's row with lazy index 0", (lane) => { + let c; + let rows; + const zero = () => rows.find((r) => r.i === 0); + + before(async () => { + c = await lane.open(PROJECT); + await openFile(c, FILE, { line: LINE, column: 1 }); + await setCursor(c, LINE, 1); + await typeText(c, " Things"); + await sleep(1500); + await pressKey(c, "Escape"); + await sleep(400); + await typeText(c, "."); + const done = await waitFor( + c, + async () => { + rows = await c.evaluate(ROWS_JS); + const others = rows.filter((r) => NAMES.includes(r.name) && r.i !== 0); + return others.length === NAMES.length - 1 && others.every((r) => r.answered); + }, + { timeout: 30 * 1000 }, + ); + assert.ok(done, `the rows were not answered: ${JSON.stringify(rows)}`); + await sleep(1500); + rows = await c.evaluate(ROWS_JS); + }); + + test("control: every other row of the list shows its type", async () => { + for (const r of rows.filter((x) => NAMES.includes(x.name) && x.i !== 0)) { + assert.equal(r.type, "Long", r.name); + assert.match(await c.evaluate(drawn(r.name)), /variable ?Long$/); + } + }); + + test("the row with lazy index 0 is never asked for, and shows no type", async () => { + assert.ok(zero(), `no row has index 0: ${JSON.stringify(rows)}`); + assert.equal(zero().asked, false); + assert.equal(zero().answered, false); + assert.match(await c.evaluate(drawn(zero().name)), /variable$/); + }); + + test("control: asked for that row, the compiler gives its type", async () => { + const x = rows.indexOf(zero()); + const reply = await c.evaluate( + `new Promise((done) => lspSocket.request("textDocument/lazyCompletion", { lazyRequestId: ${LIST}.lazyRequestId, requests: [{ x: ${x}, i: 0 }] }, (e) => done(e.results)))`, + { awaitPromise: true }, + ); + assert.equal(reply.length, 1); + assert.equal(reply[0].x, x); + assert.equal(reply[0].type, "Long"); + }); +}); diff --git a/test/ide/lanes.mjs b/test/ide/lanes.mjs index 35ee0ad65..e8703b311 100644 --- a/test/ide/lanes.mjs +++ b/test/ide/lanes.mjs @@ -49,4 +49,9 @@ export default [ { file: "zoom.test.mjs" }, { file: "object-browser.test.mjs" }, { file: "language.test.mjs" }, + { file: "starting.test.mjs" }, + { file: "troubleshooting.test.mjs" }, + { file: "troubleshooting-missing.test.mjs" }, + { file: "samples.test.mjs" }, + { file: "completion-row-zero.test.mjs" }, ]; diff --git a/test/ide/probes/completion-lazy/Settings b/test/ide/probes/completion-lazy/Settings new file mode 100644 index 000000000..2e501126e --- /dev/null +++ b/test/ide/probes/completion-lazy/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "CompletionLazyProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "test/ide/completion-row-zero.test.mjs opens it and types the name of a module and a dot.", + "project.exportPathIsV2": true, + "project.id": "{7B247500-0000-4000-9000-7B2475000035}", + "project.name": "CompletionLazyProbe", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/ide/probes/completion-lazy/Sources/Startup.twin b/test/ide/probes/completion-lazy/Sources/Startup.twin new file mode 100644 index 000000000..1204ba3e4 --- /dev/null +++ b/test/ide/probes/completion-lazy/Sources/Startup.twin @@ -0,0 +1,16 @@ +' The project test/ide/completion-row-zero.test.mjs opens. Each variable of Things +' has a row in the completion list, which asks the compiler for the row's type. +Module Things + Public Alpha As Long + Public Bravo As Long + Public Charlie As Long + Public Delta As Long + Public Echo As Long + Public Foxtrot As Long +End Module + +Module Startup + Public Sub Main() + + End Sub +End Module diff --git a/test/ide/probes/starting/Resources/RCDATA/#201 b/test/ide/probes/starting/Resources/RCDATA/#201 new file mode 100644 index 000000000..b16ba5a50 --- /dev/null +++ b/test/ide/probes/starting/Resources/RCDATA/#201 @@ -0,0 +1 @@ +A resource of the probe, RT_RCDATA with the id 201. A project cut in the middle of this file has every source and has lost this resource. The text only has to be long enough to cut in the middle of it, so it goes on for a few more words than it has anything to say, and then it stops here. diff --git a/test/ide/probes/starting/Settings b/test/ide/probes/starting/Settings new file mode 100644 index 000000000..8e8558858 --- /dev/null +++ b/test/ide/probes/starting/Settings @@ -0,0 +1,29 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "StartProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "StartProbe", + "project.exportPathIsV2": true, + "project.id": "{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA00}", + "project.name": "StartProbe", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "ignored": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/ide/probes/starting/Sources/MainMod.twin b/test/ide/probes/starting/Sources/MainMod.twin new file mode 100644 index 000000000..a4b88f642 --- /dev/null +++ b/test/ide/probes/starting/Sources/MainMod.twin @@ -0,0 +1,15 @@ +Module MainMod + + Public Sub Main() + Debug.Print Second.Twice(21) + End Sub + + Public Function Name_Of_Main() As String + Return "the first of the four sources of the probe" + End Function + + Public Function Square(ByVal x As Long) As Long + Return x * x + End Function + +End Module diff --git a/test/ide/probes/starting/Sources/Second.twin b/test/ide/probes/starting/Sources/Second.twin new file mode 100644 index 000000000..0d2281f09 --- /dev/null +++ b/test/ide/probes/starting/Sources/Second.twin @@ -0,0 +1,15 @@ +Module Second + + Public Function Twice(ByVal x As Long) As Long + Return x * 2 + End Function + + Public Function Name_Of_Second() As String + Return "the second of the four sources of the probe" + End Function + + Public Function Thrice(ByVal x As Long) As Long + Return x * 3 + End Function + +End Module diff --git a/test/ide/probes/starting/Sources/Thing.twin b/test/ide/probes/starting/Sources/Thing.twin new file mode 100644 index 000000000..26c64bcba --- /dev/null +++ b/test/ide/probes/starting/Sources/Thing.twin @@ -0,0 +1,13 @@ +Class Thing + + Public Value As Long + + Public Function Name_Of_Thing() As String + Return "the fourth of the four sources of the probe" + End Function + + Public Sub Add(ByVal x As Long) + Value = Value + x + End Sub + +End Class diff --git a/test/ide/probes/starting/Sources/Third.twin b/test/ide/probes/starting/Sources/Third.twin new file mode 100644 index 000000000..3ab50076d --- /dev/null +++ b/test/ide/probes/starting/Sources/Third.twin @@ -0,0 +1,15 @@ +Module Third + + Public Function Half(ByVal x As Long) As Long + Return x \ 2 + End Function + + Public Function Name_Of_Third() As String + Return "the third of the four sources of the probe" + End Function + + Public Function Third_Of(ByVal x As Long) As Long + Return x \ 3 + End Function + +End Module diff --git a/test/ide/probes/startup-object/Settings b/test/ide/probes/startup-object/Settings new file mode 100644 index 000000000..f45572953 --- /dev/null +++ b/test/ide/probes/startup-object/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "StartupObjectProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "test/ide/samples.test.mjs opens copies of it, each with its build type and startup object set, and presses Start.", + "project.exportPathIsV2": true, + "project.id": "{7B247500-0000-4000-9000-7B2475000034}", + "project.name": "StartupObjectProbe", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "symbolId": "stdole", + "versionMajor": 2, + "versionMinor": 0 + }, + { + "id": "{F50B82D0-DCAB-43FE-9631-11959D4A4728}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - VB Compatibility Package (Forms)", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "VB", + "versionBuild": 0, + "versionMajor": 0, + "versionMinor": 0, + "versionRevision": 31 + }, + { + "id": "{C192FB39-64CA-4D9B-B477-A5502F48EFCC}", + "isCompilerPackage": true, + "licence": "MIT", + "name": "[COMPILER PACKAGE] twinBASIC - App global class object", + "path32": "", + "path64": "", + "publisher": "TWINBASIC-COMPILER", + "symbolId": "AppGlobalClassProject", + "versionBuild": 0, + "versionMajor": 1, + "versionMinor": 0, + "versionRevision": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/ide/probes/startup-object/Sources/Startup.twin b/test/ide/probes/startup-object/Sources/Startup.twin new file mode 100644 index 000000000..b8e0f4dcd --- /dev/null +++ b/test/ide/probes/startup-object/Sources/Startup.twin @@ -0,0 +1,7 @@ +' The project test/ide/samples.test.mjs opens with its startup object set in +' several ways. It has no procedure called Main. +Module Startup + Public Sub Greet(ByVal name As String) + Debug.Print name + End Sub +End Module diff --git a/test/ide/probes/troubleshooting/Settings b/test/ide/probes/troubleshooting/Settings new file mode 100644 index 000000000..e4e4297e2 --- /dev/null +++ b/test/ide/probes/troubleshooting/Settings @@ -0,0 +1,30 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "TroubleProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "TroubleProbe", + "project.exportPathIsV2": true, + "project.id": "{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA01}", + "project.largeAddressAware": true, + "project.name": "TroubleProbe", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "ignored": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/ide/probes/troubleshooting/Sources/MainMod.twin b/test/ide/probes/troubleshooting/Sources/MainMod.twin new file mode 100644 index 000000000..75e5f1c59 --- /dev/null +++ b/test/ide/probes/troubleshooting/Sources/MainMod.twin @@ -0,0 +1,12 @@ +Module MainMod + Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal d As LongPtr, ByVal s As LongPtr, ByVal n As LongPtr) + + Public Sub Main() + Debug.Print "ran to the end" + End Sub + + ' An access violation in the compiler's process. RtlMoveMemory does nothing when the two addresses are the same. + Public Sub Crash() + CopyMemory 16, 32, 4 + End Sub +End Module diff --git a/test/ide/samples.test.mjs b/test/ide/samples.test.mjs new file mode 100644 index 000000000..91002e1f1 --- /dev/null +++ b/test/ide/samples.test.mjs @@ -0,0 +1,148 @@ +// What Start does in a project with no Main, which is where the Samples page sends a reader +// (docs/IDE/Starting/Samples.md, Before you run a sample), and a defect in a sample's own code. +// +// Each test states what BETA 1034 does, and a test not named `control` asserts a defect: +// "Start with no Main writes a compile error that gives no reason" +// bugs/start-no-main-error-text +// "a DLL whose Settings names no startup object shows (none) in Project Settings, and Start runs Main" +// bugs/dll-startup-default-runs-main +// "Sample 22's Uninstall button of service 1 registers the event log of service 2" +// bugs/sample22-event-log-wrong-button +// If one fails after an IDE update, the IDE has changed: update the NOTEs under What Start runs +// in docs/IDE/Debugging/Running.md, the entry in BUGS-TO-REPORT.md, and then this file. +// +// Run it with ide-test.bat, which gives it a lane; on its own it is skipped. + +import assert from "node:assert/strict"; +import { cpSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; +import { sleep, waitForCompile } from "../../scripts/lib/tb-ide.mjs"; +import { consoleMark, linesSince } from "../../scripts/lib/tb-ide-console.mjs"; +import { click, openFile, pressKey, setCursor, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { unpackProject } from "../../scripts/lib/tb-project.mjs"; +import { scenario } from "../addin/scenario.mjs"; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const PROJECT = path.join(HERE, "probes", "startup-object"); +const FILE = "/StartupObjectProbe/Sources/Startup.twin"; +const CLEARED = "[DEBUGGER] global variables have been cleared"; +const NO_OBJECT = "[DEBUGGER] No startup object set"; +const STARTUP_SELECT = `document.querySelector('.tbps-optionRow[data-option-id="project.startupObject"] select')`; + +scenario("what Start does with no Main", (lane) => { + let c = null; + + // A copy of the probe with these settings: an undefined one is left out of the file. + function variant(name, settings) { + const dir = path.join(lane.work, "variants", name); + cpSync(PROJECT, dir, { recursive: true }); + const file = path.join(dir, "Settings"); + const s = JSON.parse(readFileSync(file, "utf8")); + for (const [k, v] of Object.entries(settings)) { + if (v === undefined) delete s[k]; + else s[k] = v; + } + writeFileSync(file, JSON.stringify(s, null, "\t")); + return dir; + } + async function open(name, settings) { + if (c) await lane.closeProject(); + c = await lane.open(variant(name, settings)); + } + // Presses Start, and returns what the console wrote until the run was over. + async function start() { + const mark = await consoleMark(c); + await c.evaluate(`executeIdeCommand("tbDebug_StartOrContinue")`); + const over = await waitFor( + c, + async () => { + const lines = await linesSince(c, mark); + return lines.includes(CLEARED) || lines.includes(NO_OBJECT); + }, + { timeout: 60 * 1000 }, + ); + assert.ok(over, "Start wrote nothing that ends a run"); + return linesSince(c, mark); + } + + test("control: a DLL whose Settings says (none) writes that it has no startup object", async () => { + await open("dll-none", { "project.buildType": "ActiveX DLL", "project.startupObject": "(none)" }); + assert.deepEqual(await start(), [NO_OBJECT]); + }); + + test("Start with no Main writes a compile error that gives no reason", async () => { + await open("exe-submain", { "project.buildType": "Standard EXE", "project.startupObject": "Sub Main" }); + assert.deepEqual(await start(), ["Executing 'Main'...", "(compile error: Main)", CLEARED]); + }); + + test("control: F6 on a procedure it cannot call says why", async () => { + await openFile(c, FILE, { line: 5, column: 9 }); + await setCursor(c, 5, 9); + await sleep(400); + const mark = await consoleMark(c); + await pressKey(c, "F6"); + const said = await waitFor(c, async () => (await linesSince(c, mark)).length >= 2, { timeout: 20 * 1000 }); + assert.ok(said, "F6 wrote nothing"); + assert.equal((await linesSince(c, mark))[1], "(compile error: Too few arguments, expected 1 in call to 'Greet')"); + }); + + test("a DLL whose Settings names no startup object shows (none) in Project Settings, and Start runs Main", async () => { + await open("dll-unset", { "project.buildType": "ActiveX DLL", "project.startupObject": undefined }); + await c.evaluate(`executeIdeCommand("tbProject_ShowSettings")`); + assert.ok( + await waitFor(c, () => c.evaluate(`!!${STARTUP_SELECT}`), { timeout: 20 * 1000 }), + "no Startup Object row", + ); + assert.equal(await c.evaluate(`${STARTUP_SELECT}.value`), "Default: (none)"); + await click(c, { css: ".modalTitleBarCloseBtn" }); + assert.ok(await waitFor(c, () => c.evaluate(`!${STARTUP_SELECT}`)), "Project Settings is still open"); + assert.deepEqual(await start(), ["Executing 'Main'...", "(compile error: Main)", CLEARED]); + }); + + test("control: (none) chosen in Project Settings has Start say that there is no startup object", async () => { + await c.evaluate(`executeIdeCommand("tbProject_ShowSettings")`); + assert.ok( + await waitFor(c, () => c.evaluate(`!!${STARTUP_SELECT}`), { timeout: 20 * 1000 }), + "no Startup Object row", + ); + await c.evaluate(`(() => { + const s = ${STARTUP_SELECT}; + s.selectedIndex = [...s.options].findIndex((o) => o.value === "(none)"); + s.dispatchEvent(new Event("input", { bubbles: true })); + })()`); + await click(c, { css: "#SAVEBTN" }); + assert.ok( + await waitFor(c, () => c.evaluate(`!${STARTUP_SELECT}`), { timeout: 20 * 1000 }), + "Project Settings is still open", + ); + await sleep(2000); + await waitForCompile(c, { project: lane.project }); + assert.deepEqual(await start(), [NO_OBJECT]); + }); + + test("Sample 22's Uninstall button of service 1 registers the event log of service 2", (t) => { + const projects = path.join(path.dirname(path.resolve(lane.ide)), "projects"); + let folders = []; + try { + folders = readdirSync(projects).filter((f) => /^Sample 22\./.test(f)); + } catch { + // A patched copy of an install has no projects folder. + } + if (!folders.length) return t.skip("this install has no Sample 22"); + const tree = path.join(lane.work, "sample22"); + mkdirSync(tree, { recursive: true }); + unpackProject(path.join(projects, folders[0], "projectName.twinproj"), tree); + const text = readFileSync(path.join(tree, "Sources", "FORMS", "MainForm.twin"), "utf8").replace(/\r\n/g, "\n"); + const body = (name) => { + const at = text.indexOf(`Private Sub ${name}()`); + assert.notEqual(at, -1, `no ${name}`); + return text.slice(at, text.indexOf("End Sub", at)); + }; + assert.match(body("btnInstall001_Click"), /RegisterEventLog\("Application\\TBSERVICE001"\)/); + assert.match(body("btnUninstall001_Click"), /RegisterEventLog\("Application\\TBSERVICE002"\)/); + assert.doesNotMatch(body("btnInstall002_Click"), /RegisterEventLog/); + rmSync(tree, { recursive: true, force: true }); + }); +}); diff --git a/test/ide/starting-kit.mjs b/test/ide/starting-kit.mjs new file mode 100644 index 000000000..734b3c2b5 --- /dev/null +++ b/test/ide/starting-kit.mjs @@ -0,0 +1,710 @@ +// What starting.test.mjs needs from the page and the disk. Not a lane itself (lanes.mjs does not +// name it). +// +// THE GUARD (pageGuard) sits in front of the defaults hook (tb-defaults.mjs), on the page's +// window.hostAppObject, put in by defineProperty, never an assignment: an assignment would go to the +// hook's setter, and the hook would stand in front of the guard. It goes in at the document's start, +// and the page is reloaded (fresh), so the page starts for real over it, and every test can start +// from a page of its own with a project-less IDE. Nothing the IDE does there reaches the registry +// through the page: +// - GetRecentsList and GetVB6RecentProjects are answered from the page, so a row of the dialog never +// holds one of the user's paths, and no test reads the user's lists; +// - AddRecentsList, RemoveRecentsList, ChangeWindowSetting, Exit and CloseWindow do nothing; +// - SaveIDESetting does nothing and is recorded, by key; a value is kept only for the keys a test +// reads (KEPT), and of the licence key, which the page never saves here, nothing at all; +// - GetIDESetting is answered from the page for the keys a test gives (settings); +// - every native picker is answered from the page ("" is Cancel), so that none opens; +// - Shell is recorded and run only for the IDE's own taskkill of its compiler; root.Shell, made anew +// at every compiler connection, is stubbed the same way; +// - frontPageOpenProject, showNewProjectOptions and the rest are recorded with the time, so a test +// can say what ran; with blockOpen the open is recorded and goes no further. +// +// Every file a test opens, damages, saves or builds is in the lane's work folder. project() packs one +// from probes/starting; damaged() writes a cut copy of a project the lane's IDE saved. + +import assert from "node:assert/strict"; +import { cpSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { parseProject } from "../../scripts/impexp.mjs"; +import { desktopWindows, postWindowMessage, sleep } from "../../scripts/lib/tb-ide.mjs"; +import { clickAt, pressKey, typeText, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { packTree } from "../../scripts/lib/tb-project.mjs"; +import { diskText } from "./projects-kit.mjs"; + +export const J = JSON.stringify; +export { diskText, sleep }; +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const PROBE = path.join(HERE, "probes", "starting"); + +/** A project's Settings as they are in its file, parsed. */ +export const fileSettings = (file) => JSON.parse(diskText(file, "Settings")); + +/** The names of a project file's source files, its own folders first, as impexp reads them. */ +export function fileSources(file) { + const out = []; + const walk = (e, inSources) => { + if (e.kind === "file") { + if (inSources && /\.(twin|bas|tbform)$/.test(e.name)) out.push(e.name); + return; + } + for (const x of e.children) walk(x, inSources || e.name === "Sources"); + }; + walk(parseProject(readFileSync(file)), false); + return out; +} + +/** Each file under a project file's Resources, as `type/name` and its size; an empty name reads . */ +export function fileResources(file) { + const out = []; + const walk = (e, p) => { + if (e.kind === "file") out.push([p, e.content.length]); + else for (const x of e.children) walk(x, `${p}/${x.name === "" ? "" : x.name}`); + }; + const find = (e) => (e.name === "Resources" ? e : e.children ? e.children.map(find).find(Boolean) : null); + const res = find(parseProject(readFileSync(file))); + for (const x of res?.children ?? []) walk(x, x.name); + return out; +} + +/** A project file's entries in order, with where each is: a file's content and where it ends. */ +export function layout(buf) { + const v = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); + let p = 4; + const out = []; + const u32 = () => { + p += 4; + return v.getUint32(p - 4, true); + }; + let first = true; + const entry = (parent) => { + const start = p; + const kind = v.getInt16(p, true); + p += 2; + const n = u32(); + const name = buf.subarray(p, p + n).toString("utf8"); + p += n + 8 + 4 + 1; + const isRoot = first; + first = false; + const full = parent ? `${parent}/${name}` : name; + if (kind === 1 && !isRoot) { + const len = u32(); + const contentAt = p; + p += len; + const tail = u32(); + p += tail * 4; + out.push({ full, file: true, start, contentAt, len, end: p }); + } else { + const count = u32(); + out.push({ full, file: false, start }); + for (let i = 0; i < count; i++) entry(isRoot ? "" : full); + } + }; + entry(""); + return out; +} + +/** A .res file: an empty first entry, then one for each [type, name, bytes]. */ +export function writeRes(entries) { + const parts = []; + const id = (x) => { + if (typeof x === "number") { + const b = Buffer.alloc(4); + b.writeUInt16LE(0xffff, 0); + b.writeUInt16LE(x, 2); + return b; + } + const s = Buffer.from(`${x}\0`, "utf16le"); + return s.length % 4 ? Buffer.concat([s, Buffer.alloc(2)]) : s; + }; + const entry = (type, name, data, lang = 0x0409) => { + const t = id(type); + const n = id(name); + const head = Buffer.alloc(8); + head.writeUInt32LE(data.length, 0); + head.writeUInt32LE(8 + t.length + n.length + 16, 4); + const tail = Buffer.alloc(16); + tail.writeUInt16LE(0x1030, 4); + tail.writeUInt16LE(lang, 6); + parts.push(head, t, n, tail, data); + if (data.length % 4) parts.push(Buffer.alloc(4 - (data.length % 4))); + }; + entry(0, 0, Buffer.alloc(0), 0); + for (const e of entries) entry(...e); + return Buffer.concat(parts); +} + +// Runs in the page, in every document of the IDE's browser (see the top of this file). +function pageGuard(cfg) { + if (!/main\.htm$/i.test(location.pathname)) return; + if (window.__g) return; + const G = (window.__g = { + gen: cfg.gen, + settings: cfg.settings || {}, + recents: cfg.recents ?? "", + vb6: cfg.vb6 ?? "", + answers: cfg.answers || {}, + blockOpen: !!cfg.blockOpen, + saves: [], + ev: [], + errors: [], + shell: [], + target: null, + }); + const rec = (k, v) => G.ev.push([k, v]); + const KEPT = /^(NEWFRONTPAGE|OBJECT_BROWSER|LICENCEAGREEMENTV3|ALLOWERRORREPORTINGV3)$/; + const DIALOGS = [ + "ChooseFolder", + "OpenFiles", + "OpenIconCursor", + "OpenImage", + "OpenPackage", + "OpenProject", + "OpenProjectVBP", + "OpenTypeLibrary", + "SaveProjectAs", + ]; + const wrap = (host) => { + G.target = true; + return new Proxy(host, { + get(target, key) { + if (key === "GetIDESetting") { + return (...a) => + Object.hasOwn(G.settings, String(a[0])) ? Promise.resolve(G.settings[a[0]]) : target.GetIDESetting(...a); + } + if (key === "SaveIDESetting") { + return (...a) => { + const k = String(a[0]); + G.saves.push([k, KEPT.test(k) ? String(a[1]).slice(0, 200) : "(value not kept)"]); + return Promise.resolve(undefined); + }; + } + if (key === "GetRecentsList") return () => Promise.resolve(G.recents); + if (key === "GetVB6RecentProjects") return () => Promise.resolve(G.vb6); + if (key === "ChangeWindowSetting") return () => Promise.resolve(undefined); + if (key === "AddRecentsList" || key === "RemoveRecentsList") { + return () => { + rec("host", key); + return Promise.resolve(undefined); + }; + } + if (key === "Exit" || key === "CloseWindow") { + return () => { + rec("host", key); + return Promise.resolve(undefined); + }; + } + if (key === "Shell") { + return (cmdline, ...rest) => { + G.shell.push(String(cmdline).slice(0, 200)); + if (/^taskkill \/PID \d+ \/F$/.test(String(cmdline))) return target.Shell(cmdline, ...rest); + return Promise.resolve(0); + }; + } + if (DIALOGS.includes(key)) { + return () => { + let a = G.answers[key]; + if (Array.isArray(a)) a = a.length ? a.shift() : ""; + rec("dialog", key); + return Promise.resolve(a ?? ""); + }; + } + return target[key]; + }, + }); + }; + const d = Object.getOwnPropertyDescriptor(window, "hostAppObject"); + let real = d?.get ? d.get() : undefined; + if (real) real = wrap(real); + Object.defineProperty(window, "hostAppObject", { + configurable: true, + enumerable: true, + get: () => real, + set: (v) => { + if (d?.set) { + d.set(v); + v = d.get(); + } + real = v ? wrap(v) : v; + }, + }); + window.addEventListener("error", (e) => G.errors.push(String(e.message))); + const hooks = () => { + if (G.hooked) return; + G.hooked = true; + for (const name of ["closeProjectNow", "closeProjectSafely", "showNewProjectOptions"]) { + const f = window[name]; + if (typeof f === "function") { + window[name] = function (...r) { + rec("call", name); + return f.apply(this, r); + }; + } + } + const realOpen = window.frontPageOpenProject; + window.frontPageOpenProject = function (e) { + rec("open", e.projectPath); + if (!G.blockOpen) return realOpen.call(this, e); + }; + const realMsg = window.msgBox; + if (realMsg) { + window.msgBox = function (t, ...r) { + rec( + "msgBox", + String(t) + .replace(/<[^>]+>/g, " ") + .slice(0, 200), + ); + return realMsg.apply(this, [t, ...r]); + }; + } + }; + if (typeof window.showModalDialog === "function" && window.showFrontPageDialog) hooks(); + else document.addEventListener("DOMContentLoaded", hooks); +} + +// The IDE makes root anew at every compiler connection: each new one is stubbed at once. +const ROOT_STUB_JS = `if (window.__g) setInterval(() => { + try { + if (typeof root !== "undefined" && root && root.Shell && !root.Shell.__stub) { + const realShell = root.Shell.bind(root); + root.Shell = function (cmdline, muted) { + window.__g.shell.push("root.Shell: " + String(cmdline).slice(0, 200)); + if (/^taskkill \\/PID \\d+ \\/F$/.test(String(cmdline))) return realShell(cmdline, muted); + }; + root.Shell.__stub = true; + } + } catch {} +}, 20);`; + +// A project's name as a person reads it: the last part of a path, never the path. +const LEAF = (e) => `(${e} ? ${e}.split("\\\\").pop() : null)`; + +// The state of the open project, as the page holds it. +const STATE_JS = `(() => { try { return { + title: rootMenuText ? rootMenuText.innerText.split(" - ")[0].trim() : null, + tip: rootMenuText ? rootMenuText.title.split("\\\\").pop() : null, + dirty: projectIsDirty, + file: ${LEAF("projectFilePath")}, + loaded: context.projectLoaded.value, + root: Object.keys(fs.tree.rootFolder.entries || {}), + services: (document.getElementById("compilerStatusOuter")?.innerText || "").replace("tB Services: ", ""), +}; } catch (e) { return { error: e.message }; } })()`; + +// True when a project is fully open and the IDE is idle. A recovered project may have no Sources. +const READY_JS = `(() => { try { + return Object.keys(fs.tree.rootFolder.entries).length > 0 && context.projectLoaded.value + && licenceIsSet === true && !(lockUICount > 0) + && /OPERATIONAL/.test(document.getElementById("compilerStatusOuter")?.innerText || ""); +} catch { return false; } })()`; + +// The New / Open Project dialog as a person sees it, or null when it is not showing. +const DIALOG_JS = `(() => { + const r = document.getElementById("fp2root"); + if (!r || !r.getBoundingClientRect().width) return null; + const a = document.activeElement; + const name = (e) => (e.querySelector(".fp2-cardName, .fp2-rowName")?.innerText || "").trim(); + const items = [...r.querySelectorAll(".fp2-card, .fp2-row")]; + const sel = items.find((e) => e.classList.contains("fp2-selected")); + return { + rail: [...r.querySelectorAll(".fp2-railItem")].filter((e) => e.classList.contains("fp2-railItemSelected")).map((e) => e.querySelector(".fp2-railItemText").innerText)[0], + search: r.querySelector(".fp2-search").value, + selected: sel ? name(sel) : null, + focus: a === document.body ? "body" : a.classList.contains("fp2-search") ? "search" : a.classList.contains("fp2-card") ? "card:" + name(a) : a.classList.contains("modalDialogContainer") ? "dialog" : a.tagName + "." + a.className, + hilite: [...document.querySelectorAll(".buttonFocused")].map((e) => e.textContent.trim()), + }; +})()`; + +// What the search shows: each row's lines, with the marked words in << >>. +const SEARCH_JS = `[...document.querySelectorAll("#fp2root .fp2-group .fp2-row")].map((r) => + [...r.querySelector(".fp2-rowMain").children].map((l) => [...l.childNodes].map((n) => n.nodeType === 3 ? n.textContent : "<<" + n.textContent + ">>").join("")))`; + +const ITEM_AT_JS = (re) => `(() => { + const e = [...document.querySelectorAll("#fp2root .fp2-card, #fp2root .fp2-row")].find((x) => ${re}.test(x.innerText.replace(/\\s*\\n\\s*/g, " ")) && x.getBoundingClientRect().width); + if (!e) return null; + e.scrollIntoView({ block: "nearest" }); + const r = e.getBoundingClientRect(); + return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; +})()`; + +// The smallest element of the top modal box whose text is `text`. +const IN_MODAL_JS = (text, selector) => `(() => { + const ms = [...document.querySelectorAll(".modalDialogContainer")].filter((m) => m.getBoundingClientRect().width); + const m = ms[ms.length - 1]; + if (!m) return null; + const area = (e) => e.getBoundingClientRect().width * e.getBoundingClientRect().height; + const e = [...m.querySelectorAll(${J(selector)})].filter((e) => e.textContent.trim() === ${J(text)} && e.getBoundingClientRect().width).sort((a, b) => area(a) - area(b))[0]; + if (!e) return null; + const r = e.getBoundingClientRect(); + return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; +})()`; + +// A file of the open project as the IDE reads it; a path below the project's root. +const READ_JS = (rel) => `new Promise((res) => { + const k = Object.keys(fs.tree.rootFolder.entries)[0]; + const n = fs.tree.resolvePath("twinbasic:/" + k + "/" + ${J(rel)}); + if (!n) return res(null); + setTimeout(() => res("(no answer)"), 8000); + fs.readFile(n, (enc, data) => res(new TextDecoder().decode(new Uint8Array(data)))); +})`; + +// Every file of the open project below Sources. A project recovered with no source has no such folder. +const SOURCES_JS = `(() => { + const k = Object.keys(fs.tree.rootFolder.entries)[0]; + const s = fs.tree.resolvePath("twinbasic:/" + k + "/Sources"); + return s ? Object.values(s.entries).map((e) => e.name) : []; +})()`; + +// Below Resources of the open project: each row's type, its name as the tree holds it, its key. +const RESOURCES_JS = `(() => { + const res = fs.tree.resolvePath("twinbasic:/" + Object.keys(fs.tree.rootFolder.entries)[0] + "/Resources"); + const out = []; + for (const t of Object.values(res.entries)) for (const [k, e] of Object.entries(t.entries || {})) out.push([t.name, e.name, k]); + return out; +})()`; + +/** The helpers, bound to one lane. `k.c` is the open IDE's connection. `probe` is the tree project() packs. */ +export function startingKit(lane, { probe = PROBE } = {}) { + let c = null; + let scriptId = null; + let gen = 0; + let trees = 0; + let master = null; + const ev = (js, o) => c.evaluate(js, o); + const box = async () => { + const r = + await ev(`[...document.querySelectorAll(".modalDialogContainer")].filter((m) => m.getBoundingClientRect().width).map((m) => ({ + title: (m.querySelector(".modalTitleBar")?.innerText || "").trim(), + text: m.innerText.replace(/\\s+/g, " ").trim().slice(0, 300), + buttons: [...m.querySelectorAll(".msgBoxButton")].filter((b) => b.getBoundingClientRect().width).map((b) => b.textContent.trim()).filter(Boolean), + }))`); + return r.map((b) => `[${b.title}] ${b.text} {${b.buttons.join("/")}}`); + }; + const modals = () => + ev( + `[...document.querySelectorAll(".modalDialogContainer")].filter((m) => m.getBoundingClientRect().width).map((m) => (m.querySelector(".modalTitleBar")?.innerText || "").trim())`, + ); + + /** A real click on the element of the top box whose text is `text`. */ + async function clickInModal(text, selector = ".msgBoxButton") { + const p = await ev(IN_MODAL_JS(text, selector)); + assert.ok(p, `no ${text} in the top box: ${J(await box())}`); + await clickAt(c, p.x, p.y); + await sleep(250); + } + const key = async (name, mods) => { + await pressKey(c, name, mods); + await sleep(140); + }; + + /** The page again, with the guard at its document's start: what `cfg` says, and no project. */ + async function fresh(cfg = {}, { wait = true } = {}) { + await c.send("Page.enable"); + if (scriptId) await c.send("Page.removeScriptToEvaluateOnNewDocument", { identifier: scriptId }).catch(() => {}); + const n = ++gen; + const source = `(${pageGuard})(${J({ ...cfg, gen: n })}); ${ROOT_STUB_JS}`; + scriptId = (await c.send("Page.addScriptToEvaluateOnNewDocument", { source })).identifier; + await c.send("Page.reload", {}); + const up = () => + ev( + `(() => { try { return window.__g?.gen === ${n} && document.readyState === "complete"; } catch { return false; } })()`, + ); + assert.ok(await waitFor(c, up, { timeout: 30 * 1000, interval: 100 }), "the page did not start again"); + // The hook's own script runs first, or it would stand in front of the guard: wrap never runs then. + const inFront = await waitFor(c, () => ev("window.__g.target"), { timeout: 10 * 1000, interval: 50 }); + assert.ok(inFront, "the guard is not in front of the defaults hook"); + if (wait) await waitStartDialog(); + } + + async function waitStartDialog(timeout = 60 * 1000) { + const ok = await waitFor(c, () => ev(DIALOG_JS), { timeout, interval: 100 }); + assert.ok(ok, `no New / Open Project dialog: ${J(await box())}`); + await sleep(300); + } + + /** Wait for a project to be open and the IDE idle, and to stay so for a second. */ + async function waitReady(timeout = 60 * 1000) { + const until = Date.now() + timeout; + let since = null; + while (Date.now() < until) { + if (await ev(READY_JS)) { + since ??= Date.now(); + if (Date.now() - since >= 1000) return; + } else { + since = null; + } + await sleep(100); + } + assert.fail(`no project became ready: ${J(await ev(STATE_JS))} ${J(await box())}`); + } + + const nameBox = `[...document.querySelectorAll(".modalDialogContainer input")].find((x) => x.type !== "checkbox")`; + + const k = { + get c() { + return c; + }, + box, + modals, + key, + fresh, + waitStartDialog, + waitReady, + clickInModal, + button: (caption) => clickInModal(caption), + state: () => ev(STATE_JS), + dialog: () => ev(DIALOG_JS), + searchRows: () => ev(SEARCH_JS), + /** Each card or row of the dialog: its text, and the path it opens. */ + items: () => + ev( + `[...document.querySelectorAll("#fp2root .fp2-card, #fp2root .fp2-row")].map((e) => [e.innerText.replace(/\\s*\\n\\s*/g, " | "), e.fp2Item?.projectPath])`, + ), + sources: () => ev(SOURCES_JS), + resources: () => ev(RESOURCES_JS), + /** The events recorded since the last call, each as "kind what". */ + events: async () => (await ev("window.__g.ev.splice(0)")).map(([a, b]) => `${a} ${b}`), + saves: () => ev("window.__g.saves.splice(0)"), + setAnswer: (name, value) => ev(`window.__g.answers[${J(name)}] = ${J(value)}, true`), + setG: (name, value) => ev(`window.__g[${J(name)}] = ${J(value)}, true`), + cmd: (id) => ev(`executeIdeCommand(${J(id)}), true`), + flags: () => + ev( + `({ use: g_UseLastMassagedSettings, last: g_LastMassagedSettings ? g_LastMassagedSettings["project.name"] : null, laa: liveLAAOption })`, + ), + pageErrors: () => ev("window.__g.errors.splice(0)"), + + /** + * Start an IDE with no project, and the guard in front of the hook. `start` is what starts it, + * for an IDE that needs more on its command line than lane.openNoProject gives. + */ + async startIde(cfg = {}, { start = () => lane.openNoProject() } = {}) { + scriptId = null; + // An IDE started just after another can show no page for a minute: ended, and started again once. + c = await start().catch(async () => { + await lane.closeProject(); + return start(); + }); + await waitStartDialog(90 * 1000); + await fresh(cfg); + }, + /** A page with no project and no dialog. */ + async noProject(cfg = {}) { + await fresh(cfg); + await clickInModal("Cancel"); + await sleep(300); + }, + + /** A folder of the lane's work folder. */ + folder(name) { + const dir = path.join(lane.work, name); + mkdirSync(dir, { recursive: true }); + return dir; + }, + /** A .twinproj packed from the probe, with these Settings over the probe's and these Sources added. */ + project(name, settings = {}, folder = "files", sources = {}) { + const tree = path.join(lane.work, "trees", `${name}-${++trees}`); + cpSync(probe, tree, { recursive: true }); + const f = path.join(tree, "Settings"); + writeFileSync(f, J({ ...JSON.parse(readFileSync(f, "utf8")), ...settings }, null, "\t")); + for (const [rel, text] of Object.entries(sources)) writeFileSync(path.join(tree, "Sources", rel), text); + const file = path.join(k.folder(folder), `${name}.twinproj`); + packTree(file, tree); + return file; + }, + /** A .vbp with one module, and a .res when it is given entries for one. */ + vbp(folder, entries = null) { + const dir = k.folder(folder); + const lines = ["Type=Exe", "Module=Module1; Module1.bas"]; + if (entries) { + writeFileSync(path.join(dir, "Res.res"), writeRes(entries)); + lines.push('ResFile32="Res.res"'); + } + lines.push( + 'Startup="Sub Main"', + 'ExeName32="Probe.exe"', + 'Name="Probe"', + "CompilationType=0", + "OptimizationType=0", + ); + writeFileSync(path.join(dir, "Probe.vbp"), `${lines.join("\r\n")}\r\n`); + writeFileSync( + path.join(dir, "Module1.bas"), + 'Attribute VB_Name = "Module1"\r\nOption Explicit\r\nSub Main()\r\nEnd Sub\r\n', + ); + return path.join(dir, "Probe.vbp"); + }, + + /** File > Open Project..., the picker answered with `file`, and wait for the project. */ + async open(file) { + await k.setAnswer("OpenProject", file); + await k.cmd("tbProject_Open"); + await sleep(600); + await waitReady(); + }, + /** Close Project, Discard Changes if it asks; what it asked. */ + async close() { + await k.cmd("tbProject_Close"); + await sleep(800); + const asked = await box(); + if ((await modals()).length) await clickInModal("Discard Changes"); + await sleep(800); + return asked; + }, + async item(re) { + const p = await ev(ITEM_AT_JS(re)); + assert.ok(p, `no item ${re} in the dialog`); + return p; + }, + async clickItem(re) { + const p = await k.item(re); + await clickAt(c, p.x, p.y); + await sleep(200); + }, + /** A real double-click on a card or row of the dialog. */ + async dblItem(re) { + const p = await k.item(re); + for (const [type, n] of [ + ["mouseMoved", 0], + ["mousePressed", 1], + ["mouseReleased", 1], + ["mousePressed", 2], + ["mouseReleased", 2], + ]) { + await c.send("Input.dispatchMouseEvent", { + type, + x: p.x, + y: p.y, + button: n ? "left" : "none", + clickCount: n, + buttons: type === "mousePressed" ? 1 : 0, + }); + } + await sleep(200); + }, + section: (title) => clickInModal(title, ".fp2-railItemText"), + async waitOptions() { + const ok = await waitFor(c, async () => (await modals()).some((t) => t.includes("New Project Options")), { + timeout: 30 * 1000, + interval: 100, + }); + assert.ok(ok, `no New Project Options: ${J(await box())}`); + await sleep(250); + }, + nameBox: () => ev(`${nameBox}.value`), + /** The rows of New Project Options, by the first line of each. */ + optionRows: () => + ev( + `[...document.querySelectorAll(".modalDialogContainer .IDEoption")].filter((e) => e.getBoundingClientRect().width).map((e) => e.innerText.split("\\n")[0].trim())`, + ), + /** Whether the page holds New Project Options in its import form. */ + importMode: () => ev("g_IsLoadingVBP"), + /** The name box of New Project Options selected. */ + selectName: () => ev(`(() => { const i = ${nameBox}; i.focus(); i.select(); })()`), + async typeName(name) { + await k.selectName(); + await pressKey(c, "Backspace"); + await typeText(c, name); + await sleep(120); + }, + /** New, a card, a name, Continue: a new project, never saved. */ + async newProject(name, card = /^Standard EXE$/) { + await k.cmd("tbProject_New"); + await waitStartDialog(8000); + await k.dblItem(card); + await k.waitOptions(); + await k.typeName(name); + await k.button("Continue"); + await sleep(400); + await waitReady(); + }, + /** New, Import from VBP..., the picker answered with `vbp`, a name, Continue. */ + async importVbp(vbp, name = null) { + await k.setAnswer("OpenProjectVBP", vbp); + await k.cmd("tbProject_New"); + await waitStartDialog(8000); + await k.dblItem(/^Import from VBP/); + await k.waitOptions(); + if (name) await k.typeName(name); + await k.button("Continue"); + await sleep(400); + await waitReady(); + }, + /** A file of the open project as the IDE reads it. */ + readFile: (rel) => ev(READ_JS(rel), { awaitPromise: true }), + /** The Settings the IDE holds for the open project. */ + settings: async () => JSON.parse(await k.readFile("Settings")), + /** Ctrl+S, and wait for `file` to change. */ + async saveAndWait(file) { + const before = statSync(file).mtimeMs; + await pressKey(c, "s", { ctrl: true }); + await waitFor(c, () => statSync(file).mtimeMs !== before, { timeout: 10 * 1000, interval: 100 }); + await sleep(600); + }, + + // ------------------------------------------------------------------ the damaged files + + /** The master: the probe's project with LAA on, saved once by the IDE, so it has the IDE's layout. */ + async masterBytes() { + if (master) return master; + const file = k.project("Throwaway", { "project.largeAddressAware": true, "project.name": "Throwaway" }, "master"); + await k.noProject(); + await k.open(file); + await k.saveAndWait(file); + await k.close(); + master = readFileSync(file); + return master; + }, + /** A copy of the first `length` bytes of `bytes`, as Damaged.twinproj in a folder of its own. */ + damaged(folder, bytes) { + const file = path.join(k.folder(folder), "Damaged.twinproj"); + writeFileSync(file, bytes); + return file; + }, + + // ------------------------------------------------------------------ the compiler's own boxes + + /** The visible native dialog boxes on the lane's desktop: title, texts, buttons. */ + async nativeBoxes() { + const list = await desktopWindows(lane.desktop, { children: true }); + const out = []; + list.forEach((w, i) => { + if (w.class !== "#32770" || !w.visible) return; + const kids = []; + for (let j = i + 1; j < list.length && list[j].depth === 1; j++) kids.push(list[j]); + out.push({ + title: w.title, + texts: kids.filter((x) => x.class === "Static" && x.title).map((x) => x.title), + buttons: kids.filter((x) => x.class === "Button").map((x) => ({ text: x.title, hwnd: x.hwnd })), + }); + }); + return out; + }, + /** Press the first button of the first native box. BM_CLICK, since a private desktop takes no input. */ + async pressNative() { + const [b] = await k.nativeBoxes(); + if (!b) return false; + await postWindowMessage(lane.desktop, b.buttons[0].hwnd, 0xf5, 0, 0); + await sleep(500); + return true; + }, + /** Open a damaged project: OK on each native box, until a project is open. How many boxes there were, and the first. */ + async openDamaged(file) { + await k.setAnswer("OpenProject", file); + await k.cmd("tbProject_Open"); + const seen = []; + const until = Date.now() + 40 * 1000; + while (Date.now() < until) { + await sleep(300); + const nat = await k.nativeBoxes(); + if (nat.length) { + seen.push(nat[0]); + await k.pressNative(); + } else if (seen.length && (await ev(READY_JS))) break; + } + await waitReady(10 * 1000); + return seen; + }, + }; + return k; +} diff --git a/test/ide/starting.test.mjs b/test/ide/starting.test.mjs new file mode 100644 index 000000000..376c73202 --- /dev/null +++ b/test/ide/starting.test.mjs @@ -0,0 +1,946 @@ +// Starting a project: the New / Open Project dialog, opening projects and importing a VB6 project, +// tested against fourteen entries in BUGS-TO-REPORT.md: +// +// "The next project opened after a DEP / LAA reload and New Project Options takes the earlier +// project's whole Settings, silently" (bugs/open-takes-earlier-settings) +// "A recovered project is not marked changed, and Save overwrites the damaged file with what was +// recovered" (bugs/recovered-project-save-overwrites) +// "A resource whose type is a string imports with an empty name, and the build then ends in an +// access violation" (bugs/res-string-type-empty-name) +// "After New Project Options, the first open of a saved project with LAA off fails with MISMATCHED +// LAA OR DEP AWARENESS" (bugs/laa-mismatch-after-new-project) +// "A project damaged inside its Settings shows the bad file format box again after every OK" +// (bugs/damaged-settings-box-loop) +// "In the New / Open Project dialog, Enter on a button Tab highlighted also opens the selected +// item" (bugs/new-project-enter-acts-twice) +// "Import from VBP... and Import from folder... close the open project before their picker opens" +// (bugs/import-card-closes-project-first) +// "The Project Name box filters keys, not text, so a pasted name keeps a space or a dot" +// (bugs/project-name-paste-unfiltered) +// "The New / Open Project dialog has keyboard dead ends" (bugs/new-project-keyboard-dead-ends) +// "--no-save-ide-settings does not guard three settings, and is read after the first-run +// questions" (bugs/no-save-flag-unguarded-saves) +// "A Recent path holding # shows cut in two" (bugs/recent-path-hash-cut) +// "New / Open Project search rows show the internal path" (bugs/front-page-search-internal-paths) +// "Two search words in one line of a row lose the first mark" (bugs/front-page-search-one-mark) +// "Cancel in New Project Options of an import leaves the next project's box drawn as an import" +// (bugs/import-cancel-leaves-import-mode) +// +// Three IDEs, one after the other. The first has every test but three, each from a page of its own +// (fresh in starting-kit.mjs reloads the page, so its variables start again and no project is open). +// The second has the resource tests: a build that crashes leaves TRACE-MODE on for that IDE. The +// third has the test of the box that never ends, which then ends that IDE by its pid. +// +// A native picker never opens: the guard (starting-kit.mjs) answers every one from the page. The +// compiler's own boxes are native, on the lane's private desktop, and are found and pressed there. +// THE RECENT LIST IS WRITTEN BY NOTHING HERE: --no-update-recent-files stays on, and every recent +// call is answered in the page. No save reaches the registry: the guard records and drops every +// SaveIDESetting, in front of the defaults hook. Every project opened, damaged, saved or built is in +// the lane's work folder, and nothing reaches SaveSetting, so lanes.mjs names no settings. +// +// The tests share the lane's IDE in order. Each test states what BETA 1034 does; the tests named +// "control" pass whether or not the defects are fixed. If another fails after an IDE update, the IDE +// has changed: update the entry in BUGS-TO-REPORT.md, or its report in bugs/filed/, the callout on +// the page of docs/IDE/Starting/ that carries it, and then this file. +// +// Run it with ide-test.bat, which gives it a lane; on its own it is skipped. + +import assert from "node:assert/strict"; +import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { before, test } from "node:test"; +import { clickAt, pressKey, typeText, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { unpackProject } from "../../scripts/lib/tb-project.mjs"; +import { scenario } from "../addin/scenario.mjs"; +import { menu, press } from "./building-kit.mjs"; +import { fileResources, fileSettings, fileSources, J, layout, sleep, startingKit } from "./starting-kit.mjs"; + +const ID = (n) => `{5B000000-0000-4000-8000-00000000000${n}}`; +const SOURCES = ["MainMod.twin", "Second.twin", "Third.twin", "Thing.twin"]; +const B = (t) => Buffer.from(t); +const BAD_FORMAT = "WARNING: failed to deserialize file system. Some files might be lost or truncated."; +const RECENTS = "C:\\dev\\C#\\app.twinproj?C:\\Projects\\Alpha.twinproj?C:\\Projects\\Beta.twinproj"; + +scenario("Starting a project", (lane) => { + const k = startingKit(lane); + + // A saved project with LAA off, as the Standard EXE template's file is: the IDE starts with LAA on, + // so opening this one reloads it. + const old = (name = "Old") => + k.project(name, { "project.name": "OldProbe", "project.appTitle": "Old title", "project.id": ID(1) }); + // One with LAA on, which puts the IDE back as it started. + const newer = () => + k.project("Newer", { + "project.name": "NewerProbe", + "project.appTitle": "Newer title", + "project.id": ID(2), + "project.largeAddressAware": true, + }); + // The project that is opened last. Everything a test looks at differs from the earlier projects'. + const target = (name = "Target") => + k.project(name, { + "project.name": "TargetProbe", + "project.appTitle": "Target title", + "project.description": "Target description", + "project.buildPath": "${SourcePath}\\Target.exe", + "project.id": ID(3), + "project.largeAddressAware": true, + }); + const assertOwnSettings = async () => { + const st = await k.settings(); + assert.equal(st["project.name"], "TargetProbe"); + assert.equal(st["project.appTitle"], "Target title"); + assert.equal(st["project.id"], ID(3)); + }; + + before(async () => { + lane.includeProjects(); + await k.startIde(); + }); + + // ------------------------------------------------------------- N2 + + test("after a DEP / LAA reload and then New Project Options, the next project opened takes the earlier project's whole Settings, silently, and Ctrl+S writes them into its file", async () => { + await k.noProject(); + await k.open(old()); + assert.deepEqual(await k.flags(), { use: true, last: null, laa: false }); + await k.open(newer()); + assert.deepEqual(await k.flags(), { use: true, last: null, laa: true }); + await k.newProject("Zzz"); + assert.deepEqual(await k.flags(), { use: true, last: "Zzz", laa: true }); + const file = target(); + const bytes = readFileSync(file); + await k.open(file); + const st = await k.settings(); + assert.equal(st["project.name"], "Zzz"); + assert.equal(st["project.appTitle"], "Zzz"); + assert.notEqual(st["project.id"], ID(3)); + assert.notEqual(st["project.buildPath"], "${SourcePath}\\Target.exe"); + assert.equal(st["project.startupObject"], "Form1"); + assert.equal(st["project.references"].length, 3); + const s = await k.state(); + assert.equal(s.title, "Target.twinproj"); + assert.equal(s.dirty, false); + assert.deepEqual(s.root, ["zzz"]); + assert.deepEqual(await k.box(), []); + assert.deepEqual((await k.sources()).sort(), [...SOURCES].sort(), "the sources are the opened project's"); + assert.ok(readFileSync(file).equals(bytes), "opening changed the file"); + await k.saveAndWait(file); + const saved = fileSettings(file); + assert.equal(saved["project.name"], "Zzz"); + assert.equal(saved["project.appTitle"], "Zzz"); + assert.equal(saved["project.startupObject"], "Form1"); + assert.notEqual(saved["project.id"], ID(3)); + assert.notEqual(saved["project.description"], "Target description"); + }); + + test("control: the next open after that one is right, and New Project Options has to come again to repeat it", async () => { + await k.close(); + await k.open(target("TargetAgain")); + await assertOwnSettings(); + assert.deepEqual(await k.flags(), { use: false, last: null, laa: true }); + }); + + test("the same steps by an import: a project with LAA off, Import from VBP..., then any project, and closing without a save leaves its file as it was", async () => { + await k.noProject(); + await k.open(old()); + await k.close(); + await k.importVbp(k.vbp("n2-vbp"), "Zzz"); + assert.deepEqual(await k.flags(), { use: true, last: "Zzz", laa: true }); + assert.match((await k.close()).join(), /You've made changes to this project/); + const file = target("TargetImport"); + const bytes = readFileSync(file); + await k.open(file); + const st = await k.settings(); + assert.equal(st["project.name"], "Zzz"); + assert.equal(st["project.appTitle"], "Probe"); + assert.equal(st["project.startupObject"], "Sub Main"); + assert.equal((await k.state()).title, "TargetImport.twinproj"); + assert.deepEqual(await k.close(), [], "closing asked something"); + assert.ok(readFileSync(file).equals(bytes), "closing changed the file"); + }); + + test("control: New Project Options alone, with no reload before it, leaves the next project its own Settings", async () => { + await k.noProject(); + await k.newProject("Zzz"); + await k.close(); + await k.open(target("TargetDialogOnly")); + await assertOwnSettings(); + assert.deepEqual(await k.flags(), { use: false, last: "Zzz", laa: true }); + }); + + test("control: a reload alone, with no New Project Options, opens a project with LAA off and leaves the next project its own Settings", async () => { + await k.noProject(); + const file = old("OldAlone"); + await k.open(file); + assert.equal((await k.settings())["project.name"], "OldProbe"); + await k.open(target("TargetReloadOnly")); + await assertOwnSettings(); + }); + + test("control: a new project made while the IDE runs with LAA off reloads, which uses up the earlier Settings, and the next project keeps its own", async () => { + await k.noProject(); + await k.open(old("OldThenNew")); + await k.newProject("Zzz"); + assert.deepEqual(await k.flags(), { use: false, last: null, laa: true }); + await k.open(target("TargetStuck")); + await assertOwnSettings(); + }); + + // ------------------------------------------------------------- N4 + + test("with New Project Options first and no reload yet, opening a saved project with LAA off fails with MISMATCHED LAA OR DEP AWARENESS, and a second open works", async () => { + await k.noProject(); + await k.newProject("Zzz"); + await k.close(); + const file = old("OldMismatch"); + const bytes = readFileSync(file); + await k.setAnswer("OpenProject", file); + await k.cmd("tbProject_Open"); + const shown = await waitFor(k.c, async () => (await k.box()).find((b) => /MISMATCHED/.test(b)) ?? null, { + timeout: 20 * 1000, + }); + assert.match(shown, /Failed to load the project \(error code MISMATCHED LAA OR DEP AWARENESS\)/); + assert.match(shown, /\{Remove It\/Keep It\}$/); + await k.button("Keep It"); + await sleep(500); + const s = await k.state(); + assert.equal(s.loaded, false); + assert.equal(s.services, "UNAVAILABLE"); + assert.ok(readFileSync(file).equals(bytes), "the failed open changed the file"); + await k.open(file); + assert.equal((await k.settings())["project.name"], "OldProbe"); + }); + + // ------------------------------------------------------------- N1 + + const cut = async () => { + const m = await k.masterBytes(); + const lay = layout(m); + const at = (n) => lay.find((e) => e.file && e.full.endsWith(n)); + const middle = (e) => e.contentAt + Math.floor(e.len / 2); + return { + master: m, + settings: at("Settings"), + // Cut inside the second source: Settings is read, every source is lost. + inSources: m.subarray(0, middle(at("Second.twin"))), + // Cut inside the resource after Sources: every source is kept, the resource is lost. + inResources: m.subarray(0, middle(at("#201"))), + // Cut before the first entry: nothing is read. + atFirstEntry: m.subarray(0, at("Settings").start), + }; + }; + // A project whose Settings could not be read has LAA off, so it reloads, and the box shows twice. + const assertBoxes = (seen, count = 1) => { + assert.equal(seen.length, count, J(seen)); + for (const b of seen) { + assert.equal(b.title, "bad file format"); + assert.deepEqual(b.texts, [BAD_FORMAT]); + assert.deepEqual( + b.buttons.map((x) => x.text), + ["OK"], + ); + } + }; + + test("a project cut inside its Sources tree opens with every source gone and is not marked changed, and Ctrl+S overwrites the damaged file with no question", async () => { + const { inSources } = await cut(); + const file = k.damaged("n1-ctrl-s", inSources); + await k.noProject(); + assertBoxes(await k.openDamaged(file)); + const s = await k.state(); + assert.equal(s.title, "Damaged.twinproj"); + assert.equal(s.dirty, false); + assert.deepEqual(await k.sources(), []); + assert.equal((await k.settings())["project.name"], "Throwaway"); + await k.events(); + await k.saveAndWait(file); + assert.deepEqual(await k.events(), [], "the save asked something"); + assert.deepEqual(await k.box(), []); + assert.ok(statSync(file).size !== inSources.length, "the file was not written"); + assert.deepEqual(fileSources(file), []); + await k.close(); + }); + + test("a project cut at its first entry opens as RECOVERED_PROJECT, and Ctrl+S overwrites the file with that empty project", async () => { + const { atFirstEntry } = await cut(); + const file = k.damaged("n1-first", atFirstEntry); + await k.noProject(); + assertBoxes(await k.openDamaged(file), 2); + const s = await k.state(); + assert.equal(s.dirty, false); + assert.deepEqual(s.root, ["recovered_project"]); + await k.saveAndWait(file); + assert.deepEqual(fileSources(file), []); + assert.equal(fileSettings(file)["project.name"], "RECOVERED_PROJECT"); + await k.close(); + }); + + test("Add > Import... in a project cut after its sources overwrites the damaged file, with nothing pressed that says Save", async () => { + const { inResources } = await cut(); + const file = k.damaged("n1-import", inResources); + const note = path.join(k.folder("n1-import"), "note.txt"); + writeFileSync(note, "a note\r\n"); + await k.noProject(); + assertBoxes(await k.openDamaged(file)); + assert.deepEqual((await k.sources()).sort(), [...SOURCES].sort()); + await k.setAnswer("OpenFiles", note); + await k.c.evaluate(`fs.tree.selectedItems = [fs.tree.resolvePath("twinbasic:/Throwaway/Sources")], true`); + await k.events(); + await k.c.evaluate("importFile(), true"); + await waitFor(k.c, () => statSync(file).size !== inResources.length, { timeout: 15 * 1000, interval: 100 }); + assert.deepEqual(await k.box(), []); + assert.deepEqual(await k.events(), ["dialog OpenFiles"]); + assert.deepEqual(fileResources(file), []); + assert.ok(fileSources(file).includes("Second.twin")); + await k.close(); + }); + + test("a change in Project Settings that restarts the compiler overwrites the damaged file, with nothing pressed that says Save", async () => { + const { inSources } = await cut(); + const file = k.damaged("n1-settings", inSources); + await k.noProject(); + assertBoxes(await k.openDamaged(file)); + await k.cmd("tbProject_ShowSettings"); + const CONFIG = `[...document.querySelectorAll("*")].find((e) => e.globalVars?.configData)?.globalVars`; + assert.ok(await waitFor(k.c, () => k.c.evaluate(`!!${CONFIG}`), { timeout: 20 * 1000 }), "no Project Settings"); + await sleep(2500); + const section = await k.c.evaluate( + `(() => { const e = [...document.querySelectorAll(".modalDialogContainer *")].filter((x) => x.children.length === 0 && x.getBoundingClientRect().width && /^Library References/.test(x.textContent.trim())).pop(); if (!e) return null; const q = e.getBoundingClientRect(); return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; })()`, + ); + assert.ok(section, "no Library References"); + await clickAt(k.c, section.x, section.y); + await sleep(1200); + const tick = await k.c.evaluate( + `(() => { const row = [...document.querySelectorAll(".referencesListRow")].find((r) => r.getBoundingClientRect().height && /OLE Automation/.test(r.innerText)); if (!row) return null; row.scrollIntoView({ block: "center" }); const q = row.querySelector(".referencesListColEnabled").getBoundingClientRect(); return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; })()`, + ); + assert.ok(tick, "no OLE Automation row"); + await clickAt(k.c, tick.x, tick.y); + await sleep(700); + await k.clickInModal("Apply Changes", "*"); + const ok = await waitFor(k.c, () => statSync(file).size !== inSources.length, { + timeout: 15 * 1000, + interval: 100, + }); + assert.ok(ok, "the file was not written"); + await k.waitReady(); + assert.deepEqual(fileSources(file), []); + assert.equal(fileSettings(file)["project.references"].length, 0); + await k.close(); + }); + + test("control: closing a recovered project leaves the damaged file as it was, and Save Project As to a new name keeps it too", async () => { + const { inSources } = await cut(); + const file = k.damaged("n1-control", inSources); + await k.noProject(); + assertBoxes(await k.openDamaged(file)); + assert.deepEqual(await k.close(), [], "closing asked something"); + assert.ok(readFileSync(file).equals(inSources), "closing changed the file"); + assertBoxes(await k.openDamaged(file)); + const other = path.join(path.dirname(file), "NewName.twinproj"); + await k.setAnswer("SaveProjectAs", other); + await k.cmd("tbProject_SaveAs"); + await waitFor(k.c, () => existsSync(other), { timeout: 10 * 1000, interval: 100 }); + await sleep(800); + assert.ok(readFileSync(file).equals(inSources), "Save As changed the damaged file"); + assert.equal(fileSettings(other)["project.name"], "Throwaway"); + await k.close(); + }); + + // ------------------------------------------------------------- D13 + + const tabs = async (n) => { + for (let i = 0; i < n; i++) await k.key("Tab"); + }; + const plainSaved = (name) => + k.project(name, { "project.name": "MyProject", "project.largeAddressAware": true, "project.id": ID(4) }); + + test("with the highlight moved to Open by Tab, Enter runs the open twice", async () => { + await k.fresh({ blockOpen: true }); + await tabs(3); + assert.deepEqual((await k.dialog()).hilite, ["Open"]); + await k.events(); + await k.key("Enter"); + await sleep(300); + assert.deepEqual(await k.events(), ["open $\\_Standard EXE", "open $\\_Standard EXE"]); + }); + + test("with the highlight moved to Browse... by Tab, Enter opens the picker and the selected item", async () => { + await k.fresh({ blockOpen: true }); + await tabs(2); + assert.deepEqual((await k.dialog()).hilite, ["Browse..."]); + await k.events(); + await k.key("Enter"); + await sleep(300); + assert.deepEqual(await k.events(), ["dialog OpenProject", "open $\\_Standard EXE"]); + }); + + test("with the highlight moved to Cancel by Tab, Enter closes a saved project with no question and opens the selected card", async () => { + await k.noProject(); + const file = plainSaved("Saved"); + await k.open(file); + await k.cmd("tbProject_New"); + await k.waitStartDialog(8000); + await tabs(4); + assert.deepEqual((await k.dialog()).hilite, ["Cancel"]); + await k.key("Enter"); + await k.waitOptions(); + const s = await k.state(); + assert.equal(s.loaded, false); + assert.equal(s.file, null); + await k.button("Cancel"); + await sleep(1200); + const after = await k.state(); + assert.equal(after.loaded, false); + assert.equal(after.services, "UNAVAILABLE"); + assert.deepEqual(await k.box(), []); + }); + + test("control: Esc, and a click on Cancel, close the dialog and keep the project", async () => { + await k.noProject(); + await k.open(plainSaved("SavedControl")); + await k.cmd("tbProject_New"); + await k.waitStartDialog(8000); + await tabs(4); + await k.key("Escape"); + await sleep(600); + assert.equal((await k.state()).loaded, true); + assert.equal(await k.dialog(), null); + await k.cmd("tbProject_New"); + await k.waitStartDialog(8000); + await k.button("Cancel"); + await sleep(600); + assert.equal((await k.state()).loaded, true); + assert.equal(await k.dialog(), null); + }); + + test("Enter on a focused card opens the selected card, not the focused one", async () => { + await k.fresh({ blockOpen: true }); + await k.clickItem(/^ActiveX DLL/); + const p = await k.c.evaluate( + `(() => { const r = document.querySelector("#fp2root .fp2-search").getBoundingClientRect(); return { x: r.x + 50, y: r.y + r.height / 2 }; })()`, + ); + await clickAt(k.c, p.x, p.y); + await sleep(150); + await k.key("Tab"); + const d = await k.dialog(); + assert.equal(d.selected, "ActiveX DLL"); + assert.equal(d.focus, "card:Standard EXE"); + await k.events(); + await k.key("Enter"); + await sleep(300); + assert.deepEqual(await k.events(), ["open $\\_ActiveX DLL"]); + }); + + // ------------------------------------------------------------- D4 + + const closedFirst = (events, picker) => { + assert.ok(events.indexOf("call closeProjectNow") >= 0, J(events)); + assert.ok(events.indexOf("call closeProjectNow") < events.indexOf(`dialog ${picker}`), J(events)); + }; + const assertNothingLeft = async () => { + const s = await k.state(); + assert.equal(s.loaded, false); + assert.equal(s.services, "UNAVAILABLE"); + assert.equal(await k.dialog(), null); + assert.deepEqual(await k.box(), []); + }; + + test("Import from VBP... closes the open project before its picker opens, and cancelling the picker leaves no project and no dialog", async () => { + await k.noProject(); + await k.open(plainSaved("SavedVbp")); + await menu(k.c, "File", "New Project..."); + await k.waitStartDialog(8000); + await k.events(); + await k.dblItem(/^Import from VBP/); + await sleep(1500); + closedFirst(await k.events(), "OpenProjectVBP"); + await assertNothingLeft(); + }); + + test("Import from folder... closes the open project before its picker opens, and cancelling the picker leaves no project and no dialog", async () => { + await k.noProject(); + await k.open(plainSaved("SavedFolder")); + await menu(k.c, "File", "New Project..."); + await k.waitStartDialog(8000); + await k.events(); + await k.dblItem(/^Import from folder/); + await sleep(1500); + closedFirst(await k.events(), "ChooseFolder"); + await assertNothingLeft(); + }); + + test("control: File > Open Project... cancelled, and Browse... cancelled, keep the project", async () => { + await k.noProject(); + await k.open(plainSaved("SavedOpen")); + await k.events(); + await k.setAnswer("OpenProject", ""); + await menu(k.c, "File", "Open Project..."); + await sleep(800); + assert.deepEqual(await k.events(), ["dialog OpenProject"]); + assert.equal((await k.state()).loaded, true); + await menu(k.c, "File", "New Project..."); + await k.waitStartDialog(8000); + await k.button("Browse..."); + await sleep(800); + assert.deepEqual(await k.events(), ["dialog OpenProject"]); + assert.equal((await k.state()).loaded, true); + }); + + // ------------------------------------------------------------- D7 + + test("control: the Project Name box takes out of typed text what a project name cannot hold", async () => { + await k.fresh(); + await k.dblItem(/^Standard EXE$/); + await k.waitOptions(); + await k.typeName("My Project.x_1"); + assert.equal(await k.nameBox(), "MyProjectx_1"); + await k.typeName("_lead"); + assert.equal(await k.nameBox(), "lead"); + }); + + test("a name pasted into Project Name keeps its space and its dot, and becomes the project's name, title and root folder; it builds with no diagnostic", async () => { + await k.selectName(); + await k.c.send("Input.insertText", { text: "My Project.x" }); + await sleep(150); + assert.equal(await k.nameBox(), "My Project.x"); + await k.button("Continue"); + await sleep(400); + await k.waitReady(); + const st = await k.settings(); + assert.equal(st["project.name"], "My Project.x"); + assert.equal(st["project.description"], "My Project.x"); + assert.equal(st["project.appTitle"], "My Project.x"); + assert.deepEqual((await k.state()).root, ["my project.x"]); + const saved = path.join(k.folder("d7"), "Named.twinproj"); + await k.setAnswer("SaveProjectAs", saved); + await pressKey(k.c, "s", { ctrl: true }); + await waitFor(k.c, () => existsSync(saved), { timeout: 10 * 1000, interval: 100 }); + await sleep(1500); + const r = await press(k.c, "build"); + assert.ok( + existsSync(path.join(k.folder("d7"), "Build", "My Project.x_win32.exe")), + `no exe: ${r.lines.slice(-4).join(" | ")}`, + ); + }); + + // ------------------------------------------------------------- D19 + + test("Up and Down do nothing in the search box, which has the focus when the dialog opens", async () => { + await k.fresh({ blockOpen: true }); + const d = await k.dialog(); + assert.equal(d.focus, "search"); + assert.equal(d.selected, "Standard EXE"); + await k.key("ArrowDown"); + await k.key("ArrowDown"); + await k.key("ArrowUp"); + const after = await k.dialog(); + assert.equal(after.selected, "Standard EXE"); + assert.equal(after.focus, "search"); + }); + + test("control: Tab, then the arrows, move the selected item in New", async () => { + await k.key("Tab"); + await k.key("ArrowDown"); + await k.key("ArrowDown"); + const d = await k.dialog(); + assert.equal(d.selected, "ActiveX DLL"); + assert.equal(d.focus, "card:Standard EXE"); + }); + + test("no key reaches the rail: Tab and the arrows leave the section as it was", async () => { + await k.fresh({ blockOpen: true }); + for (const name of ["Tab", "ArrowDown", "ArrowLeft", "Tab", "Tab", "ArrowUp", "ArrowRight", "Tab", "ArrowLeft"]) { + await k.key(name); + assert.equal((await k.dialog()).rail, "New", `after ${name}`); + } + }); + + test("in Recent, Tab sends the focus out of the dialog, and the keyboard does nothing until a click", async () => { + await k.fresh({ blockOpen: true, recents: RECENTS }); + await k.button("Cancel"); + await k.cmd("tbProject_OpenRecent"); + await k.waitStartDialog(8000); + const d = await k.dialog(); + assert.equal(d.rail, "Recent"); + assert.equal(d.focus, "search"); + await k.key("Tab"); + assert.equal((await k.dialog()).focus, "body"); + await k.key("Tab"); + assert.equal((await k.dialog()).focus, "DIV.sectionHeader"); + await k.events(); + for (const name of ["ArrowDown", "ArrowUp", "Enter", "Escape"]) { + await k.key(name); + const now = await k.dialog(); + assert.ok(now, `${name} closed the dialog`); + assert.equal(now.selected, d.selected, `after ${name}`); + } + assert.deepEqual(await k.events(), []); + const blank = await k.c.evaluate( + `(() => { const r = document.querySelector("#fp2root .fp2-sectionDesc").getBoundingClientRect(); return { x: r.x + r.width - 8, y: r.y + r.height / 2 }; })()`, + ); + await clickAt(k.c, blank.x, blank.y); + await sleep(150); + await k.key("ArrowDown"); + assert.notEqual((await k.dialog()).selected, d.selected, "a click did not give the keyboard back"); + }); + + // ------------------------------------------------------------- the flags + + // The saves of these keys since the last look. Every page start also saves WEBPANEL_SETTINGS, + // at no fixed moment. + const savesOf = async (...keys) => (await k.saves()).filter((s) => keys.includes(s[0])); + + test("with --no-save-ide-settings, closing the New / Open Project dialog calls SaveIDESetting for NEWFRONTPAGE all the same", async () => { + await k.fresh(); + assert.equal(await k.c.evaluate("g_DontSaveIdeSettings"), true); + await k.saves(); + await k.button("Cancel"); + await sleep(300); + assert.deepEqual(await savesOf("NEWFRONTPAGE"), [["NEWFRONTPAGE", '{"section":"new"}']]); + }); + + test("with --no-save-ide-settings, a change in the Object Browser calls SaveIDESetting for OBJECT_BROWSER all the same", async () => { + await k.open(plainSaved("SavedBrowser")); + await k.cmd("tbShowObjectBrowser"); + await waitFor(k.c, () => k.c.evaluate("!!window.__twinBasicObjectBrowser"), { timeout: 10 * 1000 }); + await sleep(500); + await k.saves(); + const p = await k.c.evaluate( + `(() => { const b = window.__twinBasicObjectBrowser.element.querySelector(".tbob-viewbutton[aria-pressed=false]"); const r = b.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; })()`, + ); + await clickAt(k.c, p.x, p.y); + await sleep(500); + assert.ok((await savesOf("OBJECT_BROWSER")).length > 0, "no save of OBJECT_BROWSER"); + }); + + test("with --no-save-ide-settings, saving the publisher's details calls SaveIDESetting for PUBLISHER all the same", async () => { + await k.fresh({ settings: { PUBLISHER: "" } }); + await k.saves(); + await k.c.evaluate( + `(async () => { + packagePublisherName = "TestPublisher"; packagePublisherPassword = ""; packagePublisherRememberPassword = false; + await new Promise((r) => savePublisherInfo(r)); + })()`, + { awaitPromise: true }, + ); + assert.deepEqual(await savesOf("PUBLISHER"), [["PUBLISHER", "(value not kept)"]]); + }); + + test("control: with --no-save-ide-settings, a build target chosen for a saved project is not saved", async () => { + await k.noProject(); + await k.open(plainSaved("SavedTarget")); + await k.saves(); + await k.c.evaluate(`(() => { buildConfigSelector.value = "win64"; buildConfigSelector.onchange(); })()`); + await sleep(800); + await k.waitReady(); + assert.deepEqual(await savesOf("targetArchitectureMemory"), []); + }); + + test("--no-save-ide-settings is read only after the first-run questions: the licence answers are saved with it on the command line", async () => { + await k.fresh({ settings: { GENERAL: "", LICENCEAGREEMENTV3: "", ALLOWERRORREPORTINGV3: "" } }, { wait: false }); + await waitFor(k.c, async () => (await k.modals()).some((t) => t.includes("Licence Agreement")), { + timeout: 20 * 1000, + }); + assert.equal(await k.c.evaluate("g_DontSaveIdeSettings"), false); + await k.saves(); + await k.button("I AGREE"); + await waitFor(k.c, async () => (await k.modals()).some((t) => t.includes("Automatic Error Reporting")), { + timeout: 10 * 1000, + }); + const saved = await savesOf("LICENCEAGREEMENTV3", "ALLOWERRORREPORTINGV3"); + assert.deepEqual( + saved.map((s) => s[0]), + ["LICENCEAGREEMENTV3", "ALLOWERRORREPORTINGV3"], + ); + assert.match(saved[0][1], /^AGREED_/); + assert.match(saved[1][1], /^DISAGREED_/); + await k.button("NO"); + await waitFor(k.c, async () => (await k.modals()).some((t) => t.includes("IDE Options")), { timeout: 10 * 1000 }); + assert.equal(await k.c.evaluate("g_DontSaveIdeSettings"), false); + await k.key("Escape"); + await k.waitStartDialog(20 * 1000); + assert.equal(await k.c.evaluate("g_DontSaveIdeSettings"), true); + }); + + // ------------------------------------------------------------- D16, D17, D18 + + test("a Recent path with a # shows cut in two, at the #, and opens from the whole path", async () => { + await k.fresh({ blockOpen: true, recents: RECENTS }); + await k.section("Recent"); + await sleep(200); + assert.deepEqual((await k.items())[0], ["C:\\dev\\C | \\app.twinproj", "C:\\dev\\C#\\app.twinproj"]); + await k.events(); + await k.button("Open"); + assert.deepEqual(await k.events(), ["open C:\\dev\\C#\\app.twinproj"]); + }); + + test("control: a Recent path with no # shows whole", async () => { + assert.deepEqual((await k.items())[1], ["C:\\Projects\\Alpha.twinproj", "C:\\Projects\\Alpha.twinproj"]); + }); + + const search = async (text) => { + await k.c.evaluate( + `(() => { const i = document.querySelector("#fp2root .fp2-search"); i.focus(); i.select(); })()`, + ); + await k.key("Backspace"); + await typeText(k.c, text); + await sleep(250); + return k.searchRows(); + }; + + test("search rows of the IDE's own items show the internal path, and $ alone for an import card", async () => { + await k.fresh(); + assert.deepEqual(await search("sample 10"), [["twinBASIC IDE Addin", "$\\Sample <<10>>. twinBASIC IDE Addin"]]); + assert.deepEqual(await search("vbp"), [["Import from <>...", "$"]]); + assert.equal((await search("$")).length, 34); + }); + + test("control: a search row of a Recent project shows its path, marked", async () => { + await k.fresh({ recents: RECENTS }); + assert.deepEqual(await search("alpha"), [["C:\\Projects\\<>.twinproj"]]); + }); + + test("two search words in one line of a row lose the first mark", async () => { + await k.fresh(); + const rows = await search("standard console"); + assert.equal(rows.length, 1); + assert.equal(rows[0].at(-1), "$\\_Standard EXE#(<> App)"); + }); + + test("control: search words in different lines of a row are each marked", async () => { + const rows = await search("standard console"); + assert.deepEqual(rows[0].slice(0, 2), ["<> EXE", "(<> App)"]); + }); + + // ------------------------------------------------------------- import-cancel-leaves-import-mode + + const INCLUDE = "Include Common Controls"; + const UNICODE = /Unicode/; + const COMMON_CTLS = "{D63CB78D-9F5B-42FE-B94C-A545F2B2BFC3}"; + const ids = (st) => st["project.references"].map((r) => r.id); + let plainCard = null; + + // New, and a double-click on a card: the box's rows, with the box left open. + const boxFor = async (card, sectionName = null) => { + await k.cmd("tbProject_New"); + await k.waitStartDialog(8000); + if (sectionName) { + await k.section(sectionName); + await sleep(250); + } + await k.dblItem(card); + await k.waitOptions(); + return k.optionRows(); + }; + // The state the defect leaves: Import from VBP... on a project, Cancel in New Project Options. + const importCancelled = async () => { + await k.noProject(); + await k.setAnswer("OpenProjectVBP", k.vbp("import-cancel")); + const rows = await boxFor(/^Import from VBP/); + assert.ok( + rows.some((x) => UNICODE.test(x)), + J(rows), + ); + await k.button("Cancel"); + await sleep(800); + }; + const assertCardBox = (rows) => { + assert.ok(rows.includes(INCLUDE), J(rows)); + assert.ok(!rows.some((x) => UNICODE.test(x)), J(rows)); + }; + const assertImportBox = (rows) => { + assert.ok(!rows.includes(INCLUDE), J(rows)); + assert.ok( + rows.some((x) => UNICODE.test(x)), + J(rows), + ); + }; + + test("control: in a fresh page the Standard EXE card's box has Include Common Controls and no Unicode row, and Continue gives a project with the package, and the name as its description and title", async () => { + await k.noProject(); + assert.equal(await k.importMode(), false); + assertCardBox(await boxFor(/^Standard EXE$/)); + await k.typeName("CardName"); + await k.button("Continue"); + await sleep(400); + await k.waitReady(); + plainCard = await k.settings(); + assert.ok(ids(plainCard).includes(COMMON_CTLS)); + assert.equal(plainCard["project.description"], "CardName"); + assert.equal(plainCard["project.appTitle"], "CardName"); + assert.equal(plainCard["runtime.useUnicodeCommonControlNotifications"], undefined); + }); + + test("control: in a fresh page Sample 1's box has Include Common Controls and no Unicode row", async () => { + await k.noProject(); + assertCardBox(await boxFor(/HelloWorld/, "Samples")); + }); + + test("after Cancel in New Project Options of Import from VBP..., the next template's box is drawn as an import: a Unicode row, and no Include Common Controls", async () => { + await importCancelled(); + assert.equal(await k.importMode(), true); + assert.equal((await k.state()).loaded, false); + assertImportBox(await boxFor(/^Standard EXE$/)); + await k.button("Cancel"); + await sleep(800); + assert.equal(await k.importMode(), true, "a Cancel of that box cleared it"); + }); + + test("after that Cancel, a sample's box is drawn as an import too", async () => { + assertImportBox(await boxFor(/HelloWorld/, "Samples")); + await k.button("Cancel"); + await sleep(800); + }); + + test("Continue in that box gives a project with no WinNativeCommonCtls reference, the template's description and title in place of the name, and Unicode Common Control Notifications on; the project is otherwise normal", async () => { + await boxFor(/^Standard EXE$/); + await k.typeName("CardName"); + await k.button("Continue"); + await sleep(400); + await k.waitReady(); + const st = await k.settings(); + assert.equal(st["project.name"], "CardName"); + assert.ok(!ids(st).includes(COMMON_CTLS), J(ids(st))); + assert.deepEqual( + ids(st), + ids(plainCard).filter((id) => id !== COMMON_CTLS), + ); + assert.equal(st["project.description"], "New project"); + assert.equal(st["project.appTitle"], "New project"); + assert.equal(st["runtime.useUnicodeCommonControlNotifications"], true); + // Every other key is as the same card makes it in a fresh page; the id is new for each project. + const differs = [...new Set([...Object.keys(st), ...Object.keys(plainCard)])].filter( + (key) => J(st[key]) !== J(plainCard[key]), + ); + assert.deepEqual(differs.sort(), [ + "project.appTitle", + "project.description", + "project.id", + "project.references", + "runtime.useUnicodeCommonControlNotifications", + ]); + const s = await k.state(); + assert.equal(s.title, "Unsaved.twinproj"); + assert.equal(s.dirty, false); + assert.equal(s.services, "OPERATIONAL"); + assert.deepEqual(s.root, ["cardname"]); + assert.deepEqual((await k.sources()).sort(), ["Form1.tbform", "Form1.twin"]); + }); + + test("control: that Continue clears it, and the next box is right again", async () => { + assert.equal(await k.importMode(), false); + await k.close(); + assertCardBox(await boxFor(/^Standard EXE$/)); + }); + + test("control: Cancel in the import's own picker leaves the next box right", async () => { + await k.noProject(); + await k.setAnswer("OpenProjectVBP", ""); + await k.cmd("tbProject_New"); + await k.waitStartDialog(8000); + await k.events(); + await k.dblItem(/^Import from VBP/); + await sleep(1500); + assert.ok((await k.events()).includes("dialog OpenProjectVBP")); + assert.equal(await k.importMode(), false); + assertCardBox(await boxFor(/^Standard EXE$/)); + }); + + // ------------------------------------------------------------- N3, an IDE of its own + + test("control: a .res of numeric types imports with its ids and builds", async () => { + await lane.closeProject(); + await k.startIde(); + await k.button("Cancel"); + await sleep(300); + await k.importVbp( + k.vbp("res-numeric", [ + [10, 201, B("rcdata numeric id")], + [10, "RCNAMED", B("rcdata string id")], + [300, 301, B("numeric type, numeric id")], + ]), + ); + const rows = (await k.resources()).map((r) => `${r[0]}/${r[1]}`); + for (const r of ["RCDATA/#201", "RCDATA/RCNAMED", "#300/#301"]) assert.ok(rows.includes(r), J(rows)); + const saved = path.join(k.folder("res-numeric/out"), "ProbeRes.twinproj"); + await k.setAnswer("SaveProjectAs", saved); + await pressKey(k.c, "s", { ctrl: true }); + await waitFor(k.c, () => existsSync(saved), { timeout: 10 * 1000, interval: 100 }); + await sleep(1500); + await press(k.c, "build"); + assert.ok(existsSync(path.join(path.dirname(saved), "Probe.exe")), "no exe"); + await k.close(); + }); + + test("a .res resource whose type is a string imports with an empty name, one blank 0-byte row for all of them, and a Standard EXE then ends its build in ACCESS_VIOLATION with no exe", async () => { + const vbp = k.vbp("res-named", [ + ["CUSTOM", 101, B("custom data")], + ["CUSTOM", 102, B("second custom data")], + ["CUSTOM", "NAMEDRES", B("custom type, string id")], + [10, 201, B("rcdata numeric id")], + ]); + await k.importVbp(vbp); + const rows = (await k.resources()).filter((r) => r[0] === "CUSTOM"); + assert.deepEqual(rows, [["CUSTOM", "", ""]]); + assert.equal(await k.readFile("Resources/CUSTOM/"), "", "the IDE reads the row as 0 bytes"); + assert.match(await k.readFile("Sources/__IMPORT_LOG.md"), /IMPORT FINISHED WITH 0 ERRORS\/WARNINGS/); + const saved = path.join(k.folder("res-named/out"), "ProbeRes.twinproj"); + await k.setAnswer("SaveProjectAs", saved); + await pressKey(k.c, "s", { ctrl: true }); + await waitFor(k.c, () => existsSync(saved), { timeout: 10 * 1000, interval: 100 }); + await sleep(1500); + assert.deepEqual( + fileResources(saved) + .filter((r) => r[0].startsWith("CUSTOM")) + .map((r) => r[0]), + ["CUSTOM/", "CUSTOM/", "CUSTOM/"], + "the file holds each one under an empty name", + ); + assert.throws(() => unpackProject(saved, path.join(lane.work, "res-unpacked")), /cannot be written safely/); + const r = await press(k.c, "build"); + assert.ok( + r.lines.some((l) => /NATIVE EXCEPTION: ACCESS_VIOLATION/.test(l)), + J(r.lines), + ); + assert.equal(existsSync(path.join(path.dirname(saved), "Probe.exe")), false, "an exe was written"); + // The crash is asserted: the lane's close is not to fail on it. + await k.cmd("tbDebugConsole_Clear"); + }); + + // ------------------------------------------------------------- D20, an IDE of its own + + test("a project damaged inside its Settings entry shows the bad file format box again after every OK", { + timeout: 150 * 1000, + }, async () => { + const LIMIT = 6; + const { master, settings } = await cut(); + const file = k.damaged("d20", master.subarray(0, settings.contentAt + Math.floor(settings.len / 2))); + const bytes = readFileSync(file); + await lane.closeProject(); + await k.startIde(); + await k.button("Cancel"); + await sleep(300); + await k.setAnswer("OpenProject", file); + await k.cmd("tbProject_Open"); + for (let n = 1; n <= LIMIT; n++) { + const shown = await waitFor(k.c, async () => (await k.nativeBoxes())[0] ?? null, { + timeout: 20 * 1000, + interval: 200, + }); + assert.ok(shown, `box ${n} did not come`); + assert.equal(shown.title, "bad file format"); + await k.pressNative(); + } + await waitFor(k.c, async () => (await k.nativeBoxes()).length > 0, { timeout: 20 * 1000, interval: 200 }); + const page = await k.c.evaluate( + `({ lock: lockUICount, loaded: context.projectLoaded.value, boxes: [...document.querySelectorAll(".modalDialogContainer .modalTitleBar")].filter((m) => m.getBoundingClientRect().width).map((m) => m.innerText.trim()) })`, + ); + assert.equal(page.loaded, false); + assert.ok(page.lock > 0, J(page)); + assert.deepEqual(page.boxes, ["Please wait..."]); + assert.ok(readFileSync(file).equals(bytes), "the file changed"); + // It cannot end by itself: the IDE is ended by its pid. + await lane.closeProject(); + }); +}); diff --git a/test/ide/troubleshooting-kit.mjs b/test/ide/troubleshooting-kit.mjs new file mode 100644 index 000000000..088b0a6f4 --- /dev/null +++ b/test/ide/troubleshooting-kit.mjs @@ -0,0 +1,290 @@ +// What troubleshooting.test.mjs and troubleshooting-missing.test.mjs need, over starting-kit.mjs, +// whose guard (it says what the guard does) stands in front of the defaults hook in every IDE here, +// so that no recent call and no save of the IDE's reaches the registry. Not a lane itself (lanes.mjs +// does not name it). +// +// Every file a test opens, saves, renames or holds is in the lane's work folder. A file missing from +// an install is missing from the lane's own copy (Lane.copy), for that test only: `without` refuses +// any other path. A read-only flag goes in a `finally`, as readOnly does it. +// +// A native box closes with BM_CLICK on its button: a private desktop takes no input. Every wait on a +// box has a limit, since a box left open wedges the lane. The Windows enumeration fails when a +// desktop has no window left, so nativeBoxes reads that as no box. + +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { cpSync, existsSync, mkdirSync, readFileSync, renameSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { + attachIde, + desktopWindows, + IDE_FLAGS, + killTree, + launchIde, + launchOnDesktop, + postWindowMessage, + sleep, +} from "../../scripts/lib/tb-ide.mjs"; +import { consoleMark, linesSince } from "../../scripts/lib/tb-ide-console.mjs"; +import { fixPageSize } from "../../scripts/lib/tb-lane.mjs"; +import { click, openFile, typeText, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { packTree } from "../../scripts/lib/tb-project.mjs"; +import { diskText, J, startingKit } from "./starting-kit.mjs"; + +export { J, sleep }; +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const PROBE = path.join(HERE, "probes", "troubleshooting"); +const PACKAGE = path.join(HERE, "probes", "packages", "DocProbePkg"); +const MAIN = "/TroubleProbe/Sources/MainMod.twin"; +const WM_CLOSE = 0x10; +const BM_CLICK = 0xf5; + +// A linked package's reference, as Project Settings writes one. +const LINKED = { + id: "{7B247500-0000-4000-9000-7B2475000004}", + isLocalPackage: true, + licence: "MIT", + name: "[LINKED-PACKAGE] DocProbePkg", + path32: "/Packages/DocProbePkg", + path64: "/Packages/DocProbePkg", + publishedDate: "01-JAN-2026", + publishedTime: "00:00:00", + publisher: "probe", + symbolId: "DocProbePkg", + versionBuild: "0", + versionMajor: "1", + versionMinor: "0", + versionRevision: "0", +}; + +const SERVICES_JS = `(() => { + const b = document.getElementById("compilerStatusOuter"); + return { badge: b.innerText.trim(), tip: b.title.split("\\n").map((l) => l.trim()).filter(Boolean) }; +})()`; + +/** The helpers of starting-kit.mjs and these, bound to one lane. */ +export function troubleshootingKit(lane) { + const k = startingKit(lane, { probe: PROBE }); + const ev = (js, o) => k.c.evaluate(js, o); + const boxesOn = async (desktop) => { + const list = await desktopWindows(desktop, { children: true }).catch(() => []); + const out = []; + list.forEach((w, i) => { + if (w.class !== "#32770" || !w.visible) return; + const kids = []; + for (let j = i + 1; j < list.length && list[j].depth === 1; j++) kids.push(list[j]); + out.push({ + title: w.title, + text: kids + .filter((x) => x.class === "Static" && x.title) + .map((x) => x.title) + .join("\n") + .replace(/\r\n/g, "\n"), + buttons: kids.filter((x) => x.class === "Button").map((x) => ({ text: x.title, hwnd: x.hwnd })), + }); + }); + return out; + }; + // A WebView2 window whose title starts so: the IDE's own window has the project's name in its title. + const devToolsOn = async (desktop) => + (await desktopWindows(desktop)) + .filter((w) => /^DevTools - /.test(w.title || "") && /msedgewebview2\.exe$/i.test(w.exe || "")) + .map((w) => w.hwnd); + + return Object.assign(k, { + /** The probe's project, in a folder of its own. */ + probe: (name, settings = {}, sources = {}) => k.project(name, settings, `p-${name}`, sources), + /** A page with no project and no dialog, then `file` opened. */ + async fromClean(file) { + await k.noProject(); + await k.open(file); + }, + /** An unsaved edit: a comment holding `mark` in MainMod, typed. */ + async edit(mark) { + await openFile(k.c, MAIN, { line: 5, column: 9 }); + await sleep(250); + await typeText(k.c, `' ${mark}\n`); + await sleep(350); + }, + /** Whether the project's file holds `mark` in MainMod. */ + fileHas: (file, mark) => diskText(file, "Sources/MainMod.twin").includes(mark), + /** Whether an open model of the editor holds `mark`. */ + editorHas: (mark) => ev(`monaco.editor.getModels().some((m) => m.getValue().includes(${J(mark)}))`), + /** The next alert the page opens. tb-ide.mjs accepts each and keeps it in c.dialogs. */ + async alert(timeout = 10 * 1000) { + const shown = await waitFor(k.c, (c) => c.dialogs.length > 0, { timeout, interval: 100 }); + assert.ok(shown, `no alert: ${J(await k.box())}`); + await sleep(300); + return k.c.dialogs.splice(0).map((d) => d.message); + }, + /** Run `fn` with the read-only flag on `file`, and clear it whatever happens. */ + async readOnly(file, fn) { + assert.equal(spawnSync("attrib", ["+R", file], { windowsHide: true }).status, 0); + try { + return await fn(); + } finally { + spawnSync("attrib", ["-R", file], { windowsHide: true }); + } + }, + services: () => ev(SERVICES_JS), + pid: () => ev("g_CurrentCompilerProcessId"), + running: () => ev("!!context.codeExecuting.value"), + /** The Restart the compiler button, a real click. */ + restart: () => click(k.c, "restartIcon"), + /** The build box on Safe Mode, and OK on the box that says so. */ + async safeMode() { + const before = await k.pid(); + await ev(`buildConfigSelector.value = "nocompile"; buildConfigSelector.onchange(); 0`); + await waitFor(k.c, async () => (await k.pid()) !== before, { timeout: 30 * 1000, interval: 100 }); + const shown = await waitFor(k.c, async () => (await k.box()).some((b) => /SafeMode/.test(b)), { + timeout: 30 * 1000, + interval: 100, + }); + assert.ok(shown, `no Safe Mode box: ${J(await k.box())}`); + await sleep(300); + await k.button("OK"); + await sleep(600); + assert.equal(await ev("buildConfigSelector.value"), "nocompile"); + }, + mark: () => consoleMark(k.c), + /** The console's lines since a mark that are not empty: an empty console reads as one empty line. */ + since: async (mark) => (await linesSince(k.c, mark)).filter((l) => l.trim()), + /** The name of the sub, as a DEBUG CONSOLE line runs it. */ + async runSub(name) { + await ev(`debugSocket.request("evaluate", { expression: ${J(name)}, context: "repl" }, () => {}); 0`); + }, + + // ------------------------------------------------------------------ a linked package + + /** DocProbePkg in the lane's private APPDATA, where a linked package is kept. Its file's path. */ + packPackage() { + const packages = path.join(lane.appdataDir(), "twinBASIC", "packages"); + mkdirSync(packages, { recursive: true }); + const stage = path.join(lane.work, "pkg"); + cpSync(PACKAGE, stage, { recursive: true }); + const pack = path.join(packages, "DocProbePkg.twinpack"); + packTree(pack, stage); + return pack; + }, + /** The probe's project with DocProbePkg linked, and a call of the package's. */ + linkedProject() { + const settings = JSON.parse(readFileSync(path.join(PROBE, "Settings"), "utf8")); + return k.probe( + "Linked", + { "project.references": [...settings["project.references"], LINKED] }, + { + "UsesPackage.twin": + "Module UsesPackage\r\n Public Sub Show()\r\n Debug.Print DocProbeVersion()\r\n End Sub\r\nEnd Module\r\n", + }, + ); + }, + + // ------------------------------------------------------------------ DevTools + + /** The DevTools windows on the lane's desktop, as hwnds. */ + devTools: () => devToolsOn(lane.desktop), + /** The DevTools windows on the user's desktop, as hwnds: never a title. */ + userDevTools: () => devToolsOn("Default"), + /** Open DevTools by a key bound in the page only, as a person who bound one would. */ + async openDevTools(press) { + await ev( + `(() => { customKeyboardShortcutGroups = { DevTools: { data: ${J(J({ tbIdeDevTools_Show: ["{KEYDOWN}{CTRL}{SHIFT}F12"] }))} } }; activeKeyboardShortcutGroupNames = "|DevTools|"; syncActiveKeyboardShortcuts(); })()`, + ); + await press(); + const shown = await waitFor(k.c, async () => (await k.devTools()).length > 0, { + timeout: 15 * 1000, + interval: 200, + }); + assert.ok(shown, "DevTools did not open"); + }, + /** Close every DevTools window of the lane. */ + async closeDevTools() { + for (const hwnd of await k.devTools().catch(() => [])) + await postWindowMessage(lane.desktop, hwnd, WM_CLOSE, 0, 0).catch(() => {}); + await waitFor(k.c, async () => !(await k.devTools().catch(() => [])).length, { timeout: 8000, interval: 200 }); + }, + + // ------------------------------------------------------------------ the host's boxes + + /** The visible native dialog boxes on the lane's desktop: title, text, buttons. */ + nativeBoxes: async () => boxesOn((await lane.leased()).desktop), + /** Wait for a native box. */ + async nativeBox(timeout = 30 * 1000) { + const shown = await waitFor(null, async () => (await k.nativeBoxes())[0] ?? null, { timeout, interval: 200 }); + assert.ok(shown, "no native box came"); + return shown; + }, + /** Press the first button of a native box read by nativeBox. */ + pressBox: async (box) => { + await postWindowMessage((await lane.leased()).desktop, box.buttons[0].hwnd, BM_CLICK, 0, 0); + await sleep(600); + }, + + // ------------------------------------------------------------------ the install's files + + /** Run `fn` with the files `rels` gone from the lane's own copy of the install, and put them back. */ + async without(rels, fn) { + const files = rels.map((rel) => path.join(path.dirname(lane.copy()), rel)); + for (const file of files) { + const inside = path.relative(lane.work, file); + assert.ok(inside && !inside.startsWith("..") && !path.isAbsolute(inside), "not a file of the lane's copy"); + } + const moved = []; + try { + for (const file of files) { + renameSync(file, `${file}.gone`); + moved.push(file); + } + return await fn(); + } finally { + for (const file of moved) renameSync(`${file}.gone`, file); + } + }, + /** Whether `rel` is in the lane's copy of the install. */ + inCopy: (rel) => existsSync(path.join(path.dirname(lane.copy()), rel)), + /** + * Start the lane's copy on its desktop, as from its icon, for a start that stops before there is + * a page, so launchIde, which waits a minute for one, is no use. It has IDE_FLAGS and the lane's + * APPDATA. The box it opens, then OK on it; whether the process then ended by itself. + */ + async stoppedStart() { + const { desktop } = await lane.leased(); + const run = await launchOnDesktop({ + exe: lane.copy(), + desktop, + env: { ...process.env, APPDATA: lane.appdataDir(), TB_ADDIN_TEST: "1" }, + args: IDE_FLAGS, + }); + let ended = false; + const done = run.exited.then(() => { + ended = true; + }); + try { + const box = await k.nativeBox(); + await k.pressBox(box); + await Promise.race([done, sleep(10 * 1000)]); + return { box, ended }; + } finally { + if (!ended) killTree(run.pid); + await Promise.race([done, sleep(10 * 1000)]); + } + }, + /** What starts an IDE with these flags on its command line and no project, for k.startIde. */ + startWith: (flags) => async () => { + lane.run = await launchIde({ + exe: lane.copy(), + slot: await lane.leased(), + show: lane.show, + env: { APPDATA: lane.appdataDir() }, + browserArgs: lane.browserArgs, + flags, + }); + lane.port = lane.run.port; + lane.c = await attachIde(lane.port); + assert.ok(lane.c, "the IDE never exposed a debug port"); + if (lane.page) await fixPageSize(lane.c, lane.page); + return lane.c; + }, + }); +} diff --git a/test/ide/troubleshooting-missing.test.mjs b/test/ide/troubleshooting-missing.test.mjs new file mode 100644 index 000000000..01f436922 --- /dev/null +++ b/test/ide/troubleshooting-missing.test.mjs @@ -0,0 +1,118 @@ +// A file missing from the install, tested against three entries in BUGS-TO-REPORT.md: +// +// "With bin\twinBASIC_ide_win32.dll missing, the box names bin\twinBASIC_win64.dll" +// (bugs/launcher-box-names-wrong-file) +// "With bin\twinBASIC_win32.dll missing, a box titled shutdown names no file" +// (bugs/missing-dll-box-names-no-file) +// "With the compiler variant the IDE starts missing, the page goes quiet after one host box" +// (bugs/missing-compiler-silent-stop) +// +// A lane of its own, apart from troubleshooting.test.mjs, because each test wants an IDE start of its +// own with a file gone. EVERY FILE IS GONE FROM THE LANE'S OWN COPY OF THE INSTALL (Lane.copy, under +// the lane's work folder; `without` in troubleshooting-kit.mjs refuses any other path) and only for +// that test. No Desktop install is touched. +// +// In the first two tests and the control the host stops before it has a page, so nothing here reaches +// the defaults hook, and none is needed; it has IDE_FLAGS all the same. The boxes are native, on the +// lane's private desktop: each is read and closed there by BM_CLICK, with a limit on every wait. The +// last test's IDE has a page, and the guard (starting-kit.mjs) is in front of it, as in every IDE of +// the chapter's lanes. +// +// Each test states what BETA 1034 does; the test named "control" passes whether or not the defects +// are fixed. If another fails after an IDE update, the IDE has changed: update the entry in +// BUGS-TO-REPORT.md, or its report in bugs/filed/, the callout in +// docs/IDE/Troubleshooting/Missing-Files.md, and then this file. +// +// Run it with ide-test.bat, which gives it a lane; on its own it is skipped. + +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { scenario } from "../addin/scenario.mjs"; +import { sleep, troubleshootingKit } from "./troubleshooting-kit.mjs"; + +const LINK = "See https://twinbasic.com/missing"; +const NOT_INITIALIZED = [ + "COMPILER: Not initialized", + "FS: Not initialized", + "LSP: Not initialized", + "DEBUGGER: Not initialized", +]; + +scenario("Files missing from the install", (lane) => { + const k = troubleshootingKit(lane); + + test("with bin\\twinBASIC_ide_win32.dll missing, the box names bin\\twinBASIC_win64.dll, which is there", { + timeout: 60 * 1000, + }, async () => { + assert.ok(k.inCopy("bin/twinBASIC_win64.dll")); + const { box, ended } = await k.without(["bin/twinBASIC_ide_win32.dll"], () => k.stoppedStart()); + assert.equal(box.title, "twinBASIC Compiler"); + assert.equal( + box.text, + `ERROR: failed to load the twinBASIC DLL:\n bin\\twinBASIC_win64.dll\n\nThe file could not be found. ${LINK} \n\nThe application will now exit.`, + ); + assert.deepEqual( + box.buttons.map((b) => b.text), + ["OK"], + ); + assert.equal(ended, true, "the process went on after OK"); + }); + + test("with bin\\twinBASIC_win32.dll missing, a box titled shutdown names no file and gives no link", { + timeout: 60 * 1000, + }, async () => { + const { box, ended } = await k.without(["bin/twinBASIC_win32.dll"], () => k.stoppedStart()); + assert.equal(box.title, "shutdown"); + assert.equal(box.text, "Run-time error '-353703418'\n\nUnable to load DLL"); + assert.equal(ended, true, "the process went on after OK"); + }); + + test("control: with bin\\twinBASIC_win32.exe and its variants missing, the box names the file and gives the link", { + timeout: 60 * 1000, + }, async () => { + const gone = ["", "_noDEP", "_LAA", "_noDEP_LAA"] + .map((v) => `bin/twinBASIC_win32${v}.exe`) + .filter((f) => k.inCopy(f)); + assert.ok(gone.length >= 2, "the copy has no variants"); + const { box, ended } = await k.without(gone, () => k.stoppedStart()); + assert.equal(box.title, "twinBASIC IDE"); + assert.equal( + box.text, + `ERROR: failed to load the twinBASIC EXE:\n \\bin\\twinBASIC_win32.exe\n\nThe file could not be found. ${LINK}\n\nThe application will now exit.`, + ); + assert.equal(ended, true, "the process went on after OK"); + }); + + test("with the compiler variant the IDE starts missing, the page goes quiet after one host box: no retry, no line in the console, no message", { + timeout: 120 * 1000, + }, async () => { + await k.without(["bin/twinBASIC_win32_noDEP_LAA.exe"], async () => { + await k.startIde(); + await k.noProject(); + const m = await k.mark(); + await k.setAnswer("OpenProject", k.probe("Quiet")); + await k.cmd("tbProject_Open"); + const box = await k.nativeBox(30 * 1000); + assert.equal(box.title, "twinBASIC IDE"); + assert.equal( + box.text, + `ERROR: failed to load the twinBASIC EXE:\n \\bin\\twinBASIC_win32_noDEP_LAA.exe\n\nThe file could not be found. ${LINK}\n\nPlease close the IDE, and restore any missing files.`, + ); + await k.pressBox(box); + await sleep(12 * 1000); + assert.deepEqual(await k.nativeBoxes(), [], "the box came again"); + assert.deepEqual(await k.modals(), [], "the page shows something"); + const s = await k.services(); + assert.equal(s.badge, "tB Services: UNAVAILABLE"); + assert.deepEqual(s.tip, NOT_INITIALIZED); + assert.ok(!(await k.pid()), "a compiler started"); + assert.equal((await k.state()).loaded, false); + assert.deepEqual(await k.since(m), []); + assert.deepEqual( + (await k.events()).filter((e) => e.startsWith("msgBox")), + [], + ); + await lane.closeProject(); + }); + }); +}); diff --git a/test/ide/troubleshooting.test.mjs b/test/ide/troubleshooting.test.mjs new file mode 100644 index 000000000..90b128376 --- /dev/null +++ b/test/ide/troubleshooting.test.mjs @@ -0,0 +1,479 @@ +// When something goes wrong: a save that fails, Safe Mode, a compiler restart, TRACE mode and DevTools, +// tested against seven entries in BUGS-TO-REPORT.md: +// +// "A failed Save As still moves the project to the new path" (bugs/failed-save-as-moves-project) +// "Close Project, Save: a save that fails drops the close" (bugs/failed-save-drops-close) +// "Start in Safe Mode never runs and never ends, and Stop does nothing" +// (bugs/safe-mode-start-never-runs) +// "A linked package's missing file gives Restart a bare shutdown box and a wait of about 80 s" +// (bugs/missing-linked-package-restart-box) +// "TRACE mode auto-activated by a native exception takes effect only at the next restart" +// (bugs/trace-auto-activate-needs-restart) +// "With DevTools open, a compiler restart stops at a debugger statement in the IDE's code" +// (bugs/devtools-stops-compiler-restart) +// "A compiler connection that cannot be made restarts the compiler every 5 s, and the crash-loop +// message never fires" (bugs/compiler-connection-retry-loop) +// +// Two IDEs, one after the other. The first has every test but the last, each with a page of its own +// (k.fromClean reloads the page, so that its variables start again and no project is open). The last +// test's IDE has --debug on its command line, which is what makes a connection that cannot be made; +// it is ended by its pid. +// +// troubleshooting-missing.test.mjs is the rest of this chapter's defects: a file missing from the +// install wants an IDE start of its own for each. +// +// THE RECENT LIST IS WRITTEN BY NOTHING HERE: --no-update-recent-files stays on, and every recent +// call is answered in the page. No save of the IDE's reaches the registry: the guard +// (starting-kit.mjs) records and drops every SaveIDESetting, in front of the defaults hook. Every +// project opened, saved or held read-only is in the lane's work folder, and the linked package is in +// the lane's private APPDATA. DevTools opens on the lane's private desktop, and the test fails if a +// new DevTools window shows on the user's. +// +// The native exceptions of the TRACE tests would each post an error report to the developers if the +// page had reporting on (postError). A tool's IDE has it off (BLANKED_KEYS in tb-defaults.mjs), and +// the hook answers that address itself whatever the page says. Those tests turn it on in the page, as +// a user's IDE has it, to test that: the only test of that fix against a real report. The address is +// also blocked by CDP while they run. The error-reporting question is never opened, and its YES never +// pressed. +// +// The tests share the lane's IDE in order. Each test states what BETA 1034 does; the tests named +// "control" pass whether or not the defects are fixed. If another fails after an IDE update, the IDE +// has changed: update the entry in BUGS-TO-REPORT.md, or its report in bugs/filed/, the callout on +// the page of docs/IDE/Troubleshooting/ that carries it, and then this file. +// +// Run it with ide-test.bat, which gives it a lane; on its own it is skipped. + +import assert from "node:assert/strict"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { before, test } from "node:test"; +import { IDE_FLAGS } from "../../scripts/lib/tb-ide.mjs"; +import { click, pressKey, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { scenario } from "../addin/scenario.mjs"; +import { J, sleep, troubleshootingKit } from "./troubleshooting-kit.mjs"; + +const NOT_FOUND = /^save project failed, result code 2, GetLastErr: ERROR_PATH_NOT_FOUND/; +const DENIED = /^save project failed, result code 2, GetLastErr: ERROR_ACCESS_DENIED/; +const NATIVE = /NATIVE EXCEPTION: ACCESS_VIOLATION/; +const DUMP = /^(>>> )?thread [0-9A-F]+: /; +const DISCONNECTED = ["COMPILER: Disconnected", "FS: Disconnected", "LSP: Disconnected", "DEBUGGER: Disconnected"]; +const OK_TEXT = "ran to the end"; + +scenario("When something goes wrong", (lane) => { + const k = troubleshootingKit(lane); + const ctrlS = () => pressKey(k.c, "s", { ctrl: true }); + // Wait for a compiler other than `old`, with the project ready in it. + const newCompiler = async (old) => { + const ok = await waitFor( + k.c, + async () => { + const p = await k.pid(); + return p && p !== old; + }, + { timeout: 30 * 1000, interval: 100 }, + ); + assert.ok(ok, "no new compiler"); + await k.waitReady(30 * 1000); + }; + const restartAndWait = async () => { + const pid = await k.pid(); + await k.restart(); + await newCompiler(pid); + }; + + before(async () => { + await k.startIde(); + }); + + // ------------------------------------------------------------- N2, a failed Save As + + test("control: Save As to a good path moves the project and writes the file", async () => { + const file = k.probe("GoodFrom"); + const bytes = readFileSync(file); + await k.fromClean(file); + await k.edit("GOOD_EDIT"); + const to = path.join(k.folder("good"), "GoodTo.twinproj"); + await k.setAnswer("SaveProjectAs", to); + await k.cmd("tbProject_SaveAs"); + await waitFor(k.c, () => existsSync(to), { timeout: 10 * 1000, interval: 100 }); + await sleep(800); + assert.deepEqual(k.c.dialogs.splice(0), []); + const s = await k.state(); + assert.equal(s.file, "GoodTo.twinproj"); + assert.equal(s.tip, "GoodTo.twinproj"); + assert.equal(s.dirty, false); + assert.ok(k.fileHas(to, "GOOD_EDIT")); + assert.ok(readFileSync(file).equals(bytes), "the original was written"); + }); + + test("a failed Save As still moves the project: the title bar and the path name the new file, and a plain Save goes there", async () => { + const file = k.probe("MovedFrom"); + const bytes = readFileSync(file); + await k.fromClean(file); + await k.edit("MOVED_EDIT"); + const folder = path.join(lane.work, "no-such-folder"); + const moved = path.join(folder, "Moved.twinproj"); + await k.setAnswer("SaveProjectAs", moved); + await k.cmd("tbProject_SaveAs"); + assert.match((await k.alert())[0], NOT_FOUND); + const s = await k.state(); + assert.equal(s.file, "Moved.twinproj"); + assert.equal(s.tip, "Moved.twinproj"); + assert.equal(s.dirty, true); + assert.equal(existsSync(moved), false); + + // A plain Save tries the new path, and fails while its folder is missing. + await ctrlS(); + assert.match((await k.alert())[0], NOT_FOUND); + assert.ok(readFileSync(file).equals(bytes), "the original was written"); + + // With the folder there it writes the new path, and the file the reader opened stays as it was. + mkdirSync(folder); + await ctrlS(); + await waitFor(k.c, () => existsSync(moved), { timeout: 10 * 1000, interval: 100 }); + await sleep(800); + assert.equal((await k.state()).dirty, false); + assert.ok(k.fileHas(moved, "MOVED_EDIT")); + assert.ok(readFileSync(file).equals(bytes), "the original was written"); + }); + + test("a failed Save As over a read-only file moves the project too, and Save As back to the original puts it right", async () => { + const file = k.probe("ReadOnlyFrom"); + await k.fromClean(file); + await k.edit("READ_ONLY_EDIT"); + const target = path.join(k.folder("ro"), "ReadOnly.twinproj"); + writeFileSync(target, "x"); + await k.readOnly(target, async () => { + await k.setAnswer("SaveProjectAs", target); + await k.cmd("tbProject_SaveAs"); + assert.match((await k.alert())[0], DENIED); + const s = await k.state(); + assert.equal(s.file, "ReadOnly.twinproj"); + assert.equal(s.dirty, true); + }); + assert.equal(readFileSync(target, "utf8"), "x"); + await k.setAnswer("SaveProjectAs", file); + await k.cmd("tbProject_SaveAs"); + await waitFor(k.c, async () => !(await k.state()).dirty, { timeout: 10 * 1000, interval: 100 }); + await sleep(500); + assert.deepEqual(k.c.dialogs.splice(0), []); + assert.equal((await k.state()).file, "ReadOnlyFrom.twinproj"); + assert.ok(k.fileHas(file, "READ_ONLY_EDIT")); + }); + + // ------------------------------------------------------------- D9, a failed save in Close Project + + test("control: Close Project, Save, and a save that works closes the project", async () => { + const file = k.probe("CloseWorks"); + await k.fromClean(file); + await k.edit("CLOSE_EDIT"); + await k.cmd("tbProject_Close"); + await sleep(800); + assert.match((await k.box())[0], /You've made changes to this project\. Save now\?/); + await k.button("Save"); + await waitFor(k.c, async () => !(await k.state()).loaded, { timeout: 15 * 1000, interval: 100 }); + assert.equal((await k.state()).loaded, false); + assert.ok(k.fileHas(file, "CLOSE_EDIT")); + assert.deepEqual(k.c.dialogs.splice(0), []); + }); + + test("Close Project, Save, and a save that fails drops the close: the project stays open and changed, and the reader must close again", async () => { + const file = k.probe("CloseFails"); + await k.fromClean(file); + await k.edit("DROPPED_EDIT"); + const bytes = readFileSync(file); + await k.readOnly(file, async () => { + await k.cmd("tbProject_Close"); + await sleep(800); + assert.match((await k.box())[0], /You've made changes to this project\. Save now\?/); + await k.button("Save"); + assert.match((await k.alert())[0], DENIED); + await sleep(1500); + const s = await k.state(); + assert.equal(s.loaded, true); + assert.equal(s.dirty, true); + assert.deepEqual(await k.box(), [], "the question is not asked again"); + }); + assert.ok(readFileSync(file).equals(bytes), "the file was written"); + assert.equal(await k.editorHas("DROPPED_EDIT"), true); + }); + + // ------------------------------------------------------------- N1, Start in Safe Mode + + test("control: Start with the box on win32 runs the program to its end", async () => { + await k.fromClean(k.probe("Starts")); + const m = await k.mark(); + await k.cmd("tbDebug_StartOrContinue"); + const ended = await waitFor(k.c, async () => (await k.since(m)).includes(OK_TEXT), { timeout: 15 * 1000 }); + assert.ok(ended, J(await k.since(m))); + assert.equal(await waitFor(k.c, async () => !(await k.running()), { timeout: 10 * 1000 }), true); + }); + + test("Start in Safe Mode never runs and never ends, and Stop does nothing; a restart ends it", { + timeout: 120 * 1000, + }, async () => { + await k.fromClean(k.probe("SafeStart")); + await k.safeMode(); + assert.equal((await k.services()).badge, "tB Services: OPERATIONAL"); + const m = await k.mark(); + await k.cmd("tbDebug_StartOrContinue"); + await sleep(20 * 1000); + const lines = ["Executing 'Main'...", "WARNING: execution delayed; waiting for background compilation to finish"]; + assert.deepEqual(await k.since(m), lines); + assert.equal(await k.running(), true); + + await k.cmd("tbDebug_Stop"); + await sleep(15 * 1000); + assert.deepEqual(await k.since(m), lines); + assert.equal(await k.running(), true); + + await k.restart(); + const ended = await waitFor(k.c, async () => !(await k.running()), { timeout: 30 * 1000 }); + assert.equal(ended, true, "a restart does not end it"); + }); + + // ------------------------------------------------------------- N4, a linked package's file gone + + let linked = null; + + test("control: Restart with a linked package's file there brings the compiler back at once", async () => { + const pack = k.packPackage(); + const file = k.linkedProject(); + await k.fromClean(file); + linked = { pack, file }; + await k.edit("LINKED_EDIT"); + const t0 = Date.now(); + await restartAndWait(); + assert.ok(Date.now() - t0 < 30 * 1000); + assert.deepEqual(await k.nativeBoxes(), []); + assert.equal(await k.editorHas("LINKED_EDIT"), true); + }); + + test("a linked package's missing file: Restart opens a box titled shutdown that names neither the package nor the file, and the compiler is back about 80 s after OK, the edit kept", { + timeout: 200 * 1000, + }, async () => { + assert.ok(linked, "the control did not open the project"); + rmSync(linked.pack); + await k.restart(); + const box = await k.nativeBox(20 * 1000); + assert.equal(box.title, "shutdown"); + assert.equal(box.text, "Run-time error '-2147467259'\n\nUnspecified error"); + assert.deepEqual( + box.buttons.map((b) => b.text), + ["OK"], + ); + assert.match(J(await k.box()), /Please wait/); + assert.equal((await k.services()).badge, "tB Services: LIMITED"); + + await k.pressBox(box); + const t0 = Date.now(); + const back = await waitFor(k.c, async () => (await k.services()).badge === "tB Services: OPERATIONAL", { + timeout: 140 * 1000, + interval: 500, + }); + assert.ok(back, `no compiler came back: ${J(await k.services())}`); + const waited = Date.now() - t0; + assert.ok(waited > 60 * 1000, `it took ${waited} ms`); + await sleep(1500); + assert.deepEqual(await k.nativeBoxes(), []); + assert.equal(await k.editorHas("LINKED_EDIT"), true); + // The IDE opens the References view by itself, some time after the status bar has come back. + const readRows = () => + k.c.evaluate( + `[...document.querySelectorAll(".referencesListRow")].filter((r) => r.getBoundingClientRect().height && /DocProbe/.test(r.innerText)).map((r) => r.innerText.replace(/\\s+/g, " "))`, + ); + const rows = await waitFor( + k.c, + async () => { + const r = await readRows(); + return r.length ? r : null; + }, + { timeout: 20 * 1000 }, + ); + assert.equal(rows?.length, 1, J(rows)); + assert.match(rows[0], /\[LINKED-PACKAGE\] DocProbePkg .*ERROR unable to load this reference/); + }); + + // ------------------------------------------------------------- D3, TRACE mode + + // What leaves the page while reporting is on: the hosts of requests that are not local, and how + // many requests to the report's address CDP stopped. Neither should ever be more than none. + const out = { hosts: [], stopped: 0, on: false }; + const local = /^(https?|wss?):\/\/(localhost|127\.0\.0\.1)[:/]|^(data|blob|devtools|file):/; + async function watchReports() { + if (!k.c.__reportWatch) { + k.c.__reportWatch = true; + k.c.on((m) => { + if (m.method === "Network.requestWillBeSent" && out.on && !local.test(m.params.request.url)) + out.hosts.push(new URL(m.params.request.url).host); + if (m.method === "Fetch.requestPaused") { + out.stopped++; + k.c + .send("Fetch.failRequest", { requestId: m.params.requestId, errorReason: "BlockedByClient" }) + .catch(() => {}); + } + }); + } + await k.c.send("Fetch.enable", { patterns: [{ urlPattern: "*twinbasic.com*" }] }); + out.on = true; + await k.c.evaluate("g_ErrorReporting = true, 0"); + } + const traceItem = async () => { + await click(k.c, "rootMenuHelp"); + await sleep(350); + const item = await k.c.evaluate( + `[...document.querySelectorAll(".contextMenuItem, .contextMenu .menuItem, .contextMenuOuter div")].filter((e) => /TRACE/.test(e.textContent) && e.children.length <= 3 && e.getBoundingClientRect().height).map((e) => e.innerText.trim()).at(-1)`, + ); + await pressKey(k.c, "Escape"); + await sleep(200); + return item; + }; + /** Run Sub Crash and read what the console then gets, once nothing has come for 1.5 s. */ + const crash = async () => { + const m = await k.mark(); + await k.runSub("Crash"); + const seen = await waitFor(k.c, async () => (await k.since(m)).some((l) => NATIVE.test(l)), { + timeout: 15 * 1000, + }); + assert.ok(seen, `no native exception: ${J(await k.since(m))}`); + let lines = []; + for (let quiet = 0; quiet < 6; ) { + await sleep(250); + const now = await k.since(m); + quiet = now.length === lines.length ? quiet + 1 : 0; + lines = now; + } + return lines; + }; + + test("a native exception auto-activates TRACE mode and Help says so, but a second exception in that compiler still prints no dump", async () => { + await k.fromClean(k.probe("Trace")); + assert.equal(await k.c.evaluate("g_TraceMode"), false); + assert.equal(await traceItem(), "Compiler services TRACE mode: Disabled"); + await watchReports(); + const pid = await k.pid(); + + const first = await crash(); + assert.ok( + first.some((l) => /^0001 .*NATIVE EXCEPTION/.test(l)), + J(first), + ); + assert.ok( + first.some((l) => /auto-activated TRACE-MODE/.test(l)), + J(first), + ); + assert.equal(first.filter((l) => DUMP.test(l)).length, 0, J(first)); + assert.equal(await k.c.evaluate("g_TraceMode"), true); + assert.equal(await traceItem(), "Compiler services TRACE mode: Enabled"); + + const second = await crash(); + assert.ok( + second.some((l) => /^0002 .*NATIVE EXCEPTION/.test(l)), + J(second), + ); + assert.equal(second.filter((l) => DUMP.test(l)).length, 0, J(second)); + assert.equal(await k.pid(), pid, "the compiler was restarted"); + }); + + test("control: after the next restart the same exception prints the dump, a line per thread", async () => { + await restartAndWait(); + const lines = await crash(); + assert.ok( + lines.some((l) => /^0001 .*NATIVE EXCEPTION/.test(l)), + J(lines), + ); + assert.ok( + lines.some((l) => DUMP.test(l)), + J(lines), + ); + assert.ok( + lines.some((l) => /^>>> thread [0-9A-F]+: .*Crash/.test(l)), + J(lines), + ); + }); + + test("no error report leaves a tool's IDE: the defaults hook answers each one, and nothing else went out", async (t) => { + const reports = await k.c.evaluate("window.__tbDefaults.reports"); + t.diagnostic(`reports answered by the hook: ${reports}`); + await k.c.evaluate("g_ErrorReporting = false, 0"); + out.on = false; + await k.c.send("Fetch.disable"); + assert.ok(reports > 0, "the hook answered no report"); + assert.equal(out.stopped, 0, "a request reached CDP's block"); + assert.deepEqual(out.hosts, []); + // The crashes are asserted: the lane's close is not to fail on them. + await k.cmd("tbDebugConsole_Clear"); + }); + + // ------------------------------------------------------------- N3, DevTools + + test("control: with DevTools closed, Restart brings the compiler back", async () => { + await k.fromClean(k.probe("Closed")); + assert.deepEqual(await k.devTools(), []); + await restartAndWait(); + assert.equal((await k.services()).badge, "tB Services: OPERATIONAL"); + }); + + test("with DevTools open, a compiler restart stops at a debugger statement in the IDE's code until DevTools closes", { + timeout: 90 * 1000, + }, async () => { + await k.fromClean(k.probe("Paused")); + const mine = await k.userDevTools(); + const added = async () => (await k.userDevTools()).filter((h) => !mine.includes(h)); + try { + await k.openDevTools(() => pressKey(k.c, "F12", { ctrl: true, shift: true })); + assert.deepEqual(await added(), [], "a DevTools window is on the user's desktop"); + assert.equal((await k.services()).badge, "tB Services: OPERATIONAL"); + const pid = await k.pid(); + const m = await k.mark(); + await k.restart(); + await sleep(8 * 1000); + const s = await k.services(); + assert.equal(s.badge, "tB Services: UNAVAILABLE"); + assert.deepEqual(s.tip, DISCONNECTED); + assert.equal(await k.c.evaluate("isRestarting"), false, "it has gone past the statement"); + assert.deepEqual(await k.modals(), []); + assert.deepEqual(await k.since(m), []); + + await k.closeDevTools(); + await newCompiler(pid); + assert.ok((await k.since(m)).some((l) => /^restarting from MEMORY /.test(l))); + assert.deepEqual(await added(), [], "a DevTools window is on the user's desktop"); + } finally { + await k.closeDevTools(); + } + }); + + // ------------------------------------------------------------- D4, an IDE of its own + + test("a compiler connection that cannot be made restarts the compiler every 5 s, and the crash-loop message never fires", { + timeout: 120 * 1000, + }, async () => { + await lane.closeProject(); + await k.startIde({}, { start: k.startWith([...IDE_FLAGS, "--debug"]) }); + await k.noProject(); + const file = k.probe("NoConnection"); + await k.c.evaluate( + `(() => { window.__created = []; const real = window.createCompilerInstance; window.createCompilerInstance = function (...r) { window.__created.push(Math.round(performance.now())); return real.apply(this, r); }; })()`, + ); + const m = await k.mark(); + await k.setAnswer("OpenProject", file); + await k.cmd("tbProject_Open"); + await sleep(27 * 1000); + + const created = await k.c.evaluate("window.__created"); + assert.ok(created.length >= 5, J(created)); + const gaps = created.slice(1).map((t, i) => t - created[i]); + assert.ok( + gaps.every((g) => g > 4000 && g < 7000), + J(gaps), + ); + assert.ok((await k.since(m)).filter((l) => /^restarting from FILE/.test(l)).length >= 4, J(await k.since(m))); + assert.equal(await k.c.evaluate("autoRestartHistory.length"), 0); + const s = await k.services(); + assert.equal(s.badge, "tB Services: UNAVAILABLE"); + assert.notEqual(await k.c.evaluate("buildConfigSelector.value"), "nocompile"); + assert.equal((await k.events()).filter((e) => /crash loop/i.test(e)).length, 0, "the crash-loop message fired"); + await lane.closeProject(); + }); +}); diff --git a/test/shots/manifest.json b/test/shots/manifest.json index 0b1eb1807..81198445d 100644 --- a/test/shots/manifest.json +++ b/test/shots/manifest.json @@ -4,11 +4,83 @@ "Features/Images/017bd6f8-4b35-43a9-b6be-84cba69daf64.png": { "build": 1034 }, + "Features/Images/021f6cbf-acce-445d-ade7-3fcad0af4927.png": { + "png": { + "decision": { + "keep": "old", + "capture": "33a0dda9fbda139fd18f2b454f8b416ee912a88b" + } + } + }, + "Features/Images/0aeb25f6-d864-4ebb-a9f5-bbd7b5d242e8.png": { + "build": 1034 + }, + "Features/Images/3fa1cf2b-0af5-44ae-ae6a-3c0662f51f57.png": { + "build": 1034 + }, + "Features/Images/4829696d-788b-40ee-bebd-5afa44477460.png": { + "build": 1034 + }, + "Features/Images/4ad9c774-b31d-47d3-9963-6d99ac4f37bb.png": { + "build": 1034 + }, + "Features/Images/599a66ad-31d5-449f-bbf5-00963fe9aa2a.png": { + "build": 1034 + }, + "Features/Images/5fc60b7b-4f54-445c-8504-451019b7ec55.png": { + "build": 1034 + }, + "Features/Images/80185a8d-2952-415f-bc02-ec3ddea89568.png": { + "build": 1034 + }, + "Features/Images/85f25aa2-abc8-4d42-8510-078f8ee4a324.png": { + "build": 1034 + }, "Features/Images/IDE-FeatureMap.light.png": { "build": 1034 }, "Features/Images/IDE-FeatureMap.png": { - "build": 1033 + "build": 1034, + "png": { + "decision": { + "keep": "new", + "capture": "b97ebc39f181ff0e324866981c3f4f04d9c06c37" + } + }, + "svg": { + "decision": { + "accept": true, + "svg": "2850d9c97829c24d7f71c0f0b320353c17426c64", + "png": "b97ebc39f181ff0e324866981c3f4f04d9c06c37" + } + } + }, + "Features/Images/bc9f3756-a14b-4ee7-b819-6822497b640a.png": { + "build": 1034, + "png": { + "decision": { + "keep": "new", + "capture": "161946ecc38fc464182ab17fc30189f8b1716999" + } + } + }, + "Features/Images/fddbffa9-2b71-47f5-b925-e67fc66b9e5c.png": { + "build": 1034 + }, + "Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.light.png": { + "build": 1034 + }, + "Features/Packages/Images/6ad7a172-0e1b-4276-ac89-042681552507.png": { + "build": 1034 + }, + "Features/Packages/Images/e35d5955-9e70-4d6e-abd7-748558da75ba.png": { + "build": 1034 + }, + "Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.light.png": { + "build": 1034 + }, + "Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.png": { + "build": 1034 }, "Features/Packages/Images/f66fb240-dcb7-46ee-896d-78c3789a7876.png": { "svg": { @@ -21,7 +93,13 @@ "build": 1034 }, "IDE/AddIns/Images/Help_Hover.light.png": { - "build": 1034 + "build": 1034, + "png": { + "decision": { + "keep": "old", + "capture": "0dc7ba0425596b9219e294b051a42602e4930543" + } + } }, "IDE/AddIns/Images/Help_Window.light.png": { "build": 1033 @@ -29,6 +107,18 @@ "IDE/AddIns/Images/Help_Window.png": { "build": 1033 }, + "IDE/Code/Images/Diagnostics_Menu.light.png": { + "build": 1034 + }, + "IDE/Code/Images/Diagnostics_Menu.png": { + "build": 1034 + }, + "IDE/Code/Images/Diagnostics_Rows.light.png": { + "build": 1034 + }, + "IDE/Code/Images/Diagnostics_Rows.png": { + "build": 1034 + }, "IDE/Code/Images/Find_Project.light.png": { "build": 1033, "png": { @@ -53,12 +143,81 @@ } } }, + "IDE/Debugging/Images/CallStack_Frames.light.png": { + "build": 1034 + }, + "IDE/Debugging/Images/CallStack_Frames.png": { + "build": 1034 + }, + "IDE/Debugging/Images/Error_Panel.light.png": { + "build": 1034 + }, + "IDE/Debugging/Images/Error_Panel.png": { + "build": 1034 + }, + "IDE/Debugging/Images/Paused_Line.light.png": { + "build": 1034 + }, + "IDE/Debugging/Images/Paused_Line.png": { + "build": 1034 + }, + "IDE/Debugging/Images/Paused_Panels.light.png": { + "build": 1034 + }, + "IDE/Debugging/Images/Paused_Panels.png": { + "build": 1034 + }, + "IDE/Forms/Images/Designer_GridOptions.png": { + "build": 1034 + }, "IDE/Forms/Images/Designer_Locked.light.png": { "build": 1033 }, "IDE/Forms/Images/Designer_TabIndex.png": { "build": 1033 }, + "IDE/Forms/Images/Preview_Window.png": { + "build": 1034 + }, + "IDE/Images/CallStack.light.png": { + "build": 1034 + }, + "IDE/Images/CallStack.png": { + "build": 1034 + }, + "IDE/Images/IDE.light.png": { + "build": 1034 + }, + "IDE/Images/IDE_Tour.light.png": { + "build": 1034 + }, + "IDE/Images/IDE_Tour.png": { + "build": 1034 + }, + "IDE/Images/New_Project.light.png": { + "build": 1034 + }, + "IDE/Images/New_Project.png": { + "build": 1034 + }, + "IDE/Images/New_Project_Recent_1.light.png": { + "build": 1034 + }, + "IDE/Images/New_Project_Recent_1.png": { + "build": 1034 + }, + "IDE/Images/New_Project_Recent_2.light.png": { + "build": 1034 + }, + "IDE/Images/New_Project_Recent_2.png": { + "build": 1034 + }, + "IDE/Images/New_Project_Samples.light.png": { + "build": 1034 + }, + "IDE/Images/New_Project_Samples.png": { + "build": 1034 + }, "IDE/Images/Webpage.light.png": { "build": 1033 }, @@ -83,6 +242,12 @@ } } }, + "IDE/Menu/Images/Menu_Help_About.light.png": { + "build": 1034 + }, + "IDE/Menu/Images/Menu_Help_About.png": { + "build": 1034 + }, "IDE/Projects/Images/Settings_Close.light.png": { "build": 1033, "svg": { @@ -103,14 +268,170 @@ } } }, + "IDE/Projects/Images/Title_Changed.light.png": { + "build": 1034 + }, + "IDE/Projects/Images/Title_Changed.png": { + "build": 1034 + }, + "IDE/Starting/Images/Damaged_Box.png": { + "build": 1034 + }, + "IDE/Starting/Images/Error_Reporting.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Error_Reporting.png": { + "build": 1034 + }, + "IDE/Starting/Images/First_Run_Options.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/First_Run_Options.png": { + "build": 1034 + }, + "IDE/Starting/Images/Import_Log.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Import_Log.png": { + "build": 1034 + }, + "IDE/Starting/Images/Licence_Agreement.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Licence_Agreement.png": { + "build": 1034 + }, + "IDE/Starting/Images/Licence_Key.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Licence_Key.png": { + "build": 1034 + }, + "IDE/Starting/Images/Missing_File_Box.png": { + "build": 1034 + }, + "IDE/Starting/Images/New_Standard_EXE.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/New_Standard_EXE.png": { + "build": 1034 + }, + "IDE/Starting/Images/Options_ActiveX.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Options_ActiveX.png": { + "build": 1034 + }, + "IDE/Starting/Images/Options_Sample.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Options_Sample.png": { + "build": 1034 + }, + "IDE/Starting/Images/Options_VBP.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Options_VBP.png": { + "build": 1034 + }, + "IDE/Starting/Images/Recent_VB6.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Recent_VB6.png": { + "build": 1034 + }, + "IDE/Starting/Images/Search_Results.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Search_Results.png": { + "build": 1034 + }, + "IDE/Starting/Images/Title_Unsaved.light.png": { + "build": 1034 + }, + "IDE/Starting/Images/Title_Unsaved.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Crash_Loop.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Crash_Loop.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Floating_Boxes.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Floating_Boxes.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Menu_Help_Trace.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Menu_Help_Trace.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Missing_DLL_Box.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Native_Exception.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Native_Exception.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Please_Wait.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Please_Wait.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Safe_Mode_Bar.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Safe_Mode_Bar.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Services_Limited_Tooltip.light.png": { + "build": 1034 + }, + "IDE/Troubleshooting/Images/Services_Limited_Tooltip.png": { + "build": 1034 + }, "Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.light.png": { "build": 1034 }, "Miscellaneous/Images/0e24eb5c-c9af-49a9-a908-03968b211554.png": { "build": 1034 }, + "Miscellaneous/Images/2a1c71fd-f81c-4bd3-b61a-0f2979e8961f.png": { + "build": 1034 + }, + "Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.light.png": { + "build": 1034 + }, + "Miscellaneous/Images/71ddde83-a091-47e3-b5b8-681954b0639d.png": { + "build": 1034 + }, + "Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.light.png": { + "build": 1034 + }, + "Miscellaneous/Images/7e1cb69c-6db3-4f3f-aea1-c1fae25938a2.png": { + "build": 1034 + }, "Miscellaneous/Images/ac019c1a-dcef-4964-a730-bc5b86c644ba.png": { - "build": 1033 + "build": 1034 + }, + "Tutorials/CustomControls/Images/ccSampleProject.light.png": { + "build": 1034 + }, + "Tutorials/CustomControls/Images/ccSampleProject.png": { + "build": 1034 + }, + "Tutorials/WebView2/Images/tbWebView2Sample0.light.png": { + "build": 1034 + }, + "Tutorials/WebView2/Images/tbWebView2Sample0.png": { + "build": 1034 } } } diff --git a/test/shots/starting/Ledger/Class1.cls b/test/shots/starting/Ledger/Class1.cls new file mode 100644 index 000000000..0c1a8c2e8 --- /dev/null +++ b/test/shots/starting/Ledger/Class1.cls @@ -0,0 +1,15 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True + Persistable = 0 'NotPersistable + DataBindingBehavior = 0 'vbNone + DataSourceBehavior = 0 'vbNone + MTSTransactionMode = 0 'NotAnMTSObject +END +Attribute VB_Name = "Class1" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = True +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit +Public Value As Long diff --git a/test/shots/starting/Ledger/Form1.frm b/test/shots/starting/Ledger/Form1.frm new file mode 100644 index 000000000..446f5bf36 --- /dev/null +++ b/test/shots/starting/Ledger/Form1.frm @@ -0,0 +1,18 @@ +VERSION 5.00 +Begin VB.Form Form1 + Caption = "Form1" + ClientHeight = 3090 + ClientLeft = 60 + ClientTop = 450 + ClientWidth = 4680 + LinkTopic = "Form1" + ScaleHeight = 3090 + ScaleWidth = 4680 + StartUpPosition = 3 'Windows Default +End +Attribute VB_Name = "Form1" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = True +Attribute VB_Exposed = False +Option Explicit diff --git a/test/shots/starting/Ledger/Ledger.vbp b/test/shots/starting/Ledger/Ledger.vbp new file mode 100644 index 000000000..aa4f04831 --- /dev/null +++ b/test/shots/starting/Ledger/Ledger.vbp @@ -0,0 +1,11 @@ +Type=Exe +Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation +Form=Form1.frm +Module=Module1; Module1.bas +Class=Class1; Class1.cls +Startup="Form1" +IconForm="Form1" +ExeName32="Ledger.exe" +Name="Ledger" +CompilationType=0 +OptimizationType=0 diff --git a/test/shots/starting/Ledger/Module1.bas b/test/shots/starting/Ledger/Module1.bas new file mode 100644 index 000000000..d69180ddd --- /dev/null +++ b/test/shots/starting/Ledger/Module1.bas @@ -0,0 +1,5 @@ +Attribute VB_Name = "Module1" +Option Explicit +Public Function Twice(ByVal x As Long) As Long + Twice = x * 2 +End Function diff --git a/test/shots/starting/Missing/Missing.vbp b/test/shots/starting/Missing/Missing.vbp new file mode 100644 index 000000000..7f71d504b --- /dev/null +++ b/test/shots/starting/Missing/Missing.vbp @@ -0,0 +1,8 @@ +Type=Exe +Module=Module1; Module1.bas +Module=Gone; Gone.bas +Startup="Sub Main" +ExeName32="Missing.exe" +Name="Missing" +CompilationType=0 +OptimizationType=0 diff --git a/test/shots/starting/Missing/Module1.bas b/test/shots/starting/Missing/Module1.bas new file mode 100644 index 000000000..59632e0d9 --- /dev/null +++ b/test/shots/starting/Missing/Module1.bas @@ -0,0 +1,4 @@ +Attribute VB_Name = "Module1" +Option Explicit +Sub Main() +End Sub diff --git a/test/shots/starting/Throwaway/Resources/RCDATA/#201 b/test/shots/starting/Throwaway/Resources/RCDATA/#201 new file mode 100644 index 000000000..b16ba5a50 --- /dev/null +++ b/test/shots/starting/Throwaway/Resources/RCDATA/#201 @@ -0,0 +1 @@ +A resource of the probe, RT_RCDATA with the id 201. A project cut in the middle of this file has every source and has lost this resource. The text only has to be long enough to cut in the middle of it, so it goes on for a few more words than it has anything to say, and then it stops here. diff --git a/test/shots/starting/Throwaway/Settings b/test/shots/starting/Throwaway/Settings new file mode 100644 index 000000000..8e8558858 --- /dev/null +++ b/test/shots/starting/Throwaway/Settings @@ -0,0 +1,29 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "StartProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "StartProbe", + "project.exportPathIsV2": true, + "project.id": "{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA00}", + "project.name": "StartProbe", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "ignored": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/shots/starting/Throwaway/Sources/MainMod.twin b/test/shots/starting/Throwaway/Sources/MainMod.twin new file mode 100644 index 000000000..a4b88f642 --- /dev/null +++ b/test/shots/starting/Throwaway/Sources/MainMod.twin @@ -0,0 +1,15 @@ +Module MainMod + + Public Sub Main() + Debug.Print Second.Twice(21) + End Sub + + Public Function Name_Of_Main() As String + Return "the first of the four sources of the probe" + End Function + + Public Function Square(ByVal x As Long) As Long + Return x * x + End Function + +End Module diff --git a/test/shots/starting/Throwaway/Sources/Second.twin b/test/shots/starting/Throwaway/Sources/Second.twin new file mode 100644 index 000000000..0d2281f09 --- /dev/null +++ b/test/shots/starting/Throwaway/Sources/Second.twin @@ -0,0 +1,15 @@ +Module Second + + Public Function Twice(ByVal x As Long) As Long + Return x * 2 + End Function + + Public Function Name_Of_Second() As String + Return "the second of the four sources of the probe" + End Function + + Public Function Thrice(ByVal x As Long) As Long + Return x * 3 + End Function + +End Module diff --git a/test/shots/starting/Throwaway/Sources/Thing.twin b/test/shots/starting/Throwaway/Sources/Thing.twin new file mode 100644 index 000000000..26c64bcba --- /dev/null +++ b/test/shots/starting/Throwaway/Sources/Thing.twin @@ -0,0 +1,13 @@ +Class Thing + + Public Value As Long + + Public Function Name_Of_Thing() As String + Return "the fourth of the four sources of the probe" + End Function + + Public Sub Add(ByVal x As Long) + Value = Value + x + End Sub + +End Class diff --git a/test/shots/starting/Throwaway/Sources/Third.twin b/test/shots/starting/Throwaway/Sources/Third.twin new file mode 100644 index 000000000..3ab50076d --- /dev/null +++ b/test/shots/starting/Throwaway/Sources/Third.twin @@ -0,0 +1,15 @@ +Module Third + + Public Function Half(ByVal x As Long) As Long + Return x \ 2 + End Function + + Public Function Name_Of_Third() As String + Return "the third of the four sources of the probe" + End Function + + Public Function Third_Of(ByVal x As Long) As Long + Return x \ 3 + End Function + +End Module diff --git a/test/shots/troubleshooting/Trouble/Settings b/test/shots/troubleshooting/Trouble/Settings new file mode 100644 index 000000000..fbdceda31 --- /dev/null +++ b/test/shots/troubleshooting/Trouble/Settings @@ -0,0 +1,30 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "Trouble", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "Trouble", + "project.exportPathIsV2": true, + "project.id": "{EAEAEAEA-EAEA-EAEA-EAEA-EAEAEAEAEA01}", + "project.largeAddressAware": true, + "project.name": "Trouble", + "project.optionExplicit": true, + "project.references": [ + { + "id": "{00020430-0000-0000-C000-000000000046}", + "lcid": 0, + "name": "OLE Automation", + "path32": "C:\\Windows\\SysWOW64\\stdole2.tlb", + "path64": "C:\\Windows\\System32\\stdole2.tlb", + "versionMajor": 2, + "versionMinor": 0 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "ignored": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/shots/troubleshooting/Trouble/Sources/MainMod.twin b/test/shots/troubleshooting/Trouble/Sources/MainMod.twin new file mode 100644 index 000000000..75e5f1c59 --- /dev/null +++ b/test/shots/troubleshooting/Trouble/Sources/MainMod.twin @@ -0,0 +1,12 @@ +Module MainMod + Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal d As LongPtr, ByVal s As LongPtr, ByVal n As LongPtr) + + Public Sub Main() + Debug.Print "ran to the end" + End Sub + + ' An access violation in the compiler's process. RtlMoveMemory does nothing when the two addresses are the same. + Public Sub Crash() + CopyMemory 16, 32, 4 + End Sub +End Module diff --git a/test/svgshot.test.mjs b/test/svgshot.test.mjs index b451f3e63..311b45c36 100644 --- a/test/svgshot.test.mjs +++ b/test/svgshot.test.mjs @@ -85,7 +85,9 @@ import { SVG_FAITHFUL, svgFollowsPng, svgRule, + themeTextDifference, } from "../scripts/lib/shot-policy.mjs"; +import { holdsRoot, rootPattern, SHOWN_ROOT, showRoot, TOOL_ROOT } from "../scripts/lib/shot-root.mjs"; import { earlierPngDecision, earlierSvgDecision, @@ -3471,6 +3473,117 @@ describe("which pictures need a look (shot-queue.mjs)", () => { }); }); +describe("the tool's folder as a picture shows it (shot-root.mjs)", () => { + const REAL = "D:\\Work\\docs.v2\\.claude\\tbshots"; + const SHOWN = "C:\\tbshots"; + const pattern = rootPattern(REAL); + const show = (text) => showRoot(text, pattern, SHOWN); + + test("the real folder becomes the shown name, whatever follows it", () => { + assert.equal(show(`${REAL}\\Building\\project.twinproj`), "C:\\tbshots\\Building\\project.twinproj"); + assert.equal(show(`File: ${REAL}\\a.vbp, and ${REAL}\\b.vbp`), "File: C:\\tbshots\\a.vbp, and C:\\tbshots\\b.vbp"); + assert.equal(show(REAL), SHOWN); + }); + + test("in any case, with either slash, and with a doubled one", () => { + assert.equal(show("d:\\work\\DOCS.V2\\.CLAUDE\\TBSHOTS\\x"), "C:\\tbshots\\x"); + assert.equal(show("D:/Work/docs.v2/.claude/tbshots/x"), "C:\\tbshots/x"); + assert.equal(show("D:\\\\Work\\\\docs.v2\\\\.claude\\\\tbshots\\\\x"), "C:\\tbshots\\\\x"); + assert.equal(show("D:/Work\\docs.v2/.claude\\tbshots"), SHOWN); + }); + + test("with no slash before a part that starts with a dot, as Markdown leaves it", () => { + assert.equal(show("File: D:\\Work\\docs.v2.claude\\tbshots\\Ledger.vbp"), "File: C:\\tbshots\\Ledger.vbp"); + assert.equal(show("D:Work\\docs.v2\\.claude\\tbshots"), "D:Work\\docs.v2\\.claude\\tbshots"); + }); + + test("a folder beside it, and text that only looks like it, stay as they are", () => { + for (const text of [`${REAL}2\\x`, `${REAL}-old\\x`, "D:\\Work\\docsXv2\\.claude\\tbshots", "C:\\tbshots\\x", ""]) { + assert.equal(show(text), text); + } + }); + + test("the same pattern works twice running, as a global one must", () => { + assert.equal(show(REAL), SHOWN); + assert.equal(show(REAL), SHOWN); + assert.equal(holdsRoot(REAL, pattern), true); + assert.equal(holdsRoot(REAL, pattern), true); + }); + + test("the guard finds the real folder, and not in an embedded image's bytes", () => { + assert.equal(holdsRoot(`${REAL.toUpperCase()}\\x`, pattern), true); + assert.equal(holdsRoot("C:\\tbshots\\x", pattern), false); + assert.equal(holdsRoot("data:image/png;base64,D+Work+docs/v2/tbshots==", rootPattern("D:/Work")), false); + }); + + test("the folder is in the checkout and the name shown is no real one", () => { + assert.equal(path.relative(REPO_ROOT, TOOL_ROOT), path.join(".claude", "tbshots")); + assert.equal(SHOWN_ROOT, SHOWN); + }); +}); + +describe("what a picture's two themes say (themeTextDifference)", () => { + const OPTIONS = ["Project Name", "Option Explicit ON"]; + // What a pass read: blocks that show whole, and blocks an edge of the clip cuts. + const read = (whole, cut = []) => ({ whole, cut }); + const differ = (dark, light, allow) => themeTextDifference(read(dark), read(light), allow); + + test("the same text is no difference", () => { + assert.equal(differ(OPTIONS, [...OPTIONS]), null); + assert.equal(differ([], []), null); + }); + + test("white space does not count, wherever a theme wraps the text", () => { + assert.equal(differ(["More components..."], ["More\ncomponents..."]), null); + assert.equal(differ(["More components..."], ["More compon ents. .."]), null); + assert.equal(differ(["Project", "Name"], ["Project Name"]), null); + }); + + test("the same blocks in another order are no difference", () => { + assert.equal(differ(["Alpha", "Beta"], ["Beta", "Alpha"]), null); + }); + + test("a real difference names the words on each side", () => { + const include = ["Include Common Controls", "(adds a reference to WinNativeCommonCtls)"]; + const unicode = ["Unicode Common Control Notifications", "(affects manual WM_NOTIFY handling)"]; + assert.deepEqual(differ([...OPTIONS, ...include], [...OPTIONS, ...unicode]), { + dark: ["Include", "Controls", "(adds", "a", "reference", "to", "WinNativeCommonCtls)"], + light: ["Unicode", "Control", "Notifications", "(affects", "manual", "WM_NOTIFY", "handling)"], + }); + }); + + test("a word shown more often on one side counts", () => { + assert.deepEqual(differ(["Sub Sub"], ["Sub"]), { dark: ["Sub"], light: [] }); + }); + + test("a line an edge cuts in one theme answers for the same line whole in the other", () => { + assert.equal(themeTextDifference(read(["24", "25"]), read(["24"], ["25"])), null); + assert.equal(themeTextDifference(read(["24"], ["25"]), read(["24", "25"])), null); + }); + + test("a line an edge cuts in one theme and the other does not show is no difference", () => { + assert.equal(themeTextDifference(read(["24"], ["25"]), read(["24"])), null); + assert.equal(themeTextDifference(read(["24"]), read(["24"], ["price: 3"])), null); + }); + + test("a line whole in one theme and not shown in the other is a difference", () => { + assert.deepEqual(themeTextDifference(read(["24", "25"], ["26"]), read(["24"], ["26"])), { + dark: ["25"], + light: [], + }); + }); + + test("an allowance takes out the words it names, each on its own side, and no others", () => { + const allow = { dark: ["Dark"], light: ["Light"] }; + assert.equal(differ(["Colour Theme", "Dark"], ["Colour Theme", "Light"], allow), null); + assert.deepEqual(differ(["Colour Theme", "Dark", "win32"], ["Colour Theme", "Light"], allow), { + dark: ["win32"], + light: [], + }); + assert.deepEqual(differ(["Light"], ["Dark"], allow), { dark: ["Light"], light: ["Dark"] }); + }); +}); + describe("what becomes of a picture and its SVG (shot-policy.mjs, shot-apply.mjs)", () => { // A PNG's header with its size, and a tag that tells two of one size apart. const shot = (w, h, tag) => { diff --git a/wisdom/PLAN-1.md b/wisdom/PLAN-1.md index b689ea9ae..42e5fe650 100644 --- a/wisdom/PLAN-1.md +++ b/wisdom/PLAN-1.md @@ -49,18 +49,20 @@ Token is read from the `DISCORD_TOKEN` environment variable, with a fallback to ``` Guild -├── Text channels (type 0) regular discussion, announcements -├── Forum channels (type 15) Q&A, support, show & tell +├── Text channels (type 0) regular discussion +├── Announcement channels (type 5) releases +├── Forum channels (type 15) Q&A, support, show & tell +│ (each of the three can hold threads) │ ├── Active threads │ └── Archived threads (paginated) -└── Voice, stage, categories skipped +└── Voice, stage, categories skipped ``` -**Step 1 — Channel list.** `GET /guilds/{guild_id}/channels` returns all channels in the server. Keep text channels (type 0) and forum channels (type 15); skip everything else. Channel objects are preserved in full — in particular, forum channels carry an `available_tags` array that Phase 2 needs to resolve thread tag IDs to display names. +**Step 1 — Channel list.** `GET /guilds/{guild_id}/channels` returns all channels in the server. Keep text channels (type 0), announcement channels (type 5) and forum channels (type 15); skip everything else. Channel objects are preserved in full — in particular, forum channels carry an `available_tags` array that Phase 2 needs to resolve thread tag IDs to display names. -**Step 2 — Active threads.** `GET /guilds/{guild_id}/threads/active` returns all currently open threads across the entire guild. Filter to threads whose `parent_id` is one of the forum channels from Step 1. This endpoint is bot-only; user tokens receive a 403, which the tool handles by skipping active thread discovery and logging a warning. +**Step 2 — Active threads.** `GET /guilds/{guild_id}/threads/active` returns all currently open threads across the entire guild. Filter to threads whose `parent_id` is one of the channels from Step 1. This endpoint is bot-only; user tokens receive a 403, which the tool handles by skipping active thread discovery and logging a warning. -**Step 3 — Archived threads.** `GET /channels/{channel_id}/threads/archived/public?limit=100` paginates archived threads per forum channel. Paginate using the `has_more` flag and the `before` query parameter (ISO 8601 timestamp of the last thread's `archive_timestamp`, URL-encoded). Forum channels that return 403 (restricted access) are skipped with a warning. +**Step 3 — Archived threads.** `GET /channels/{channel_id}/threads/archived/public?limit=100` paginates archived threads per channel. Paginate using the `has_more` flag and the `before` query parameter (ISO 8601 timestamp of the last thread's `archive_timestamp`, URL-encoded). Channels that return 403 (restricted access) are skipped with a warning. **Step 4 — Message fetch.** For each thread (and each in-scope text channel), fetch messages via `GET /channels/{channel_id}/messages?limit=100&before={snowflake}`, paginating until the response is empty or the incremental cutoff is reached. @@ -154,11 +156,11 @@ data/raw/ "log-.*" ], "exclude_ids": [], - "include_types": ["text", "forum"] + "include_types": ["text", "announcement", "forum"] }, "threads": { - "min_message_count": 1 + "min_message_count": 0 }, "export": { @@ -186,9 +188,9 @@ data/raw/ **`channels.exclude_ids`** — a fallback for channels whose names are ambiguous or that lack a useful name pattern. Matched by exact Snowflake ID string. -**`channels.include_types`** — limit discovery to `"text"` (type 0) and/or `"forum"` (type 15) channels. Both are included by default; remove `"text"` to skip regular text channels entirely if the server's knowledge is concentrated in forum threads. +**`channels.include_types`** — limit discovery to `"text"` (type 0), `"announcement"` (type 5) and/or `"forum"` (type 15) channels. All three are included by default; remove `"text"` to skip regular text channels entirely if the server's knowledge is concentrated in forum threads. -**`threads.min_message_count`** — threads with fewer messages than this threshold are not fetched. Default: 1 (skips only empty/deleted threads with zero messages). +**`threads.min_message_count`** — threads with fewer messages than this threshold are not fetched. Default: 0, which fetches every thread. Discord's count leaves out a thread's first message, so a forum post nobody answered counts 0 and a threshold of 1 would skip it. Phase 2 decides what such a post is worth; the raw export holds it either way. **`export.concurrency`** — default parallel fetch count, overridable at runtime via `--concurrency`. @@ -208,7 +210,7 @@ The config is loaded at startup and merged with any CLI flags; explicit CLI flag node wisdom/wisdom.mjs export [options] --guild Guild (server) ID [required, or DISCORD_GUILD_ID env var] - --channel Restrict to this channel ID (repeatable; default: all text + forum channels) + --channel Restrict to this channel ID (repeatable; default: all text, announcement and forum channels) --since Fetch only content after this ISO 8601 date (overrides manifest) --force Ignore manifest; re-fetch complete history --out Output directory [default: wisdom/data/raw] diff --git a/wisdom/PLAN-2.md b/wisdom/PLAN-2.md index d0e96b3dc..332d88f98 100644 --- a/wisdom/PLAN-2.md +++ b/wisdom/PLAN-2.md @@ -106,11 +106,12 @@ top_reactions: ``` data/threads/ {channel_name}.md text channel (one file per channel) - {forum_channel_name}/ - {thread_id}--{slugified-title}.md forum thread (one file per thread) + {channel_name}/ + {thread_id}--{slugified-title}.md thread (one file per thread) + {channel_id}-{YYYY-MM}--{channel}-{YYYY-MM}.md a month of a text channel ``` -Forum threads nest under a subdirectory named after the parent forum channel. Text channels produce a single `.md` at the root of `data/threads/`. +Threads nest under a subdirectory named after their parent channel, forum or text. Text and announcement channels produce a single `.md` at the root of `data/threads/`, and one file per calendar month beside their threads, unless `process.unmined_channels` in `config.jsonc` names them. A month has the frontmatter of a thread, so Phase 3 treats it as one; the single file is not read by Phase 3. The `--` separator makes the ID and slug visually distinct without conflicting with slug hyphens. The ID prefix guarantees uniqueness even when two threads share similar titles after slugification. @@ -155,7 +156,7 @@ Code blocks in Discord messages (backtick-fenced, with or without a language hin Process applies only light, mechanical filtering — not relevance filtering (Phase 3's job): -- **System messages** — skip messages whose `type` is not 0 (DEFAULT) or 19 (REPLY). Type 19 messages are Discord's "reply to a specific message" — they carry real user content and the `message_reference` that the renderer uses for reply threading. Types to skip include joins (7), pins (18), thread-created markers (21), etc. +- **System messages** — skip messages whose `type` is not 0 (DEFAULT) or 19 (REPLY). Type 19 messages are Discord's "reply to a specific message" — they carry real user content and the `message_reference` that the renderer uses for reply threading. Types to skip include joins (7), thread-created markers (18), etc. A thread started from a text channel's message opens with a type 21 stub, because that message is in the parent channel; the stub's `referenced_message` is kept in its place, so the thread keeps its opening post. - **Empty threads** — skip threads with zero non-system messages after the starter. For text channels, skip if zero non-system messages total. - **Bot-only** — skip threads (or text channels) where every message author has `bot: true`. diff --git a/wisdom/PLAN-3.md b/wisdom/PLAN-3.md index fe955dc69..54adfbc3d 100644 --- a/wisdom/PLAN-3.md +++ b/wisdom/PLAN-3.md @@ -318,7 +318,7 @@ New findings merge into the existing `staging.md` without disturbing pending rev - `last_message_id` and `message_count` are read from each thread's frontmatter (emitted by Phase 2) and compared against the file's current values. A thread is **included** for re-extraction iff either field differs from the stored value, or the thread isn't in `processedThreads` at all. - `emitted` records the `(target_page, section)` pairs this thread has previously contributed to `staging.md`. The merge step uses this to detect "previously-emitted finding that's no longer in `staging.md`" and emit the `[REFINED?]` marker described above. -- State is updated only on **successful merge**. A workflow failure mid-pipeline leaves state untouched, so the next run retries the same threads. +- State is updated only on **successful merge**. A workflow failure mid-pipeline leaves state untouched, so the next run retries the same threads. The merge records every thread the workflow returns in `read`, whether or not it gave an addition; a group whose agent died is not in `read`, so the next run reads it again. ### Why `last_message_id`, not filesystem mtime diff --git a/wisdom/config.jsonc b/wisdom/config.jsonc index 5e0a49ebf..77b5383a3 100644 --- a/wisdom/config.jsonc +++ b/wisdom/config.jsonc @@ -11,13 +11,14 @@ ], // Channel IDs to exclude by exact match. "exclude_ids": [], - // Channel types to include: "text" (type 0) and/or "forum" (type 15). - "include_types": ["text", "forum"] + // Channel types to include: "text" (type 0), "announcement" (type 5) and/or "forum" (type 15). + "include_types": ["text", "announcement", "forum"] }, "threads": { - // Skip threads with fewer messages than this. - "min_message_count": 1 + // Skip threads with fewer messages than this. Discord's count leaves out a thread's + // first message, so a forum post nobody answered counts 0, and 1 would skip it. + "min_message_count": 0 }, "export": { @@ -25,6 +26,13 @@ "concurrency": 3 }, + "process": { + // Text channels left out of the monthly files the extract step reads. Their one-file + // dump is still written, and their threads are still read. A month of #general is + // several hundred KB, so taking it off this list costs far more than any other channel. + "unmined_channels": ["general", "off-topic", "docs", "rules"] + }, + // Tool-imposed rate and session limits, per token type. // Auto-detection selects the active tier; see Authentication in PLAN-1. "limits": { diff --git a/wisdom/data/findings/staging.md b/wisdom/data/findings/staging.md index ec665e47f..554b538f5 100644 --- a/wisdom/data/findings/staging.md +++ b/wisdom/data/findings/staging.md @@ -2140,14995 +2140,18403 @@ _Reviewer note: UNMAPPED. Package: Core, no symbol. No existing page covers touc --- -## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1083431012417155154, 1448280876780752936, 1417830539024662548, 1437514645354315888] +## UNMAPPED · new-section > [!NOTE] > -> A type library (`.tlb`) compiled for 64-bit only (for example, built with MIDL's `/env amd64` flag) will silently fail to load if the current project is configured for 32-bit mode. No architecture-mismatch error is shown, making the cause easy to misattribute. To add such a reference successfully, switch the project to 64-bit mode first, then add the TLB. If a TLB that loads correctly in a tool like OLEView still fails to load in the IDE, restarting all open twinBASIC IDE instances (not just the affected one) may clear the problem. +> External tools and AI agents that edit twinBASIC source or `.twin` project files directly must write them as UTF-8 text. The default write tool of some agents uses an encoding that twinBASIC cannot read correctly. Whether a byte-order mark is required is unconfirmed. Importing and building are interactive IDE operations, so an agent cannot detect a failed build and the build is normally started by hand. -_Source threads: 1160439577429954620 · confidence: medium_ -_Date range: 2023-10-08_ -_Reviewer note: Verify whether the silent-fail behavior has since been improved with a clearer error message in later IDE builds._ +_Source threads: 1478865333245837444-2026-09 · confidence: low_ +_Date range: 2026-09-11 to 2026-09-20_ +_Reviewer note: Package tbIDE, symbol none. Not an addin-SDK topic; better placed on a project-file format or IDE workflow page. BOM requirement is unconfirmed by the source (user unsure). The remark about planned command-line builds was omitted because it is a roadmap statement._ --- -## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1448280876780752936, 1417830539024662548, 1437514645354315888] +## UNMAPPED · after-remarks > [!NOTE] -> The Windows Recovery Environment (WinRE) does not include the WoW64 compatibility subsystem, so 32-bit executables will not run there. A 64-bit twinBASIC build is required for utilities that must operate outside a full Windows installation. +> In a COM add-in, a ribbon `getImage` callback that returns the result of **LoadPicture** applied to **LoadResData** (for PNG or BMP data) as an **IPictureDisp** displays its icons in Excel 2013 and later, but not in 64-bit Excel 2010. This is an unresolved defect, possibly in Office itself. Splitting the expression into separate statements and checking that the resulting **StdPicture** is valid helps to isolate the cause. -_Source threads: 1083431012417155154 · confidence: low_ -_Date range: 2023-03-17_ -_Reviewer note: Sourced from a single Discord post; verify that WinRE genuinely lacks WoW64 before publishing. The claim is plausible but has not been cross-checked against Microsoft documentation._ +_Source threads: 1408374498268414043 · confidence: low_ +_Date range: 2025-08-22 to 2026-10-12_ +_Reviewer note: Package VBA, symbol LoadPicture. No LoadPicture page exists in the index; triage placement (possibly a COM add-in or Global.LoadPicture page). Thread says it began in tb848, still present in TB1005; unresolved and unconfirmed (Wayne could not reproduce)._ --- -## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1417830539024662548, 1437514645354315888] +## UNMAPPED · after-remarks > [!NOTE] -> The roadmap item for running 32-bit components inside a 64-bit twinBASIC process via a proxy process covers **32-bit ActiveX controls only**. It does not extend to general-purpose 32-bit COM DLLs (such as RichClient5/6). Even for ActiveX controls, graphical operations that rely on Windows device contexts (DCs) are unlikely to work correctly when the control runs in a separate proxy process. For non-graphical 32-bit COM servers, the Windows OS-level `DLLSurrogate` mechanism may work as an alternative. +> The IDE is built as a web page rather than from native Windows controls. Long lists, such as the editor and the Debug Console, are virtual lists that render only the visible rows, because rendering every element would slow the IDE down. As a result, keyboard selection (for example Shift+Up in the Debug Console) can continue into the adjacent panel. -_Source threads: 1448280876780752936 · confidence: high_ -_Date range: 2025-12-10_ -_Reviewer note: Verify the proxy-process roadmap scope against the twinBASIC roadmap or release notes before publishing; the Q3 2026 timeline and "ActiveX controls only" scope came from a Discord thread._ +_Source threads: 1550544438290554941 · confidence: high_ +_Date range: 2026-09-18_ +_Reviewer note: Package Core, no symbol. Suggested placement: docs/IDE/index.md or the Debug Console page under docs/IDE/Debugging._ --- -## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1448280876780752936, 1437514645354315888] +## UNMAPPED · after-remarks > [!NOTE] -> -> When porting VB6 tooltip code that sets the `TTS_BALLOON` style via `GetWindowLong` / `SetWindowLong`, replace both calls with `GetWindowLongPtr` / `SetWindowLongPtr` for 64-bit targets. The non-`Ptr` variants silently return without applying the style in a 64-bit process, resulting in square tooltips instead of the balloon appearance. This is a standard Win32 64-bit porting requirement that applies to any window-style manipulation, not only tooltips. +> The editor has been reported to remove a space on an unrelated line, away from the text being edited. One case turned an `Attribute` line with a trailing comment into a syntax error, because the space before the comment was lost. Reports link this to malformed definitions, such as a **Property Get** without parentheses, that were pasted or imported earlier. If a line changes unexpectedly, check the file for such definitions. -_Source threads: 1417830539024662548 · confidence: medium_ -_Date range: 2025-09-17_ -_Reviewer note: Confirm that GetWindowLong vs GetWindowLongPtr behaviour on 64-bit is consistent with the Win32 documentation; the thread reports this from practical observation._ +_Source threads: 1535419475837591582 · confidence: low_ +_Date range: 2026-08-07 to 2026-08-09_ +_Reviewer note: Package Core, no symbol. Unresolved, intermittent report; the cause is unconfirmed, so consider holding back. Suggested placement: docs/IDE/Troubleshooting/index.md._ --- -## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1448280876780752936, 1417830539024662548] +## UNMAPPED · after-remarks > [!NOTE] -> When importing resources from a VB6 `.res` file that embeds a manifest, the manifest's `processorArchitecture` attribute is typically set to `x86`. An `x86` manifest causes 64-bit builds to fail or behave incorrectly. Change `processorArchitecture` to `*` (the wildcard value) in the manifest XML so the same embedded manifest applies to both 32-bit and 64-bit builds. +> When the Windows taskbar is set to auto-hide, a maximised twinBASIC IDE window does not reveal it when the mouse moves to the screen edge. Enabling the IDE setting **IDE: Show real OS titlebar** and restarting the IDE fixes this, because the IDE then behaves like a normal window instead of a borderless window with a drawn titlebar. The setting affects only the look and behaviour of the IDE, not compiled applications. -_Source threads: 1437514645354315888 · confidence: medium_ -_Date range: 2025-11-28_ +_Source threads: 1560548683731308624 · confidence: high_ +_Date range: 2026-10-16_ +_Reviewer note: Package Core, no symbol: IDE setting. Find a suitable IDE settings page; none was resolved._ --- -## docs/Features/64bit.md · after-remarks +## UNMAPPED · after-remarks > [!NOTE] -> WinDevLib supersedes the oleexp type library and covers more APIs and interfaces, but it is not a drop-in replacement. Newer DirectX APIs use `ByVal` UDT parameters and same-interface overloads, which twinBASIC supports and VB6 does not, so VB6 workarounds such as passing points through **Currency** conflict with them. Switching an existing VB6 project to WinDevLib can therefore produce hundreds of errors. WinDevLib can be built with the `ByVal` UDT declarations disabled, which reduces the errors where VB6 compatibility must be kept. Type libraries such as oleexp still work in twinBASIC, but only in 32-bit projects. +> To block inline on a WinRT asynchronous operation, a completion handler that calls **SetEvent** can wake a **MsgWaitForMultipleObjects** wait. The handler must also answer `IAgileObject`, because WinRT sometimes runs the operation synchronously and posts no message; without agility the thread deadlocks. The handler should report itself as agile only when it is used for the event wait. In regular callback mode it must not be agile, or it crashes when called from another thread. +> +> **CoWaitForMultipleHandles** and **CoWaitForMultipleObjects**, including with `COWAIT_DISPATCH_CALLS`, did not wake the thread on completion and produced the system "application is busy" dialog. **MsgWaitForMultipleObjects** was the only API that worked. -_Source threads: 1513860400305672302 · confidence: medium_ -_Date range: 2026-06-09 to 2026-06-10_ -_Reviewer note: Placed after the WinDevLib paragraph on the 64-bit page because of the 32-bit-only typelib point. Package-specific, third-party statement from a Discord thread; verify the build option for disabling ByVal UDT declarations against the WinDevLib README before publishing._ +_Source threads: 1565317933645832373 · confidence: medium_ +_Date range: 2026-11-03 to 2026-11-04_ +_Reviewer note: Package Core, symbols MsgWaitForMultipleObjects / CoWaitForMultipleHandles / WinRT async interop (no matching page in the index). Triage placement; user-reported experience, not verified._ --- -## docs/Features/Advanced/API-Declarations.md · after-remarks [DUPLICATE? -- see also thread 1086977208171634708] +## UNMAPPED · after-remarks > [!NOTE] -> **DeclareWide** is particularly useful when migrating VB6 code that aliases an ANSI entry point (e.g. `Alias "ThingA"`). Replacing `Declare` with `DeclareWide` and updating the alias to the wide entry point (e.g. `"ThingW"`) enables Unicode operation while leaving all **String** parameter types and call sites unchanged. +> Importing `mswinsck.ocx` (the Winsock control) into a project has been reported to cause a runtime error each time **Project Settings** is opened, with errors in the Debug Console and an empty COM references list. Unregistering the OCX or removing it from the enabled libraries did not help in the reported case; only running a fresh copy of twinBASIC restored the page. -_Source threads: 1303293694950244352 · confidence: medium_ -_Date range: 2024-11-06_ -_Reviewer note: The finding targets 'Core/DeclareWide', which has no standalone page in the reference index. The closest existing location is the DeclareWide section in docs/Features/Advanced/API-Declarations.md. Verify placement and consider whether a cross-reference from docs/Reference/Core/Declare.md is also needed._ +_Source threads: 1566511728747876472 · confidence: low_ +_Date range: 2026-11-01_ +_Reviewer note: Package Core, no symbol. Candidate placement: docs/IDE/Project Settings.md or docs/IDE/Troubleshooting/index.md. Single unconfirmed report, no maintainer response; may be a defect that has since been fixed._ --- -## docs/Features/Advanced/API-Declarations.md · after-remarks [DUPLICATE? -- see also thread 1303293694950244352] +## UNMAPPED · after-remarks > [!NOTE] -> From BETA 283 onwards, the IDE hover tooltip explicitly identifies whether a declaration uses **Declare** or **DeclareWide**, along with other declaration attributes such as **CDecl** and **Alias**. This distinction prevents the common mistake of treating a **DeclareWide**-declared function as ANSI and adding an unnecessary separate W-suffixed declaration. +> An executable that supplies only a single small icon (for example one 64x64, 16-colour image) can look oversized or framed in modern app switchers. Supplying the full range of icon sizes, including a 256x256 PNG, lets Windows render a natural large icon. -_Source threads: 1086977208171634708 · confidence: high_ -_Date range: 2023-03-19 to 2023-03-28_ -_Reviewer note: Insert after the DeclareWide section or as a closing note within it. Verify that BETA 283 is still the correct version boundary._ +_Source threads: 1564969798088134656 · confidence: low_ +_Date range: 2026-10-28 to 2026-11-02_ +_Reviewer note: Package Core, no symbol. Originally observed on the twinBASIC IDE exe in a Parallels VM; the general tip about app icon resources may fit the project icon documentation. Not maintainer-confirmed. Dropped the App.Path hidden-module finding because Path.md already states the IDE behaviour and the ordinal-based declare is an unsupported internal detail._ --- -## docs/Features/Advanced/API-Declarations.md · after-remarks +## UNMAPPED · after-remarks > [!NOTE] -> `DeclareWide` changes only how **String** arguments are passed: they go to the API as wide (UTF-16) text, without conversion to ANSI. It does not choose which function is called. The `A` or `W` suffix comes from the declared name or its **Alias**, as in the example above. Using `DeclareWide` on an ANSI entry point such as `PostMessageA` passes wide text to a function that expects ANSI, and caused edge-case problems in WinDevLib. +> Building very large strings by repeated concatenation is slow. Algorithms that grow strings to millions of characters in a hot loop run much faster when rewritten to work on arrays of `Long` values. -_Source threads: 1125359845936205844 · confidence: medium_ -_Date range: 2023-12-18 to 2026-09-02_ -_Reviewer note: There is no Core/DeclareWide page, so this targets the page that documents DeclareWide. Insert directly after the DeclareWide WARNING callout. The last sentence is an inference from a WinDevLib bug report about PostMessageA; verify the wording. The thread's remark about the built-in hidden-module declarations is omitted because docs/Reference/Attributes.md already shows that form._ +_Source threads: 927641929905209405-2022-11 · confidence: low_ +_Date range: 2022-11-09_ +_Reviewer note: Package: Core, no symbol. Single anecdote (Advent of Code 2015 Day 10: 6 s with Long arrays vs hours with strings), from 2022-11; may no longer hold. Needs placement (e.g. a performance page) and verification._ --- -## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1155949699585540216, 1503093611246391338, 1507659583437668512] +## UNMAPPED · after-remarks -## Porting VB6 Assembly Thunks +> [!NOTE] +> Forms from the WinNativeForms package and controls from the CustomControls package cannot be mixed. CustomControls are not usable on a WinNativeForms form, and the reverse is not supported either. -VB6 projects often embed raw machine code as base64-encoded strings, decoding them at run time into an executable memory block and calling the resulting function pointer. This technique was used primarily to call CDecl functions or to perform low-level operations without a `Declare` statement. It relies on internal VB6 runtime implementation details that twinBASIC does not replicate, so these thunks do not work when porting such a project. +_Source threads: 927641929905209405-2022-02 · confidence: high_ +_Date range: 2022-02-17 to 2022-02-18_ +_Reviewer note: Package: WinNativeForms, no symbol. Source said CustomControls on WinNativeForms was planned (not the reverse), as of Feb 2022; verify current status before publishing. Candidate placement: docs/IDE/Forms/Designer.md or the WinNativeForms package page._ -Replace them with twinBASIC-native equivalents: +--- -- Use `Emit()` inside a `[Naked]` function (as shown above) to inject the same byte sequence directly into the compiled output, with no run-time decoding step. -- Use a typed [**Delegate**](../../Features/Language/Delegates) function pointer and a `CDecl Declare` to call a CDecl API cleanly without any thunk. +## UNMAPPED · new-section -See [Enhanced API Declarations](API-Declarations) for `CDecl` support on `Declare` statements and callbacks. +> [!NOTE] +> When text read through ADO or ODBC arrives corrupted, the cause is often the driver configuration rather than twinBASIC string handling. Some drivers return character columns as narrow bytes unless told to report them as wide characters. For the Informix ODBC driver, setting `REPORTCHARCOLASWIDECHARCOL=1` in the ODBC configuration makes character columns arrive as wide-character (Unicode) data. Check the driver's Unicode settings first when such text is garbled. -_Source threads: 1111087497389441095 · confidence: medium_ -_Date range: 2023-05-25 to 2023-05-29_ +_Source threads: 927641929905209405-2025-07 · confidence: medium_ +_Date range: 2025-06-30 to 2025-07-01_ +_Reviewer note: UNMAPPED: package VBA, no symbol. Driver-specific (Informix) ADO/ODBC tip; place on an ADODB/database page if one exists, or the StrConv page near the UTF-8 note._ --- -## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1503093611246391338, 1507659583437668512] +## UNMAPPED · new-section -> [!IMPORTANT] -> When executing code written into a stack-allocated byte array and called through a function pointer (for example, `CallWindowProcA` used as a trampoline), twinBASIC enforces Data Execution Prevention (DEP) in two independent places: the **project settings** and the **IDE settings**. Disabling DEP only in the project settings is not sufficient --- if the IDE setting remains enabled, the thunk fails at runtime when run inside the IDE. Both settings must be set to **off** for stack-allocated executable code to work. +> [!NOTE] +> Building a shell context menu for several files through `IShellItemArray.BindToHandler` (with `BHID_SFUIObject` and `IID_IContextMenu`) works reliably only when all items are in the same folder. With items from different folders the call can fail with error 80004005 (automation error), and the result varies between Windows versions and machines. Separately, `IContextMenu.QueryContextMenu` can hang indefinitely for some file types (for example VOB and TTF), even for a single file. +> +> `SHCreateDefaultContextMenu` with a `DEFCONTEXTMENU` structure against the desktop folder works for multiple folders, but it loads no shell extensions. -_Source threads: 1155949699585540216 · confidence: high_ -_Date range: 2023-09-25_ -_Reviewer note: Verify the exact names of both DEP settings in the current twinBASIC IDE and project settings UI, and confirm they remain separate controls._ +To show the full context menu, including shell extensions, for items in several folders, select the items inside a shell view that spans all the folders, such as a Shell Library or search folder containing them, and then request the menu for that selection. Match items by their full-path property rather than by display name, so that same-named files in other folders are not matched. Call `InvokeCommand` only when `TrackPopupMenu` returned a nonzero command; invoking it with no selection crashes on release. A custom `IShellFolder` implementation is a possible alternative. + +_Source threads: 1384432623706968206 · confidence: medium_ +_Date range: 2025-06-17 to 2025-06-18_ +_Reviewer note: UNMAPPED: package Core, symbol IShellItemArray.BindToHandler (no page exists; likely a WinDevLib/Windows-API topic, not Core). Merges two findings. Behaviour varies by Windows version; reported on IDE build 806 win64._ --- -## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1155949699585540216, 1507659583437668512] +## UNMAPPED · after-remarks -## API hooking and memory patching +> [!NOTE] +> The editor does not complete block statements (`If`, `For`, `Select Case` and similar) automatically when the statement starts on a continuation line of a line-continuation (`_`) construct. -### Reading and writing executable memory +_Source threads: 927641929905209405-2026-08 · confidence: low_ +_Date range: 2026-08-19_ +_Reviewer note: UNMAPPED: package Core, no symbol; IDE editor behaviour, needs an IDE editor page. Unknown whether this matches VB6; verify in the IDE before publishing. Note: finding on the terminate-on-link-failure dialog was dropped because docs/IDE/Building/Build-and-Clean.md already covers it._ -`WriteProcessMemory` and `ReadProcessMemory` always have full read/write access to the current process's memory, so they can patch executable pages without first changing the page's protection flags with `VirtualProtect`. Avoiding `VirtualProtect` removes the extra cleanup step and reduces the number of API calls. +--- -### Trampoline stubs and antivirus detection +## UNMAPPED · example -When constructing a trampoline or hook stub, a relative `JMP` opcode is commonly flagged as suspicious by antivirus scanners. Replacing the relative `JMP` with a `PUSH targetAddr` / `RET` sequence achieves the same unconditional transfer and is not flagged. The opcodes differ between x86 and x64: +With a reference to the WinHTTP library added, `WinHttpRequest` can download a file asynchronously and report progress through its events. ```tb -#If Win64 Then - ' x64: PUSH lower32; MOV [rsp+4], upper32; RET - Emit &H68 ' PUSH imm32 (low dword) - Emit targetLow, 0, 0, 0 ' placeholder: fill at runtime - Emit &HC7, &H44, &H24, &H04 ' MOV DWORD PTR [rsp+4], imm32 - Emit targetHigh, 0, 0, 0 ' placeholder: fill at runtime - Emit &HC3 ' RET -#Else - ' x86: PUSH imm32; RET - Emit &H68 ' PUSH imm32 - Emit target, 0, 0, 0 ' placeholder: fill at runtime - Emit &HC3 ' RET -#End If -``` - -_Source threads: 1503093611246391338 · confidence: medium_ -_Date range: 2026-05-11_ -_Reviewer note: The code block is illustrative; confirm exact opcode sequences against the referenced Discord project before publishing. The WriteProcessMemory/ReadProcessMemory preference over VirtualProtect is medium-confidence community advice, not a documented compiler behavior._ - ---- +Private WithEvents m_oDownload As WinHttpRequest -## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1155949699585540216, 1503093611246391338] +Private Sub StartDownload(ByVal Url As String) + Set m_oDownload = New WinHttpRequest + m_oDownload.Open "GET", Url, True ' Async + m_oDownload.Send +End Sub -> [!IMPORTANT] -> Vectored Exception Handler (VEH) techniques that modify the CPU context --- setting `Eip` (32-bit) or `Rip` (64-bit) to skip a faulting instruction --- work only in compiled executables. The same code does not function when run inside the twinBASIC IDE in interpreted (debug) mode. VEH-based safe-copy or access-violation recovery patterns cannot protect code running in the IDE. +Private Sub m_oDownload_OnResponseStart(ByVal Status As Long, ByVal ContentType As String) + ' The response headers are available; Content-Length can be read + ' with GetResponseHeader. +End Sub -_Source threads: 1507659583437668512 · confidence: high_ -_Date range: 2026-05-23_ +Private Sub m_oDownload_OnResponseDataAvailable(Data() As Byte) + ' Called for each received chunk. +End Sub ---- +Private Sub m_oDownload_OnResponseFinished() + ' The download is complete. +End Sub -## docs/Features/Advanced/Multithreading.md · after-remarks +Private Sub m_oDownload_OnError(ByVal ErrorNumber As Long, ByVal ErrorDescription As String) + ' The request failed. +End Sub +``` -## COM on background threads +_Source threads: 1029063722846658655 · confidence: low_ +_Date range: 2022-10-10 to 2022-10-11_ +_Reviewer note: UNMAPPED: package WinHttpRequest, symbol WinHttpRequest (no page in index). Source thread is from beta 145: event handlers failed to compile ('incompatible signature ... must return HRESULT') and win32 downloads were very slow while win64 was fast. Verify the example compiles and the event signatures against the current build before publishing._ -Background threads that need to create or call COM objects must call `OleInitialize` (or `CoInitialize` / `CoInitializeEx`) on the thread before using COM. The main UI thread initialises COM automatically; background threads created with `CreateThread` do not inherit that initialisation. +--- -```tb -Private Declare PtrSafe Function OleInitialize Lib "ole32" ( _ - ByVal pvReserved As LongPtr) As Long -Private Declare PtrSafe Sub OleUninitialize Lib "ole32" () +## UNMAPPED · new-section -Public Sub MyBackgroundThread() - OleInitialize 0 - ' ... use COM objects here ... - OleUninitialize -End Sub -``` +WinRT APIs, including XAML, can be used from twinBASIC directly as native code, with no .NET Framework and no wrapper or bridge. XAML controls such as text boxes, and their bindings, can be created in code, for example in `Form_Load`, or XAML text can be loaded directly. WinRT is available on Windows only. -_Source threads: 1099362561482301523 · confidence: medium_ -_Date range: 2023-05-12_ -_Reviewer note: Confidence is medium: the claim that the main UI thread initialises COM automatically should be verified against twinBASIC runtime behaviour. The OleInitialize requirement is standard Win32 practice but the automatic main-thread initialisation claim comes from a community discussion, not primary source documentation._ +_Source threads: 1416685158156730399 · confidence: medium_ +_Date range: 2025-09-14_ +_Reviewer note: Package: Core, symbol: none. Based on a community sample; verify and find a suitable page (for example a features or FAQ page)._ --- -## docs/Features/Advanced/Static-Linking.md · after-remarks [DUPLICATE? -- see also thread 1437751213981700156] +## UNMAPPED · new-section > [!NOTE] -> The twinBASIC IDE includes an automatic safe mode that detects repeated rapid compiler restarts and breaks the loop. If the compiler restarts several times in quick succession --- for example, when editing a source file that contains an `Import Library` statement triggers a compiler error --- the IDE enters safe mode automatically, allowing edits to be made before the next full restart. Safe mode can also be entered manually at startup if an `Import Library`-related edit causes persistent restart failures. +> Opening a file type with a twinBASIC program needs only a file association in the registry. A shell extension handler is not required for that. -_Source threads: 1208948615368941579 · confidence: high_ -_Date range: 2024-02-19 to 2024-02-20_ -_Reviewer note: This is based on a fixed bug from BETA 452-453 (February 2024). The auto-safe mode feature shipped in BETA 453. Verify that automatic safe mode is still present in current builds and that the description of its trigger conditions is accurate before publishing. The specific `Import Library` editing bug that caused the loop has been fixed; only the auto-safe mode note is intended here._ +A context-menu handler project (the first shell extension project type) ships with a `.reg` file. To restrict the handler to one file type, replace the `*` in that file with the file type. The handler receives the file name when it initializes, so it can inspect the file contents and vary its behavior. + +_Source threads: 1220474102515503174-2024-10 · confidence: medium_ +_Date range: 2024-10-11_ +_Reviewer note: Package Core, no symbol. No existing page found for shell extension projects; triage placement (shell extension / project types docs). Verify .reg behavior against the project template._ --- -## docs/Features/Advanced/Static-Linking.md · after-remarks [DUPLICATE? -- see also thread 1208948615368941579] +## UNMAPPED · new-section -> [!IMPORTANT] -> Only C (not C++) object code can be statically linked. A `.lib` or `.obj` file that contains C++ object code --- even when the intended entry points are exposed through an `extern "C"` wrapper --- cannot be linked in twinBASIC because the linker cannot resolve C++ runtime symbols from the embedded object files. The workaround is to compile the C++ code as a DLL and use a standard `Declare` statement to access its exported functions. +> [!NOTE] +> The Community edition of twinBASIC is free and carries no restriction on commercial use. -_Source threads: 1437751213981700156 · confidence: medium_ -_Date range: 2025-11-11 to 2025-11-14_ -_Reviewer note: The finding mentions SQLite Library 1.2 as an affected package due to unresolved api-ms-win-crt-math-l1-1-0 symbols. Verify whether this still applies or was resolved in a later SQLite library version. The general C-only restriction should be confirmed against the twinBASIC linker documentation or a recent build._ +The time between major versions, from version 1 onwards, is expected to be about 18 months. Any perpetual licences, if offered, would include bug-fix updates within their scope, plus access to betas of the next major release. + +_Source threads: 1220474102515503174-2024-06 · confidence: high_ +_Date range: 2024-06-17 to 2024-06-25_ +_Reviewer note: Package Core, no symbol. Licensing/editions statement; the 18-month and perpetual-licence points are forward-looking plans stated by Wayne and may be outdated; verify before publishing. Triage placement (editions/licensing or FAQ page)._ --- -## docs/Features/Advanced/Static-Linking.md · after-remarks +## UNMAPPED · after-remarks > [!NOTE] -> The **Miscellaneous** folder is not ignored by the compiler. It holds arbitrary files, but the compiler links the `.obj` and `.lib` files named by an **Import Library** line into the executable. The files need not be in that folder: a project can keep them in a folder of its own, such as `StaticLibraries`, and give that path to **Import Library**. -> -> Files in **Miscellaneous** are not available at run time. To embed a binary that the program reads while it runs, put it in the **Resources** folder instead. See [How do I use resources in twinBASIC?](../../FAQ#resources). +> Background twinBASIC EXE processes can stay running after the IDE and the programs under development are closed. They keep the old version folder locked, so a third-party updater such as TBLOADER cannot replace it. End all of them in Task Manager before updating over an existing installation. Ending them also discards the compiler's code cache, so the first 64-bit build afterwards takes longer. -_Source threads: 1522084764817948903 · confidence: high_ -_Date range: 2026-07-02 to 2026-07-06_ -_Reviewer note: Finding was filed under package Core with no symbol. The page index has no Features pages, so the target was chosen by reading nearby files (Static-Linking.md, which names the Miscellaneous folder). Verify the claim that Import Library accepts a path outside Miscellaneous, and that the FAQ anchor #resources exists (the summary element has id="resources")._ +_Source threads: 1556815494034489394 · confidence: high_ +_Date range: 2026-10-05 to 2026-10-06_ +_Reviewer note: Package Core, no symbol. Installation/updating topic; no obvious page found, triage placement._ --- -## docs/Features/Compiler-IDE/CodeLens.md · after-remarks +## UNMAPPED · after-remarks > [!NOTE] -> In projects that reference large dependency packages, the **Show Run Procedure Bar** setting can cause Go to Definition to take 20--40 seconds per lookup, leaving the editor with white text during that time. The delay occurs on every lookup, even when the target file is already open. Disabling **Show Run Procedure Bar** in the project settings makes Go to Definition near-instant. This is a known performance interaction; disabling CodeLens is the current workaround when working with large packages. +> When a registered twinBASIC ActiveX DLL cannot be added as a COM reference in another host, such as a .NET project in Visual Studio, check that the DLL's bitness matches the bitness of the referencing application. Referencing the same DLL from another host, for example Office, helps to isolate the cause. -_Source threads: 1375013634656309360 · confidence: medium_ -_Date range: 2025-05-22_ -_Reviewer note: Medium confidence; verify whether this has been fixed in a recent BETA build before publishing, or add a note about which BETA version introduced the fix if one exists._ +_Source threads: 1524431049214394418 · confidence: low_ +_Date range: 2026-07-08 to 2026-07-10_ +_Reviewer note: Package Core, no symbol. Thread unresolved; the bitness cause is only a suggestion. Consider dropping._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · new-section -## Known Limitations with Custom Win32 Message Pumps +### ActiveX controls fail with "instance creation failed" -> [!NOTE] -> Two IDE-specific limitations apply when a program runs its own Win32 message loop (`TranslateMessage` / `DispatchMessage`) instead of using VB's built-in event model. +On a machine without VB6 installed, licensed OCX controls (for example the StatusBar from MSComctlLib or the CommonDialog from MSComDlg) can register and appear in the toolbox, yet fail to instantiate in the IDE with "instance creation failed". The usual cause is a missing design-time license. -**Window not displayed in the IDE.** A window created entirely via Win32 API calls --- not using a VB `Form` --- with its own message loop may fail to appear on screen when the program is launched from the twinBASIC IDE. The window handle is valid, all API calls succeed, and the window is visible in Spy++, but the window surface never renders and the `WindowProc` is never called. The same code works correctly when compiled and run as a standalone executable, and also works in VB6. The root cause has not been identified. +To fix this: -**Stop and Break buttons unresponsive.** When a custom Win32 message pump is active, the IDE Stop and Break buttons do not respond. This has been observed in single-threaded programs, so it is distinct from the known limitation where the IDE loses track of execution across multiple threads. +1. Find the license file for the control (a `.srg` file) in the `OS\SYSTEM` folder of the VB6 or Visual Studio 6 installation media. The file is usually named after the OCX, but not always; `MSFLXGRD.OCX`, for example, uses `VSDBFLEX.SRG`. +2. Add the line `Windows Registry Editor Version 5.00` and a blank line at the top of the file, and save it with a `.reg` extension. +3. Import the `.reg` file into the registry. +4. Make sure the OCX itself is registered with `regsvr32`. A 32-bit OCX belongs in the `SysWOW64` folder on 64-bit Windows. -_Source threads: 1077672242999210076 · confidence: medium_ -_Date range: 2023-02-21_ -_Reviewer note: Findings originate from VB package / null symbol; mapped here (Debugging page) as the closest existing page for IDE-behavior gotchas. Verify whether either limitation has been fixed in a later twinBASIC release before publishing, as the thread is from February 2023. The window-not-displayed finding mentions a workaround (stepping through the message loop once with breakpoints causes the window to appear and it continues to appear after the breakpoints are cleared) -- consider whether to include it._ +_Source threads: 1564725968499900636 · confidence: high_ +_Date range: 2026-10-27 to 2026-10-28_ +_Reviewer note: UNMAPPED: package Core, symbol null (ActiveX/OCX licensing). Suggest placing in docs/Miscellaneous/FAQs.md or an ActiveX controls feature page; triage placement._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · new-section -## Immediate Memory Invalidation +> [!NOTE] +> The Windows Forms controls package is named **WinNativeForms**. It is built into the compiler, so it needs no separate download or manual update. Sample projects reference it, or the CustomControls package, as a built-in package. -The debugger settings include an **Immediate memory invalidation** option. When enabled, freed memory is invalidated immediately rather than lazily. This causes bugs involving access to memory after it has been freed --- such as an array or object that has been released too soon --- to crash consistently rather than intermittently, making them easier to reproduce and isolate. +The form designer converts SVG images to EMF using Inkscape, which must be installed separately. The designer also offers an **Align Controls To Grid** option with configurable grid width and height, and can lock and unlock controls. + +_Source threads: 927834661298798602-2022-02 · confidence: medium_ +_Date range: 2022-02-04 to 2022-02-24_ +_Reviewer note: Package WinNativeCommonCtls (no symbol) plus Core form designer finding (no symbol). Source: v0.14.19, v0.14.20, v0.14.33. Package was renamed several times; verify current name, since the index lists WinNativeCommonCtls. Candidate target for the first part: docs/Reference/Built-In/WinNativeCommonCtls/index.md; the designer part needs a designer/IDE page._ + +--- + +## UNMAPPED · new-section > [!NOTE] +> Objects, strings and interfaces are reference-counted, as in COM and VB6, and there is no cycle collector. Two objects that hold references to each other are never freed once the last outside reference is gone. Break such a cycle by hand, for example with a cleanup method that clears the back-reference before the owner is released. > -> This option applies only when running under the IDE debugger. It does not help diagnose crashes that occur only in a compiled binary invoked outside the IDE, such as a standard DLL called from an external host. +> Handles to external resources (files, kernel or GDI handles) are best wrapped in a class whose destructor releases the handle. Memory allocated manually, with **AllocMem**, **HeapAlloc** or a block returned by a Windows API, is never freed automatically and must be released explicitly. -_Source threads: 1099208311313813534 · confidence: high_ -_Date range: 2023-04-22_ +_Source threads: 1565126681415520426 · confidence: medium_ +_Date range: 2026-10-28_ +_Reviewer note: UNMAPPED: package Core, no symbol (reference counting / memory leaks). No obvious Core index page; consider the Set statement page or a language-features page on object lifetime. Verify against compiler behaviour._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · new-section -## Native Exception &H406D1388 +> [!NOTE] +> Partial compilation is not supported. Support for external project files such as **.vbp** is planned. -The native exception code `&H406D1388` that appears in the twinBASIC IDE output log during a debug session is a continuable C++ exception --- specifically, the standard Microsoft Visual C++ runtime "set thread name" exception. It does not indicate incorrect Basic code. +_Source threads: 1566536995113668708 · confidence: high_ +_Date range: 2026-11-01 to 2026-11-02_ +_Reviewer note: UNMAPPED: package Core, no symbol (roadmap: partial compilation, .vbp support). Maintainer said partial compilation moved from v1.0 to v1.1. Likely belongs in a roadmap/limitations page, if any; may be unsuitable for the reference docs._ -Prior to BETA 299, the twinBASIC debugger aborted the debug session when it encountered this exception. From BETA 299 onward, the exception is caught and execution continues. The exception still appears in the IDE log, but no longer causes a session restart. No action is required. +--- -_Source threads: 1101704869804511265 · confidence: high_ -_Date range: 2023-05-12_ +## UNMAPPED · after-remarks + +> [!NOTE] +> The project References list offers two options per COM reference: overriding the library's name, and referencing a type library without automatically wrapping its ActiveX controls. Using an **Ax** control data type automatically refers to the extended wrapper type, as VBx does. + +_Source threads: 927834661298798602-2022-12 · confidence: high_ +_Date range: 2022-12-16_ +_Reviewer note: Package Core, no symbol. Belongs on a project References / COM references IDE page; from BETA 207-208. Needs triage._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · after-remarks > [!NOTE] -> When trace mode is active and the **TracePrint** option is enabled in the project's trace settings, tracing can be paused and resumed at runtime using sentinel strings. Call `Debug.TracePrint "~~OFF"` to pause logging and `Debug.TracePrint "~~ON"` to resume it. Both conditions must be met: the sentinel call alone has no effect if **TracePrint** is not enabled in project settings. -> -> A common pattern is to call `Debug.TracePrint "~~OFF"` at the top of `Sub Main` to skip the trace overhead during form load, then re-enable it later --- for example, from a button click handler --- once the UI is ready. +> **TypeOf** applied to a **Variant** handles a **Variant** that holds a non-object value correctly, rather than assuming an object reference. -_Source threads: 1374402188474056704 · confidence: high_ -_Date range: 2025-05-20_ -_Reviewer note: Verify that the sentinel strings are exactly `~~OFF` and `~~ON` (case-sensitive?) and that they require the TracePrint setting to be enabled in project settings. The finding describes this clearly but the exact sentinel format and project-settings prerequisite should be confirmed against the twinBASIC source or documentation._ +_Source threads: 927834661298798602-2023-02 · confidence: medium_ +_Date range: 2023-02-16_ +_Reviewer note: UNMAPPED: package Core, symbol TypeOf. No TypeOf page found under docs/Reference/Core; the reviewer should find the right place (perhaps Is.md or the operators page). Source: BETA 251 fix. Wording is vague about the exact result; verify._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · new-section -## Recovering from a Stuck IDE +Calls to `[_HiddenModule]` expose several functions redirected from MSVBVM60: `GetMem1`, `GetMem2`, `GetMem3`, `GetMem4`, `PutMem1`, `PutMem2`, `PutMem4`, `PutMem8`, `vbaObjSetAddref`, `vbaObjSet`, `vbaCopyBytes` and `vbaCopyBytesZero`. `CreateGUID` wraps `CoCreateGuid` and can be used from the debug console. `PictureToByteArray` is also available. -When a code-generation error occurs during a preview or run, clicking the **Stop** button may have no effect and the IDE may become unresponsive. In this situation, use the **compiler reset** button on the toolbar to forcibly reset the compiler state. This is the general-purpose recovery action for IDE hangs caused by compiler errors. +A **Declare** that targets MSVBVM50.dll or MSVBVM60.dll is redirected to an internal implementation, and a **Declare** of a legacy DLL such as OLEPRO32.DLL is redirected to its modern replacement with a warning. -_Source threads: 1403430684219146300 · confidence: medium_ -_Date range: 2025-08-08 to 2025-08-14_ -_Reviewer note: Placed in the Debugging features page as a new section. If a more specific troubleshooting page exists or is created, move this there. The section heading uses new-section formatting inside after-remarks due to how the target page is structured (existing content is headings)._ +_Source threads: 927834661298798602-2023-05 · confidence: high_ +_Date range: 2023-04-15 to 2023-06-27_ +_Reviewer note: Package Core, symbols _HiddenModule and MSVBVM50/60 redirection; no page found. Candidate: docs/Features/Advanced/API-Declarations.md. The OLEPRO32 redirect is already in Compiler-Warnings.md. Verify member list against the .twin source._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · new-section > [!NOTE] -> twinBASIC does not yet support **Edit and Continue**. Any code change made while execution is paused requires a full program restart before it takes effect. This is a known missing feature. +> A parameter cannot be declared **Optional** if it is an array or a user-defined type. Both cases are compile errors, as in VBx. Passing a user-defined type of the wrong type to a procedure is also a compile error. -_Source threads: 1403579629482020934 · confidence: high_ -_Date range: 2025-08-09_ -_Reviewer note: Verify that the Debugging features page does not already mention Edit and Continue. If the feature has shipped since this thread (August 2025), remove this note._ +`Len(var)` and `LenB(var)` can be used in the bound expressions of a fixed-size array. **Array** can be used as a UDT member name, and **Line** can be used as a variable name without conflicting with the **Line** statement. + +_Source threads: 927834661298798602-2023-04 · confidence: high_ +_Date range: 2023-04-16 to 2023-05-26_ +_Reviewer note: Package Core, no symbol; language compatibility notes. Candidate: docs/Features/Language pages. TB0014 inline ByVal already documented in Compiler-Warnings.md. Verify against the compiler._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · after-remarks > [!NOTE] -> `Debug.TracePrint` output does not appear in the Debug Console unless the trace logger destination is explicitly configured to `${DEBUG}`. Without that explicit configuration the calls are silently discarded, even though the configuration UI displays `${DEBUG}` as a hint value. This was fixed in twinBASIC BETA 951. +> The project setting **Project: Build Stack Reserve Size** sets the stack reserve size written to the PE header of the built executable. The debugger's main thread uses a 4 MB stack reservation. -_Source threads: 1454461049003835433 · confidence: high_ -_Date range: 2025-12-27 to 2026-01-16_ -_Reviewer note: Insert after the Debug Trace Logger section in Debugging.md. Confirm the BETA 951 fix version against the release log._ +_Source threads: 927834661298798602-2023-07 · confidence: high_ +_Date range: 2023-07-06_ +_Reviewer note: Package Core, no symbol. Belongs on a project-settings page. Source build: BETA 348._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · after-remarks -> [!NOTE] -> -> The **Compiler services: TRACE mode** option in the IDE's About menu is unrelated to `Debug.TracePrint`. That option enables extra IDE diagnostic information written to the debug console when a crash occurs --- the IDE also turns it on automatically after a crash, for the next run. To enable `Debug.TracePrint` output, use the trace logger option in the project settings instead. +The `${IdePath}` variable can be used in the **Project: Build Output Path** setting. It expands to the folder of the running IDE. Addin project templates default to `${IdePath}\addins\${Architecture}\`. -_Source threads: 1455208656328200224 · confidence: high_ -_Date range: 2025-12-29 to 2026-01-17_ -_Reviewer note: Confirm current project settings UI label for the trace logger option before publishing._ +_Source threads: 927834661298798602-2023-08 · confidence: high_ +_Date range: 2023-08-18_ +_Reviewer note: Package Core, no symbol. Belongs on the project-settings or build-variables page. Source build: BETA 379. Verify the meaning of IdePath._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] - -## Module-Level Variable State Between Debug Runs - -Stopping a program in the IDE and running it again without restarting the compiler does not fully reset module-level (global) variables. They can retain values from the previous run, causing unexpected behavior on subsequent debug runs. +## UNMAPPED · after-remarks -To guarantee a clean state, restart the compiler before each run by clicking the blue arrow next to the win32/win64 target dropdown. This reinitialises all module-level state. +> [!NOTE] +> **Opacity** and **TransparencyKey** on a windowed control require a project target OS of 6.2 (Windows 8) or later, and take effect only in a compiled executable, not when the project runs in the IDE. -_Source threads: 1253786438454214788 · confidence: medium_ -_Date range: 2024-06-24_ -_Reviewer note: Verified from a single Discord report (medium confidence). The blue-arrow restart control description should be verified against the current IDE layout before publishing --- the UI may have changed since the thread date (2024-06-24)._ +_Source threads: 927834661298798602-2024-05 · confidence: high_ +_Date range: 2024-05-25_ +_Reviewer note: Package VB, symbol Opacity. The per-control pages (e.g. docs/Reference/Default/VB/CheckBox/index.md, #opacity and #transparencykey) say only 'Windows 8 or later'; the compiled-only limitation could be added to each or to a shared page._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · after-remarks > [!NOTE] -> Messages of the form "IDE internal error: Uncaught Error: ..." that appear in the twinBASIC IDE are diagnostic messages intended for the IDE developer, not errors caused by user code. Encountering one does not mean a language rule has been broken. These messages can generally be ignored when they appear alongside normal project behaviour. +> Opening a VB6 project (`.vbp`) switches the IDE to the 32-bit (win32) architecture. The IDE remembers the target architecture of each project between sessions, so no separate option for starting in 64-bit mode is needed. -_Source threads: 1289417040540467221 · confidence: high_ -_Date range: 2024-09-28_ +_Source threads: 927834661298798602-2025-01 · confidence: high_ +_Date range: 2025-01-06 to 2025-01-14_ +_Reviewer note: Package Core, no symbol. Sources BETA 637 and 646. Candidate placement: VBP import page or docs/IDE/Customising/index.md near targetArchitectureMemory. Triage placement._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] +## UNMAPPED · new-section -> [!NOTE] -> Memory leak warnings that appear in the twinBASIC IDE refer to memory leaks within the IDE process itself, not in compiled executables. Compiled twinBASIC programs ported from VB6 have been reported to run stably in server deployments for extended periods without the memory issues associated with the IDE. -> -> **Trace mode** is a separate concern: enabling Trace mode during a long-running session causes memory usage to climb continuously, in addition to producing large trace log files. Trace mode should not be left enabled in production or long-running deployments. +The IDE option **Show mini-toolbar in the form designer (for in-place editing)** is on by default. The commands `tbFormDesigner_TurnInPlaceEditingToolbarOn`, `tbFormDesigner_TurnInPlaceEditingToolbarOff` and `tbFormDesigner_ToggleInPlaceEditingToolbar` control it and can be assigned to keyboard shortcuts. The option **Single line of code panel tabs** keeps code panel tabs on one row. -_Source threads: 1361658401666498601 · confidence: medium_ -_Date range: 2025-04-15 to 2025-04-16_ -_Reviewer note: Confidence is medium: the claim about compiled executables running stably is based on community reports, not official documentation. Verify against any known stability issues in the twinBASIC changelog before publishing._ +_Source threads: 927834661298798602-2025-02 · confidence: high_ +_Date range: 2025-02-15 to 2025-02-19_ +_Reviewer note: Package tbIDE, symbol none. Triage placement in the IDE settings / form designer docs. From BETA 683 and 686._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1372291630773567709, 1449035570796957878, 1367648395933646929] - -## Diagnosing native crashes with trace mode - -When a compiled twinBASIC application crashes with a native exception (such as exception code `0xC0000005` ACCESS_VIOLATION), the Windows error report does not identify which line of Basic code triggered the crash. Enabling **trace mode** in the project settings causes the runtime to write verbose log files during execution. After reproducing the crash, examining the log file identifies the last line of code that ran before the failure. +## UNMAPPED · after-remarks > [!NOTE] -> Trace mode logs are very verbose. For complex cases it may be more practical to send the log file to the twinBASIC maintainer rather than reading it directly. +> twinBASIC embeds the licence key of a licensed ActiveX control only when the control's type library sets the `TYPEFLAG_FLICENSED` flag. Many controls do not set it correctly, and for those the key may not be embedded. -_Source threads: 1369362026119565432 · confidence: high_ -_Date range: 2025-05-06_ -_Reviewer note: Verify the exact name and location of the 'trace mode' option in project settings, and confirm whether it is exposed under Project Configuration / Compiler Options or a separate settings panel, so the prose can link to or name it precisely._ +_Source threads: 927834661298798602-2025-03 · confidence: medium_ +_Date range: 2025-03-28 to 2025-03-30_ +_Reviewer note: Package Core, no symbol. Suggested placement: the ActiveX/COM references documentation. From BETA 725 and 729, which added extra handling; verify current behaviour before publishing. The trace options finding (BETA 726) was dropped because docs/IDE/Project Settings.md already lists both options, and the For / For Each inline declaration findings because both pages already document the `As type` form._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1449035570796957878, 1367648395933646929] - -## Step Into (F8) stepping over a call - -In the twinBASIC IDE debugger, pressing **F8** (Step Into) from a breakpoint may step over a function call rather than entering it. This behaviour appears when the target function contains code that the debugger cannot step through, often because of a problem in that function. +## UNMAPPED · after-remarks -Workaround: use **Go to Definition** on the function name to navigate to it, place a breakpoint before the problematic section, then press **F8** from that internal breakpoint to continue stepping. +> [!NOTE] +> The theme properties **CodeFoldingVisible** and **CodeMinimapVisible** are deprecated. The IDE commands `tbEditor_ToggleCodeFoldingVisibility` and `tbEditor_ToggleMiniMapVisibility` replace them, and are also in the ellipsis menu of the EDITOR panel. The command `tbEditor_ToggleShowLineNumbers` toggles line numbers. `tbEditor_FoldProcedures` and `tbEditor_UnfoldProcedures` fold and unfold all procedures (Ctrl+Alt+Left and Ctrl+Alt+Right). Themes can also style named operators with the **SymbolNamedOperator** properties **Color**, **FontStyle**, **FontWeight** and **TextDecoration**. -_Source threads: 1372291630773567709 · confidence: medium_ -_Date range: 2025-05-14 to 2025-05-15_ -_Reviewer note: Observed in BETA 769. Verify whether this has been fixed in a later build before publishing._ +_Source threads: 927834661298798602-2025-06 · confidence: high_ +_Date range: 2025-06-09 to 2025-06-26_ +_Reviewer note: Package tbIDE, symbol Themes. Source: BETAs 799, 813, 820. No theme or IDE-commands page found; check exact property names (SymbolNamedOperator* prefix) against the theme schema. Place on an IDE themes or commands page._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1367648395933646929] +## UNMAPPED · after-remarks -### Diagnosing ACCESS_VIOLATION Crashes +> [!NOTE] +> The compiler leaves unused global variables, including static module variables, out of the built executable. -When a native `ACCESS_VIOLATION` occurs during build or run and no error appears in the Project Explorer or Diagnostics pane, the trace log is the primary diagnostic tool. +_Source threads: 927834661298798602-2025-12 · confidence: high_ +_Date range: 2025-12-07 to 2025-12-08_ +_Reviewer note: Package Core, no symbol (BETA 917). Candidate placement: a build or optimisation page. Dropped as already-fixed past defects: ArrayBoundsChecks fixes (914, 917), CustomControls breakage (909-934), Printer.Font, App.Path and Clipboard.GetFormat fixes (already documented as current behaviour), and Alias syntax (page already covers it)._ -To use it: +--- -1. Enable the trace log in **Project Settings** before reproducing the crash. A useful starting configuration is to log procedure entry and exit only. -2. Reproduce the crash. -3. Open the resulting log and locate the thread marked with `>>>` --- this is the offending thread. -4. The thread state marker at the point of failure identifies which compiler stage crashed. For example, `GlobalVarsInitializationCodegenStart` means the crash occurred during code generation for global variable initialization, before any user code ran. +## UNMAPPED · after-remarks -This technique is particularly useful for crashes in generated code that produce no user-visible error message. +> [!NOTE] +> The designer property sheet accepts color values in the VB6 hexadecimal format, such as `&H12345678&`. -_Source threads: 1449035570796957878 · confidence: high_ -_Date range: 2025-12-12_ +_Source threads: 927834661298798602-2026-01 · confidence: high_ +_Date range: 2026-01-30_ +_Reviewer note: Package Core, no symbol. IDE form designer feature (BETA 965); find a page under docs/IDE for the designer or property sheet._ --- -## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878] +## UNMAPPED · after-remarks > [!NOTE] -> When running a project with a debugger attached, a continuable native exception with code **&H406D1388** may appear on startup. This exception is the standard Win32 thread-naming notification (documented in the Microsoft blog post *"How to: Set a Thread Name in Native Code"*) and does not affect application functionality. Resume execution to continue normally. +> Opening a form designer while the project is running with runtime forms open resets the running project so that the designer can open. -_Source threads: 1367648395933646929 · confidence: medium_ -_Date range: 2025-09-12_ -_Reviewer note: The target page (docs/Features/Compiler-IDE/Debugging.md) is not listed in the page index. Confirm this is the appropriate placement, or consider the VB Package index or a general troubleshooting page. The exception is most commonly observed when the project uses WinRT classes but may appear in other contexts too._ +_Source threads: 927834661298798602-2026-03 · confidence: high_ +_Date range: 2026-03-14_ +_Reviewer note: Package Core, no symbol. IDE designer behaviour (BETA 974); find a page under docs/IDE. Also unmapped: block comments may be preceded by extra spacing (BETA 980, Core, no symbol); root Form/MDIForm/UserControl classes use full inheritance instead of Implements-Via (BETA 951, Core, no symbol)._ --- -## docs/Features/Compiler-IDE/Debugging.md · new-section [DUPLICATE? -- see also thread 1463977430661464095] +## UNMAPPED · after-remarks -## Debug Console +> [!NOTE] +> A union nested in a structure must be replaced by a stand-in that keeps both size and alignment. On a 64-bit build, in the structure `{int a; union {struct {int b; void *c;}; void *d;}; int e;}`, the union is aligned to 8 and starts at offset 8 rather than 4, occupies 16 bytes, and 4 bytes of trailing padding follow the structure. A plain **Byte** array stand-in with the wrong size or alignment gives a wrong layout. -The Debug Console pane shows output from `Debug.Print` and related calls. To enter a command interactively, use the input bar --- the bar at the bottom of the pane marked with a `>` prompt. The output area above it is read-only; typing directly there has no effect. +> [!NOTE] +> WinDevLib APIs that take a `LARGE_INTEGER`, such as `GetFileSizeEx`, expect that type. Code that passes a **LongLong** variable compiles without a diagnostic but crashes at run time. Declare the variable as `LARGE_INTEGER` and use its `.QuadPart` member. -The input bar supports a command history: press **Up Arrow** or **Down Arrow** to cycle through previously entered commands. +_Source threads: 1125359845936205844 · confidence: high_ +_Date range: 2025-03-09 to 2025-11-19_ +_Reviewer note: Package WinDevLib, no symbol; no page in index. Merged the union-alignment and LARGE_INTEGER findings; the alignment note may fit better on the UDT enhancements page (Features/Language/UDTs.md) under packing rules. WinDevLib 8.8.504 fixes omitted._ + +--- + +## UNMAPPED · new-section > [!NOTE] -> This is different from the VB6 Immediate window, where the same pane accepted both output and typed input. In twinBASIC, input and output use separate areas. +> Adding `WINDEVLIB_LITE = 1` under **Project Settings** > **Conditional compilation arguments** excludes the API and common-control definitions, miscellaneous definitions and PROPERTYKEYs, which shortens IntelliSense load time in the IDE. Project-level compiler constants are not passed through to packages, so others such as `WDL_NO_DIRECTX` or `WDL_QUALIFY` (which requires a `WinDevLib.` prefix) take effect only in a custom .twinpack built from the GitHub twinproj with the constant defined. -_Source threads: 1077971955052982312 · confidence: high_ -_Date range: 2023-05-23_ +> [!NOTE] +> If two packages, such as WinDevLib and WinRT, define the same type, the package earlier in the **References** resolve order wins, and the other needs a package prefix. Reorder with the arrows in **References**. Updating a package removes and re-adds it, which moves it to the end of the resolve order. Each package's `changelog.md`, including BREAKING CHANGE notes, is visible under `Packages\` in the project explorer. + +_Source threads: 1125359845936205844 · confidence: medium_ +_Date range: 2023-07-03 to 2025-12-29_ +_Reviewer note: Package WinDevLib, no symbol; no package index page found. Merged the LITE/conditional-constant and package-order findings. The LITE speed figures and 'rarely needed' remark omitted. Restoring an old version from GitHub history into %APPDATA%\Roaming\twinBASIC\packages was also mentioned and not drafted._ --- -## docs/Features/Compiler-IDE/Debugging.md · new-section [DUPLICATE? -- see also thread 1077971955052982312] +## UNMAPPED · after-remarks -## Variables Pane and Compiler Restart +> [!NOTE] +> **TaskDialog** accepts common-button flags beyond the documented ones: `TDCBF_ABORT_BUTTON` (&H10000), `TDCBF_IGNORE_BUTTON` (&H20000), `TDCBF_TRYAGAIN_BUTTON` (&H40000), `TDCBF_CONTINUE_BUTTON` (&H80000) and `TDCBF_HELP_BUTTON` (&H100000, decimal 1048576). The dialog returns `TD_ABORT`, `TD_IGNORE`, `TD_TRYAGAIN` or `TD_CONTINUE` for the first four. The Help button raises the Help event and does not close the dialog. -The twinBASIC debugger and compiler run in the same background process. If a property getter raises an unhandled exception while the debugger evaluates Variables pane nodes at a breakpoint, the IDE triggers a full compiler restart rather than continuing --- the shared process means memory corruption from a hard crash could affect compiler state and the internal project file system. +> [!NOTE] +> Structures such as `TASKDIALOG_BUTTON` are packed differently in 64-bit VBA, so VBA64 code needs a separate alternate declaration of each. -The Variables pane remembers the expansion state of its nodes between breakpoints. If an expanded node contains a property getter that throws, the pane will crash each time that breakpoint is reached. +_Source threads: 1156840378213867620 · confidence: medium_ +_Date range: 2024-01-17_ +_Reviewer note: UNMAPPED. Package VBA, symbol TASKDIALOG_COMMON_BUTTON_FLAGS; no such page exists in the docs (TaskDialog appears only in Miscellaneous/FAQs.md). Likely belongs with a TaskDialog or cTaskDialog page if one is added. Constants come from a community class, verify against Win32 headers._ -**Workaround:** stop the debugger at a safe location first --- for example, a breakpoint in `Sub Main` or another routine that does not involve the offending object --- then collapse the root node in the Variables pane. The collapsed state is retained for the remainder of the session. +--- -The expansion state is stored in the project's `.meta` file in the project root folder, under the JSON key `variables_expandedCache`. The project exporter does not include this file in exports, so the cache cannot be edited from outside a running IDE session. +## UNMAPPED · example -> [!NOTE] -> A long-term plan exists to separate the compiler and debugger into distinct processes, which would allow the debugger to report a failed property evaluation without restarting. +When the host executable has no version 6 common controls entry in its manifest, a call to `TaskDialogIndirect` or `TaskDialog` can still use the visual-styles version of comctl32. The caller activates a version 6 activation context with the SxS Activation Context API for the duration of the call, then deactivates it. This applies to hosts such as 32-bit Excel, or to a standard DLL or Control Panel applet written in twinBASIC. A manifest remains the preferred approach where the project controls it. -_Source threads: 1463977430661464095 · confidence: high_ -_Date range: 2026-01-23 to 2026-01-25_ +_Source threads: 1156840378213867620 · confidence: medium_ +_Date range: 2024-06-15_ +_Reviewer note: UNMAPPED. Package Core, no symbol. Possible home is Features/Advanced/API-Declarations.md or a comctl32 manifest topic. An example needs CreateActCtx/ActivateActCtx declarations, which the thread did not supply._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · after-remarks > [!NOTE] -> The typelib viewer resolves UDT and enum names in a referenced typelib by numeric index. If a typelib uses `importlib` to reference types from a second typelib, and the second typelib has since had new type entries inserted --- without the outer typelib being recompiled against the updated version --- the numeric indexes embedded in the outer typelib will be off by one or more. The viewer then displays the type immediately before the correct one, producing wrong UDT or enum names for those references. The fix is to recompile the outer typelib against the current version of the referenced typelib and deploy both files together as a matched set. When both typelibs are version-consistent, the viewer shows correct signatures. +> **IExtractImage** does not appear to work for `.ico` files on Windows 10 and later. Use **IShellItemImageFactory** to obtain the image of an icon file. -_Source threads: 1066231176462872656 · confidence: high_ -_Date range: 2023-01-21 to 2023-02-03_ -_Reviewer note: Finding targets tbIDE package (no specific symbol) but the typelib viewer is an IDE feature documented in docs/Features/Compiler-IDE/IDE-Features.md, not in the addin SDK reference. Mapped here because the existing bullet 'A type library viewer for controls and TLB files' is the natural anchor. The note should be placed after or alongside that bullet in the Advanced Features section. Verify that the version-mismatch behavior still applies to the current IDE build._ +_Source threads: 1437514645354315888 · confidence: low_ +_Date range: 2026-03-08_ +_Reviewer note: Package WinDevLib, symbol IExtractImage. No page found in the index; triage placement. Low confidence ("apparently" in the thread)._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · after-remarks > [!NOTE] -> Prior to BETA 256, the type library viewer incorrectly rendered parameters whose COM IDL declaration used `defaultvalue("")` (an empty-string default). The generated twinBASIC syntax showed a backslash-escaped quote sequence instead of a proper empty string literal, producing invalid output. This was fixed in BETA 256. +> The Background Intelligent Transfer Service (BITS) interfaces, such as **IBackgroundCopyJob**, are usable only with a recent Windows Development Library. Older versions of the package define some BITS interfaces incorrectly, so code that wraps BITS for non-blocking downloads and uploads with progress events needs the current package. -_Source threads: 1077017629279125574 · confidence: high_ -_Date range: 2023-02-20 to 2023-02-27_ -_Reviewer note: This documents a historical bug that was fixed in BETA 256. The note is accurate but may be too version-specific for a general features page. Reviewer should decide whether to include it (as a historical gotcha for users who encountered it) or discard it as no longer relevant to current versions._ +_Source threads: 1525878462471077888 · confidence: medium_ +_Date range: 2026-07-12_ +_Reviewer note: Package WinDevLib, symbol IBackgroundCopyJob. No page found. Thread states WinDevLib v9.3.709 or later is required because of a bugfix and API standards change to a couple of BITS interfaces; build/version deliberately omitted from the draft._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · example -> [!NOTE] -> When copying or cutting code, the IDE places spaces on the clipboard rather than literal tab characters. The number of spaces per tab matches the configured indent width. This ensures that code pasted into external editors or forums displays with consistent indentation regardless of the tab-stop settings in the destination. This behavior was introduced in BETA 288; earlier builds placed literal tab characters on the clipboard. +The WinRT **WebViewControl** can show local web content without a web server. **NavigateToLocalStreamUri** loads the HTML, CSS and JavaScript from a folder of the application, for example a `HtmlContent` folder. -_Source threads: 1091247313214783518 · confidence: high_ -_Date range: 2023-03-31 to 2023-04-04_ -_Reviewer note: Draft targets the Editing Features section of IDE-Features.md. Consider inserting this as a bullet point under that section rather than a standalone NOTE callout, depending on how the page evolves. The BETA 288 version number should be verified against the twinBASIC changelog if possible._ +The site files can also be stored compressed (for example with the `MAXIMUM_XPRESS_HUFF` algorithm, which makes them three to four times smaller). In that case a custom implementation of **IAsyncOperationIInputStream** serves as the resolver: it decompresses each file when the control asks for it. The page can pass JSON data back to the main form. + +_Source threads: 1528775724826230804 · confidence: medium_ +_Date range: 2026-07-20 to 2026-07-27_ +_Reviewer note: Package WinRT, symbol WebViewControl.NavigateToLocalStreamUri. No page found; no code in the finding, so prose only. Verify against the demo source._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · after-remarks > [!NOTE] -> The form designer's **Align Left** command sets every selected control's `Left` property to the same X coordinate. Controls that were not already overlapping will overlap after the operation. This differs from Microsoft Access, where alignment preserves horizontal spacing between controls. The twinBASIC behavior matches Visual Studio WinForms. -> -> There is also no concept of a primary reference control when multiple controls are selected. In VB6, the control whose resize handles appear in a different color acts as the alignment anchor; other controls align to it. twinBASIC's designer uses a fixed rule (the minimum left coordinate) instead. Spacing adjustment commands such as **Make Spacing Equal**, **Increase Spacing**, and **Decrease Spacing** from VB6 are not yet implemented. +> A project that uses the tbRegExp package does not currently compile with LLVM. The build reports many errors of the form "LLVM compilation error in 'StaticRegex.AppendStr': a feature used in your code is not yet supported with the LLVM compiler", and the resulting program can crash. Build such a project without LLVM. -_Source threads: 1122091213177229362 · confidence: high_ -_Date range: 2023-06-24_ -_Reviewer note: Verify whether spacing adjustment commands have been added to the designer since mid-2023, and whether a primary-selection concept has been implemented. The maintainer described the alignment as 'simple naïve left alignment' at that time._ +_Source threads: 1535364685556088993 · confidence: low_ +_Date range: 2026-10-22 to 2026-10-23_ +_Reviewer note: Package tbRegExp, symbol StaticRegex.AppendStr. No page found. Known-defect callout; the thread did not give a build number. Verify whether still reproducible._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · example -> [!NOTE] -> When opening a project, the editor restores the previously saved cursor position before inserting the **Run ``** codelens decorations above each procedure in standard modules. Because the decorations are inserted after position restoration, the document shifts downward and the visible area ends up roughly 10 lines above the actual last editing position. Scrolling down one page returns to the correct location. This limitation is most noticeable in large projects where codelens decorations are numerous. +The ReadTypelib tool, which requires WinDevLib, reads the type library of an ActiveX control or DLL and prints a set of attributes. Apply these attributes to the classes in a `.twin` file to make them binary compatible with the original component. The tool also prints the library ID, which is to be entered as the **Project ID** in Project Settings. -_Source threads: 1201200584821313586 · confidence: high_ -_Date range: 2024-01-28_ -_Reviewer note: Confirmed by WaynePhillipsEA in the thread as a known limitation. Verify whether this has since been fixed before publishing._ +_Source threads: 1560715630460805261 · confidence: medium_ +_Date range: 2026-10-16_ +_Reviewer note: Package Core, no symbol. Likely belongs on an ActiveX / binary compatibility page; triage placement._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · after-remarks > [!NOTE] -> **Rename symbol** (F2 or right-click > Rename) displays a text-entry field pre-filled with the symbol name, but pressing Enter or Esc has no effect. The feature is not yet implemented; it is listed on the roadmap for 2026-Q1. As a workaround, use Find/Replace or the Monaco editor's built-in "select all occurrences" command to rename a symbol across the current file. +> XAML hosted in a twinBASIC window has the following known quirks: +> +> - **Icons.** A `PathIcon` takes a XAML geometry string in its `Data` property. For example, `Data="M12 2 2 22h20Z"` draws a triangle. SVG path data also works when it uses only static path commands. An `Image` control can load an SVG through `SvgImageSource`. +> - **Flyouts and scrolling.** A `ComboBox` or `Flyout` drop-down stays open when the enclosing `ScrollViewer` scrolls. This is a known XAML issue. To work around it, handle the `ScrollViewer` `ViewChanging` event and set `IsOpen = False` on each popup returned by `VisualTreeHelper.GetOpenPopupsForXamlRoot`. +> - **Blurry text.** XAML text can look slightly blurry when the project runs from the IDE at a display scaling above 100%. The compiled EXE, whose manifest declares per-monitor DPI awareness, renders crisply. Test with the built EXE before diagnosing a font problem. A global `Style` with `TargetType="Border"` has also been reported to blur content after appearance animations; removing it fixed the problem. +> - **Dragging a borderless window.** Sending `WM_NCLBUTTONDOWN` with `HTCAPTION` through `SendMessage` from a XAML pointer handler does not move the window. Handle `PointerMoved` and move the host window with `SetWindowPos` instead. -_Source threads: 1216245949278457856 · confidence: high_ -_Date range: 2024-03-10 to 2025-07-15_ -_Reviewer note: Insert under the Editing Features section of IDE-Features.md. Verify the 2026-Q1 roadmap entry is still current as of the publication date._ +_Source threads: 1566461365072433272 · confidence: medium_ +_Date range: 2026-11-03 to 2026-11-04_ +_Reviewer note: UNMAPPED: package WinRT, no symbol (XAML Island tips, merged from four findings; the blur and dragging items were low confidence and are user-reported). Verify before publishing._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · example -> [!NOTE] -> In the form designer, lasso-dragging *inside* a **Frame** or other container selects the container itself rather than the controls within it. To select controls inside a container, draw the lasso from *outside* the container so the selection rectangle encompasses the container and its child controls, then hold **Ctrl** and click the container to deselect it. The remaining selection covers only the inner controls. +The `DxcCreateInstance` entry point can be called by loading the library at run time and using the `DxcCreateInstanceProc` delegate: -_Source threads: 1407004587570892974 · confidence: medium_ -_Date range: 2025-08-18_ -_Reviewer note: Placed in the Form Designer Enhancements section of the IDE Features page. If a dedicated form-designer page is created, move this note there._ +```tb +Dim hDXC As LongPtr = LoadLibrary("dxcompiler.dll") +Dim pfnCreate As DxcCreateInstanceProc = GetProcAddress(hDXC, "DxcCreateInstance") +' Call pfnCreate with the required CLSID and arguments +``` + +_Source threads: 1566461365072433272 · confidence: medium_ +_Date range: 2026-11-04_ +_Reviewer note: UNMAPPED: package WinDevLib, symbol DxcCreateInstance. The thread says the declaration was not yet updated in WinDevLib; verify against the current package source, since a direct declaration may now exist and make this workaround unnecessary._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## UNMAPPED · after-remarks > [!NOTE] -> The twinBASIC IDE process is 32-bit regardless of the target architecture selected for the project. When the project targets Win64, only the backend compiler process and the debugger process run as 64-bit; the IDE host process remains 32-bit. Because WebView2's memory usage grows over long editing sessions and is not fully released between compile cycles, the IDE can exhaust its 32-bit address space and crash with an out-of-memory error even on machines with large amounts of physical RAM. Save frequently during long sessions. The toolbar's **Restart Compiler** button can sometimes recover without a full IDE restart. +> WinRT collections cannot be created natively from twinBASIC. No API creates a new typed **IVector**, because the C++ helper used for this cannot be exported. A workaround is to instantiate an unrelated class with a property that returns an empty vector, and use that vector. Map types such as **IMap** can be used directly, because the **PropertySet** class is activatable. -_Source threads: 1453807327982649424 · confidence: high_ -_Date range: 2025-12-25 to 2025-12-30_ -_Reviewer note: This is an IDE architecture clarification. It should sit near the top of the IDE Features page or in a dedicated 'Known limitations' section. Reviewer should decide exact placement._ +_Source threads: 1397669015777968289-2025-07 · confidence: low_ +_Date range: 2025-07-23_ +_Reviewer note: Package Core, topic WinRT interop; no matching page found in the index. Verify against WinRT/WinDevLib docs before placing._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] - -## Keyboard Shortcuts +## UNMAPPED · new-section -- **Alt+Up Arrow / Alt+Down Arrow** moves the current line or selection up or down in the file. This shortcut is not listed in any menu but works in the code editor. +To find failures in a trace log, replace every occurrence of `HRESULT:=S_OK` with a placeholder such as `HRESULT_SOK`, then search for `HRESULT:=`. The remaining matches are calls that returned a value other than S_OK. -_Source threads: 1077971955052982312 · confidence: medium_ -_Date range: 2023-02-22_ -_Reviewer note: Verify that this shortcut is still present and works as described in a recent build. It was discovered accidentally by a community member and confirmed via reactions, not official documentation._ +_Source threads: 1164973320157012129-2024-10 · confidence: high_ +_Date range: 2024-10-23_ +_Reviewer note: UNMAPPED: package Core, no symbol. Belongs on a page about trace logging or debugging, if one exists._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1083431012417155154, 1448280876780752936, 1417830539024662548, 1437514645354315888] > [!NOTE] -> Find/replace can be restricted to a selected range of lines. Open **Edit > Replace**, enter the search and replacement terms, select the target lines in the editor, then click the rows icon in the Replace dialog (between the down-arrow button and the close button). The dialog shows how many items fall within the selection. Click **Replace All** to apply only within that range. +> +> A type library (`.tlb`) compiled for 64-bit only (for example, built with MIDL's `/env amd64` flag) will silently fail to load if the current project is configured for 32-bit mode. No architecture-mismatch error is shown, making the cause easy to misattribute. To add such a reference successfully, switch the project to 64-bit mode first, then add the TLB. If a TLB that loads correctly in a tool like OLEView still fails to load in the IDE, restarting all open twinBASIC IDE instances (not just the affected one) may clear the problem. -_Source threads: 1077971955052982312 · confidence: medium_ -_Date range: 2023-04-22_ -_Reviewer note: Verify the exact UI layout (rows icon position) against a current build. The description is from a community member explaining the feature to another user._ +_Source threads: 1160439577429954620 · confidence: medium_ +_Date range: 2023-10-08_ +_Reviewer note: Verify whether the silent-fail behavior has since been improved with a clearer error message in later IDE builds._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1448280876780752936, 1417830539024662548, 1437514645354315888] -> [!NOTE] -> **Type library import vs. reference.** twinBASIC supports importing `.tlb` and `.olb` type library files directly into a project as well as referencing them. Once a type library is present in the project, the IDE can display its definitions as equivalent twinBASIC source. This applies to third-party type libraries such as `oleexp.tlb`, `OLEGuids.tlb`, and DirectX TLBs. +## When 64-bit is Required -_Source threads: 1077971955052982312 · confidence: high_ -_Date range: 2023-05-29_ -_Reviewer note: The IDE-Features page already notes 'A type library viewer for controls and TLB files that displays the full contents in twinBASIC-style syntax.' Verify whether the import-vs-reference distinction is accurate and worth calling out separately, or whether this note should be merged into the existing bullet._ +Some functionality is only reachable from a 64-bit process: + +- A 64-bit Office installation registers only 64-bit shell extensions. A 32-bit application therefore cannot read or edit Office document properties through the Windows property system, and cannot load Office preview handlers. +- The Windows Recovery Environment does not provide WoW64, so only 64-bit executables run there. + +A **win64** build restores this functionality for applications that depend on shell integration. + +_Source threads: 1083431012417155154 · confidence: medium_ +_Date range: 2023-03-09 to 2023-03-17_ +_Reviewer note: Source: Discord thread, fafalone comments dated March 2023. Claims about Office shell extension registration and WinRE come from the thread, not verified against Microsoft docs. Finding had no symbol; Core package index not in the page index, so 64bit.md chosen as the best fit. The vbNullPtr finding was dropped because docs/Features/Language/Pointers.md already covers it. The Direct2D legacy-mode and issue #2107 findings (low confidence, no symbol) were dropped as too unverified and unmappable; reviewer may want to triage them manually._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1417830539024662548, 1437514645354315888] > [!NOTE] -> **IntelliSense and Enter key.** In some contexts, pressing Enter once dismisses the IntelliSense suggestion list without performing the associated expansion. A second Enter is then required to complete the action --- for example, to expand `Sub Test` into `Sub Test() ... End Sub` with the cursor inside, or to advance to the next line after completing a declaration. This differs from VB6, where a single Enter both dismissed IntelliSense and applied the selection. +> The roadmap item for running 32-bit components inside a 64-bit twinBASIC process via a proxy process covers **32-bit ActiveX controls only**. It does not extend to general-purpose 32-bit COM DLLs (such as RichClient5/6). Even for ActiveX controls, graphical operations that rely on Windows device contexts (DCs) are unlikely to work correctly when the control runs in a separate proxy process. For non-graphical 32-bit COM servers, the Windows OS-level `DLLSurrogate` mechanism may work as an alternative. -_Source threads: 1077971955052982312 · confidence: medium_ -_Date range: 2023-03-07_ -_Reviewer note: Reported by one community member without contradiction. Verify whether this is still the behavior in current builds, or whether it has been fixed since March 2023._ +_Source threads: 1448280876780752936 · confidence: high_ +_Date range: 2025-12-10_ +_Reviewer note: Verify the proxy-process roadmap scope against the twinBASIC roadmap or release notes before publishing; the Q3 2026 timeline and "ActiveX controls only" scope came from a Discord thread._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1448280876780752936, 1437514645354315888] > [!NOTE] -> **Auto-insertion of block-closing keywords.** The editor inserts block-closing keywords (such as `End If`, `End Sub`) automatically when you start a new block and press Enter. This feature is less reliable in larger projects and when the line immediately after the cursor is not empty --- in those cases the editor may only indent to match the previous line instead of inserting the closing keyword. +> +> When porting VB6 tooltip code that sets the `TTS_BALLOON` style via `GetWindowLong` / `SetWindowLong`, replace both calls with `GetWindowLongPtr` / `SetWindowLongPtr` for 64-bit targets. The non-`Ptr` variants silently return without applying the style in a 64-bit process, resulting in square tooltips instead of the balloon appearance. This is a standard Win32 64-bit porting requirement that applies to any window-style manipulation, not only tooltips. -_Source threads: 1077971955052982312 · confidence: medium_ -_Date range: 2023-03-05 to 2023-03-06_ -_Reviewer note: Described as a known beta-era bug by fafalone. Verify whether this has been fixed in more recent builds before including._ +_Source threads: 1417830539024662548 · confidence: medium_ +_Date range: 2025-09-17_ +_Reviewer note: Confirm that GetWindowLong vs GetWindowLongPtr behaviour on 64-bit is consistent with the Win32 documentation; the thread reports this from practical observation._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks [DUPLICATE? -- see also thread 1160439577429954620, 1083431012417155154, 1448280876780752936, 1417830539024662548] > [!NOTE] -> The twinBASIC IDE is built on the Monaco editor (the same editor that powers VS Code), which does not implement overtype mode. Pressing the **Insert** key to toggle between insert and overtype modes---as expected in the classic VB6 IDE---has no effect. This is a Monaco upstream limitation, not a twinBASIC-specific decision. +> When importing resources from a VB6 `.res` file that embeds a manifest, the manifest's `processorArchitecture` attribute is typically set to `x86`. An `x86` manifest causes 64-bit builds to fail or behave incorrectly. Change `processorArchitecture` to `*` (the wildcard value) in the manifest XML so the same embedded manifest applies to both 32-bit and 64-bit builds. -_Source threads: 1087939028826984480 · confidence: high_ -_Date range: 2023-03-22 to 2023-03-28_ -_Reviewer note: Check that the Editing Features section is the right placement on this page; insert after the existing bullet list or as a new sub-note under the overtype bullet if one is added._ +_Source threads: 1437514645354315888 · confidence: medium_ +_Date range: 2025-11-28_ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks > [!NOTE] -> **Auto-insert of `End Sub` / `End Function` / `End Property`.** The editor inserts the matching closing statement automatically when the user types a procedure header and presses Enter. This trigger activates only when the procedure keyword begins with a capital letter (for example `Sub`, `Private Sub`, `Public Function`). Typing an all-lowercase keyword such as `private sub test()` and pressing Enter does not insert the closing statement, leaving subsequent code flagged as errors. To recover, press the up-arrow key to return to the header line, move to the end of the line, and press Enter again after the IDE has capitalised the keyword. +> WinDevLib supersedes the oleexp type library and covers more APIs and interfaces, but it is not a drop-in replacement. Newer DirectX APIs use `ByVal` UDT parameters and same-interface overloads, which twinBASIC supports and VB6 does not, so VB6 workarounds such as passing points through **Currency** conflict with them. Switching an existing VB6 project to WinDevLib can therefore produce hundreds of errors. WinDevLib can be built with the `ByVal` UDT declarations disabled, which reduces the errors where VB6 compatibility must be kept. Type libraries such as oleexp still work in twinBASIC, but only in 32-bit projects. -_Source threads: 1169977003705909349 · confidence: high_ -_Date range: 2023-11-03_ -_Reviewer note: Place under the Editing Features section in IDE-Features.md, after the existing bullet list. Verify that the all-lowercase behaviour has not changed in recent IDE builds._ +_Source threads: 1513860400305672302 · confidence: medium_ +_Date range: 2026-06-09 to 2026-06-10_ +_Reviewer note: Placed after the WinDevLib paragraph on the 64-bit page because of the 32-bit-only typelib point. Package-specific, third-party statement from a Discord thread; verify the build option for disabling ByVal UDT declarations against the WinDevLib README before publishing._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/64bit.md · after-remarks > [!NOTE] -> **Block comment / uncomment.** Selecting multiple lines and pressing **Ctrl+/** toggles line comments on and off for the selection. This shortcut does not appear in the right-click context menu or the Edit menu; users who look for it there will not find it. +> Code that must also run in 64-bit VBA (VBA7) needs care with callbacks. In VBA64, the addresses of the procedures in a standard module are not valid until some procedure of that module has been called from VBA code. A callback or window procedure in a `.bas` module can crash when an external DLL calls it before that happens. To avoid this, add an empty `Public Sub` to the same module as the callbacks and call it first, for example from `Class_Initialize`, before any call that hands the callback address to the system. +> +> ```tb +> ' In the module that holds the callbacks and window procedures +> Public Sub InitModule() +> ' Intentionally empty. Calling it makes the module's procedure addresses valid in VBA64. +> End Sub +> ``` -_Source threads: 1176838610327187517 · confidence: high_ -_Date range: 2023-11-22_ -_Reviewer note: Place under the Editing Features section in IDE-Features.md, after the existing bullet list (or after the auto-insert note added from thread 1169977003705909349 if that addition lands nearby)._ +_Source threads: 1156840378213867620 · confidence: medium_ +_Date range: 2023-09-28 to 2025-12-02_ +_Reviewer note: Finding is about VBA64 behaviour, not twinBASIC; VBA package page was a poor fit so 64bit.md was chosen. Verify placement and that this belongs in the twinBASIC docs._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/Advanced/API-Declarations.md · after-remarks [DUPLICATE? -- see also thread 1086977208171634708] -## Title Bar and Taskbar Behavior +> [!NOTE] +> **DeclareWide** is particularly useful when migrating VB6 code that aliases an ANSI entry point (e.g. `Alias "ThingA"`). Replacing `Declare` with `DeclareWide` and updating the alias to the wide entry point (e.g. `"ThingW"`) enables Unicode operation while leaving all **String** parameter types and call sites unchanged. -By default the twinBASIC IDE uses a custom title bar. This custom title bar does not respond to a double-click to toggle between the maximized and restored window state, and may prevent an auto-hidden Windows taskbar from appearing over the IDE window. +_Source threads: 1303293694950244352 · confidence: medium_ +_Date range: 2024-11-06_ +_Reviewer note: The finding targets 'Core/DeclareWide', which has no standalone page in the reference index. The closest existing location is the DeclareWide section in docs/Features/Advanced/API-Declarations.md. Verify placement and consider whether a cross-reference from docs/Reference/Core/Declare.md is also needed._ -Enabling **Use real OS titlebar** in IDE Options switches to the standard Windows title bar, which restores double-click maximize/restore behavior and resolves the auto-hidden taskbar issue. +--- + +## docs/Features/Advanced/API-Declarations.md · after-remarks [DUPLICATE? -- see also thread 1303293694950244352] > [!NOTE] -> A fix for the double-click maximize/restore behavior was included in BETA 874. The **Use real OS titlebar** option remains available as an alternative if the default behavior is still not preferred. +> From BETA 283 onwards, the IDE hover tooltip explicitly identifies whether a declaration uses **Declare** or **DeclareWide**, along with other declaration attributes such as **CDecl** and **Alias**. This distinction prevents the common mistake of treating a **DeclareWide**-declared function as ANSI and adding an unnecessary separate W-suffixed declaration. -_Source threads: 1397666706197057627 · confidence: high_ -_Date range: 2025-07-23 to 2025-09-30_ -_Reviewer note: No dedicated IDE options page exists in the docs; IDE-Features.md is the closest match. The reviewer should confirm the exact menu path for 'Use real OS titlebar' in the current IDE, and verify whether BETA 874 fully resolved the double-click behavior._ +_Source threads: 1086977208171634708 · confidence: high_ +_Date range: 2023-03-19 to 2023-03-28_ +_Reviewer note: Insert after the DeclareWide section or as a closing note within it. Verify that BETA 283 is still the correct version boundary._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/Advanced/API-Declarations.md · after-remarks > [!NOTE] -> The twinBASIC IDE does not include a Procedure View / Full Module View toggle equivalent to the one found in the VB6 IDE. Code folding (expand/collapse regions and procedures) is available because it is provided by the Monaco editor. A Procedure View feature is tracked as GitHub issue #2160. +> `DeclareWide` changes only how **String** arguments are passed: they go to the API as wide (UTF-16) text, without conversion to ANSI. It does not choose which function is called. The `A` or `W` suffix comes from the declared name or its **Alias**, as in the example above. Using `DeclareWide` on an ANSI entry point such as `PostMessageA` passes wide text to a function that expects ANSI, and caused edge-case problems in WinDevLib. -_Source threads: 1411082650902335678 · confidence: high_ -_Date range: 2025-08-29_ -_Reviewer note: No dedicated VB6-compatibility IDE page exists. IDE-Features.md is the best available placement. Reviewer should verify that GitHub issue #2160 still accurately reflects the current status of this feature request._ +_Source threads: 1125359845936205844 · confidence: medium_ +_Date range: 2023-12-18 to 2026-09-02_ +_Reviewer note: There is no Core/DeclareWide page, so this targets the page that documents DeclareWide. Insert directly after the DeclareWide WARNING callout. The last sentence is an inference from a WinDevLib bug report about PostMessageA; verify the wording. The thread's remark about the built-in hidden-module declarations is omitted because docs/Reference/Attributes.md already shows that form._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] - -### Customising syntax colours - -The IDE loads syntax-highlight colours from two CSS files in its installation folder: - -- `styles-twinbasic-light.css` --- colours used in the Light and Classic themes. -- `styles-twinbasic-dark.css` --- colours used in the Dark theme. +## docs/Features/Advanced/API-Declarations.md · after-remarks -Editing these files changes the colour applied to the corresponding token classes: `.basicConstant`, `.basicVariable`, `.basicField`, `.basicEnum`, `.basicEnumMember`, `.basicKeyword`, and others. The changes take effect when the IDE reloads the stylesheet. This provides a way to adjust individual token colours without replacing an entire built-in theme. +> [!NOTE] +> A **Declare** cannot take a UDT **ByVal**. For an API that expects a structure by value, branch on the bitness. On 64-bit builds the ABI passes large structures by pointer, so declare the parameter **ByRef**. On 32-bit builds, expand the structure into its members as separate **ByVal** parameters (for a 4x4 matrix of **Single**, sixteen **Single** parameters), or pad with **ByVal Long** parameters to the same stack size. +> +> ```tb +> #If Win64 Then +> Private Declare PtrSafe Sub DrawMatrix Lib "mylib" (ByRef m As Matrix4x4) +> #Else +> Private Declare Sub DrawMatrix Lib "mylib" (ByVal m11 As Single, ByVal m12 As Single, ByVal m13 As Single, ByVal m14 As Single, ByVal m21 As Single, ByVal m22 As Single, ByVal m23 As Single, ByVal m24 As Single, ByVal m31 As Single, ByVal m32 As Single, ByVal m33 As Single, ByVal m34 As Single, ByVal m41 As Single, ByVal m42 As Single, ByVal m43 As Single, ByVal m44 As Single) +> #End If +> ``` -_Source threads: 1233485738516156486 · confidence: medium_ -_Date range: 2024-04-26 to 2024-04-27_ -_Reviewer note: Verify that `styles-twinbasic-light.css` and `styles-twinbasic-dark.css` are still the correct file names and that the CSS class names listed (`.basicConstant` etc.) are current. This mechanism may have been superseded by a built-in theme picker in later BETAs._ +_Source threads: 1352268312117252126 · confidence: medium_ +_Date range: 2025-06-13_ +_Reviewer note: Medium confidence; the ByRef-on-64-bit claim applies only to structures larger than 8 bytes (small structs pass in registers). Verify before publishing; symbol was null so placed on API-Declarations._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/Advanced/API-Declarations.md · after-remarks > [!NOTE] -> The IDE process has a known memory leak. Leaving the IDE running continuously for an extended period eventually exhausts available memory and produces an **Out of memory** crash. Closing the IDE after use releases all memory back to the system. Compiled twinBASIC applications are not affected by the same leak and can run continuously as background services, though monitoring memory consumption is still advisable. +> On a 32-bit build, a **FastCall** declaration whose first parameter is 64 bits wide, such as a **LongLong**, can fail with an access violation. The 32-bit fastcall convention passes only 32-bit values in ECX and EDX, so a 64-bit argument has to be split across registers, and twinBASIC does not do this. **Double** and **Currency** parameters are probably affected in the same way. The same declaration with **Long** or **Integer** parameters works. -_Source threads: 1319559634453008415 · confidence: medium_ -_Date range: 2024-12-20_ -_Reviewer note: This is sourced from fafalone's statement in a Discord thread and marked medium confidence. Check whether the IDE memory leak has been fixed in a more recent twinBASIC release before publishing --- the statement dates from December 2024. The IDE-Features page covers IDE behavior generally and is the closest existing target; a dedicated Known Issues or Release Notes page would be a better long-term home if one exists._ +_Source threads: 1555560444473774171 · confidence: low_ +_Date range: 2026-10-02_ +_Reviewer note: Cause is a suspicion in the thread, not confirmed by a maintainer; verify against current build before publishing. Insert after the RtlUlongByteSwap FastCall example. Original symbol: Declare FastCall (Core)._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1155949699585540216, 1503093611246391338, 1507659583437668512] -> [!NOTE] -> The twinBASIC IDE does not currently support split editor windows or side-by-side source views. The entire editor surface is rendered as a single WebView2 HWND, so intercepting or extending it by subclassing the window handle is not practical. Side-by-side multiple editor windows are on the planned feature list. An addin workaround using the [tbIDE SDK](../../Reference/tbIDE/) can open a file as a read-only text panel in a tool window; the IDE ships sample 12, which demonstrates embedding a Monaco editor instance in a tool window via the addin SDK. +## Porting VB6 Assembly Thunks -_Source threads: 1379796923224952892 · confidence: high_ -_Date range: 2025-06-04_ -_Reviewer note: Verify the beta number (784) is still relevant or omit it. Confirm whether side-by-side windows have been added in any beta after 784. Relative link from IDE-Features.md to tbIDE/ index: ../../Reference/tbIDE/ — confirm against rendered URL._ +VB6 projects often embed raw machine code as base64-encoded strings, decoding them at run time into an executable memory block and calling the resulting function pointer. This technique was used primarily to call CDecl functions or to perform low-level operations without a `Declare` statement. It relies on internal VB6 runtime implementation details that twinBASIC does not replicate, so these thunks do not work when porting such a project. ---- +Replace them with twinBASIC-native equivalents: -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +- Use `Emit()` inside a `[Naked]` function (as shown above) to inject the same byte sequence directly into the compiled output, with no run-time decoding step. +- Use a typed [**Delegate**](../../Features/Language/Delegates) function pointer and a `CDecl Declare` to call a CDecl API cleanly without any thunk. -> [!NOTE] -> The code minimap and bracket pair colorization settings were removed from the IDE Options dialog and moved to the theme system. To enable them, create a `.theme` file in `C:\Users\\AppData\Roaming\twinBASIC\themes`, then select it via **Window > Theme**. A minimal file that enables both features while inheriting the built-in dark theme: -> -> ``` -> inherits: dark; -> CodeMinimapVisible: 1; -> CodeBracketPairColorization: 1; -> ``` -> -> Replace `dark` with `light` or `classic` to base the theme on one of the other built-in themes. Placing the file in the directory is not enough --- the theme must be selected explicitly in the IDE after it is created. +See [Enhanced API Declarations](API-Declarations) for `CDecl` support on `Declare` statements and callbacks. -_Source threads: 1381120139893674015 · confidence: high_ -_Date range: 2025-06-08_ -_Reviewer note: Addition targets the Theme System section in IDE-Features.md. Verify the AppData path and theme key names against the latest IDE build, as these were described in a Discord thread and not in official docs._ +_Source threads: 1111087497389441095 · confidence: medium_ +_Date range: 2023-05-25 to 2023-05-29_ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1503093611246391338, 1507659583437668512] -> [!WARNING] -> The **SafeMode** target (listed after `win64` in the target architecture dropdown) disables the compiler to allow raw file editing without diagnostic overhead. At least one report confirms that when SafeMode is active, the IDE does not detect file changes and does not save edits to disk. Switching back to a normal target causes all edits made during the SafeMode session to be lost. Switching the project to `win32` temporarily is a safer alternative when the goal is to suppress diagnostics during editing. +> [!IMPORTANT] +> When executing code written into a stack-allocated byte array and called through a function pointer (for example, `CallWindowProcA` used as a trampoline), twinBASIC enforces Data Execution Prevention (DEP) in two independent places: the **project settings** and the **IDE settings**. Disabling DEP only in the project settings is not sufficient --- if the IDE setting remains enabled, the thunk fails at runtime when run inside the IDE. Both settings must be set to **off** for stack-allocated executable code to work. -_Source threads: 1382393005973180457 · confidence: medium_ -_Date range: 2025-06-11 to 2025-06-12_ -_Reviewer note: SafeMode behavior (edits not saved) may have been fixed in a subsequent IDE release. Verify against a current build before publishing. This warning could be promoted to a NOTE if the issue has been resolved._ +_Source threads: 1155949699585540216 · confidence: high_ +_Date range: 2023-09-25_ +_Reviewer note: Verify the exact names of both DEP settings in the current twinBASIC IDE and project settings UI, and confirm they remain separate controls._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] - -> [!NOTE] -> When an editor panel is floating (undocked), it can be returned to its docked position in two ways: drag it by the grabber control at the top-left corner of the floating window, or use the **Window** menu to apply any saved panel layout (such as the default layout). Closing and reopening the source file also restores the default dock state. - -_Source threads: 1391112464422404208 · confidence: high_ -_Date range: 2025-07-05_ -_Reviewer note: The grabber control description ("top-left corner") is from the maintainer's own reply; verify it still matches the current IDE layout before publishing._ +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1155949699585540216, 1507659583437668512] ---- +## API hooking and memory patching -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] +### Reading and writing executable memory -### Custom theme files +`WriteProcessMemory` and `ReadProcessMemory` always have full read/write access to the current process's memory, so they can patch executable pages without first changing the page's protection flags with `VirtualProtect`. Avoiding `VirtualProtect` removes the extra cleanup step and reduces the number of API calls. -Custom themes are plain text files with a `.theme` extension placed in `%APPDATA%\Roaming\twinbasic\themes`. A theme file can inherit from a built-in theme and override individual properties using the syntax `inherits: ;` followed by `PropertyName: value;` lines. +### Trampoline stubs and antivirus detection -The `InlineDecorationColor` property controls the color of the end-of-block tooltips shown at closing lines such as `End Sub` and `End If` in the editor. The value accepts any CSS color expression, including `rgba()` for semi-transparency: +When constructing a trampoline or hook stub, a relative `JMP` opcode is commonly flagged as suspicious by antivirus scanners. Replacing the relative `JMP` with a `PUSH targetAddr` / `RET` sequence achieves the same unconditional transfer and is not flagged. The opcodes differ between x86 and x64: -``` -inherits: Dark; -InlineDecorationColor: rgba(200, 200, 200, 0.5); +```tb +#If Win64 Then + ' x64: PUSH lower32; MOV [rsp+4], upper32; RET + Emit &H68 ' PUSH imm32 (low dword) + Emit targetLow, 0, 0, 0 ' placeholder: fill at runtime + Emit &HC7, &H44, &H24, &H04 ' MOV DWORD PTR [rsp+4], imm32 + Emit targetHigh, 0, 0, 0 ' placeholder: fill at runtime + Emit &HC3 ' RET +#Else + ' x86: PUSH imm32; RET + Emit &H68 ' PUSH imm32 + Emit target, 0, 0, 0 ' placeholder: fill at runtime + Emit &HC3 ' RET +#End If ``` -_Source threads: 1398426772021841961 · confidence: medium_ -_Date range: 2025-07-26 to 2025-08-06_ -_Reviewer note: The Theme System section in IDE-Features.md currently only has one sentence. Verify the property name InlineDecorationColor and the file format against an actual .theme file before publishing. The %APPDATA% path casing (twinbasic vs twinBASIC) should also be confirmed._ +_Source threads: 1503093611246391338 · confidence: medium_ +_Date range: 2026-05-11_ +_Reviewer note: The code block is illustrative; confirm exact opcode sequences against the referenced Discord project before publishing. The WriteProcessMemory/ReadProcessMemory preference over VirtualProtect is medium-confidence community advice, not a documented compiler behavior._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] - -### Align controls and snap to grid +## docs/Features/Advanced/Assembly.md · after-remarks [DUPLICATE? -- see also thread 1111087497389441095, 1155949699585540216, 1503093611246391338] -The Form Designer includes a snap-to-grid / align-controls feature. Hover over the designer toolbar area to reveal the **Enable align controls** button. When enabled, control movement is constrained to grid increments, which prevents accidental fine-grained shifts when clicking to select a control. +> [!IMPORTANT] +> Vectored Exception Handler (VEH) techniques that modify the CPU context --- setting `Eip` (32-bit) or `Rip` (64-bit) to skip a faulting instruction --- work only in compiled executables. The same code does not function when run inside the twinBASIC IDE in interpreted (debug) mode. VEH-based safe-copy or access-violation recovery patterns cannot protect code running in the IDE. -### Locking controls +_Source threads: 1507659583437668512 · confidence: high_ +_Date range: 2026-05-23_ -Controls in the Form Designer can be locked to prevent accidental repositioning by mouse drag. To lock a single control, right-click it and choose **Lock** from the context menu. To lock all controls at once, press **Ctrl+A** to select all, then right-click and choose **Lock**. Locked controls remain selectable and their properties remain editable. To unlock, use the same context menu or **Edit > Unlock controls** / **Unlock all controls**. +--- -_Source threads: 1414851467046551552 · confidence: high_ -_Date range: 2025-09-09 to 2025-09-16_ -_Reviewer note: Merges two findings (snap-to-grid from thread 1414851467046551552 and lock controls from thread 1417476641105514707). Verify menu item names and exact toolbar button label against the current IDE build._ +## docs/Features/Advanced/Multithreading.md · after-remarks ---- +## COM on background threads -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1432631707735031808, 1436747952189083799, 1448666378998382753] +Background threads that need to create or call COM objects must call `OleInitialize` (or `CoInitialize` / `CoInitializeEx`) on the thread before using COM. The main UI thread initialises COM automatically; background threads created with `CreateThread` do not inherit that initialisation. -### Saving panel layout +```tb +Private Declare PtrSafe Function OleInitialize Lib "ole32" ( _ + ByVal pvReserved As LongPtr) As Long +Private Declare PtrSafe Sub OleUninitialize Lib "ole32" () -By default, the DEBUG CONSOLE, Variables, Diagnostics, and Call Stack panels reopen on each IDE start, even if they were closed in the previous session. To persist a custom panel arrangement, use **Save Panel Layout as** from the IDE's View or panel management menu. Once saved, that layout is restored on subsequent starts. +Public Sub MyBackgroundThread() + OleInitialize 0 + ' ... use COM objects here ... + OleUninitialize +End Sub +``` -_Source threads: 1414583335472070776 · confidence: medium_ -_Date range: 2025-09-08_ -_Reviewer note: Verify the exact menu path for 'Save Panel Layout as' in the current IDE build._ +_Source threads: 1099362561482301523 · confidence: medium_ +_Date range: 2023-05-12_ +_Reviewer note: Confidence is medium: the claim that the main UI thread initialises COM automatically should be verified against twinBASIC runtime behaviour. The OleInitialize requirement is standard Win32 practice but the automatic main-thread initialisation claim comes from a community discussion, not primary source documentation._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1436747952189083799, 1448666378998382753] - -## Form designer known issues +## docs/Features/Advanced/Static-Linking.md · after-remarks [DUPLICATE? -- see also thread 1437751213981700156] > [!NOTE] -> Pressing the Alt key alone in the IDE (regardless of whether the form designer or code editor is active) opens the File menu. After the menu closes, the form designer may enter a broken state: clicking a control inside a container (Frame, PictureBox, or similar) selects both the child control and its container, and controls can no longer be moved with the mouse or arrow keys. Restarting the IDE clears the problem. -> -> Workaround: while in this state, use the control drop-down list at the top of the Properties pane to select the exact control whose properties need to be changed. The drop-down shows all controls available in the current context and is not affected by the selection state of the designer. +> The twinBASIC IDE includes an automatic safe mode that detects repeated rapid compiler restarts and breaks the loop. If the compiler restarts several times in quick succession --- for example, when editing a source file that contains an `Import Library` statement triggers a compiler error --- the IDE enters safe mode automatically, allowing edits to be made before the next full restart. Safe mode can also be entered manually at startup if an `Import Library`-related edit causes persistent restart failures. -_Source threads: 1432631707735031808 · confidence: high_ -_Date range: 2025-10-28_ -_Reviewer note: Merges two findings from the same thread (gotcha + workaround). Confirm the Properties pane drop-down description is accurate and that the Alt-key behavior has not been fixed in a post-October-2025 build._ +_Source threads: 1208948615368941579 · confidence: high_ +_Date range: 2024-02-19 to 2024-02-20_ +_Reviewer note: This is based on a fixed bug from BETA 452-453 (February 2024). The auto-safe mode feature shipped in BETA 453. Verify that automatic safe mode is still present in current builds and that the description of its trigger conditions is accurate before publishing. The specific `Import Library` editing bug that caused the loop has been fixed; only the auto-safe mode note is intended here._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1448666378998382753] - -The following keyboard shortcuts operate on all procedures in the active editor at once: +## docs/Features/Advanced/Static-Linking.md · after-remarks [DUPLICATE? -- see also thread 1208948615368941579] -| Shortcut | Action | -|----------|--------| -| **Ctrl+Alt+Left** | Collapse all procedures except the one containing the cursor | -| **Ctrl+Alt+Right** | Expand all procedures | +> [!IMPORTANT] +> Only C (not C++) object code can be statically linked. A `.lib` or `.obj` file that contains C++ object code --- even when the intended entry points are exposed through an `extern "C"` wrapper --- cannot be linked in twinBASIC because the linker cannot resolve C++ runtime symbols from the embedded object files. The workaround is to compile the C++ code as a DLL and use a standard `Declare` statement to access its exported functions. -_Source threads: 1436747952189083799 · confidence: high_ -_Date range: 2025-11-08_ +_Source threads: 1437751213981700156 · confidence: medium_ +_Date range: 2025-11-11 to 2025-11-14_ +_Reviewer note: The finding mentions SQLite Library 1.2 as an affected package due to unresolved api-ms-win-crt-math-l1-1-0 symbols. Verify whether this still applies or was resolved in a later SQLite library version. The general C-only restriction should be confirmed against the twinBASIC linker documentation or a recent build._ --- -## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799] - -### Code Formatting +## docs/Features/Advanced/Static-Linking.md · after-remarks -The **Format Document** command reformats the entire source file. It is available under **Edit** > **Format Document**. A **Format Selection** command reformats only the selected text. Both commands have default keyboard shortcuts that can be customized through the **Window** menu's keyboard shortcut manager. The internal command names displayed in the IDE status bar are `tbEditor_FormatDocument` and `tbEditor_FormatSelection`. +> [!NOTE] +> The **Miscellaneous** folder is not ignored by the compiler. It holds arbitrary files, but the compiler links the `.obj` and `.lib` files named by an **Import Library** line into the executable. The files need not be in that folder: a project can keep them in a folder of its own, such as `StaticLibraries`, and give that path to **Import Library**. +> +> Files in **Miscellaneous** are not available at run time. To embed a binary that the program reads while it runs, put it in the **Resources** folder instead. See [How do I use resources in twinBASIC?](../../FAQ#resources). -_Source threads: 1448666378998382753 · confidence: high_ -_Date range: 2025-12-12_ +_Source threads: 1522084764817948903 · confidence: high_ +_Date range: 2026-07-02 to 2026-07-06_ +_Reviewer note: Finding was filed under package Core with no symbol. The page index has no Features pages, so the target was chosen by reading nearby files (Static-Linking.md, which names the Miscellaneous folder). Verify the claim that Import Library accepts a path outside Miscellaneous, and that the FAQ anchor #resources exists (the summary element has id="resources")._ --- -## docs/Features/GUI-Components/Forms.md · after-remarks +## docs/Features/Compiler-IDE/CodeLens.md · after-remarks > [!NOTE] -> The `true` manifest element enables the "System (Enhanced)" DPI scaling mode in a compiled EXE, but has no effect when running under the twinBASIC IDE. To simulate the same mode during IDE execution, call `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)` in `Sub Main` before any forms are loaded. To check the current DPI context programmatically, call `GetDpiAwarenessContextForProcess(0)` and compare the result with `AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, lDpiContext)`. +> In projects that reference large dependency packages, the **Show Run Procedure Bar** setting can cause Go to Definition to take 20--40 seconds per lookup, leaving the editor with white text during that time. The delay occurs on every lookup, even when the target file is already open. Disabling **Show Run Procedure Bar** in the project settings makes Go to Definition near-instant. This is a known performance interaction; disabling CodeLens is the current workaround when working with large packages. -_Source threads: 1457732092715733173 · confidence: medium_ -_Date range: 2026-01-05 to 2026-01-06_ -_Reviewer note: Insert in the DPI Scaling section of Forms.md. The Win32 API declarations for SetProcessDpiAwarenessContext, GetDpiAwarenessContextForProcess, and AreDpiAwarenessContextsEqual are not in the docs; the reviewer may want to add them or link to the API declarations page._ +_Source threads: 1375013634656309360 · confidence: medium_ +_Date range: 2025-05-22_ +_Reviewer note: Medium confidence; verify whether this has been fixed in a recent BETA build before publishing, or add a note about which BETA version introduced the fix if one exists._ --- -## docs/Features/Language/Comments.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Known Limitations with Custom Win32 Message Pumps > [!NOTE] -> The block-comment lexer scans for the first `*/` sequence unconditionally, without recognizing string delimiters. If the character sequence `*/` appears inside a quoted string within a block comment, the comment ends at that point and the remaining text is parsed as twinBASIC source. For example: -> -> ```tb -> /* comment with "*/" more comment */ -> ``` -> -> The comment ends after the `*/` inside the string, not at the trailing `*/`. This is by-design behavior consistent with how most languages handle block-comment lexing. To avoid this, either rephrase the comment to avoid `*/` inside block comments, or use line comments (`'`) for code that contains such strings. +> Two IDE-specific limitations apply when a program runs its own Win32 message loop (`TranslateMessage` / `DispatchMessage`) instead of using VB's built-in event model. -_Source threads: 1162324498083745802 · confidence: high_ -_Date range: 2023-10-13_ +**Window not displayed in the IDE.** A window created entirely via Win32 API calls --- not using a VB `Form` --- with its own message loop may fail to appear on screen when the program is launched from the twinBASIC IDE. The window handle is valid, all API calls succeed, and the window is visible in Spy++, but the window surface never renders and the `WindowProc` is never called. The same code works correctly when compiled and run as a standalone executable, and also works in VB6. The root cause has not been identified. + +**Stop and Break buttons unresponsive.** When a custom Win32 message pump is active, the IDE Stop and Break buttons do not respond. This has been observed in single-threaded programs, so it is distinct from the known limitation where the IDE loses track of execution across multiple threads. + +_Source threads: 1077672242999210076 · confidence: medium_ +_Date range: 2023-02-21_ +_Reviewer note: Findings originate from VB package / null symbol; mapped here (Debugging page) as the closest existing page for IDE-behavior gotchas. Verify whether either limitation has been fixed in a later twinBASIC release before publishing, as the thread is from February 2023. The window-not-displayed finding mentions a workaround (stepping through the message loop once with breakpoints causes the window to appear and it continues to appear after the breakpoints are cleared) -- consider whether to include it._ --- -## docs/Features/Language/Inheritance.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Immediate Memory Invalidation + +The debugger settings include an **Immediate memory invalidation** option. When enabled, freed memory is invalidated immediately rather than lazily. This causes bugs involving access to memory after it has been freed --- such as an array or object that has been released too soon --- to crash consistently rather than intermittently, making them easier to reproduce and isolate. > [!NOTE] -> twinBASIC's object model is COM-based and inherits VB6's "object-based" rather than fully object-oriented architecture. Operator overloading is not supported, and **String** and array types are not first-class objects. OOP patterns that depend on these features --- such as arithmetic operators on custom numeric types --- cannot be replicated directly. The additions of generics, implementation inheritance (**Inherits**), and parameterized constructors (**Sub New**) go significantly beyond VB6 semantics, but the underlying system remains lower-level than C#, Java, or VB.NET. +> +> This option applies only when running under the IDE debugger. It does not help diagnose crashes that occur only in a compiled binary invoked outside the IDE, such as a standard DLL called from an external host. -_Source threads: 1436287137350946816 · confidence: high_ -_Date range: 2025-11-07 to 2025-11-10_ +_Source threads: 1099208311313813534 · confidence: high_ +_Date range: 2023-04-22_ --- -## docs/Features/Language/Module-Organization.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] -> [!NOTE] -> Semantic highlighting in the IDE processes tokens near the visible area on each scroll event. In very large source files (roughly 60,000 lines or more), earlier IDE versions processed all tokens in the file on each scroll, causing choppy scrolling and high CPU usage. This was fixed in BETA 426, which introduced paging so that only the tokens close to the current view are processed. If working with an older version, splitting large files into smaller modules reduces the number of tokens processed per scroll. +## Native Exception &H406D1388 -_Source threads: 1200262841345773578 · confidence: high_ -_Date range: 2024-01-26_ +The native exception code `&H406D1388` that appears in the twinBASIC IDE output log during a debug session is a continuable C++ exception --- specifically, the standard Microsoft Visual C++ runtime "set thread name" exception. It does not indicate incorrect Basic code. ---- +Prior to BETA 299, the twinBASIC debugger aborted the debug session when it encountered this exception. From BETA 299 onward, the exception is caught and execution continues. The exception still appears in the IDE log, but no longer causes a session restart. No action is required. -## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1084723164296261672, 1283432466471845993] +_Source threads: 1101704869804511265 · confidence: high_ +_Date range: 2023-05-12_ -## Known Limitations +--- + +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] > [!NOTE] -> The overload resolver has two known edge cases that can produce spurious ambiguity errors. -> -> **Specific interface types as the last parameter.** When two overloads differ only in parameter count but share the same trailing parameter type, the compiler resolves the call correctly when that type is `Object`. Changing the trailing type to a specific interface (such as a COM interface imported via a `Declare` or type-library reference) can cause the shorter overload to be reported as ambiguous, even though the argument counts differ unambiguously. Reversing the declaration order --- declaring the shorter overload with `Object` and the longer with the interface type --- avoids the error. +> When trace mode is active and the **TracePrint** option is enabled in the project's trace settings, tracing can be paused and resumed at runtime using sentinel strings. Call `Debug.TracePrint "~~OFF"` to pause logging and `Debug.TracePrint "~~ON"` to resume it. Both conditions must be met: the sentinel call alone has no effect if **TracePrint** is not enabled in project settings. > -> **Implicit widening between `Long` and `Single`.** When one overload takes a single `Single` parameter and another takes two `Long` parameters, passing a `Long` variable to the one-argument call produces an ambiguity error because the resolver considers widening `Long` to `Single` as a candidate match for the two-argument form. Passing a `Long`-typed literal (e.g. `0&`) resolves without error because the explicit type suffix removes the widening candidate. +> A common pattern is to call `Debug.TracePrint "~~OFF"` at the top of `Sub Main` to skip the trace overhead during form load, then re-enable it later --- for example, from a button click handler --- once the UI is ready. -_Source threads: 1081364889815228416 · confidence: medium_ -_Date range: 2023-03-03 to 2023-03-04_ -_Reviewer note: Both issues were reported by fafalone with minimal repro projects (Discord, early March 2023). Verify whether either has been fixed in later compiler builds before publishing. The first issue involves specific COM interface types as trailing overload parameters; the second involves Long-to-Single widening across overloads of different arity._ +_Source threads: 1374402188474056704 · confidence: high_ +_Date range: 2025-05-20_ +_Reviewer note: Verify that the sentinel strings are exactly `~~OFF` and `~~ON` (case-sensitive?) and that they require the TracePrint setting to be enabled in project settings. The finding describes this clearly but the exact sentinel format and project-settings prerequisite should be confirmed against the twinBASIC source or documentation._ --- -## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1081364889815228416, 1283432466471845993] +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] -> [!NOTE] -> -> All overloads of a given procedure name must be declared in the same module. The symbol resolver groups overloads by name within a module and registers them as a single entry; overloads defined in different modules are not recognized as overloads of each other and will instead cause an ambiguity error at the call site. This constraint matches VB6 behavior and is an intentional property of the context-free resolver. +## Recovering from a Stuck IDE -_Source threads: 1084723164296261672 · confidence: high_ -_Date range: 2023-03-13_ +When a code-generation error occurs during a preview or run, clicking the **Stop** button may have no effect and the IDE may become unresponsive. In this situation, use the **compiler reset** button on the toolbar to forcibly reset the compiler state. This is the general-purpose recovery action for IDE hangs caused by compiler errors. + +_Source threads: 1403430684219146300 · confidence: medium_ +_Date range: 2025-08-08 to 2025-08-14_ +_Reviewer note: Placed in the Debugging features page as a new section. If a more specific troubleshooting page exists or is created, move this there. The section heading uses new-section formatting inside after-remarks due to how the target page is structured (existing content is headings)._ --- -## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1081364889815228416, 1084723164296261672] +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] > [!NOTE] -> Compiler error **TB5073** --- "Unable to disambiguate between overloaded methods" with the sub-message "*N* Incompatible matches found" --- does not mean the overloads cannot be distinguished from each other. It means the arguments at the call site are incompatible with every candidate overload. Even if each overload had a unique name, the call would still fail. The fix is to correct the argument types at the call site, not to restructure the overloads. +> twinBASIC does not yet support **Edit and Continue**. Any code change made while execution is paused requires a full program restart before it takes effect. This is a known missing feature. -_Source threads: 1283432466471845993 · confidence: high_ -_Date range: 2024-09-11 to 2024-09-12_ +_Source threads: 1403579629482020934 · confidence: high_ +_Date range: 2025-08-09_ +_Reviewer note: Verify that the Debugging features page does not already mention Edit and Continue. If the feature has shipped since this thread (August 2025), remove this note._ --- -## docs/Features/Language/Overloading.md · new-section [DUPLICATE? -- see also thread 1245329874777014302] - -## Overload Resolution and Interface Types - -twinBASIC's overload resolution evaluates each candidate overload by testing all arguments. A candidate is eliminated when any argument requires a narrowing conversion to match the candidate's declared parameter type. The process succeeds when exactly one candidate remains. - -Interface types use stricter matching than value types. This is intentional: it lets code declare overloads that target different specific COM interface types --- for example, one overload for `IStream` and a separate overload for `ISequentialStream` --- where traditional VBx would accept any interface at the call site and only fail at runtime. +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] > [!NOTE] -> Because all arguments are checked before a candidate is eliminated, an overload can be incorrectly discarded even when another argument (such as `String` vs `LongPtr`) already uniquely identifies it. If every candidate is eliminated, the compiler reports **TB5073** (unable to disambiguate) even though the intent is unambiguous. - -**Passing `Nothing`.** Prior to BETA 536, passing `Nothing` to a parameter declared as a specific interface type (e.g., `IBindCtx`) was treated as a narrowing conversion, eliminating that candidate. When all overloads shared the same specific-interface parameter, every candidate was eliminated and TB5073 was reported. BETA 536 corrects this: `Nothing` is treated as non-narrowing for any interface parameter. +> `Debug.TracePrint` output does not appear in the Debug Console unless the trace logger destination is explicitly configured to `${DEBUG}`. Without that explicit configuration the calls are silently discarded, even though the configuration UI displays `${DEBUG}` as a hint value. This was fixed in twinBASIC BETA 951. -**Passing a base-interface variable.** Passing a variable typed as a base interface (e.g., `IUnknown`) to overloads that all expect more derived interface types still results in TB5073. The compiler does not yet traverse the COM interface inheritance chain to find the best match. The workaround is to cast the variable to the exact interface type required by the intended overload before passing it: +_Source threads: 1454461049003835433 · confidence: high_ +_Date range: 2025-12-27 to 2026-01-16_ +_Reviewer note: Insert after the Debug Trace Logger section in Debugging.md. Confirm the BETA 951 fix version against the release log._ -```tb -' Suppose two overloads exist: -' Sub Foo(pbc As IBindCtx, pszPath As String) -' Sub Foo(pbc As IBindCtx, pszPath As LongPtr) +--- -Dim unk As IUnknown -' This causes TB5073 -- IUnknown is narrowing to IBindCtx: -' Foo unk, "C:\example" +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] -' Cast first to avoid the ambiguity: -Dim bc As IBindCtx -Set bc = CType(unk, IBindCtx) -Foo bc, "C:\example" -``` +> [!NOTE] +> +> The **Compiler services: TRACE mode** option in the IDE's About menu is unrelated to `Debug.TracePrint`. That option enables extra IDE diagnostic information written to the debug console when a crash occurs --- the IDE also turns it on automatically after a crash, for the next run. To enable `Debug.TracePrint` output, use the trace logger option in the project settings instead. -_Source threads: 1238767956050051114 · confidence: high_ -_Date range: 2024-05-11 to 2024-05-12_ -_Reviewer note: The `Nothing`-as-non-narrowing fix is confirmed for BETA 536 by WaynePhillipsEA in the thread. The IUnknown / base-interface limitation was confirmed as not yet implemented as of the thread date (2024-05-12). Verify whether COM interface inheritance traversal has since been added before publishing._ +_Source threads: 1455208656328200224 · confidence: high_ +_Date range: 2025-12-29 to 2026-01-17_ +_Reviewer note: Confirm current project settings UI label for the trace logger option before publishing._ --- -## docs/Features/Language/Overloading.md · new-section [DUPLICATE? -- see also thread 1238767956050051114] +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] -## Overload resolution and widening conversions +## Module-Level Variable State Between Debug Runs -When no candidate overload is an exact match for every argument --- that is, every candidate requires at least one widening conversion for at least one argument --- the compiler cannot choose between them and reports an ambiguity error. It does not narrow the candidate set by comparing individual parameter types across overloads and then resolve the remainder independently. +Stopping a program in the IDE and running it again without restarting the compiler does not fully reset module-level (global) variables. They can retain values from the previous run, causing unexpected behavior on subsequent debug runs. -A common trigger is passing an unqualified integer literal where a **Long** parameter is declared. The literal is typed as **Integer** by default, so the call requires a widening conversion to **Long**, and if another overload also requires a widening conversion for a different parameter, neither candidate is preferred. +To guarantee a clean state, restart the compiler before each run by clicking the blue arrow next to the win32/win64 target dropdown. This reinitialises all module-level state. -The fix is to use a typed literal suffix so the argument matches the intended overload exactly: +_Source threads: 1253786438454214788 · confidence: medium_ +_Date range: 2024-06-24_ +_Reviewer note: Verified from a single Discord report (medium confidence). The blue-arrow restart control description should be verified against the current IDE layout before publishing --- the UI may have changed since the thread date (2024-06-24)._ -```tb -' Ambiguous: 1 is Integer; both overloads need a widening conversion -Foo 1, "hello" ' error if Foo(Long, String) and Foo(Integer, Long) both exist +--- -' Unambiguous: 1& is Long; matches the Long overload exactly -Foo 1&, "hello" -``` +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] -See [Features → Literals](/Features/Language/Literals) for the full list of typed literal suffixes. +> [!NOTE] +> Messages of the form "IDE internal error: Uncaught Error: ..." that appear in the twinBASIC IDE are diagnostic messages intended for the IDE developer, not errors caused by user code. Encountering one does not mean a language rule has been broken. These messages can generally be ignored when they appear alongside normal project behaviour. -_Source threads: 1245329874777014302 · confidence: high_ -_Date range: 2024-05-29 to 2024-05-30_ -_Reviewer note: The Overloading.md page is a feature overview page, not a reference page. Confirm the Literals page permalink /Features/Language/Literals exists and covers typed literal suffixes (e.g. 1& for Long). If not, adjust the See Also link._ +_Source threads: 1289417040540467221 · confidence: high_ +_Date range: 2024-09-28_ --- -## docs/Features/Language/Overloading.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1369362026119565432, 1372291630773567709, 1449035570796957878, 1367648395933646929] > [!NOTE] -> When a derived COM interface redeclares a method with the same name as one on its base interface, a call can fail with TB5073 (*Unable to disambiguate between overloaded methods*). For example, `d2dDevice1.CreateDeviceContext` with a variable declared as `ID2D1Device1` and an out-parameter declared as `ID2D1DeviceContext1` gives this error, with both the oleexp type library and WinDevLib. This is a known compiler limitation in overload resolution. -> -> Declaring the variable as the base type removes the error, but in that case the call crashed at run time with an access violation inside `D2d1.dll`. A workaround is to cast the value to a separate variable of the required interface first: -> -> ```tb -> Set d2dDevice1 = CType(Of ID2D1Device1)(d2dDevice1) -> ``` +> Memory leak warnings that appear in the twinBASIC IDE refer to memory leaks within the IDE process itself, not in compiled executables. Compiled twinBASIC programs ported from VB6 have been reported to run stably in server deployments for extended periods without the memory issues associated with the IDE. > -> Place the cast directly after the `D2D1CreateDevice` call, which creates a base `ID2D1Device`. +> **Trace mode** is a separate concern: enabling Trace mode during a long-running session causes memory usage to climb continuously, in addition to producing large trace log files. Trace mode should not be left enabled in production or long-running deployments. -_Source threads: 1513860400305672302 · confidence: medium_ -_Date range: 2026-06-09 to 2026-06-10_ -_Reviewer note: Not compiled here: needs a WinDevLib project to reproduce, so the code block is not marked check_build. The workaround is a suggestion from the thread and the outcome was not confirmed. The fenced code inside the callout should be checked against the build's code-region handling. Consider whether this belongs in BUGS-TO-REPORT.md instead, since the thread says it is tracked upstream (twinbasic issue 2107)._ +_Source threads: 1361658401666498601 · confidence: medium_ +_Date range: 2025-04-15 to 2025-04-16_ +_Reviewer note: Confidence is medium: the claim about compiled executables running stably is based on community reports, not official documentation. Verify against any known stability issues in the twinBASIC changelog before publishing._ --- -## docs/Features/Packages/Creating a TWINPACK package.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1372291630773567709, 1449035570796957878, 1367648395933646929] + +## Diagnosing native crashes with trace mode + +When a compiled twinBASIC application crashes with a native exception (such as exception code `0xC0000005` ACCESS_VIOLATION), the Windows error report does not identify which line of Basic code triggered the crash. Enabling **trace mode** in the project settings causes the runtime to write verbose log files during execution. After reproducing the crash, examining the log file identifies the last line of code that ran before the failure. > [!NOTE] -> Older tutorials and documentation refer to a dedicated **TWINBASIC PACKAGE MANAGER** panel inside the main Explorer activity panel. That panel was removed in a subsequent IDE redesign. The equivalent publishing controls are now under **View** > **Package Publishing**, which controls whether the package is published to TWINSERV as public or private. To produce a local `.twinpack` file for use in other projects, use the regular **Build** command; the output path is shown in the debug log and is configurable in project settings. +> Trace mode logs are very verbose. For complex cases it may be more practical to send the log file to the twinBASIC maintainer rather than reading it directly. -_Source threads: 1312685090232471612 · confidence: high_ -_Date range: 2024-12-01_ -_Reviewer note: Verify the exact menu path (View > Package Publishing) against the current IDE. The page already shows a screenshot of the PACKAGE PUBLISHING panel as a popup --- confirm whether the screenshot is current or needs updating._ +_Source threads: 1369362026119565432 · confidence: high_ +_Date range: 2025-05-06_ +_Reviewer note: Verify the exact name and location of the 'trace mode' option in project settings, and confirm whether it is exposed under Project Configuration / Compiler Options or a separate settings panel, so the prose can link to or name it precisely._ --- -## docs/Features/Packages/Import-export tool.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1449035570796957878, 1367648395933646929] -> [!NOTE] -> The **Export on Save** setting writes individual source files to a folder on disk, which makes the exported files suitable for version control. However, the export folder is overwritten in full on every save, so placing a `.git` directory directly inside it will destroy the repository. The recommended approach is to export into a dedicated subfolder within a broader git repository, keeping the `.git` directory and any other project assets outside that subfolder. Git will not record spurious changes when unchanged files are replaced with identical content. +## Step Into (F8) stepping over a call -_Source threads: 1314117578024812604 · confidence: high_ -_Date range: 2024-12-05 to 2025-01-30_ -_Reviewer note: This gotcha is currently undocumented. The Import-export tool page is a reasonable location but the finding is really about the IDE's built-in Export on Save feature rather than the standalone command-line tool. A more precise home would be a dedicated page on version-control workflows if one is added, or the project-settings / project-configuration section. Reviewer should consider whether to place this note here or on a more targeted page._ +In the twinBASIC IDE debugger, pressing **F8** (Step Into) from a breakpoint may step over a function call rather than entering it. This behaviour appears when the target function contains code that the debugger cannot step through, often because of a problem in that function. + +Workaround: use **Go to Definition** on the function name to navigate to it, place a breakpoint before the problematic section, then press **F8** from that internal breakpoint to continue stepping. + +_Source threads: 1372291630773567709 · confidence: medium_ +_Date range: 2025-05-14 to 2025-05-15_ +_Reviewer note: Observed in BETA 769. Verify whether this has been fixed in a later build before publishing._ --- -## docs/Features/Packages/Import-export tool.md · new-section +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1367648395933646929] -## Version control via the IDE +### Diagnosing ACCESS_VIOLATION Crashes -The IDE provides a built-in path for version-controlling a twinBASIC project without the command-line tool. Enable the **Auto export to files on save** option in the project settings; the IDE then writes each module as an individual file alongside the `.twinproj` file on every save. The exported files are plain text and diff cleanly in Git, SVN, or any other version-control system. +When a native `ACCESS_VIOLATION` occurs during build or run and no error appears in the Project Explorer or Diagnostics pane, the trace log is the primary diagnostic tool. -To reconstruct a project from a set of exported files --- for example after cloning a repository --- use the **Import from folder** option on the IDE start-up screen. This option appears only on the initial dialog before a project is open; it is not available from inside an already-open project. +To use it: -> [!NOTE] -> Full external-file support --- where the `.twinproj` file references files on disk directly, analogous to a `.vbp` file --- is planned for a future release but is not yet available. +1. Enable the trace log in **Project Settings** before reproducing the crash. A useful starting configuration is to log procedure entry and exit only. +2. Reproduce the crash. +3. Open the resulting log and locate the thread marked with `>>>` --- this is the offending thread. +4. The thread state marker at the point of failure identifies which compiler stage crashed. For example, `GlobalVarsInitializationCodegenStart` means the crash occurred during code generation for global variable initialization, before any user code ran. -_Source threads: 1302320508800602153 · confidence: high_ -_Date range: 2024-11-02 to 2024-11-06_ -_Reviewer note: Verify the exact name of the project-settings option ("Auto export to files on save" is paraphrased from Discord). Confirm that "Import from folder" is still on the startup screen and not elsewhere in a newer IDE build._ +This technique is particularly useful for crashes in generated code that produce no user-visible error message. + +_Source threads: 1449035570796957878 · confidence: high_ +_Date range: 2025-12-12_ --- -## docs/Features/Packages/Import-export tool.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · after-remarks [DUPLICATE? -- see also thread 1077672242999210076, 1099208311313813534, 1101704869804511265, 1374402188474056704, 1403430684219146300, 1403579629482020934, 1454461049003835433, 1455208656328200224, 1253786438454214788, 1289417040540467221, 1361658401666498601, 1369362026119565432, 1372291630773567709, 1449035570796957878] > [!NOTE] -> The IDE expects source files with Windows (CRLF) line endings and UTF-8 encoding. A `.twin`, `.bas` or `.cls` file that enters a project with LF-only line endings raises no error, but the editor loses track of the syntax: highlighting goes wrong partway through the file, and edits misbehave. A file written by a tool on a system that uses LF endings is a typical cause. Both programs described here convert LF to CRLF in these three file types on `import`. When a file reaches a project by any other route, convert it to CRLF before opening it. +> When running a project with a debugger attached, a continuable native exception with code **&H406D1388** may appear on startup. This exception is the standard Win32 thread-naming notification (documented in the Microsoft blog post *"How to: Set a Thread Name in Native Code"*) and does not affect application functionality. Resume execution to continue normally. -_Source threads: 1542250945574871071 · confidence: high_ -_Date range: 2026-08-26 to 2026-09-23_ -_Reviewer note: The page already states that import converts LF to CRLF, so only the symptom description and the UTF-8 requirement are new. The thread says the script was fixed to convert on import and now shares the tB executable's command vocabulary; both are already described on this page. Verify the UTF-8 claim and the 'any other route' advice against the IDE. The thread also mentions a related importer/exporter bug in tB itself that is not identified, so it is left out._ +_Source threads: 1367648395933646929 · confidence: medium_ +_Date range: 2025-09-12_ +_Reviewer note: The target page (docs/Features/Compiler-IDE/Debugging.md) is not listed in the page index. Confirm this is the appropriate placement, or consider the VB Package index or a general troubleshooting page. The exception is most commonly observed when the project uses WinRT classes but may appear in other contexts too._ --- -## docs/Features/Packages/Import-export tool.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · new-section [DUPLICATE? -- see also thread 1463977430661464095] -> [!NOTE] -> A project is still stored inside its `.twinproj` file; the folder the tool produces is a copy. Keeping the project's source files outside the `.twinproj` file, so that source control and team work need no export step, is planned for version 1.0. +## Debug Console -The folder form is also not a way back to VB6. Plain `.bas` and `.cls` files can be imported into twinBASIC, at the cost of the newer language features and Unicode support. Forms and controls (`.frm` and `.ctl`) cannot be stored in the VB6 format without losing information, because twinBASIC forms have many new properties and required attributes that the format has no place for. Moving a project from VB6 to twinBASIC is effectively one way: form changes made in the twinBASIC designer cannot be carried back. +The Debug Console pane shows output from `Debug.Print` and related calls. To enter a command interactively, use the input bar --- the bar at the bottom of the pane marked with a `>` prompt. The output area above it is read-only; typing directly there has no effect. -_Source threads: 1554511541443891293 · confidence: medium_ -_Date range: 2026-08-13 to 2026-09-29_ -_Reviewer note: Merged from two threads (v1.0 external source files, dated 2026-09-29; VB6 file-format limits, 2026-08-13 to 2026-08-15). Both are statements by a community expert, not from the sources. Check the 'planned for v1.0' wording against the current roadmap before publishing; consider splitting the VB6 paragraph into docs/Miscellaneous/FAQs.md near the import instructions. Insert in the 'Keeping a project in Git' section._ +The input bar supports a command history: press **Up Arrow** or **Down Arrow** to cycle through previously entered commands. + +> [!NOTE] +> This is different from the VB6 Immediate window, where the same pane accepted both output and typed input. In twinBASIC, input and output use separate areas. + +_Source threads: 1077971955052982312 · confidence: high_ +_Date range: 2023-05-23_ --- -## docs/Features/Packages/Import-export tool.md · after-remarks +## docs/Features/Compiler-IDE/Debugging.md · new-section [DUPLICATE? -- see also thread 1077971955052982312] + +## Variables Pane and Compiler Restart + +The twinBASIC debugger and compiler run in the same background process. If a property getter raises an unhandled exception while the debugger evaluates Variables pane nodes at a breakpoint, the IDE triggers a full compiler restart rather than continuing --- the shared process means memory corruption from a hard crash could affect compiler state and the internal project file system. + +The Variables pane remembers the expansion state of its nodes between breakpoints. If an expanded node contains a property getter that throws, the pane will crash each time that breakpoint is reached. + +**Workaround:** stop the debugger at a safe location first --- for example, a breakpoint in `Sub Main` or another routine that does not involve the offending object --- then collapse the root node in the Variables pane. The collapsed state is retained for the remainder of the session. + +The expansion state is stored in the project's `.meta` file in the project root folder, under the JSON key `variables_expandedCache`. The project exporter does not include this file in exports, so the cache cannot be edited from outside a running IDE session. > [!NOTE] -> The `--self-test` option is for the script's developers. It needs a `sample.twinpack` file, which is not part of the download, so it cannot run in an ordinary installation. Do not use it to check a copy of the script; run one of the commands above on a project of your own. +> A long-term plan exists to separate the compiler and debugger into distinct processes, which would allow the debugger to report a failed property evaluation without restarting. -_Source threads: 1524049386953375946 · confidence: medium_ -_Date range: 2026-07-07 to 2026-07-22_ -_Reviewer note: The rest of the finding (using the tools to keep a project in git) is already covered on this page, including the .gitignore section. Only the self-test caveat is new. The page index has no Features pages, so the target was found by reading nearby files. Confirm the sample.twinpack requirement against scripts/impexp.mjs before publishing; the finding is medium confidence and the page may want a different wording if the file is meant to ship with the repository._ +_Source threads: 1463977430661464095 · confidence: high_ +_Date range: 2026-01-23 to 2026-01-25_ --- -## docs/Features/Packages/Importing a package from a TWINPACK file.md · after-remarks [DUPLICATE? -- see also thread 1333426890291023884] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> After importing a `.twinpack` file, the package appears in the Project Explorer under **Packages** but its symbols are not yet active. Check the checkbox next to the package in the **Available Packages** list (the same list used to import it), then save **Settings** to activate it. Skipping this step is a common source of unresolved-symbol errors --- the package is present but inactive until the box is checked. +> The typelib viewer resolves UDT and enum names in a referenced typelib by numeric index. If a typelib uses `importlib` to reference types from a second typelib, and the second typelib has since had new type entries inserted --- without the outer typelib being recompiled against the updated version --- the numeric indexes embedded in the outer typelib will be off by one or more. The viewer then displays the type immediately before the correct one, producing wrong UDT or enum names for those references. The fix is to recompile the outer typelib against the current version of the referenced typelib and deploy both files together as a matched set. When both typelibs are version-consistent, the viewer shows correct signatures. -_Source threads: 1312685090232471612 · confidence: high_ -_Date range: 2024-12-01_ +_Source threads: 1066231176462872656 · confidence: high_ +_Date range: 2023-01-21 to 2023-02-03_ +_Reviewer note: Finding targets tbIDE package (no specific symbol) but the typelib viewer is an IDE feature documented in docs/Features/Compiler-IDE/IDE-Features.md, not in the addin SDK reference. Mapped here because the existing bullet 'A type library viewer for controls and TLB files' is the natural anchor. The note should be placed after or alongside that bullet in the Advanced Features section. Verify that the version-mismatch behavior still applies to the current IDE build._ --- -## docs/Features/Packages/Importing a package from a TWINPACK file.md · after-remarks [DUPLICATE? -- see also thread 1312685090232471612] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -> [!IMPORTANT] -> The 'Import from file...' button must be clicked while the **Packages** tab (also labelled 'Available Packages') is active in the project settings. If the button is clicked while the **References** tab or any other tab is shown, twinBASIC treats the `.twinpack` file as a COM type library and fails with error `0xc000012f`. The error message does not indicate the tab-selection cause. +> [!NOTE] +> Prior to BETA 256, the type library viewer incorrectly rendered parameters whose COM IDL declaration used `defaultvalue("")` (an empty-string default). The generated twinBASIC syntax showed a backslash-escaped quote sequence instead of a proper empty string literal, producing invalid output. This was fixed in BETA 256. -_Source threads: 1333426890291023884 · confidence: high_ -_Date range: 2025-01-27_ -_Reviewer note: The existing page refers to a 'References section' / 'Available Packages' button flow that may reflect an older UI. Verify the current tab labels in the IDE before merging, and update the main page text if needed._ +_Source threads: 1077017629279125574 · confidence: high_ +_Date range: 2023-02-20 to 2023-02-27_ +_Reviewer note: This documents a historical bug that was fixed in BETA 256. The note is accurate but may be too version-specific for a general features page. Reviewer should decide whether to include it (as a historical gotcha for users who encountered it) or discard it as no longer relevant to current versions._ --- -## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1427261753544409168, 1460777854714515728, 1495944076632002572] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> A `.twinproj` file placed in `%APPDATA%\Roaming\twinBASIC\packages` also appears in the **Available Packages** list and can be referenced as a linked package. When adding the reference, leave the **Embedded** checkbox unticked so the reference stays linked rather than copying the source into the referencing project. Changes made to the shared project are visible to referencing projects the next time they are loaded. This pattern is the recommended approach for sharing code across multiple twinBASIC projects without duplicating it. +> When copying or cutting code, the IDE places spaces on the clipboard rather than literal tab characters. The number of spaces per tab matches the configured indent width. This ensures that code pasted into external editors or forums displays with consistent indentation regardless of the tab-stop settings in the destination. This behavior was introduced in BETA 288; earlier builds placed literal tab characters on the clipboard. -_Source threads: 1392532961366376469 · confidence: high_ -_Date range: 2025-07-11_ -_Reviewer note: The existing Linked Packages page describes placing .twinpack files in %APPDATA%\Roaming\twinBASIC\packages. Verify that .twinproj files placed there also appear in Available Packages before publishing._ +_Source threads: 1091247313214783518 · confidence: high_ +_Date range: 2023-03-31 to 2023-04-04_ +_Reviewer note: Draft targets the Editing Features section of IDE-Features.md. Consider inserting this as a bullet point under that section rather than a standalone NOTE callout, depending on how the page evolves. The BETA 288 version number should be verified against the twinBASIC changelog if possible._ --- -## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1460777854714515728, 1495944076632002572] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] +> The form designer's **Align Left** command sets every selected control's `Left` property to the same X coordinate. Controls that were not already overlapping will overlap after the operation. This differs from Microsoft Access, where alignment preserves horizontal spacing between controls. The twinBASIC behavior matches Visual Studio WinForms. > -> When sharing a `.twinproj` file that uses a linked package, the recipient must have that package registered on their machine. If the package is missing, open the project, uncheck (remove) the broken reference in the Available Packages list, then re-add it and uncheck the Embedded column to restore the linked reference. The "Fix" option is not currently implemented. +> There is also no concept of a primary reference control when multiple controls are selected. In VB6, the control whose resize handles appear in a different color acts as the alignment anchor; other controls align to it. twinBASIC's designer uses a fixed rule (the minimum left coordinate) instead. Spacing adjustment commands such as **Make Spacing Equal**, **Increase Spacing**, and **Decrease Spacing** from VB6 are not yet implemented. -_Source threads: 1427261753544409168 · confidence: medium_ -_Date range: 2025-10-24_ -_Reviewer note: The existing 'Opening a project with missing linked package' section already covers this workflow. This note may be redundant; reviewer should decide whether it adds enough clarity to keep, or whether it should simply be removed in favour of the existing section._ +_Source threads: 1122091213177229362 · confidence: high_ +_Date range: 2023-06-24_ +_Reviewer note: Verify whether spacing adjustment commands have been added to the designer since mid-2023, and whether a primary-selection concept has been implemented. The maintainer described the alignment as 'simple naïve left alignment' at that time._ --- -## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1427261753544409168, 1495944076632002572] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -Linking also keeps a project or a package you build small. In one reported case, a package that embedded WinDevLib was 27 MB, and less than 800 kB with WinDevLib linked instead. A linked dependency also removes the need to copy its definitions into the project. +> [!NOTE] +> When opening a project, the editor restores the previously saved cursor position before inserting the **Run ``** codelens decorations above each procedure in standard modules. Because the decorations are inserted after position restoration, the document shifts downward and the visible area ends up roughly 10 lines above the actual last editing position. Scrolling down one page returns to the correct location. This limitation is most noticeable in large projects where codelens decorations are numerous. -_Source threads: 1460777854714515728 · confidence: medium_ -_Date range: 2026-02-15_ -_Reviewer note: Insert after the opening paragraph of the page. The sizes come from a single user's report, not a measurement; verify that a .twinpack that references a linked package stores only the reference. Also check whether a package that links a dependency needs that dependency to be linked on the consuming machine._ +_Source threads: 1201200584821313586 · confidence: high_ +_Date range: 2024-01-28_ +_Reviewer note: Confirmed by WaynePhillipsEA in the thread as a known limitation. Verify whether this has since been fixed before publishing._ --- -## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1427261753544409168, 1460777854714515728] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -## Fixing a failed package reference +> [!NOTE] +> **Rename symbol** (F2 or right-click > Rename) displays a text-entry field pre-filled with the symbol name, but pressing Enter or Esc has no effect. The feature is not yet implemented; it is listed on the roadmap for 2026-Q1. As a workaround, use Find/Replace or the Monaco editor's built-in "select all occurrences" command to rename a symbol across the current file. -When a package reference fails to load --- shown as "Unable to load this reference" in the project settings --- re-selecting the same package in the References dialog does not repair the reference. The correct sequence is: +_Source threads: 1216245949278457856 · confidence: high_ +_Date range: 2024-03-10 to 2025-07-15_ +_Reviewer note: Insert under the Editing Features section of IDE-Features.md. Verify the 2026-Q1 roadmap entry is still current as of the publication date._ -1. Clear the broken reference by unchecking it. -2. Click **Apply Changes** to commit the removal. -3. Reopen References, go to the **Available Packages** tab, and select the package again. +--- -Attempting to re-add without first clearing and applying leaves the stale entry in place. +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> A very large number of compile errors (hundreds) when opening a project is a common symptom of a missing package reference, not a code defect. If the project uses a linked package that has not been added as a reference, the compiler reports one error per unresolved identifier. Adding the package under **Project -> References -> Available Packages** clears them all. +> In the form designer, lasso-dragging *inside* a **Frame** or other container selects the container itself rather than the controls within it. To select controls inside a container, draw the lasso from *outside* the container so the selection rectangle encompasses the container and its child controls, then hold **Ctrl** and click the container to deselect it. The remaining selection covers only the inner controls. -_Source threads: 1495944076632002572 · confidence: high_ -_Date range: 2026-04-05 to 2026-04-21_ +_Source threads: 1407004587570892974 · confidence: medium_ +_Date range: 2025-08-18_ +_Reviewer note: Placed in the Form Designer Enhancements section of the IDE Features page. If a dedicated form-designer page is created, move this note there._ --- -## docs/Features/Packages/Linked Packages.md · after-remarks - -The IDE has no command that rolls a linked package back to an older build. To downgrade, replace the package's `.twinpack` file in `%APPDATA%\twinBASIC\packages` with the older file, then restart the compiler. If the package is published in a GitHub repository that has no Releases page, an older `.twinpack` can be downloaded from the repository's commit history, using the raw file at the commit in question. +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -A package opened in the IDE is read-only. When the package has a `changelog.md`, it is visible in the Project Explorer under `Packages\`, which helps in choosing the build to go back to. +> [!NOTE] +> The twinBASIC IDE process is 32-bit regardless of the target architecture selected for the project. When the project targets Win64, only the backend compiler process and the debugger process run as 64-bit; the IDE host process remains 32-bit. Because WebView2's memory usage grows over long editing sessions and is not fully released between compile cycles, the IDE can exhaust its 32-bit address space and crash with an out-of-memory error even on machines with large amounts of physical RAM. Save frequently during long sessions. The toolbar's **Restart Compiler** button can sometimes recover without a full IDE restart. -_Source threads: 1125359845936205844 · confidence: medium_ -_Date range: 2025-11-19 to 2025-12-29_ -_Reviewer note: Insert at the end of the Manual management section. The restart-the-compiler sentence comes from the existing page text, not from the thread._ +_Source threads: 1453807327982649424 · confidence: high_ +_Date range: 2025-12-25 to 2025-12-30_ +_Reviewer note: This is an IDE architecture clarification. It should sit near the top of the IDE Features page or in a dedicated 'Known limitations' section. Reviewer should decide exact placement._ --- -## docs/Features/Packages/Linked Packages.md · after-remarks +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -### Repairing several broken linked references +## Keyboard Shortcuts -The **Fix** button on a broken linked reference is not implemented. It reports that the feature is not yet available and asks for the reference to be removed and added again by hand. +- **Alt+Up Arrow / Alt+Down Arrow** moves the current line or selection up or down in the file. This shortcut is not listed in any menu but works in the code editor. -Unticking a linked reference hides it from the list, so note the exact names of the packages before removing them. When several linked packages depend on each other --- for example the WinRT packages and the Windows Development Library packages --- the order of the references matters. This procedure restores them reliably: +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-02-22_ +_Reviewer note: Verify that this shortcut is still present and works as described in a recent build. It was discovered accidentally by a community member and confirmed via reactions, not official documentation._ -1. Untick the broken linked references and press **Apply Changes**. -2. On the **Available Packages** tab, add the packages back one at a time. -3. On the **Enabled Libraries** tab, use the up and down arrows to put each package after the packages it depends on. +--- + +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> -> Add a package from the **Available Packages** tab, not from **Available COM References**. A user reported that adding the VB6 WinRT type library through **Available COM References** makes the IDE extremely slow. +> Find/replace can be restricted to a selected range of lines. Open **Edit > Replace**, enter the search and replacement terms, select the target lines in the editor, then click the rows icon in the Replace dialog (between the down-arrow button and the close button). The dialog shows how many items fall within the selection. Click **Replace All** to apply only within that range. -_Source threads: 1551367356507226154 · confidence: medium_ -_Date range: 2026-09-23_ -_Reviewer note: Findings were tagged package Core with no symbol, so the page was chosen by hand: Linked Packages already says Fix is not implemented, and this adds the multi-package ordering procedure. The dependency-order advice and the slow-IDE report come from a single user; verify before publishing. Step 3 assumes the up/down arrows are on the Enabled Libraries tab (Project Settings.md says that tab lists references in priority order); the thread does not name the tab._ +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-04-22_ +_Reviewer note: Verify the exact UI layout (rows icon position) against a current build. The description is from a community member explaining the feature to another user._ --- -## docs/Features/Packages/TWINPACK file format.md · after-remarks [DUPLICATE? -- see also thread 1368264887448371372] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> `.twinproj` files produced by the twinBASIC IDE can legitimately contain a mix of CRLF and LF line endings within the same file, even without any third-party tooling involved. This is a known characteristic of the format and causes no functional issues. When tracking `.twinproj` files in git, configure them as binary to prevent git from normalizing line endings: -> -> ``` -> # .gitattributes -> *.twinproj binary -> *.twinpack binary -> ``` +> **Type library import vs. reference.** twinBASIC supports importing `.tlb` and `.olb` type library files directly into a project as well as referencing them. Once a type library is present in the project, the IDE can display its definitions as equivalent twinBASIC source. This applies to third-party type libraries such as `oleexp.tlb`, `OLEGuids.tlb`, and DirectX TLBs. -_Source threads: 1397374588056371350 · confidence: medium_ -_Date range: 2025-07-23_ -_Reviewer note: TWINPACK File Format page is a binary-format spec. This git note may fit better in docs/Features/Packages/Linked Packages.md or a new version-control tips section. Reviewer should confirm the best placement._ +_Source threads: 1077971955052982312 · confidence: high_ +_Date range: 2023-05-29_ +_Reviewer note: The IDE-Features page already notes 'A type library viewer for controls and TLB files that displays the full contents in twinBASIC-style syntax.' Verify whether the import-vs-reference distinction is accurate and worth calling out separately, or whether this note should be merged into the existing bullet._ --- -## docs/Features/Packages/TWINPACK file format.md · after-remarks [DUPLICATE? -- see also thread 1397374588056371350] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> The `.twinproj` file is a binary container format and is not a plain-text file. A text editor may display some readable text within it, but the file is not suitable for direct editing or syntax highlighting. To work with project source files in an external editor, use the [Import/Export Tool](Import-Export-Tool) to unpack the project to a directory of `.twin` (and `.bas`, `.cls`) plain-text source files. After editing those files externally, reopen the original `.twinproj` file in twinBASIC to continue work. +> **IntelliSense and Enter key.** In some contexts, pressing Enter once dismisses the IntelliSense suggestion list without performing the associated expansion. A second Enter is then required to complete the action --- for example, to expand `Sub Test` into `Sub Test() ... End Sub` with the cursor inside, or to advance to the next line after completing a declaration. This differs from VB6, where a single Enter both dismissed IntelliSense and applied the selection. -_Source threads: 1368264887448371372 · confidence: high_ -_Date range: 2025-05-06_ +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-03-07_ +_Reviewer note: Reported by one community member without contradiction. Verify whether this is still the behavior in current builds, or whether it has been fixed since March 2023._ --- -## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1201636475306000536, 1210853350707429406, 1301076697642438656] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> When a project contains two packages that have a dependency relationship --- package B depends on package A --- and the version of package A in the project does not match the version that package B was compiled against, the compiler reports 'unrecognized symbol' errors on symbols from package A when they appear inside package B. The error message gives no indication that a version conflict is the cause. If unexpected 'unrecognized symbol' errors appear on symbols from a package that other packages depend on, check that all inter-dependent packages are at mutually compatible versions and update the dependent package (B) to a version compiled against the currently installed version of package A. +> **Auto-insertion of block-closing keywords.** The editor inserts block-closing keywords (such as `End If`, `End Sub`) automatically when you start a new block and press Enter. This feature is less reliable in larger projects and when the line immediately after the cursor is not empty --- in those cases the editor may only indent to match the previous line instead of inserting the closing keyword. -_Source threads: 1171155850832908318 · confidence: medium_ -_Date range: 2023-11-06 to 2023-11-07_ -_Reviewer note: This page is in docs/Features/Packages/ and is not in page-index.json (which covers only Reference docs). The finding has package='Core', symbol=null -- no Reference page covers package version compatibility errors. Placement here is an editorial judgement; a reviewer may prefer a different Features page or a new troubleshooting section. Also verify whether the IDE now emits a clearer error message for this case._ +_Source threads: 1077971955052982312 · confidence: medium_ +_Date range: 2023-03-05 to 2023-03-06_ +_Reviewer note: Described as a known beta-era bug by fafalone. Verify whether this has been fixed in more recent builds before including._ --- -## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1210853350707429406, 1301076697642438656] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> In IDE versions before BETA 444, removing a package reference in the Settings dialog could leave the IDE in a locked state. The secondary confirmation prompt ("also remove the imported package from the filesystem") appeared behind the Settings modal; after responding to it the modal was not restored, and the IDE behaved as though the dialog was still open. In some cases the package contents were deleted from the project file while the reference entry remained, producing a project file that could not be opened normally. Projects that reach this corrupt state can be recovered by opening the project in safe mode, which makes the Settings dialog accessible so the orphaned reference can be removed. This issue was fixed in BETA 444. +> The twinBASIC IDE is built on the Monaco editor (the same editor that powers VS Code), which does not implement overtype mode. Pressing the **Insert** key to toggle between insert and overtype modes---as expected in the classic VB6 IDE---has no effect. This is a Monaco upstream limitation, not a twinBASIC-specific decision. -_Source threads: 1201636475306000536 · confidence: high_ -_Date range: 2024-01-29 to 2024-02-10_ -_Reviewer note: Target page is in Features/Packages (not in the page-index), chosen as the best fit because it explicitly covers the remove-package workflow. The safe-mode recovery path should be verified against current IDE capabilities --- older IDE versions had a safe mode option; confirm it still exists or adjust wording accordingly._ +_Source threads: 1087939028826984480 · confidence: high_ +_Date range: 2023-03-22 to 2023-03-28_ +_Reviewer note: Check that the Editing Features section is the right placement on this page; insert after the existing bullet list or as a new sub-note under the overtype bullet if one is added._ --- -## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1201636475306000536, 1301076697642438656] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> Unticking a package reference in Project Settings disables the package --- the IDE removes the reference and makes the package's symbols unavailable to the compiler --- but does not immediately delete the locally cached package files. A second dialog then appears offering two choices: **Remove it** deletes the cached files from disk, and **Leave it** keeps them. Choosing **Leave it** lets you re-enable the same version later without re-downloading. To upgrade to a newer version, you must choose **Remove it** so the cache is cleared before importing the updated package from the package server. +> **Auto-insert of `End Sub` / `End Function` / `End Property`.** The editor inserts the matching closing statement automatically when the user types a procedure header and presses Enter. This trigger activates only when the procedure keyword begins with a capital letter (for example `Sub`, `Private Sub`, `Public Function`). Typing an all-lowercase keyword such as `private sub test()` and pressing Enter does not insert the closing statement, leaving subsequent code flagged as errors. To recover, press the up-arrow key to return to the header line, move to the end of the line, and press Enter again after the IDE has capitalised the keyword. -_Source threads: 1210853350707429406 · confidence: high_ -_Date range: 2024-02-24 to 2024-02-27_ +_Source threads: 1169977003705909349 · confidence: high_ +_Date range: 2023-11-03_ +_Reviewer note: Place under the Editing Features section in IDE-Features.md, after the existing bullet list. Verify that the all-lowercase behaviour has not changed in recent IDE builds._ --- -## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1201636475306000536, 1210853350707429406] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> The new-version notification can be turned off in the IDE settings. One-click automatic update to the latest package version is planned for a future release; the current workflow requires removing the old package and re-importing the newer one as described below. +> **Block comment / uncomment.** Selecting multiple lines and pressing **Ctrl+/** toggles line comments on and off for the selection. This shortcut does not appear in the right-click context menu or the Edit menu; users who look for it there will not find it. -_Source threads: 1301076697642438656 · confidence: high_ -_Date range: 2024-10-30 to 2024-11-18_ -_Reviewer note: Confirm the exact settings path in the IDE where the notification can be disabled, and verify whether the opt-out affects all packages or per-package._ +_Source threads: 1176838610327187517 · confidence: high_ +_Date range: 2023-11-22_ +_Reviewer note: Place under the Editing Features section in IDE-Features.md, after the existing bullet list (or after the auto-insert note added from thread 1169977003705909349 if that addition lands nearby)._ --- -## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1359918618573275286, 1503959771949301922] - -> [!NOTE] -> Packages installed into a project are opened as read-only in the code editor and cannot be edited directly, similar to a referenced type library in VB6. To customise package code, export and import the source into regular project code, or rebuild the package from its `.twinproj` source file if available. If a symbol from a package is copied into the project under the same name, the project's own version takes priority at call sites --- there is no conflict, and the project copy is called unless the call site explicitly qualifies with the package name. +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -_Source threads: 1285758420326027407 · confidence: medium_ -_Date range: 2024-09-18_ +## Title Bar and Taskbar Behavior ---- +By default the twinBASIC IDE uses a custom title bar. This custom title bar does not respond to a double-click to toggle between the maximized and restored window state, and may prevent an auto-hidden Windows taskbar from appearing over the IDE window. -## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1285758420326027407, 1503959771949301922] +Enabling **Use real OS titlebar** in IDE Options switches to the standard Windows title bar, which restores double-click maximize/restore behavior and resolves the auto-hidden taskbar issue. > [!NOTE] -> All twinBASIC packages --- including the built-in runtime packages --- are currently distributed as source code, not pre-compiled binaries. When a package is added to a project, it is compiled once at load time and its contents appear as read-only in the project explorer; the source cannot be edited from within the consuming project. A package does not need to be self-contained or independently compilable: it may contain only interfaces, API declarations, or abstract classes that require the consuming project to supply implementations. -> -> Building a package via **File > Build** (or the toolbar) produces a `.twinpack` file locally but does not upload anything. Uploading to the package server requires the **Package Publishing** panel, where visibility (public or private) can also be set. Compiled packages --- distributing binaries without exposing source --- are planned for a future release. +> A fix for the double-click maximize/restore behavior was included in BETA 874. The **Use real OS titlebar** option remains available as an alternative if the default behavior is still not preferred. -_Source threads: 1359918618573275286 · confidence: high_ -_Date range: 2025-04-10 to 2025-04-15_ -_Reviewer note: The page already mentions that TWINPACK files contain full source code in the last paragraph. The new note expands on the read-only behavior in the consuming project and the publishing workflow. Merge with or replace that existing paragraph to avoid duplication._ +_Source threads: 1397666706197057627 · confidence: high_ +_Date range: 2025-07-23 to 2025-09-30_ +_Reviewer note: No dedicated IDE options page exists in the docs; IDE-Features.md is the closest match. The reviewer should confirm the exact menu path for 'Use real OS titlebar' in the current IDE, and verify whether BETA 874 fully resolved the double-click behavior._ --- -## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1285758420326027407, 1359918618573275286] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> twinBASIC does not yet support referencing `.bas` or `.cls` source files stored outside the `.twinproj` project file in a way that keeps them linked rather than copied. The recommended approach for sharing modules across projects is to package them into a private twinBASIC package and reference that package from each consuming project. Note that module files in a referenced package are read-only within the consuming project, and changes to the package source are not reflected until the package is recompiled and re-imported. +> The twinBASIC IDE does not include a Procedure View / Full Module View toggle equivalent to the one found in the VB6 IDE. Code folding (expand/collapse regions and procedures) is available because it is provided by the Monaco editor. A Procedure View feature is tracked as GitHub issue #2160. -_Source threads: 1503959771949301922 · confidence: high_ -_Date range: 2026-05-13 to 2026-05-18_ +_Source threads: 1411082650902335678 · confidence: high_ +_Date range: 2025-08-29_ +_Reviewer note: No dedicated VB6-compatibility IDE page exists. IDE-Features.md is the best available placement. Reviewer should verify that GitHub issue #2160 still accurately reflects the current status of this feature request._ --- -## docs/Features/Packages/index.md · after-remarks - -> [!NOTE] -> A package adds no DLL dependency to the built executable. Its components are source code, and the compiler builds them into the executable together with the project's own code. Only the components the project actually uses are included, so referencing a large package, such as one with tens of thousands of Windows API and COM declarations, does not enlarge the executable. +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -_Source threads: 1536079009673257010 · confidence: medium_ -_Date range: 2026-08-10_ -_Reviewer note: The existing page says a referencing project imports the whole package into its file system; the added text is about the compiled output, not the project file. Confirm with the compiler team that unused package symbols are always excluded from the executable, and that this holds for every package type._ +### Customising syntax colours ---- +The IDE loads syntax-highlight colours from two CSS files in its installation folder: -## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1117993757170749490, 1261148532157714432, 1388075026892455987] +- `styles-twinbasic-light.css` --- colours used in the Light and Classic themes. +- `styles-twinbasic-dark.css` --- colours used in the Dark theme. -> [!NOTE] -> Automatic registration occurs immediately after every build. The **Project: Register DLL after build** option in the project settings controls whether this registration step runs. Disabling it is useful when the binary needs to be copied to a different location before registering, for example during deployment to a test machine. +Editing these files changes the colour applied to the corresponding token classes: `.basicConstant`, `.basicVariable`, `.basicField`, `.basicEnum`, `.basicEnumMember`, `.basicKeyword`, and others. The changes take effect when the IDE reloads the stylesheet. This provides a way to adjust individual token colours without replacing an entire built-in theme. -_Source threads: 1073568869106597938 · confidence: high_ -_Date range: 2023-02-10_ -_Reviewer note: The existing ActiveX-Registration.md page already documents the 'Register DLL after build' option and says registration is optional. The Discord finding (from early 2023) states the option did not exist at that time. Verify whether this NOTE adds anything not already covered by the existing page content, and adjust or drop accordingly._ +_Source threads: 1233485738516156486 · confidence: medium_ +_Date range: 2024-04-26 to 2024-04-27_ +_Reviewer note: Verify that `styles-twinbasic-light.css` and `styles-twinbasic-dark.css` are still the correct file names and that the CSS class names listed (`.basicConstant` etc.) are current. This mechanism may have been superseded by a built-in theme picker in later BETAs._ --- -## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1261148532157714432, 1388075026892455987] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> If a host application (such as Microsoft Access) crashes at its own compile step after a reference to a twinBASIC COM DLL is added, the tB code is not executing at that point. The host is parsing the DLL's type library during its own project compilation. The type library contains an entry the host cannot parse. To diagnose the problem, progressively remove public exports from the tB project until the crash stops, then identify which declaration is incompatible. Also verify that the host and the DLL share the same processor architecture: a 32-bit host cannot load a 64-bit DLL, even when the reference is successfully added. +> The IDE process has a known memory leak. Leaving the IDE running continuously for an extended period eventually exhausts available memory and produces an **Out of memory** crash. Closing the IDE after use releases all memory back to the system. Compiled twinBASIC applications are not affected by the same leak and can run continuously as background services, though monitoring memory consumption is still advisable. -_Source threads: 1117993757170749490 · confidence: high_ -_Date range: 2023-06-13 to 2023-06-15_ -_Reviewer note: The page-index does not include docs/Features/ pages. Reviewer should confirm placement in docs/Features/Project-Configuration/ActiveX-Registration.md or consider docs/Features/Project-Configuration/Project-Types.md. The finding is specifically about COM ActiveX DLL type-library compatibility with VBA host applications._ +_Source threads: 1319559634453008415 · confidence: medium_ +_Date range: 2024-12-20_ +_Reviewer note: This is sourced from fafalone's statement in a Discord thread and marked medium confidence. Check whether the IDE memory leak has been fixed in a more recent twinBASIC release before publishing --- the statement dates from December 2024. The IDE-Features page covers IDE behavior generally and is the closest existing target; a dedicated Known Issues or Release Notes page would be a better long-term home if one exists._ --- -## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1117993757170749490, 1388075026892455987] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -> [!IMPORTANT] -> When a twinBASIC ActiveX DLL is built or registered by a non-elevated process, its registration goes to `HKEY_CURRENT_USER` by default. If VB6 (or any other host) is then launched with elevated (administrator) privileges, it cannot see that per-user registration and reports a "Class not registered" error. To avoid this, set the project's registration target to `HKEY_LOCAL_MACHINE` in the project settings, or build and register the DLL from an elevated instance of twinBASIC. Manually registering the DLL from an elevated command prompt alone is not sufficient if the project setting still points to `HKEY_CURRENT_USER` --- the per-user registration is written again on the next build. +> [!NOTE] +> The twinBASIC IDE does not currently support split editor windows or side-by-side source views. The entire editor surface is rendered as a single WebView2 HWND, so intercepting or extending it by subclassing the window handle is not practical. Side-by-side multiple editor windows are on the planned feature list. An addin workaround using the [tbIDE SDK](../../Reference/tbIDE/) can open a file as a read-only text panel in a tool window; the IDE ships sample 12, which demonstrates embedding a Monaco editor instance in a tool window via the addin SDK. -_Source threads: 1261148532157714432 · confidence: high_ -_Date range: 2024-07-12_ +_Source threads: 1379796923224952892 · confidence: high_ +_Date range: 2025-06-04_ +_Reviewer note: Verify the beta number (784) is still relevant or omit it. Confirm whether side-by-side windows have been added in any beta after 784. Relative link from IDE-Features.md to tbIDE/ index: ../../Reference/tbIDE/ — confirm against rendered URL._ --- -## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1117993757170749490, 1261148532157714432] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> When consuming a twinBASIC ActiveX DLL from Microsoft Access or Excel VBA, declare the object using the fully qualified form `ProjectName.ClassName`, not `ClassName` alone. Using the class name without the project prefix produces the error "Expected user-defined type, not project". For example, if the project is named `MyLib` and the class is `MyClass`, the declaration in the host VBA module must read: +> The code minimap and bracket pair colorization settings were removed from the IDE Options dialog and moved to the theme system. To enable them, create a `.theme` file in `C:\Users\\AppData\Roaming\twinBASIC\themes`, then select it via **Window > Theme**. A minimal file that enables both features while inheriting the built-in dark theme: > -> ```vb -> Dim obj As New MyLib.MyClass +> ``` +> inherits: dark; +> CodeMinimapVisible: 1; +> CodeBracketPairColorization: 1; > ``` > -> This is standard COM early-binding behavior. - -> [!NOTE] -> twinBASIC does not support interactive cross-project debugging of an ActiveX DLL from a host application such as Access or Excel. Breakpoints and step-through from the host are not available. The recommended approach is to copy the DLL classes into a Standard EXE project and debug there. For runtime diagnostics in the compiled DLL, use `Debug.TracePrint` with the trace logger rather than manual log-file writes. +> Replace `dark` with `light` or `classic` to base the theme on one of the other built-in themes. Placing the file in the directory is not enough --- the theme must be selected explicitly in the IDE after it is created. -_Source threads: 1388075026892455987 · confidence: high_ -_Date range: 2025-06-27_ +_Source threads: 1381120139893674015 · confidence: high_ +_Date range: 2025-06-08_ +_Reviewer note: Addition targets the Theme System section in IDE-Features.md. Verify the AppData path and theme key names against the latest IDE build, as these were described in a Discord thread and not in official docs._ --- -## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -> [!NOTE] -> As in VB6, building an ActiveX DLL project generates new GUIDs, unregisters the previous build and registers the new one. If the new DLL cannot start, its registration code never runs, and the component then stays unregistered. +> [!WARNING] +> The **SafeMode** target (listed after `win64` in the target architecture dropdown) disables the compiler to allow raw file editing without diagnostic overhead. At least one report confirms that when SafeMode is active, the IDE does not detect file changes and does not save edits to disk. Switching back to a normal target causes all edits made during the SafeMode session to be lost. Switching the project to `win32` temporarily is a safer alternative when the goal is to suppress diagnostics during editing. -_Source threads: 1550087924027236442 · confidence: medium_ -_Date range: 2026-09-17_ -_Reviewer note: Based on one experienced user's statement, not a maintainer's. Verify against the compiler, in particular whether the GUIDs change on every build or only without a fixed-GUID or compatibility setting, and whether the old build is unregistered first. Insert under Build-Time Registration._ +_Source threads: 1382393005973180457 · confidence: medium_ +_Date range: 2025-06-11 to 2025-06-12_ +_Reviewer note: SafeMode behavior (edits not saved) may have been fixed in a subsequent IDE release. Verify against a current build before publishing. This warning could be promoted to a NOTE if the issue has been resolved._ --- -## docs/Features/Project-Configuration/Compiler-Options.md · after-remarks - -## Stack Reserve Size - -The twinBASIC non-optimizing compiler allocates larger stack frames than VB6, so recursive procedures exhaust the call stack much sooner than an equivalent VB6 project. The project setting **Build Stack Reserve Size** (default 1 MB) controls the stack reserve for compiled EXEs. Increasing it to 4 MB or 16 MB allows substantially deeper recursion; for 64-bit processes the additional reserve is only committed as needed and carries no practical memory cost at rest. +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] > [!NOTE] -> Stack frame sizes are expected to decrease significantly once LLVM-based code generation is fully operational. +> When an editor panel is floating (undocked), it can be returned to its docked position in two ways: drag it by the grabber control at the top-left corner of the floating window, or use the **Window** menu to apply any saved panel layout (such as the default layout). Closing and reopening the source file also restores the default dock state. -_Source threads: 1406596075032412234 · confidence: high_ -_Date range: 2025-08-17_ -_Reviewer note: The Compiler Options page currently does not mention stack size at all. Verify the exact setting name ('Build Stack Reserve Size') against the twinBASIC IDE project settings UI before publishing._ +_Source threads: 1391112464422404208 · confidence: high_ +_Date range: 2025-07-05_ +_Reviewer note: The grabber control description ("top-left corner") is from the maintainer's own reply; verify it still matches the current IDE layout before publishing._ --- -## docs/Features/Project-Configuration/Compiler-Options.md · new-section +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] -## LLVM Optimization (Professional) +### Custom theme files -The Professional tier adds an LLVM-based optimization pass on top of the standard code generator. The gains are workload-dependent: +Custom themes are plain text files with a `.theme` extension placed in `%APPDATA%\Roaming\twinbasic\themes`. A theme file can inherit from a built-in theme and override individual properties using the syntax `inherits: ;` followed by `PropertyName: value;` lines. -- **Where LLVM helps most:** tight numeric or algorithmic loops in standard modules that avoid strings, Variants, classes, interfaces, and Windows API calls in the hot path. In those cases the optimizer can inline aggressively, unroll loops, fold constants, and remove redundant checks, giving large multipliers over the baseline. -- **Where LLVM helps less:** code whose hot path calls runtime string functions (`Mid$`, `InStr`, `Left$`, etc.), passes strings `ByVal`, or dispatches through COM objects. The bottleneck in those cases is the runtime or the API, not the compiled glue code, so gains are small. +The `InlineDecorationColor` property controls the color of the end-of-block tooltips shown at closing lines such as `End Sub` and `End If` in the editor. The value accepts any CSS color expression, including `rgba()` for semi-transparency: -As of late 2025 the LLVM backend was partially implemented and 32-bit only; full 64-bit coverage is planned for v1.0. +``` +inherits: Dark; +InlineDecorationColor: rgba(200, 200, 200, 0.5); +``` -### Performance relative to VBA and VB6 +_Source threads: 1398426772021841961 · confidence: medium_ +_Date range: 2025-07-26 to 2025-08-06_ +_Reviewer note: The Theme System section in IDE-Features.md currently only has one sentence. Verify the property name InlineDecorationColor and the file format against an actual .theme file before publishing. The %APPDATA% path casing (twinbasic vs twinBASIC) should also be confirmed._ -Without LLVM, twinBASIC compiled code is faster than VBA and VB6 compiled to p-code. Results against VB6 native-compiled and speed-optimized executables are mixed and workload-dependent --- computation-heavy code (math, array operations) has been measured 25--30% faster in twinBASIC by independent users, while simple micro-benchmarks with default settings sometimes show VB6 native ahead. The twinBASIC maintainer has confirmed that the standard benchmark suite runs faster in a twinBASIC EXE than in VBA. +--- -_Source threads: 1392249968852471899 · confidence: high_ -_Date range: 2025-06-30 to 2025-12-01_ -_Reviewer note: LLVM 64-bit status and v1.0 timeline should be verified against the latest roadmap before publishing, as the thread data is from late 2025 and the situation may have changed._ +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414583335472070776, 1432631707735031808, 1436747952189083799, 1448666378998382753] ---- +### Align controls and snap to grid -## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1113070283004313701, 1380597011354484738, 1415716215204085864, 1220612763873972314] +The Form Designer includes a snap-to-grid / align-controls feature. Hover over the designer toolbar area to reveal the **Enable align controls** button. When enabled, control movement is constrained to grid increments, which prevents accidental fine-grained shifts when clicking to select a control. -> [!NOTE] -> The `Console` class added to a new Console Application project is a minimal stub. It does not include a `Write` method by default. To use `Console.Write`, add the method manually by editing `Console.twin` in the project. +### Locking controls -_Source threads: 1109285180759347321 · confidence: high_ -_Date range: 2023-05-20 to 2023-05-23_ -_Reviewer note: Verify the current state of the Console.twin stub shipped with new Console Application projects -- the set of methods it provides may have changed since this Discord report._ +Controls in the Form Designer can be locked to prevent accidental repositioning by mouse drag. To lock a single control, right-click it and choose **Lock** from the context menu. To lock all controls at once, press **Ctrl+A** to select all, then right-click and choose **Lock**. Locked controls remain selectable and their properties remain editable. To unlock, use the same context menu or **Edit > Unlock controls** / **Unlock all controls**. + +_Source threads: 1414851467046551552 · confidence: high_ +_Date range: 2025-09-09 to 2025-09-16_ +_Reviewer note: Merges two findings (snap-to-grid from thread 1414851467046551552 and lock controls from thread 1417476641105514707). Verify menu item names and exact toolbar button label against the current IDE build._ --- -## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1380597011354484738, 1415716215204085864, 1220612763873972314] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1432631707735031808, 1436747952189083799, 1448666378998382753] -> [!NOTE] -> -> Prior to twinBASIC BETA 334, calling `SetConsoleMode` (or any function that calls it internally, such as `Console.ReadChar`) from code running inside the IDE returned 0 and set `Err.LastDllError` to 5 (`ERROR_ACCESS_DENIED`). The same code ran correctly in a compiled `.exe`. This discrepancy was a known IDE limitation and has been resolved as of BETA 334. +### Saving panel layout -_Source threads: 1113070283004313701 · confidence: high_ -_Date range: 2023-05-30 to 2023-06-15_ -_Reviewer note: Confirm that BETA 334 is the correct fix version and that Console.ReadChar is the right example of an affected built-in function. The fix was described by WaynePhillipsEA on Discord._ +By default, the DEBUG CONSOLE, Variables, Diagnostics, and Call Stack panels reopen on each IDE start, even if they were closed in the previous session. To persist a custom panel arrangement, use **Save Panel Layout as** from the IDE's View or panel management menu. Once saved, that layout is restored on subsequent starts. + +_Source threads: 1414583335472070776 · confidence: medium_ +_Date range: 2025-09-08_ +_Reviewer note: Verify the exact menu path for 'Save Panel Layout as' in the current IDE build._ --- -## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1415716215204085864, 1220612763873972314] +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1436747952189083799, 1448666378998382753] + +## Form designer known issues > [!NOTE] -> A **Standard EXE** that uses a `Sub Main` entry point instead of a startup form is not a Console App. The **Is Console App** setting in Project Settings is what makes a project a console application --- not the presence of a `Main` sub. To create a Standard EXE that starts from `Main` without a default form: start from the Standard EXE template, add a module (which provides the `Main` sub), then delete the default form. Starting from the Console App template and removing its console-specific code leaves the **Is Console App** flag set, which causes the Debug Console window to appear whenever a form editor is opened. +> Pressing the Alt key alone in the IDE (regardless of whether the form designer or code editor is active) opens the File menu. After the menu closes, the form designer may enter a broken state: clicking a control inside a container (Frame, PictureBox, or similar) selects both the child control and its container, and controls can no longer be moved with the mouse or arrow keys. Restarting the IDE clears the problem. +> +> Workaround: while in this state, use the control drop-down list at the top of the Properties pane to select the exact control whose properties need to be changed. The drop-down shows all controls available in the current context and is not affected by the selection state of the designer. -_Source threads: 1380597011354484738 · confidence: high_ -_Date range: 2025-06-06_ +_Source threads: 1432631707735031808 · confidence: high_ +_Date range: 2025-10-28_ +_Reviewer note: Merges two findings from the same thread (gotcha + workaround). Confirm the Properties pane drop-down description is accurate and that the Alt-key behavior has not been fixed in a post-October-2025 build._ --- -## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1380597011354484738, 1220612763873972314] - -## Startup object - -A standard executable project requires one of two startup configurations, set in the project settings: +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1448666378998382753] -- **Sub Main** --- a `Public Sub Main()` procedure in a module. The IDE calls this procedure when the executable starts. Use this for non-GUI applications or when startup logic needs to run before any form is displayed. -- **Startup Form** --- a form designated as the startup object. The IDE displays that form when the executable starts, and the application ends when all forms are closed. +The following keyboard shortcuts operate on all procedures in the active editor at once: -If neither is configured the project has no valid entry point and cannot run. +| Shortcut | Action | +|----------|--------| +| **Ctrl+Alt+Left** | Collapse all procedures except the one containing the cursor | +| **Ctrl+Alt+Right** | Expand all procedures | -_Source threads: 1415716215204085864 · confidence: medium_ -_Date range: 2025-09-11_ -_Reviewer note: Basic project setup fact; verify exact UI label and setting location in the current IDE. The Project-Types.md page focuses on advanced project types; a general Startup Object section may fit better in Project-Configuration/index.md or a dedicated page._ +_Source threads: 1436747952189083799 · confidence: high_ +_Date range: 2025-11-08_ --- -## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1380597011354484738, 1415716215204085864] - -### Control Panel Applets +## docs/Features/Compiler-IDE/IDE-Features.md · after-remarks [DUPLICATE? -- see also thread 1066231176462872656, 1077017629279125574, 1091247313214783518, 1122091213177229362, 1201200584821313586, 1216245949278457856, 1407004587570892974, 1453807327982649424, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1077971955052982312, 1087939028826984480, 1169977003705909349, 1176838610327187517, 1397666706197057627, 1411082650902335678, 1233485738516156486, 1319559634453008415, 1379796923224952892, 1381120139893674015, 1382393005973180457, 1391112464422404208, 1398426772021841961, 1414851467046551552, 1414583335472070776, 1432631707735031808, 1436747952189083799] -A Standard DLL project can produce a classic Windows Control Panel applet (`.cpl`) by exporting a function named `CPlApplet`. Classic `.cpl` applets are still recognized and loaded by Windows 10 and Windows 11. +### Code Formatting -When the applet's dialog uses ComCtl6-style controls but is loaded by a host process that does not provide a ComCtl6 activation context manifest (which `control.exe` does not), the DLL must call `CreateActCtx` and `ActivateActCtx` before creating each ComCtl6 window, then `DeactivateActCtx` afterward. All windows created while the context is active inherit the ComCtl6 theme. Resources within the DLL are loaded using the DLL's own module handle rather than the host's. +The **Format Document** command reformats the entire source file. It is available under **Edit** > **Format Document**. A **Format Selection** command reformats only the selected text. Both commands have default keyboard shortcuts that can be customized through the **Window** menu's keyboard shortcut manager. The internal command names displayed in the IDE status bar are `tbEditor_FormatDocument` and `tbEditor_FormatSelection`. -_Source threads: 1220612763873972314 · confidence: medium_ -_Date range: 2024-03-22_ -_Reviewer note: This is a Features page, not a Reference page. Verify that the Standard DLL section of Project-Types.md is the right home, or whether a dedicated .cpl tutorial page would be more appropriate. The ComCtl6 activation context pattern applies generally but is described here in the .cpl context._ +_Source threads: 1448666378998382753 · confidence: high_ +_Date range: 2025-12-12_ --- -## docs/Features/Standard-Library/Unicode-Support.md · after-remarks - -## Unicode characters in string literals - -Because `.twin` files store source code as full Unicode, pasting code from a source that uses Unicode curly apostrophes (U+2018 / U+2019, the typographic single-quote pair) instead of the plain ASCII apostrophe (U+0027) produces string literals that contain non-ASCII characters. WMI queries and SQL `WHERE` clauses that rely on ASCII apostrophes as delimiters fail at runtime with an 'Invalid query' error when the literal contains these characters. +## docs/Features/GUI-Components/Forms.md · after-remarks -The fix is to replace the Unicode apostrophes with standard ASCII apostrophes (U+0027) in the source. VB6 silently substituted a backtick in the same situation; twinBASIC preserves the original Unicode character, making the problem visible only at runtime. +> [!NOTE] +> The `true` manifest element enables the "System (Enhanced)" DPI scaling mode in a compiled EXE, but has no effect when running under the twinBASIC IDE. To simulate the same mode during IDE execution, call `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED)` in `Sub Main` before any forms are loaded. To check the current DPI context programmatically, call `GetDpiAwarenessContextForProcess(0)` and compare the result with `AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, lDpiContext)`. -_Source threads: 1233612072835616871 · confidence: high_ -_Date range: 2024-04-27_ +_Source threads: 1457732092715733173 · confidence: medium_ +_Date range: 2026-01-05 to 2026-01-06_ +_Reviewer note: Insert in the DPI Scaling section of Forms.md. The Win32 API declarations for SetProcessDpiAwarenessContext, GetDpiAwarenessContextForProcess, and AreDpiAwarenessContextsEqual are not in the docs; the reviewer may want to add them or link to the API declarations page._ --- -## docs/IDE/Debug Console.md · after-remarks +## docs/Features/Language/Comments.md · after-remarks > [!NOTE] -> The IDE execution log may include messages of the form **IDE internal error: Uncaught ReferenceError: *name* is not defined** or **IDE internal error: Uncaught TypeError: Cannot set properties of null**. These are internal diagnostics intended for the IDE developer, not indicators of a problem in user code. As long as the IDE does not halt or freeze, they can be ignored. +> The block-comment lexer scans for the first `*/` sequence unconditionally, without recognizing string delimiters. If the character sequence `*/` appears inside a quoted string within a block comment, the comment ends at that point and the remaining text is parsed as twinBASIC source. For example: > -> In very large projects an internal overflow in the IDE's error-reporting path can cause the line number shown for a genuine runtime error to be incorrect. The actual failing line may differ significantly from the reported location. When a runtime error occurs and the reported line appears unrelated to the surrounding code, treat the line number as unreliable and inspect a broader area---particularly any recently changed code---to locate the real fault. +> ```tb +> /* comment with "*/" more comment */ +> ``` +> +> The comment ends after the `*/` inside the string, not at the trailing `*/`. This is by-design behavior consistent with how most languages handle block-comment lexing. To avoid this, either rephrase the comment to avoid `*/` inside block comments, or use line comments (`'`) for code that contains such strings. -_Source threads: 1312778660079730690 · confidence: high_ -_Date range: 2024-12-01 to 2024-12-02_ -_Reviewer note: Content is based on WaynePhillipsEA's direct explanation in the Discord thread. No .twin source to verify against --- this is IDE operational behavior. Confirm that the execution log shown in the thread (timestamped [SHELL] output) surfaces in the Debug Console rather than a separate IDE log pane before placing this addition here._ +_Source threads: 1162324498083745802 · confidence: high_ +_Date range: 2023-10-13_ --- -## docs/IDE/Editor.md · after-remarks [DUPLICATE? -- see also thread 1313837849690378280] +## docs/Features/Language/Inheritance.md · after-remarks > [!NOTE] -> When a method call is inserted by IntelliSense tab-completion (a snippet insertion), typing `)` or `]` after the insertion adds a second closing character instead of moving past the one already inserted. The skip-over mechanism --- which normally lets `)` advance the cursor past an auto-inserted `)` --- only activates when Monaco itself recorded the corresponding `(` keystroke. Tab-completed snippets do not trigger that recording, so the mechanism does not activate. This is a Monaco editor limitation. Use the arrow key to move past the auto-inserted character, or delete the extra one with **Backspace**. +> twinBASIC's object model is COM-based and inherits VB6's "object-based" rather than fully object-oriented architecture. Operator overloading is not supported, and **String** and array types are not first-class objects. OOP patterns that depend on these features --- such as arithmetic operators on custom numeric types --- cannot be replicated directly. The additions of generics, implementation inheritance (**Inherits**), and parameterized constructors (**Sub New**) go significantly beyond VB6 semantics, but the underlying system remains lower-level than C#, Java, or VB.NET. -_Source threads: 1200413549567156324 · confidence: high_ -_Date range: 2024-01-26 to 2024-02-10_ -_Reviewer note: Confirmed by the twinBASIC developer (WaynePhillipsEA) as a Monaco limitation as of February 2024. Verify whether a fix has been shipped in a later release before publishing._ +_Source threads: 1436287137350946816 · confidence: high_ +_Date range: 2025-11-07 to 2025-11-10_ --- -## docs/IDE/Editor.md · after-remarks [DUPLICATE? -- see also thread 1200413549567156324] +## docs/Features/Language/Module-Organization.md · after-remarks > [!NOTE] -> -> The code editor uses the Monaco editor engine, which may render certain fonts that are not strictly monospaced (such as MeowSans) with incorrect cursor or caret positioning. The same fonts render correctly in a native Windows text editor. To correct cursor alignment for such fonts, enable font ligatures in the IDE Options dialog (Tools > IDE Options..., Editor section, **Font Ligatures** toggle). This option was added in twinBASIC BETA builds from late December 2024 onwards. In older builds without this option, open `ide/main.js`, find the call `editor.updateOptions({fontFamily:n})`, and change it to `editor.updateOptions({fontFamily:n,fontLigatures:true})`. +> Semantic highlighting in the IDE processes tokens near the visible area on each scroll event. In very large source files (roughly 60,000 lines or more), earlier IDE versions processed all tokens in the file on each scroll, causing choppy scrolling and high CPU usage. This was fixed in BETA 426, which introduced paging so that only the tokens close to the current view are processed. If working with an older version, splitting large files into smaller modules reduces the number of tokens processed per scroll. -_Source threads: 1313837849690378280 · confidence: high_ -_Date range: 2024-12-04 to 2024-12-29_ -_Reviewer note: The Editor.md page currently lists editor options but does not include a Font Ligatures entry. Verify whether this option now appears in the IDE Options dialog and update the option list accordingly._ +_Source threads: 1200262841345773578 · confidence: high_ +_Date range: 2024-01-26_ --- -## docs/IDE/Editor.md · new-section +## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1084723164296261672, 1283432466471845993] -## Troubleshooting +## Known Limitations > [!NOTE] -> If code in a `.twin` file loses syntax colouring --- all text appears uncoloured --- the most common cause is that the background compiler process has crashed. The IDE continues to function but cannot produce colour information. To restore highlighting, use the **Restart the compiler** button on the toolbar. The Debug Console or the status bar at the bottom of the IDE may show additional information when this happens. If restarting the compiler does not help, some users have also resolved the issue by recreating the project file or moving it to a shorter file path. +> The overload resolver has two known edge cases that can produce spurious ambiguity errors. +> +> **Specific interface types as the last parameter.** When two overloads differ only in parameter count but share the same trailing parameter type, the compiler resolves the call correctly when that type is `Object`. Changing the trailing type to a specific interface (such as a COM interface imported via a `Declare` or type-library reference) can cause the shorter overload to be reported as ambiguous, even though the argument counts differ unambiguously. Reversing the declaration order --- declaring the shorter overload with `Object` and the longer with the interface type --- avoids the error. +> +> **Implicit widening between `Long` and `Single`.** When one overload takes a single `Single` parameter and another takes two `Long` parameters, passing a `Long` variable to the one-argument call produces an ambiguity error because the resolver considers widening `Long` to `Single` as a candidate match for the two-argument form. Passing a `Long`-typed literal (e.g. `0&`) resolves without error because the explicit type suffix removes the widening candidate. -_Source threads: 1053718492647522314 · confidence: medium_ -_Date range: 2022-12-17_ -_Reviewer note: Verify that the 'Restart the compiler' toolbar button name matches the current IDE UI text. The claim about short file paths as an alternative fix came from user reports and may be version-specific._ +_Source threads: 1081364889815228416 · confidence: medium_ +_Date range: 2023-03-03 to 2023-03-04_ +_Reviewer note: Both issues were reported by fafalone with minimal repro projects (Discord, early March 2023). Verify whether either has been fixed in later compiler builds before publishing. The first issue involves specific COM interface types as trailing overload parameters; the second involves Long-to-Single widening across overloads of different arity._ --- -## docs/IDE/Editor.md · after-remarks +## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1081364889815228416, 1283432466471845993] > [!NOTE] -> When **Auto Prettify Code** is ticked, the editor reformats each line as it is edited. Opening an **Enum** or **Type** block near the top of a module before typing its **End Enum** or **End Type** line can cause damage elsewhere in the file. The parser temporarily treats every line below as a member of that block, reports many syntax errors, and formats those lines as members. Calls such as `myfunction(1)` become `myfunction (1)`, and unwanted `()` pairs may appear at the end of the line being edited. To avoid this, type the closing **End Enum** or **End Type** line first, or check the code below the block for stray spaces after adding one. +> +> All overloads of a given procedure name must be declared in the same module. The symbol resolver groups overloads by name within a module and registers them as a single entry; overloads defined in different modules are not recognized as overloads of each other and will instead cause an ambiguity error at the call site. This constraint matches VB6 behavior and is an intentional property of the context-free resolver. -_Source threads: 1526407024529768509 · confidence: high_ -_Date range: 2026-07-14 to 2026-07-23_ -_Reviewer note: Finding had no symbol (package Core); placed on the IDE Editor page, after the Options list, because the behaviour belongs to the Auto Prettify Code option. The link between this behaviour and that specific option is inferred; confirm. Users also reported spurious parentheses after `Load FormX` and `[Object].Clear`, not included._ +_Source threads: 1084723164296261672 · confidence: high_ +_Date range: 2023-03-13_ --- -## docs/IDE/FindReplace.md · after-remarks +## docs/Features/Language/Overloading.md · after-remarks [DUPLICATE? -- see also thread 1081364889815228416, 1084723164296261672] > [!NOTE] -> The Find/Replace dialog retains a history of previously entered search strings. Pressing the up or down arrow key inside the **Find What** field cycles through earlier queries, avoiding the need to retype recent searches. +> Compiler error **TB5073** --- "Unable to disambiguate between overloaded methods" with the sub-message "*N* Incompatible matches found" --- does not mean the overloads cannot be distinguished from each other. It means the arguments at the call site are incompatible with every candidate overload. Even if each overload had a unique name, the call would still fail. The fix is to correct the argument types at the call site, not to restructure the overloads. -_Source threads: 1478833214532620381 · confidence: medium_ -_Date range: 2026-03-04_ -_Reviewer note: The page-index does not include IDE pages. Reviewer should confirm the path docs/IDE/FindReplace.md and verify the feature is present in the current IDE build before publishing._ +_Source threads: 1283432466471845993 · confidence: high_ +_Date range: 2024-09-11 to 2024-09-12_ --- -## docs/IDE/History.md · new-section +## docs/Features/Language/Overloading.md · new-section [DUPLICATE? -- see also thread 1245329874777014302] -## Troubleshooting +## Overload Resolution and Interface Types -If the History panel stops responding, or if switching panel layouts repeatedly produces errors such as "internal error: unable to close floating panel" or "couldn't close hidden panel", the IDE's stored panel layout may be corrupted in the Windows registry. +twinBASIC's overload resolution evaluates each candidate overload by testing all arguments. A candidate is eliminated when any argument requires a narrowing conversion to match the candidate's declared parameter type. The process succeeds when exactly one candidate remains. -The IDE stores panel layout state under: +Interface types use stricter matching than value types. This is intentional: it lets code declare overloads that target different specific COM interface types --- for example, one overload for `IStream` and a separate overload for `ISequentialStream` --- where traditional VBx would accept any interface at the call site and only fail at runtime. -``` -HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE\IDESettings -``` +> [!NOTE] +> Because all arguments are checked before a candidate is eliminated, an overload can be incorrectly discarded even when another argument (such as `String` vs `LongPtr`) already uniquely identifies it. If every candidate is eliminated, the compiler reports **TB5073** (unable to disambiguate) even though the intent is unambiguous. -Three values in that key control the layout: +**Passing `Nothing`.** Prior to BETA 536, passing `Nothing` to a parameter declared as a specific interface type (e.g., `IBindCtx`) was treated as a narrowing conversion, eliminating that candidate. When all overloads shared the same specific-interface parameter, every candidate was eliminated and TB5073 was reported. BETA 536 corrects this: `Nothing` is treated as non-narrowing for any interface parameter. -| Value | Purpose | -|-------|---------| -| `LAYOUTv2` | The active panel layout | -| `CUSTOM_PANEL_LAYOUTS` | All saved custom panel configurations | -| `LAYOUT` | Legacy layout entry | +**Passing a base-interface variable.** Passing a variable typed as a base interface (e.g., `IUnknown`) to overloads that all expect more derived interface types still results in TB5073. The compiler does not yet traverse the COM interface inheritance chain to find the best match. The workaround is to cast the variable to the exact interface type required by the intended overload before passing it: -Deleting all three values and restarting the IDE resets the layout to its defaults. The IDE regenerates these entries on start-up. Any custom layout previously exported as JSON from within the IDE can be re-imported afterward. +```tb +' Suppose two overloads exist: +' Sub Foo(pbc As IBindCtx, pszPath As String) +' Sub Foo(pbc As IBindCtx, pszPath As LongPtr) -> [!NOTE] -> This issue is machine-specific and does not reproduce in clean environments. If it recurs, export any custom layouts as JSON before deleting the registry values so they can be restored. +Dim unk As IUnknown +' This causes TB5073 -- IUnknown is narrowing to IBindCtx: +' Foo unk, "C:\example" -_Source threads: 1230091088560128061 · confidence: high_ -_Date range: 2024-04-17 to 2024-04-23_ -_Reviewer note: The History page is not in the page-index (package: tbIDE, symbol: null). The content concerns the IDE's panel layout registry, not the tbIDE addin SDK — docs/IDE/History.md is the closest existing page. Verify that the registry path is still current and that the value names (LAYOUTv2, CUSTOM_PANEL_LAYOUTS, LAYOUT) match the shipping IDE._ +' Cast first to avoid the ambiguity: +Dim bc As IBindCtx +Set bc = CType(unk, IBindCtx) +Foo bc, "C:\example" +``` + +_Source threads: 1238767956050051114 · confidence: high_ +_Date range: 2024-05-11 to 2024-05-12_ +_Reviewer note: The `Nothing`-as-non-narrowing fix is confirmed for BETA 536 by WaynePhillipsEA in the thread. The IUnknown / base-interface limitation was confirmed as not yet implemented as of the thread date (2024-05-12). Verify whether COM interface inheritance traversal has since been added before publishing._ --- -## docs/IDE/Menu/Window.md · after-remarks [DUPLICATE? -- see also thread 1207651928356552704] +## docs/Features/Language/Overloading.md · new-section [DUPLICATE? -- see also thread 1238767956050051114] -> [!NOTE] -> Dragging a panel by its title bar detaches it into a floating window. To move a panel to a different position within the docked layout without detaching it, drag the six-dot gripper icon at the left end of the panel's title bar and drop it onto the target docked position. To return all panels to their original positions, use **Window** > **Panel Layouts** > **Default Built-in Layout**. +## Overload resolution and widening conversions -_Source threads: 1209855011530743908 · confidence: high_ -_Date range: 2024-02-21_ -_Reviewer note: Reviewer should confirm placement -- this note is most relevant to the Panel Layouts section of the Window menu page._ +When no candidate overload is an exact match for every argument --- that is, every candidate requires at least one widening conversion for at least one argument --- the compiler cannot choose between them and reports an ambiguity error. It does not narrow the candidate set by comparing individual parameter types across overloads and then resolve the remainder independently. ---- +A common trigger is passing an unqualified integer literal where a **Long** parameter is declared. The literal is typed as **Integer** by default, so the call requires a widening conversion to **Long**, and if another overload also requires a widening conversion for a different parameter, neither candidate is preferred. -## docs/IDE/Menu/Window.md · after-remarks [DUPLICATE? -- see also thread 1209855011530743908] +The fix is to use a typed literal suffix so the argument matches the intended overload exactly: -> [!NOTE] -> The `tbEditor_DeleteLines` command deletes the current line (or all selected lines) in the code editor. Its default keyboard shortcut is Ctrl+Shift+Delete. This command was added in BETA 451. The shortcut can be viewed and reassigned through **Window** > **Keyboard Shortcuts** > **Manage Keyboard Shortcuts**. +```tb +' Ambiguous: 1 is Integer; both overloads need a widening conversion +Foo 1, "hello" ' error if Foo(Long, String) and Foo(Integer, Long) both exist -_Source threads: 1207651928356552704 · confidence: high_ -_Date range: 2024-02-15 to 2024-02-16_ -_Reviewer note: Reviewer should confirm placement -- this note belongs near the Keyboard Shortcuts section of the Window menu page, alongside the `tbEditor_DeleteLines` entry in the default shortcuts JSON._ +' Unambiguous: 1& is Long; matches the Long overload exactly +Foo 1&, "hello" +``` ---- +See [Features → Literals](/Features/Language/Literals) for the full list of typed literal suffixes. -## docs/IDE/Menu/Window.md · after-remarks +_Source threads: 1245329874777014302 · confidence: high_ +_Date range: 2024-05-29 to 2024-05-30_ +_Reviewer note: The Overloading.md page is a feature overview page, not a reference page. Confirm the Literals page permalink /Features/Language/Literals exists and covers typed literal suffixes (e.g. 1& for Long). If not, adjust the See Also link._ -### Changing the colour of inline value hints -{: .no_toc } +--- -The dim text that IntelliSense shows after an enum member declaration (the member's value) is coloured by the theme property `InlineDecorationColor`. To change it, set the property in the theme file: - -```css -InlineDecorationColor: red; -``` +## docs/Features/Language/Overloading.md · after-remarks > [!NOTE] -> Edit the theme file that belongs to the twinBASIC installation actually in use. Edits to a theme file of another installation have no effect. - -One user reported that an edited and saved theme took effect only after twinBASIC was restarted, and that switching to another theme and back loaded the original version instead. If an edit does not appear, restart the IDE, or try **Reload from disk** in the **Theme** submenu. +> When a derived COM interface redeclares a method with the same name as one on its base interface, a call can fail with TB5073 (*Unable to disambiguate between overloaded methods*). For example, `d2dDevice1.CreateDeviceContext` with a variable declared as `ID2D1Device1` and an out-parameter declared as `ID2D1DeviceContext1` gives this error, with both the oleexp type library and WinDevLib. This is a known compiler limitation in overload resolution. +> +> Declaring the variable as the base type removes the error, but in that case the call crashed at run time with an access violation inside `D2d1.dll`. A workaround is to cast the value to a separate variable of the required interface first: +> +> ```tb +> Set d2dDevice1 = CType(Of ID2D1Device1)(d2dDevice1) +> ``` +> +> Place the cast directly after the `D2D1CreateDevice` call, which creates a base `ID2D1Device`. -_Source threads: 1548997779148382328 · confidence: high_ -_Date range: 2026-09-14_ -_Reviewer note: Insert at the end of the Theme section. The InlineDecorationColor property was confirmed by Wayne Phillips; the restart behaviour is a single-user observation, and the suggestion that Reload from disk helps is an inference from the menu entry, not stated in the thread. Consider whether a dedicated theme-authoring page (or IDE-Features.md, Theme System) is a better home, and confirm the exact theme-file syntax (CSS-style) against a shipped theme._ +_Source threads: 1513860400305672302 · confidence: medium_ +_Date range: 2026-06-09 to 2026-06-10_ +_Reviewer note: Not compiled here: needs a WinDevLib project to reproduce, so the code block is not marked check_build. The workaround is a suggestion from the thread and the outcome was not confirmed. The fenced code inside the callout should be checked against the build's code-region handling. Consider whether this belongs in BUGS-TO-REPORT.md instead, since the thread says it is tracked upstream (twinbasic issue 2107)._ --- -## docs/IDE/New Project.md · after-remarks [DUPLICATE? -- see also thread 1203088725605621801] +## docs/Features/Language/Overloading.md · after-remarks > [!NOTE] -> Importing a VB6 project that embeds the Windows Media Player ActiveX control (`WMPLibCtl.WindowsMediaPlayer`, from `wmp.dll`) fails at load time with a JSON deserialization error, even though the reference import step reports success. The control is embedded in `.ctl` form files using a `Begin WMPLibCtl.WindowsMediaPlayer` block, and twinBASIC cannot parse the control's serialized property data. The project will not load correctly until the Windows Media Player control is removed or replaced with an alternative. +> In overload resolution, **Nothing** is an exact match for a parameter of any interface type, so a call that passes **Nothing** does not become ambiguous between overloads that take different interfaces only because of the argument. -_Source threads: 1109156485201661972 · confidence: medium_ -_Date range: 2023-05-19_ -_Reviewer note: Finding is tagged package=VB, symbol=null. No dedicated VB6-import page exists; placed on docs/IDE/New Project.md as the closest page covering 'Import from VBP...'. A more appropriate home would be a dedicated VB6 migration / known-limitations page if one is created. The JSON deserialization error was reported by a community user in May 2023 and may have been fixed since then -- verify against a current IDE build before publishing._ +_Source threads: 927834661298798602-2024-05 · confidence: high_ +_Date range: 2024-05-12_ +_Reviewer note: From BETA 536. Wording on ambiguity is inferred; verify against compiler behaviour with two interface-typed overloads._ --- -## docs/IDE/New Project.md · after-remarks [DUPLICATE? -- see also thread 1109156485201661972] +## docs/Features/Packages/Creating a TWINPACK package.md · after-remarks > [!NOTE] -> The New Projects dialog uses an invisible modal overlay that blocks all interaction with the main IDE window while the dialog is open. If the dialog opens positioned so that its close button is off-screen or otherwise inaccessible, the IDE can appear completely unresponsive. Press **Escape** to dismiss the dialog and restore access to the main window. -> -> Starting from BETA 438, the IDE includes a configurable option to display a real OS-native titlebar. Enabling it reduces the likelihood of this situation, because the OS titlebar remains draggable independently of any modal overlay. +> Older tutorials and documentation refer to a dedicated **TWINBASIC PACKAGE MANAGER** panel inside the main Explorer activity panel. That panel was removed in a subsequent IDE redesign. The equivalent publishing controls are now under **View** > **Package Publishing**, which controls whether the package is published to TWINSERV as public or private. To produce a local `.twinpack` file for use in other projects, use the regular **Build** command; the output path is shown in the debug log and is configurable in project settings. -_Source threads: 1203088725605621801 · confidence: high_ -_Date range: 2024-02-02 to 2024-02-06_ -_Reviewer note: Target page is docs/IDE/New Project.md, which is not in the page-index (the finding package is tbIDE with null symbol). The page content is minimal and has no existing remarks section -- reviewer should confirm placement and that BETA 438 native-titlebar option description is still accurate._ +_Source threads: 1312685090232471612 · confidence: high_ +_Date range: 2024-12-01_ +_Reviewer note: Verify the exact menu path (View > Package Publishing) against the current IDE. The page already shows a screenshot of the PACKAGE PUBLISHING panel as a popup --- confirm whether the screenshot is current or needs updating._ --- -## docs/IDE/New Project.md · after-remarks +## docs/Features/Packages/Import-export tool.md · after-remarks -The list on the **Recent** tab is stored in the Windows registry, under `HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE\RecentlyOpened`. To remove many entries at once, close the IDE and delete the entries under that key with a registry editor such as RegEdit, instead of clicking the X beside each one. +> [!NOTE] +> The **Export on Save** setting writes individual source files to a folder on disk, which makes the exported files suitable for version control. However, the export folder is overwritten in full on every save, so placing a `.git` directory directly inside it will destroy the repository. The recommended approach is to export into a dedicated subfolder within a broader git repository, keeping the `.git` directory and any other project assets outside that subfolder. Git will not record spurious changes when unchanged files are replaced with identical content. -_Source threads: 1511584056893112360 · confidence: high_ -_Date range: 2026-06-03_ -_Reviewer note: Registry path comes from a maintainer's Discord answer. Closing the IDE first is a precaution added by the drafter, not stated in the thread. Placed at the end of the Recent section._ +_Source threads: 1314117578024812604 · confidence: high_ +_Date range: 2024-12-05 to 2025-01-30_ +_Reviewer note: This gotcha is currently undocumented. The Import-export tool page is a reasonable location but the finding is really about the IDE's built-in Export on Save feature rather than the standalone command-line tool. A more precise home would be a dedicated page on version-control workflows if one is added, or the project-settings / project-configuration section. Reviewer should consider whether to place this note here or on a more targeted page._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] +## docs/Features/Packages/Import-export tool.md · new-section + +## Version control via the IDE + +The IDE provides a built-in path for version-controlling a twinBASIC project without the command-line tool. Enable the **Auto export to files on save** option in the project settings; the IDE then writes each module as an individual file alongside the `.twinproj` file on every save. The exported files are plain text and diff cleanly in Git, SVN, or any other version-control system. + +To reconstruct a project from a set of exported files --- for example after cloning a repository --- use the **Import from folder** option on the IDE start-up screen. This option appears only on the initial dialog before a project is open; it is not available from inside an already-open project. > [!NOTE] -> Export can intermittently fail with a message such as "failed to create output file: `` (check permissions and storage space)" or "failed to clean directory", even when disk space and permissions are not the cause. The underlying reason is a Windows file-system behavior: when the IDE removes the export output folder before writing a fresh copy, Windows queues the deletion rather than completing it immediately. The folder still appears in the directory listing for an indeterminate period; any attempt to recreate, rename, or write into it during that window fails silently. Once Windows processes the deletion, the path becomes available again. Retrying the export immediately --- without making any other changes --- typically succeeds. +> Full external-file support --- where the `.twinproj` file references files on disk directly, analogous to a `.vbp` file --- is planned for a future release but is not yet available. -_Source threads: 1065982376154501120 · confidence: high_ -_Date range: 2023-01-20 to 2023-02-22_ -_Reviewer note: This page (docs/IDE/Project Settings.md) is not in the page index --- it is an IDE guide page, not a Reference page. The finding's package is 'tbIDE' with null symbol; the Export section of Project Settings is the closest fit for this IDE-level gotcha. Verify that the Export section heading anchor is `## Export` (line 138) and that the NOTE sits inside that section. Also confirm the error message text against current IDE behaviour._ +_Source threads: 1302320508800602153 · confidence: high_ +_Date range: 2024-11-02 to 2024-11-06_ +_Reviewer note: Verify the exact name of the project-settings option ("Auto export to files on save" is paraphrased from Discord). Confirm that "Import from folder" is still on the startup screen and not elsewhere in a newer IDE build._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] +## docs/Features/Packages/Import-export tool.md · after-remarks > [!NOTE] -> When the Available COM References list shows two entries with identical names and version numbers, this is not a bug. Each entry has a different GUID and corresponds to a distinct type library registered on the system (for example, two installations of the same library from different applications). Selecting each entry in turn and comparing the displayed file path or GUID identifies the specific library to reference. Checking both entries is unnecessary and may cause conflicts. +> The IDE expects source files with Windows (CRLF) line endings and UTF-8 encoding. A `.twin`, `.bas` or `.cls` file that enters a project with LF-only line endings raises no error, but the editor loses track of the syntax: highlighting goes wrong partway through the file, and edits misbehave. A file written by a tool on a system that uses LF endings is a typical cause. Both programs described here convert LF to CRLF in these three file types on `import`. When a file reaches a project by any other route, convert it to CRLF before opening it. -_Source threads: 1111079336079020102 · confidence: medium_ -_Date range: 2023-05-25_ -_Reviewer note: Target page is docs/IDE/Project Settings.md (Library References section), which is outside the page-index. The note should be inserted under the '## Library References' heading on that page, near the Available COM References screenshot. Verify the wording accurately describes the IDE's current References dialog behavior -- in particular that selecting a duplicate entry does display a file path or GUID that distinguishes the two libraries._ +_Source threads: 1542250945574871071 · confidence: high_ +_Date range: 2026-08-26 to 2026-09-23_ +_Reviewer note: The page already states that import converts LF to CRLF, so only the symptom description and the UTF-8 requirement are new. The thread says the script was fixed to convert on import and now shares the tB executable's command vocabulary; both are already described on this page. Verify the UTF-8 claim and the 'any other route' advice against the IDE. The thread also mentions a related importer/exporter bug in tB itself that is not identified, so it is left out._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] +## docs/Features/Packages/Import-export tool.md · after-remarks > [!NOTE] -> When a type library carries the `TYPEFLAG_FCONTROL` flag on one of its interfaces---as the Microsoft Access type library does on `Access.Control`---twinBASIC treats the entire reference as a Components entry (equivalent to an ActiveX control registered via the Components list in VB6). Types from that reference are wrapped in `_ActiveXControlExtender` classes, which implement only the standard COM control interface and are marked non-extensible. Late-bound access to members that are not part of that base interface---such as `ControlSource`, `ControlType`, and other Access-specific control properties---fails with error 5216: `Unrecognized member "..." on type "_ActiveXControlExtender" [non-extensible object]`. -> -> To restore normal late-bound access, open the project's Settings file and set the **Use ActiveX Controls** flag to **False** on the affected reference. After saving, twinBASIC treats the reference as a plain COM type library, and late-bound calls to interface members work as expected. -> -> This is a known limitation. A future release will separate Components and References more precisely, matching VB6 behavior. +> A project is still stored inside its `.twinproj` file; the folder the tool produces is a copy. Keeping the project's source files outside the `.twinproj` file, so that source control and team work need no export step, is planned for version 1.0. -_Source threads: 1114028559451828314 · confidence: high_ -_Date range: 2023-06-02_ -_Reviewer note: Verify the exact name of the 'Use ActiveX Controls' flag as it appears in the .twinproj / Settings JSON, and confirm the error number 5216 is still current. The finding comes from a 2023 Discord thread; the behavior or flag name may have changed. Target page (docs/IDE/Project Settings.md) is not in the Reference page-index --- this is the IDE section. Consider whether the note belongs in the Library References subsection of that page or needs a dedicated new paragraph there._ +The folder form is also not a way back to VB6. Plain `.bas` and `.cls` files can be imported into twinBASIC, at the cost of the newer language features and Unicode support. Forms and controls (`.frm` and `.ctl`) cannot be stored in the VB6 format without losing information, because twinBASIC forms have many new properties and required attributes that the format has no place for. Moving a project from VB6 to twinBASIC is effectively one way: form changes made in the twinBASIC designer cannot be carried back. + +_Source threads: 1554511541443891293 · confidence: medium_ +_Date range: 2026-08-13 to 2026-09-29_ +_Reviewer note: Merged from two threads (v1.0 external source files, dated 2026-09-29; VB6 file-format limits, 2026-08-13 to 2026-08-15). Both are statements by a community expert, not from the sources. Check the 'planned for v1.0' wording against the current roadmap before publishing; consider splitting the VB6 paragraph into docs/Miscellaneous/FAQs.md near the import instructions. Insert in the 'Keeping a project in Git' section._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] +## docs/Features/Packages/Import-export tool.md · after-remarks > [!NOTE] -> When the references list is partially obscured --- for example, when a list item is clipped halfway by the bottom edge of the dialog --- clicking the topmost visible item may select the wrong entry. To avoid this, scroll or resize the dialog so that no item is partially clipped before clicking. This is a known bug; the references and packages lists are planned for an overhaul, at which point the issue is expected to be resolved. +> The `--self-test` option is for the script's developers. It needs a `sample.twinpack` file, which is not part of the download, so it cannot run in an ordinary installation. Do not use it to check a copy of the script; run one of the commands above on a project of your own. -_Source threads: 1126479798156394516 · confidence: high_ -_Date range: 2023-07-06 to 2023-07-11_ -_Reviewer note: Insertion point is the Library References section of docs/IDE/Project Settings.md. The page is not in the page-index (which covers only docs/Reference/ and a few top-level pages), but was identified by reading the IDE menu pages. Verify that the workaround description still applies to the current IDE version before publishing._ +_Source threads: 1524049386953375946 · confidence: medium_ +_Date range: 2026-07-07 to 2026-07-22_ +_Reviewer note: The rest of the finding (using the tools to keep a project in git) is already covered on this page, including the .gitignore section. Only the self-test caveat is new. The page index has no Features pages, so the target was found by reading nearby files. Confirm the sample.twinpack requirement against scripts/impexp.mjs before publishing; the finding is medium confidence and the page may want a different wording if the file is meant to ship with the repository._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1208711450240090122, 1339525725778345997, 1440729215682220074] +## docs/Features/Packages/Import-export tool.md · after-remarks > [!NOTE] -> The icon shown in Windows Explorer and the taskbar for a compiled executable comes from the `Resources\ICON` folder inside the project, not from the **Icon Form** setting. twinBASIC selects the icon file in that folder whose name sorts first alphabetically. To control the application icon, add the desired `.ico` file to the `Resources\ICON` folder and name it so that it sorts before any other icon files in that folder. If the folder is empty, the compiled executable uses a generic icon. +> A failed `--buildAndExit32` or `--buildAndExit64` compile writes no error or log file. The IDE window stays open and is the only sign of the failure. A script that must use these switches can treat a missing output `.exe` or `.dll` as a failed build, and then end the `twinBASIC.exe` process itself. -_Source threads: 1205564031482593380 · confidence: medium_ -_Date range: 2024-02-09 to 2024-02-10_ -_Reviewer note: Verify against current IDE behavior -- the claim is that the icon is taken from Resources\ICON (alphabetically first), not from the Icon Form project property. Needs confirmation against recent IDE release._ +_Source threads: 1478865333245837444-2026-06 · confidence: high_ +_Date range: 2026-06-25_ +_Reviewer note: Page already says the switches are unsuitable for unattended checks; only the no-error-file detail and the missing-output workaround are new. The workaround is a community suggestion and is not verified. The maintainer also said full command-line compilation is planned after LLVM support; this was left out as forward-looking. Resolved by reading nearby pages: no Core/--buildAndExit32 key in the index._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1339525725778345997, 1440729215682220074] +## docs/Features/Packages/Import-export tool.md · new-section -> [!NOTE] -> When a **Package** project type is built, the output filename includes a `_win32` or `_win64` suffix even though a `.twin` package file is architecture-neutral (the consuming project determines the target architecture). This suffix comes from the default build path template, which includes an `_${Architecture}` token. To produce a filename without the suffix, set a custom **Build Output Path** in the project settings that omits the `_${Architecture}` token. +## Working with AI tools +{: #working-with-ai-tools } -_Source threads: 1208711450240090122 · confidence: medium_ -_Date range: 2024-02-18_ -_Reviewer note: Reviewer should confirm placement -- this note is most relevant to the Build Output Path section of Project Settings. Verify whether the behavior has changed in recent IDE releases._ +A `.twinproj` is one binary file, which an AI assistant cannot read or edit directly. Export the project to a folder of text files, point the assistant at that folder, and pack the folder back into a project file afterwards to review and test the result. ---- +Tell the assistant that the tool exists, with the command lines from [Usage](#usage), so that it can run the export and import itself. The Node.js, Python and twinBASIC editions all work for this; the twinBASIC edition needs no interpreter. -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1440729215682220074] +A script that wraps the tool can make the cycle safer: -> [!NOTE] -> **Import from file** embeds the selected binary (e.g. an `.ocx` file) inside the `.twinproj` file, which can increase the project file size from a few kilobytes to several megabytes. To keep the project file small, use **Add reference** (the available references list) instead --- that stores only a path to the library. When a twinBASIC-native package is available for the library (e.g. `VBCCR18`), prefer the native package: it supports both 32-bit and 64-bit targets and does not embed a binary. **Import from file** is generally not recommended for OCX or DLL files. +1. **Export script.** Check that the IDE is not running, and exit with a warning if it is, so that the IDE does not save over the packed result. Copy the `.twinproj` into a backup subfolder. Then run `export --overwrite` into a subfolder of its own, such as `ImportExport`. +2. **Edit.** The assistant changes the text files in that subfolder. +3. **Pack script.** Run `import --overwrite` to rebuild the `.twinproj`, then open it in the IDE to review the changes and test. -_Source threads: 1339525725778345997 · confidence: high_ -_Date range: 2025-02-13_ -_Reviewer note: Insert under the 'Library References' section of the Project Settings page (after line 34 in the current file). Verify the UI labels ('Import from file' and 'Add reference') match what the IDE actually shows._ +Keep the exported folder for the project's files alone, as described in [What both programs do](#what-both-programs-do), and [check the result](#checking-the-result) of each command rather than assuming it worked. + +_Source threads: 1478865333245837444-2026-10 · confidence: medium_ +_Date range: 2026-10-14 to 2026-10-15_ +_Reviewer note: Finding came from the ai-talk thread of Oct 2026. The thread's mention of the native twinBASIC edition is already on the page. The step 'check the IDE is not running' is the user's own workflow, not documented IDE behaviour; verify the claim about the IDE saving over the packed result. The page index had no mapping for ImportExportTool, so the page was found by search._ --- -## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997] +## docs/Features/Packages/Import-export tool.md · after-remarks > [!NOTE] -> When opening a project created with an older version of twinBASIC (before version 9), the IDE may display an "Unable to Load Library" dialog whose title bar shows a GUID rather than a human-readable name. That GUID is the type library ID of the `twinBASIC - WindowsControls DESIGNER Package`, which was a built-in package removed in a later release. To resolve the error: dismiss the dialog, open **Project Settings**, locate the reference entry for that GUID in the **Library References** list, untick it, and save the project. +> `--self-test` is a developer option. It looks for the file `indexer/sample.twinpack` from the twinBASIC source repository, which is not shipped to users, so the test cannot run from an installed copy. -_Source threads: 1440729215682220074 · confidence: high_ -_Date range: 2025-11-19_ -_Reviewer note: Page is docs/IDE/Project Settings.md -- not in page-index. Reviewer should confirm placement under the Library References section and verify the package name and tB version number ("before version 9") against the changelog._ +_Source threads: 1519120323864432850 · confidence: high_ +_Date range: 2026-06-23 to 2026-06-24_ +_Reviewer note: Maintainer plans to embed the sample in the tool; the note may become obsolete._ --- -## docs/IDE/Project Settings.md · after-remarks +## docs/Features/Packages/Importing a package from a TWINPACK file.md · after-remarks [DUPLICATE? -- see also thread 1333426890291023884] > [!NOTE] -> A built program that Windows refuses to start with *This app can't run on your PC* usually has one of two causes. First, a manifest imported from a VB6 project keeps `processorArchitecture="X86"`, which a 64-bit build cannot use; change it to `*`. Second, *Target OS Version* is higher than the Windows version that runs the program: a project set to Windows 10 does not run on Windows 7. When both are correct, delete the manifest and create it again. If the program still fails to start, check whether security software or file permissions on that machine block it. +> After importing a `.twinpack` file, the package appears in the Project Explorer under **Packages** but its symbols are not yet active. Check the checkbox next to the package in the **Available Packages** list (the same list used to import it), then save **Settings** to activate it. Skipping this step is a common source of unresolved-symbol errors --- the package is present but inactive until the box is checked. -_Source threads: 1540209036325298176 · confidence: medium_ -_Date range: 2026-08-21 to 2026-08-22_ -_Reviewer note: Place after the Target OS Version section. The thread's title also mentions an 'Access is denied' message that the draft does not reproduce. The diagnosis of each cause is by the thread's participants, not confirmed against the compiler._ +_Source threads: 1312685090232471612 · confidence: high_ +_Date range: 2024-12-01_ --- -## docs/IDE/Project Settings.md · after-remarks +## docs/Features/Packages/Importing a package from a TWINPACK file.md · after-remarks [DUPLICATE? -- see also thread 1312685090232471612] -> [!NOTE] -> Compression does not work together with an overridden entry point. A build that sets a custom entry point, such as a `DllMain` in a Standard DLL project, reports "Build Error: Internal error, compression". Untick the three compression boxes in [Feature Flags](#feature-flags) --- **Compress Runtime Class Dispatch Info**, **Compress Runtime Error Tables** and **Compress Misc Data** --- to remove the error. In the reported case, the built DLL's entry point was still not the custom procedure, so custom entry points did not work completely at that time. +> [!IMPORTANT] +> The 'Import from file...' button must be clicked while the **Packages** tab (also labelled 'Available Packages') is active in the project settings. If the button is clicked while the **References** tab or any other tab is shown, twinBASIC treats the `.twinpack` file as a COM type library and fails with error `0xc000012f`. The error message does not indicate the tab-selection cause. -_Source threads: 1550752244469735475 · confidence: high_ -_Date range: 2026-09-19_ -_Reviewer note: Insert at the end of the Override Entry Point section. The maintainer said to turn off 'the compression options among the feature flags'; the three names are taken from the existing Feature Flags - continued list, so confirm that all three are needed. The final sentence records an unresolved defect (entry point at 0x1000, 'entry point not found') and may be dated quickly._ +_Source threads: 1333426890291023884 · confidence: high_ +_Date range: 2025-01-27_ +_Reviewer note: The existing page refers to a 'References section' / 'Available Packages' button flow that may reflect an older UI. Verify the current tab labels in the IDE before merging, and update the main page text if needed._ --- -## docs/IDE/Project Settings.md · after-remarks +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1427261753544409168, 1460777854714515728, 1495944076632002572] > [!NOTE] -> The 32-bit compiler is limited to 4 GB of address space, so a very large project, such as an imported VB6 project of over a million lines, can fail with "INTERNAL ERROR: OUT OF MEMORY". The limit is address space, not physical memory, so adding RAM does not help. Two ways to reduce the compiler's memory use are to untick the features the project does not use in Feature Flags, and to split the program into smaller projects. A 64-bit compiler is planned. +> A `.twinproj` file placed in `%APPDATA%\Roaming\twinBASIC\packages` also appears in the **Available Packages** list and can be referenced as a linked package. When adding the reference, leave the **Embedded** checkbox unticked so the reference stays linked rather than copying the source into the referencing project. Changes made to the shared project are visible to referencing projects the next time they are loaded. This pattern is the recommended approach for sharing code across multiple twinBASIC projects without duplicating it. -_Source threads: 1551873936898261022 · confidence: high_ -_Date range: 2026-09-22_ -_Reviewer note: Placed after the Feature Flags section. It may fit better in a compiler-limits or FAQ page. The claim that the compiler is x86 Large Address Aware comes from one process sample in the thread; the maintainer's 20-30% reduction and the 64-bit compiler plan are stated intentions and are worded loosely. Suggestions to disable features and split the program came from other users, not the maintainer._ +_Source threads: 1392532961366376469 · confidence: high_ +_Date range: 2025-07-11_ +_Reviewer note: The existing Linked Packages page describes placing .twinpack files in %APPDATA%\Roaming\twinBASIC\packages. Verify that .twinproj files placed there also appear in Available Packages before publishing._ --- -## docs/IDE/Project Settings.md · after-remarks +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1460777854714515728, 1495944076632002572] > [!NOTE] -> The LLVM compiler did not support this option in BETA 984 to 987. A project with it on could report "a feature used in your code is not yet supported with the LLVM compiler" and produce a DLL or OCX that crashed when the affected procedure was called. BETA 988 fixes it. See [Language support](../../../LLVM/Getting-Started#language-support) in the LLVM section. +> +> When sharing a `.twinproj` file that uses a linked package, the recipient must have that package registered on their machine. If the package is missing, open the project, uncheck (remove) the broken reference in the Available Packages list, then re-add it and uncheck the Embedded column to restore the linked reference. The "Fix" option is not currently implemented. -_Source threads: 1553387151842877491 · confidence: high_ -_Date range: 2026-09-26 to 2026-09-28_ -_Reviewer note: Place at the end of the 'Sanitize Booleans' section, before the 'Constant Function Folding' heading. Overlaps with the note added to the LLVM Getting Started page; keep only one if the reviewer prefers less duplication._ +_Source threads: 1427261753544409168 · confidence: medium_ +_Date range: 2025-10-24_ +_Reviewer note: The existing 'Opening a project with missing linked package' section already covers this workflow. This note may be redundant; reviewer should decide whether it adds enough clarity to keep, or whether it should simply be removed in favour of the existing section._ --- -## docs/IDE/Project Settings.md · after-remarks +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1427261753544409168, 1495944076632002572] -When two referenced packages define the same name, an unqualified use of that name resolves to the package that is higher in the priority order. A use of the other package's name must be prefixed with the package name. For example, WinDevLib and a WinRT package can each define `IPersistFile` with a different signature. +Linking also keeps a project or a package you build small. In one reported case, a package that embedded WinDevLib was 27 MB, and less than 800 kB with WinDevLib linked instead. A linked dependency also removes the need to copy its definitions into the project. -To change the order, use the arrow buttons in the References dialog, as in VB6. Putting the package with fewer clashing names first keeps the number of prefixes small. +_Source threads: 1460777854714515728 · confidence: medium_ +_Date range: 2026-02-15_ +_Reviewer note: Insert after the opening paragraph of the page. The sizes come from a single user's report, not a measurement; verify that a .twinpack that references a linked package stores only the reference. Also check whether a package that links a dependency needs that dependency to be linked on the consuming machine._ -> [!NOTE] -> Updating a package removes it and adds it again, so it may not return to its earlier position in the order. Check the order after each update. Diagnostics does not report the case where the order silently changes which type a name refers to. +--- -_Source threads: 1125359845936205844 · confidence: medium_ -_Date range: 2026-06-17 to 2026-06-18_ -_Reviewer note: The thread says an updated package lands at the end of the resolve chain, but Features/Packages/Updating a package.md says the entry moves to the top, below the built-in packages. Reconcile before publishing; the draft avoids stating the new position. The exact caption of the arrow buttons in the Library References dialog is not confirmed._ +## docs/Features/Packages/Linked Packages.md · after-remarks [DUPLICATE? -- see also thread 1392532961366376469, 1427261753544409168, 1460777854714515728] ---- +## Fixing a failed package reference -## docs/IDE/Toolbar.md · after-remarks +When a package reference fails to load --- shown as "Unable to load this reference" in the project settings --- re-selecting the same package in the References dialog does not repair the reference. The correct sequence is: + +1. Clear the broken reference by unchecking it. +2. Click **Apply Changes** to commit the removal. +3. Reopen References, go to the **Available Packages** tab, and select the package again. + +Attempting to re-add without first clearing and applying leaves the stale entry in place. > [!NOTE] -> When a transient runtime error occurs during execution, restarting the entire IDE is not necessary. The **Restart Compiler** button on the toolbar --- the circular-arrow icon next to the architecture dropdown --- resets the compiler process and usually clears the error state without closing and reopening the IDE. +> A very large number of compile errors (hundreds) when opening a project is a common symptom of a missing package reference, not a code defect. If the project uses a linked package that has not been added as a reference, the compiler reports one error per unresolved identifier. Adding the package under **Project -> References -> Available Packages** clears them all. -_Source threads: 1395281720277991444 · confidence: medium_ -_Date range: 2025-07-18_ -_Reviewer note: Toolbar.md is a sparse list-based page with no prose sections. Verify whether a prose note fits here or whether it belongs in docs/Miscellaneous/FAQs.md instead._ +_Source threads: 1495944076632002572 · confidence: high_ +_Date range: 2026-04-05 to 2026-04-21_ --- -## docs/IDE/Toolbar.md · after-remarks +## docs/Features/Packages/Linked Packages.md · after-remarks -> [!NOTE] -> A *Compiler crash loop* message means the compiler keeps crashing and restarting, and its progress bar loops near 5%. A single compiler restart is normal: changing project settings restarts the compiler by design. In BETA 985 to 991 the loop had two known causes. One was a hard crash while the [Project Explorer](Project%20Explorer) was showing the object view instead of the file view. The other was a fault in error reporting, which appeared when the [Debug Console](Debug%20Console) had errors to show, for example after the VBA reference was removed. BETA 992 fixes both for the reporters. On one machine, restarting Windows cleared the loop, which is a workaround and not a fix. To recover a project meanwhile, use **safeMode**. +The IDE has no command that rolls a linked package back to an older build. To downgrade, replace the package's `.twinpack` file in `%APPDATA%\twinBASIC\packages` with the older file, then restart the compiler. If the package is published in a GitHub repository that has no Releases page, an older `.twinpack` can be downloaded from the repository's commit history, using the raw file at the commit in question. -_Source threads: 1553605375423553679 · confidence: high_ -_Date range: 2026-09-25 to 2026-09-29_ -_Reviewer note: Place after the paragraph about **safeMode** at the end of the page. Verify the relative links to Project Explorer and Debug Console resolve (the pages use spaces in their filenames; use their permalinks instead if the build's link check objects). The two causes are from developer statements in the thread; the 'object view' name for the Project Explorer mode should be checked against the IDE, since the Project Explorer page calls the toggle 'Toggle file view'._ +A package opened in the IDE is read-only. When the package has a `changelog.md`, it is visible in the Project Explorer under `Packages\`, which helps in choosing the build to go back to. + +_Source threads: 1125359845936205844 · confidence: medium_ +_Date range: 2025-11-19 to 2025-12-29_ +_Reviewer note: Insert at the end of the Manual management section. The restart-the-compiler sentence comes from the existing page text, not from the thread._ --- -## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1336104425520893972, 1118376310725673091] +## docs/Features/Packages/Linked Packages.md · after-remarks -> [!NOTE] -> Panel and window layout state is stored independently of project files. A corrupt or incompatible saved layout therefore affects all projects, not only the one that was open when the problem appeared. To recover from a broken layout, delete the saved layout file and restore the default layout from within the IDE. Opening a different project does not reset the layout. +### Repairing several broken linked references -_Source threads: 1200411286937276508 · confidence: high_ -_Date range: 2024-01-26_ -_Reviewer note: The exact file path for the saved layout is not documented in the thread. Verify the location (likely a per-user app-data file) and consider adding it to the note so users can find it. The IDE index page (docs/IDE/index.md) is not in the page-index --- this is an IDE guide page, not a Reference page._ +The **Fix** button on a broken linked reference is not implemented. It reports that the feature is not yet available and asks for the reference to be removed and added again by hand. ---- +Unticking a linked reference hides it from the list, so note the exact names of the packages before removing them. When several linked packages depend on each other --- for example the WinRT packages and the Windows Development Library packages --- the order of the references matters. This procedure restores them reliably: -## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1200411286937276508, 1118376310725673091] +1. Untick the broken linked references and press **Apply Changes**. +2. On the **Available Packages** tab, add the packages back one at a time. +3. On the **Enabled Libraries** tab, use the up and down arrows to put each package after the packages it depends on. -> [!IMPORTANT] -> The twinBASIC IDE uses loopback sockets (127.0.0.1) for all communication between its frontend and backend compiler processes. Any code that installs Windows Filtering Platform (WFP) rules or other network-filter mechanisms must explicitly exempt the loopback address range (127.0.0.0/8) from blocking. A WFP filter that blocks outbound traffic to non-LAN destinations but omits 127.0.0.0/8 from its exclusion list will sever the IDE's internal channels, causing every open twinBASIC instance to enter a crash loop. The fix is to add a filter condition on `FWPM_CONDITION_IP_LOCAL_ADDRESS` excluding 127.0.0.0/8 in addition to any private-range exclusions on `FWPM_CONDITION_IP_REMOTE_ADDRESS`. This loopback architecture is planned to change in a future release. +> [!NOTE] +> +> Add a package from the **Available Packages** tab, not from **Available COM References**. A user reported that adding the VB6 WinRT type library through **Available COM References** makes the IDE extremely slow. -_Source threads: 1336104425520893972 · confidence: high_ -_Date range: 2025-02-03 to 2025-02-05_ -_Reviewer note: The finding was tagged package=Core, symbol=null, which maps to UNMAPPED in the page index. Placed here on docs/IDE/index.md as this is an IDE-level architectural warning. Verify that the IDE index page is the appropriate home, or consider a dedicated troubleshooting page if one is added in future._ +_Source threads: 1551367356507226154 · confidence: medium_ +_Date range: 2026-09-23_ +_Reviewer note: Findings were tagged package Core with no symbol, so the page was chosen by hand: Linked Packages already says Fix is not implemented, and this adds the multi-package ordering procedure. The dependency-order advice and the slow-IDE report come from a single user; verify before publishing. Step 3 assumes the up/down arrows are on the Enabled Libraries tab (Project Settings.md says that tab lists references in priority order); the thread does not name the tab._ --- -## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1200411286937276508, 1336104425520893972] +## docs/Features/Packages/Linked Packages.md · after-remarks > [!NOTE] -> The IDE stores all its settings in the Windows registry under `HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE`. Because settings are stored in the registry rather than in the installation folder, installing a new release to a fresh directory preserves existing settings automatically. To reset the IDE to its defaults, delete or rename this registry key. +> Linked packages are designed for the `%APPDATA%\twinBASIC\packages` folder. If a project does not pick up changes made to a linked package, check that the package's build output goes to that folder rather than a custom path, then save, run a clean build and refresh the compiler. Restarting the compiler may reload the package from memory only; reopening the project through **File > Open Recent**, even when it is already open, reloads it from file. Importing a package file whose version equals the online version does not list it for selection. -_Source threads: 1118376310725673091 · confidence: high_ -_Date range: 2023-06-14_ -_Reviewer note: The IDE section pages (docs/IDE/) are not in the page-index. This addition targets docs/IDE/index.md as the general IDE overview page. Reviewer should confirm placement --- the Project Settings page (docs/IDE/Project Settings.md) covers per-project settings, not IDE-level settings, so the index page is the better fit._ +_Source threads: 1563249111380262922 · confidence: medium_ +_Date range: 2026-10-24 to 2026-10-25_ +_Reviewer note: Based on user and maintainer reports in the thread; verify the exact refresh steps in the IDE._ --- -## docs/IDE/index.md · new-section +## docs/Features/Packages/Linked Packages.md · new-section -## Requirements +## Packages that depend on other packages -### Microsoft Edge WebView2 Runtime +One package can depend on another. A package that implements interfaces from a base package, for example, needs that base package to be present in the consuming project. -The twinBASIC IDE requires the Microsoft Edge WebView2 Runtime to be installed on the development machine. Without it, opening certain project types (such as Standard EXE) can produce an "Unable to Load Library" error and a non-functional designer. +> [!NOTE] +> Dependencies between packages have limits. +> - Some dependent packages work only when the base package is **Linked** rather than **Embedded**. +> - An embedded dependency can still conflict when the consuming project also references a package that one of its own packages uses. +> - Linked packages carry no version information, so any change to a base package should remain backwards compatible. +> - When the base package changes, the dependent package must be updated and republished, so that its reference to the base package is refreshed. -Download the x86 (32-bit) Evergreen Standalone Installer from the [Microsoft Edge WebView2 download page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) and run it on the development machine. +_Source threads: 1397669015777968289-2025-07 · confidence: medium_ +_Date range: 2025-07-23_ +_Reviewer note: Source: twinpack-dev thread, July 2025 (build not stated in finding). Verify against current behaviour; the claim that linked packages have no version support may conflict with the version shown in the local/TWINSERV prompt described earlier on this page._ -> [!NOTE] -> This is a development machine requirement only. Executables compiled with twinBASIC do not require the WebView2 Runtime on end-user machines. - -### Extracting the distribution ZIP +--- -Before extracting the twinBASIC distribution ZIP, remove the Windows zone identifier ("Mark of the Web") from the file: right-click the ZIP in File Explorer, open **Properties**, and click **Unblock** if the button is present. Extracting without doing this can cause the IDE to treat the extracted files as untrusted, resulting in library-load failures. +## docs/Features/Packages/TWINPACK file format.md · after-remarks [DUPLICATE? -- see also thread 1368264887448371372] -Extract to a conventional location such as a subfolder of Downloads, Documents, or a dedicated tools directory. Extracting directly into a special shell folder (for example the user profile root `C:\Users\`) can produce a broken installation in which packages such as WinNativeFormsBase fail to load, even when the file counts appear correct. +> [!NOTE] +> `.twinproj` files produced by the twinBASIC IDE can legitimately contain a mix of CRLF and LF line endings within the same file, even without any third-party tooling involved. This is a known characteristic of the format and causes no functional issues. When tracking `.twinproj` files in git, configure them as binary to prevent git from normalizing line endings: +> +> ``` +> # .gitattributes +> *.twinproj binary +> *.twinpack binary +> ``` -_Source threads: 1111043339987468380 · confidence: medium_ -_Date range: 2023-05-24 to 2023-05-26_ -_Reviewer note: Target page docs/IDE/index.md is not in the page-index (package 'Core', symbol null has no page-index match). Placement here is a best-effort choice -- the IDE index page is the closest existing page covering the IDE as a whole. A dedicated installation/setup page may be a better long-term home. The WebView2 Runtime finding (high confidence, 2023-05-24--25) and the two ZIP-extraction findings (medium confidence, 2023-05-25--26) are merged into one section. Verify: (1) that the IDE still requires the 32-bit Evergreen installer rather than the Evergreen bootstrapper or a fixed-version installer; (2) whether the user-profile-root extraction issue still reproduces on current builds; (3) whether 'Unblock' behavior differs between Windows 10 and 11._ +_Source threads: 1397374588056371350 · confidence: medium_ +_Date range: 2025-07-23_ +_Reviewer note: TWINPACK File Format page is a binary-format spec. This git note may fit better in docs/Features/Packages/Linked Packages.md or a new version-control tips section. Reviewer should confirm the best placement._ --- -## docs/IDE/tbForm.md · after-remarks +## docs/Features/Packages/TWINPACK file format.md · after-remarks [DUPLICATE? -- see also thread 1397374588056371350] > [!NOTE] -> If the form designer fails to finish loading and stays at a fixed percentage (for example, 15%) without progressing, the most likely cause is that the IDE toolbar panel has been closed. Go to **Window** > **Panel Layouts** > **Default Built-in Layout** to restore the default panel arrangement, then restart the IDE. An anti-virus or firewall blocking the WebSocket connection between the IDE and the compiler can also prevent the designer from loading. This issue was resolved in BETA 449. +> The `.twinproj` file is a binary container format and is not a plain-text file. A text editor may display some readable text within it, but the file is not suitable for direct editing or syntax highlighting. To work with project source files in an external editor, use the [Import/Export Tool](Import-Export-Tool) to unpack the project to a directory of `.twin` (and `.bas`, `.cls`) plain-text source files. After editing those files externally, reopen the original `.twinproj` file in twinBASIC to continue work. -_Source threads: 1205932211954786425 · confidence: high_ -_Date range: 2024-02-10 to 2024-02-11_ +_Source threads: 1368264887448371372 · confidence: high_ +_Date range: 2025-05-06_ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1201636475306000536, 1210853350707429406, 1301076697642438656] -
-I get "Internal Error 33330: IDE was unable to read builtin package file data" when adding a package reference. +> [!NOTE] +> When a project contains two packages that have a dependency relationship --- package B depends on package A --- and the version of package A in the project does not match the version that package B was compiled against, the compiler reports 'unrecognized symbol' errors on symbols from package A when they appear inside package B. The error message gives no indication that a version conflict is the cause. If unexpected 'unrecognized symbol' errors appear on symbols from a package that other packages depend on, check that all inter-dependent packages are at mutually compatible versions and update the dependent package (B) to a version compiled against the currently installed version of package A. -This error occurs when twinBASIC is extracted on top of a previous installation rather than into a clean folder. The IDE cannot read its built-in package data if stale files from an older release are present alongside the new ones. +_Source threads: 1171155850832908318 · confidence: medium_ +_Date range: 2023-11-06 to 2023-11-07_ +_Reviewer note: This page is in docs/Features/Packages/ and is not in page-index.json (which covers only Reference docs). The finding has package='Core', symbol=null -- no Reference page covers package version compatibility errors. Placement here is an editorial judgement; a reviewer may prefer a different Features page or a new troubleshooting section. Also verify whether the IDE now emits a clearer error message for this case._ -To resolve it, extract the new release into an empty folder. Delete the contents of the old installation folder first, or use a different folder entirely. See [How do I install twinBASIC](#installation) for the recommended extraction steps. +--- -The error affects all built-in package references and `.twinpack` files, not only the one that was being added when it first appeared. +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1210853350707429406, 1301076697642438656] -
+> [!NOTE] +> In IDE versions before BETA 444, removing a package reference in the Settings dialog could leave the IDE in a locked state. The secondary confirmation prompt ("also remove the imported package from the filesystem") appeared behind the Settings modal; after responding to it the modal was not restored, and the IDE behaved as though the dialog was still open. In some cases the package contents were deleted from the project file while the reference entry remained, producing a project file that could not be opened normally. Projects that reach this corrupt state can be recovered by opening the project in safe mode, which makes the Settings dialog accessible so the orphaned reference can be removed. This issue was fixed in BETA 444. -_Source threads: 1241295009110687764 · confidence: high_ -_Date range: 2024-05-18_ -_Reviewer note: This finding maps to the Installation section of docs/Miscellaneous/FAQs.md (not in the page-index). Insert the new
block after the existing #installation entry (around line 188) and before #installation-size. The existing installation entry already mentions extracting to an empty folder but only vaguely; this new entry gives the specific error number (33330) and symptom (package load failure) so users can find it by searching. Confirmed by WaynePhillipsEA in the Discord thread._ +_Source threads: 1201636475306000536 · confidence: high_ +_Date range: 2024-01-29 to 2024-02-10_ +_Reviewer note: Target page is in Features/Packages (not in the page-index), chosen as the best fit because it explicitly covers the remove-package workflow. The safe-mode recovery path should be verified against current IDE capabilities --- older IDE versions had a safe mode option; confirm it still exists or adjust wording accordingly._ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1201636475306000536, 1301076697642438656] -Installing the specific updates KB4490628 and KB4516065 (with a reboot between them) has been reported to resolve this requirement on Windows 7 SP1. After applying all available updates, the entry-point error should no longer appear; if WebView2 is absent or at the wrong version for Windows 7, the next error on launch will be about WebView2 instead --- see [How do I install WebView2 on Windows 7 or Windows 8.1?](#webview2-windows7) for the correct procedure. +> [!NOTE] +> Unticking a package reference in Project Settings disables the package --- the IDE removes the reference and makes the package's symbols unavailable to the compiler --- but does not immediately delete the locally cached package files. A second dialog then appears offering two choices: **Remove it** deletes the cached files from disk, and **Leave it** keeps them. Choosing **Leave it** lets you re-enable the same version later without re-downloading. To upgrade to a newer version, you must choose **Remove it** so the cache is cleared before importing the updated package from the package server. -_Source threads: 1332299294006448170 · confidence: high_ -_Date range: 2025-01-24 to 2025-01-26_ -_Reviewer note: This text should be appended to the body of the existing 'invalid-entry-point' FAQ entry (around line 159-163 of FAQs.md), which currently ends after advising the user to run Windows Update. The KB numbers (KB4490628, KB4516065) came from a community report and should be verified before publication._ +_Source threads: 1210853350707429406 · confidence: high_ +_Date range: 2024-02-24 to 2024-02-27_ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] +## docs/Features/Packages/Updating a package.md · after-remarks [DUPLICATE? -- see also thread 1171155850832908318, 1201636475306000536, 1210853350707429406] -After importing, the recommended first step is to save the project in the same directory as the original VBP so that relative paths to referenced files remain intact, then run or debug the project to identify any remaining issues. +> [!NOTE] +> The new-version notification can be turned off in the IDE settings. One-click automatic update to the latest package version is planned for a future release; the current workflow requires removing the old package and re-importing the newer one as described below. -_Source threads: 1083431012417155154 · confidence: medium_ -_Date range: 2023-05-27_ -_Reviewer note: This workflow tip belongs in the 'How do I import my VB6 project into twinBASIC?' FAQ entry, appended after the existing content. The surrounding FAQ entry already notes that resource files are imported automatically when selecting the .vbp; this sentence adds the save-in-place step. Verify that the "save in same directory" advice is still current best practice._ +_Source threads: 1301076697642438656 · confidence: high_ +_Date range: 2024-10-30 to 2024-11-18_ +_Reviewer note: Confirm the exact settings path in the IDE where the notification can be disabled, and verify whether the opt-out affects all packages or per-package._ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] +## docs/Features/Packages/Updating a package.md · after-remarks + +A package author can automate the copy. In the package's own project, a `Public Sub` with the [`[RunAfterBuild]`](../../tB/Core/Attributes#runafterbuild) attribute copies the build output into `%APPDATA%\twinBASIC\packages`, so each build replaces the shared file. Other projects pick up the new build after the compiler restarts. + +```tb +[RunAfterBuild] +Public Sub PublishToLinkedFolder() + ' Copy the built .twinpack into the linked packages folder. + FileCopy App.LastBuildPath, Environ$("APPDATA") & "\twinBASIC\packages\MyPackage.twinpack" +End Sub +``` > [!NOTE] -> To convert an imported VB6 project into a twinBASIC package, open the `.vbp` file in twinBASIC as usual, then open the project Settings and change the project type from **Standard EXE** to **Package**. After saving the settings the compiler restarts and the Package Manager pane becomes visible in the IDE. +> [**App.LastBuildPath**](../../tB/Packages/AppGlobalClassObject/_App/LastBuildPath) is not filled in for a package project, so the file name in the destination path has to be written out in the code. Leaving the architecture out of the output file name helps, since it does not apply to a package. **RunAfterBuild** is meant for package authors: it cannot download or update a third-party package from the package server. -_Source threads: 1180541541945712700 · confidence: high_ -_Date range: 2023-12-02_ -_Reviewer note: Insert near the existing `#vb6-import` FAQ entry. Confirmed by WaynePhillipsEA in the thread._ +_Source threads: 1554941508921073855 · confidence: medium_ +_Date range: 2026-09-30 to 2026-10-01_ +_Reviewer note: Example code is a sketch: the source thread did not give code. Verify that FileCopy from a RunAfterBuild Sub works as shown and that the source path (the build output, since LastBuildPath is empty) is right. Check the attribute link target. Source thread dated 2026-09-30 to 2026-10-01._ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1330341380496560128, 1331867785944371301, 1396409493981626409] +## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1359918618573275286, 1503959771949301922] > [!NOTE] -> Windows 95 and Windows NT 4.0 are not supported targets. The runtime startup routine calls APIs absent from those platforms---for example, `GetVolumePathNameW` is not part of the Windows 95 / NT 4.0 kernel32---so produced binaries will fail to launch. The minimum version currently confirmed to work is Windows XP; the intended minimum target is Windows 2000. +> Packages installed into a project are opened as read-only in the code editor and cannot be edited directly, similar to a referenced type library in VB6. To customise package code, export and import the source into regular project code, or rebuild the package from its `.twinproj` source file if available. If a symbol from a package is copied into the project under the same name, the project's own version takes priority at call sites --- there is no conflict, and the project copy is called unless the call site explicitly qualifies with the package name. -_Source threads: 1181674508449480815 · confidence: high_ -_Date range: 2023-12-05 to 2023-12-08_ -_Reviewer note: Insert into or alongside the existing `#runtime-requirements` FAQ entry, which already states 'minimum supported Windows version is Windows XP'. The WinMain entry-point override (for bypassing the startup routine) was described in the thread as unsupported for general use and intended only for kernel-mode driver targets --- omitted here to avoid encouraging an unsupported path. Verify that Windows 2000 status has not changed since the thread date._ +_Source threads: 1285758420326027407 · confidence: medium_ +_Date range: 2024-09-18_ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1331867785944371301, 1396409493981626409] +## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1285758420326027407, 1503959771949301922] > [!NOTE] -> Individual `.frm` form files cannot be added to an existing twinBASIC project after the initial import. To incorporate additional VB6 forms, start a new project that includes all required forms from the beginning. `.bas` module files and `.cls` class files can be imported individually into an existing project at any time. +> All twinBASIC packages --- including the built-in runtime packages --- are currently distributed as source code, not pre-compiled binaries. When a package is added to a project, it is compiled once at load time and its contents appear as read-only in the project explorer; the source cannot be edited from within the consuming project. A package does not need to be self-contained or independently compilable: it may contain only interfaces, API declarations, or abstract classes that require the consuming project to supply implementations. +> +> Building a package via **File > Build** (or the toolbar) produces a `.twinpack` file locally but does not upload anything. Uploading to the package server requires the **Package Publishing** panel, where visibility (public or private) can also be set. Compiled packages --- distributing binaries without exposing source --- are planned for a future release. -_Source threads: 1330341380496560128 · confidence: medium_ -_Date range: 2025-01-19_ -_Reviewer note: This note should be added inside the `vb6-import` `
` block in the FAQ, after the existing NOTE callout about .bas/.cls import. Verify that the one-way form conversion restriction still applies in current builds._ +_Source threads: 1359918618573275286 · confidence: high_ +_Date range: 2025-04-10 to 2025-04-15_ +_Reviewer note: The page already mentions that TWINPACK files contain full source code in the last paragraph. The new note expands on the read-only behavior in the consuming project and the publishing workflow. Merge with or replace that existing paragraph to avoid duplication._ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1396409493981626409] +## docs/Features/Packages/index.md · after-remarks [DUPLICATE? -- see also thread 1285758420326027407, 1359918618573275286] > [!NOTE] -> Windows XP is not supported. The twinBASIC IDE requires Windows 7 or later because the IDE depends on WebView2, which does not run on Windows XP. Both 32-bit and 64-bit editions of Windows 7 are supported (fully updated). See the entry above for the update requirement on Windows 7. +> twinBASIC does not yet support referencing `.bas` or `.cls` source files stored outside the `.twinproj` project file in a way that keeps them linked rather than copied. The recommended approach for sharing modules across projects is to package them into a private twinBASIC package and reference that package from each consuming project. Note that module files in a referenced package are read-only within the consuming project, and changes to the package source are not reflected until the package is recompiled and re-imported. -_Source threads: 1331867785944371301 · confidence: high_ -_Date range: 2025-01-23_ -_Reviewer note: Insert inside or directly after the `system-requirements` `
` block, as a clarification that Windows XP is out of scope. The block already states 'Windows 7 through Windows 11' but does not explicitly call out XP. Note that the `runtime-requirements` entry separately states that *compiled EXEs* support Windows XP as a minimum --- this addition is only about the IDE._ +_Source threads: 1503959771949301922 · confidence: high_ +_Date range: 2026-05-13 to 2026-05-18_ --- -## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301] +## docs/Features/Packages/index.md · after-remarks > [!NOTE] -> twinBASIC produces native machine code comparable to a C++ or VB6 native-compilation build. Tools designed to decompile VB6 p-code or .NET IL --- such as VB6 Decompiler or .NET reverse-engineering utilities --- cannot be applied to twinBASIC executables or DLLs. Analyzing a twinBASIC binary requires native-code tools such as x64dbg or IDA. +> A package adds no DLL dependency to the built executable. Its components are source code, and the compiler builds them into the executable together with the project's own code. Only the components the project actually uses are included, so referencing a large package, such as one with tens of thousands of Windows API and COM declarations, does not enlarge the executable. -_Source threads: 1396409493981626409 · confidence: high_ -_Date range: 2025-07-20 to 2025-07-27_ -_Reviewer note: FAQs.md is not in the page-index. The note fits best in the 'Using twinBASIC' section, potentially as a new FAQ entry about reverse engineering / decompilation. Reviewer should decide whether to add a new
entry or fold this into the existing backwards-compatibility FAQ._ +_Source threads: 1536079009673257010 · confidence: medium_ +_Date range: 2026-08-10_ +_Reviewer note: The existing page says a referencing project imports the whole package into its file system; the added text is about the compiled output, not the project file. Confirm with the compiler team that unused package symbols are always excluded from the executable, and that this holds for every package type._ --- -## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1202802099356966962, 1332299294006448170, 1447957791770677382] - -
-The IDE reports "IDE was unable to read builtin package file data" (Internal Error 33330). - -This error occurs when a new twinBASIC ZIP is extracted into a folder that already contains an older installation. A slight change in the folder structure between releases means the mixed old and new files cause the error. - -To fix it: delete the old twinBASIC folder completely, then extract the new ZIP into a fresh, empty directory. If the error persists after a clean extraction, check whether your antivirus software has quarantined any of the twinBASIC files --- see [The IDE reports missing files](#missing-files) for steps to resolve that. +## docs/Features/Packages/index.md · after-remarks -
+> [!NOTE] +> A referencing project treats the imported package as part of its own source code, so scope restrictions inside a package have limited effect. **Friend** members of the package's classes appear in the project's IntelliSense, and the source stays readable. The restrictions apply fully only when the package contents are compiled into an ActiveX DLL or a control. To hide internals and source from consumers, build the component as an ActiveX DLL and reference that instead. -_Source threads: 1169313912823496866 · confidence: high_ -_Date range: 2023-11-01 to 2023-11-10_ -_Reviewer note: This entry belongs in the Installation section of FAQs.md, ideally placed after the existing 'missing-files' entry (around line 177) since it references that entry. The existing 'installation' FAQ (line 180) already advises extracting to an empty folder but does not describe this specific error code. Confirm the exact placement within the Installation section._ +_Source threads: 1516861763042279534 · confidence: high_ +_Date range: 2026-06-17 to 2026-10-01_ +_Reviewer note: Partly overlaps Friend.md, which already says a package's Friend members are usable; this adds the ActiveX DLL workaround and the IntelliSense point._ --- -## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1332299294006448170, 1447957791770677382] +## docs/Features/Packages/index.md · after-remarks -
-My VB6 import stalls partway through on a large project. +> [!NOTE] +> twinBASIC has no language-level namespaces. The symbols of every referenced package share a single global namespace, so a very large package (such as a complete Windows API declaration package) adds all of its names to IntelliSense and can collide with names from other packages. For the same reason, splitting a large package into several smaller ones is difficult when the parts overlap. To limit the clutter, group related members as static members of a nested class, and use a **With** block to shorten repeated access. -When converting a large VB6 project, the import progress bar may appear to complete, the Project Explorer fills briefly, then clears---after which a second progress bar appears but never advances. The IDE remains open but unresponsive. This is a symptom of the compiler process running out of virtual address space. +_Source threads: 1397669015777968289-2025-12 · confidence: low_ +_Date range: 2025-12-23 to 2025-12-24_ +_Reviewer note: No Core package page exists, so placed on the Packages index. The existing page says packages are grouped 'into their own namespace', which may conflict with this note; verify the wording against current behaviour (see Library Symbols page). Namespaces reportedly planned after v1 per second-hand relay of Wayne's remarks; not stated in the draft. Thread source is December 2025._ -On 64-bit Windows, a standard Win32 process has a 2 GB virtual address space limit. Known memory leaks in the compiler (present during the BETA period) can push memory use above 1.8 GB for large projects, which is enough to cause a crash at that limit. +--- -The workaround is to enable the **Large Address Aware (LAA)** flag, which raises the virtual address limit to 4 GB for a 32-bit process running on 64-bit Windows. Enable it in Project Settings before retrying the import. +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1117993757170749490, 1261148532157714432, 1388075026892455987] > [!NOTE] -> This workaround addresses the symptom. The underlying memory leaks are a known issue being resolved in later releases. Once the leaks are fixed, LAA will no longer be required for import. - -
+> Automatic registration occurs immediately after every build. The **Project: Register DLL after build** option in the project settings controls whether this registration step runs. Disabling it is useful when the binary needs to be copied to a different location before registering, for example during deployment to a test machine. -_Source threads: 1202802099356966962 · confidence: high_ -_Date range: 2024-02-02 to 2024-02-07_ -_Reviewer note: Insertion point: after the closing
of the 'vb6-import' entry (line 246 in the current file) and before the 'unrecognized-variables' entry. The LAA setting referenced here appears in docs/IDE/Project Settings.md at the 'Large Address Aware (LAA)' heading. Verify the current compiler memory situation -- the thread is from February 2024 (BETA) and the leaks may have been partially addressed since then; if so, soften the NOTE or remove it._ +_Source threads: 1073568869106597938 · confidence: high_ +_Date range: 2023-02-10_ +_Reviewer note: The existing ActiveX-Registration.md page already documents the 'Register DLL after build' option and says registration is optional. The Discord finding (from early 2023) states the option did not exist at that time. Verify whether this NOTE adds anything not already covered by the existing page content, and adjust or drop accordingly._ --- -## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1202802099356966962, 1447957791770677382] +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1261148532157714432, 1388075026892455987] -
-How do I install WebView2 on Windows 7 or Windows 8.1? +> [!NOTE] +> If a host application (such as Microsoft Access) crashes at its own compile step after a reference to a twinBASIC COM DLL is added, the tB code is not executing at that point. The host is parsing the DLL's type library during its own project compilation. The type library contains an entry the host cannot parse. To diagnose the problem, progressively remove public exports from the tB project until the crash stops, then identify which declaration is incompatible. Also verify that the host and the DLL share the same processor architecture: a 32-bit host cannot load a 64-bit DLL, even when the reference is successfully added. -Microsoft dropped Windows 7 and Windows 8.1 support from the WebView2 evergreen installer after version 109. Running any newer evergreen installer on those systems produces an "installer failed to start" error. Version 109 (109.0.1518.140) is the last release to support these operating systems and must be installed instead. +_Source threads: 1117993757170749490 · confidence: high_ +_Date range: 2023-06-13 to 2023-06-15_ +_Reviewer note: The page-index does not include docs/Features/ pages. Reviewer should confirm placement in docs/Features/Project-Configuration/ActiveX-Registration.md or consider docs/Features/Project-Configuration/Project-Types.md. The finding is specifically about COM ActiveX DLL type-library compatibility with VBA host applications._ -Because version 109 is no longer distributed as a standalone download, the procedure to install it is: +--- -1. Open the [Microsoft Update Catalog](https://www.catalog.update.microsoft.com/Search.aspx?q=webview2%20109) and search for **webview2 109**. -2. Download the package for your architecture. -3. The downloaded file cannot be run directly --- it must first be extracted using a tool such as 7-Zip. -4. After extraction, locate the file named `MicrosoftEdge_X64_109.0.1518.140.exe.{GUID}` (the exact GUID varies). Rename it by removing the `.{GUID}` suffix so that the name ends in `.exe`. -5. Running the renamed executable without arguments installs the full Edge browser. To install only the WebView2 runtime that twinBASIC requires, pass the following flags: +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1117993757170749490, 1388075026892455987] - ``` - MicrosoftEdge_X64_109.0.1518.140.exe --msedgewebview --system-level - ``` +> [!IMPORTANT] +> When a twinBASIC ActiveX DLL is built or registered by a non-elevated process, its registration goes to `HKEY_CURRENT_USER` by default. If VB6 (or any other host) is then launched with elevated (administrator) privileges, it cannot see that per-user registration and reports a "Class not registered" error. To avoid this, set the project's registration target to `HKEY_LOCAL_MACHINE` in the project settings, or build and register the DLL from an elevated instance of twinBASIC. Manually registering the DLL from an elevated command prompt alone is not sufficient if the project setting still points to `HKEY_CURRENT_USER` --- the per-user registration is written again on the next build. -After the runtime installs, relaunch the twinBASIC IDE. If the IDE still reports a missing WebView2 installation, verify that the correct architecture was downloaded and that the `--system-level` flag was included. +_Source threads: 1261148532157714432 · confidence: high_ +_Date range: 2024-07-12_ -
+--- -_Source threads: 1332299294006448170 · confidence: high_ -_Date range: 2025-01-26 to 2025-03-07_ -_Reviewer note: This new FAQ entry should be placed in the Installation section, immediately after the existing 'system-requirements' entry (around line 156 of FAQs.md). The x64 filename in the extract step should be verified against the actual catalog download --- the 32-bit variant uses the x86 filename instead._ +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks [DUPLICATE? -- see also thread 1073568869106597938, 1117993757170749490, 1261148532157714432] ---- +> [!NOTE] +> When consuming a twinBASIC ActiveX DLL from Microsoft Access or Excel VBA, declare the object using the fully qualified form `ProjectName.ClassName`, not `ClassName` alone. Using the class name without the project prefix produces the error "Expected user-defined type, not project". For example, if the project is named `MyLib` and the class is `MyClass`, the declaration in the host VBA module must read: +> +> ```vb +> Dim obj As New MyLib.MyClass +> ``` +> +> This is standard COM early-binding behavior. -## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1202802099356966962, 1332299294006448170] +> [!NOTE] +> twinBASIC does not support interactive cross-project debugging of an ActiveX DLL from a host application such as Access or Excel. Breakpoints and step-through from the host are not available. The recommended approach is to copy the DLL classes into a Standard EXE project and debug there. For runtime diagnostics in the compiled DLL, use `Debug.TracePrint` with the trace logger rather than manual log-file writes. -
-Double-clicking a .twinproj file in Explorer does not open twinBASIC. +_Source threads: 1388075026892455987 · confidence: high_ +_Date range: 2025-06-27_ -Each time `twinbasic.exe` is launched, it registers itself in the Windows registry as the application associated with `.twinproj` files. Because twinBASIC has no installer, if the IDE executable is deleted or moved (for example when removing an older BETA release) before the replacement version has been launched at least once, the `.twinproj` file association points to a non-existent path. Double-clicking a `.twinproj` file in Explorer will then fail to open twinBASIC. +--- -The fix is to launch the new `twinbasic.exe` directly from its folder before removing the old version, so that the registry entry is updated to the new path. +## docs/Features/Project-Configuration/ActiveX-Registration.md · after-remarks -
+> [!NOTE] +> As in VB6, building an ActiveX DLL project generates new GUIDs, unregisters the previous build and registers the new one. If the new DLL cannot start, its registration code never runs, and the component then stays unregistered. -_Source threads: 1447957791770677382 · confidence: high_ -_Date range: 2025-12-09 to 2025-12-10_ -_Reviewer note: This entry belongs in the Installation section of the FAQs page, after the existing entries for missing files and the installation procedure. The page uses
/ HTML elements; match that formatting exactly._ +_Source threads: 1550087924027236442 · confidence: medium_ +_Date range: 2026-09-17_ +_Reviewer note: Based on one experienced user's statement, not a maintainer's. Verify against the compiler, in particular whether the GUIDs change on every build or only without a fixed-GUID or compatibility setting, and whether the old build is unregistered first. Insert under Build-Time Registration._ --- -## docs/Miscellaneous/FAQs.md · new-section +## docs/Features/Project-Configuration/Compiler-Options.md · after-remarks -
-The IDE window opens off-screen after I disconnect a monitor. How do I get it back? +## Stack Reserve Size -If the IDE was last used on an extra monitor that is no longer connected, its window can open outside the visible area of the screen. Right-click the IDE's title bar or its taskbar button and choose **Move**, then use the arrow keys or the mouse to bring the window back onto the screen. +The twinBASIC non-optimizing compiler allocates larger stack frames than VB6, so recursive procedures exhaust the call stack much sooner than an equivalent VB6 project. The project setting **Build Stack Reserve Size** (default 1 MB) controls the stack reserve for compiled EXEs. Increasing it to 4 MB or 16 MB allows substantially deeper recursion; for 64-bit processes the additional reserve is only committed as needed and carries no practical memory cost at rest. -
+> [!NOTE] +> Stack frame sizes are expected to decrease significantly once LLVM-based code generation is fully operational. -_Source threads: 1363863830437822484 · confidence: low_ -_Date range: 2025-04-21 to 2026-08-21_ -_Reviewer note: Add under 'Using twinBASIC' in the FAQs. Low confidence: a user reported it still happening in August 2026, so it may be fixed or not; the suggestion of a default window.left=0 setting was not confirmed to exist. Verify the Move steps on Windows 10 and 11._ +_Source threads: 1406596075032412234 · confidence: high_ +_Date range: 2025-08-17_ +_Reviewer note: The Compiler Options page currently does not mention stack size at all. Verify the exact setting name ('Build Stack Reserve Size') against the twinBASIC IDE project settings UI before publishing._ --- -## docs/Miscellaneous/FAQs.md · after-remarks +## docs/Features/Project-Configuration/Compiler-Options.md · new-section -If the extraction fails or a file cannot be replaced, a `twinBASIC_win64.exe` process may still be running from the previous session. It is the LLVM cache process, which keeps running after the IDE closes. End it in Task Manager, or see [General LLVM options](LLVM/Getting-Started#general-llvm-options) to turn the behaviour off. +## LLVM Optimization (Professional) -_Source threads: 1553387151842877491 · confidence: medium_ -_Date range: 2026-09-26 to 2026-09-27_ -_Reviewer note: Place at the end of the 'How do I install twinBASIC?' FAQ entry, after the sentence about not overwriting a previous version. The thread's community advice (extract into a new folder or empty the old one, because overwriting old betas has caused hard-to-trace bugs) is already on that page. Verify the relative link resolves from /FAQ._ +The Professional tier adds an LLVM-based optimization pass on top of the standard code generator. The gains are workload-dependent: ---- +- **Where LLVM helps most:** tight numeric or algorithmic loops in standard modules that avoid strings, Variants, classes, interfaces, and Windows API calls in the hot path. In those cases the optimizer can inline aggressively, unroll loops, fold constants, and remove redundant checks, giving large multipliers over the baseline. +- **Where LLVM helps less:** code whose hot path calls runtime string functions (`Mid$`, `InStr`, `Left$`, etc.), passes strings `ByVal`, or dispatches through COM objects. The bottleneck in those cases is the runtime or the API, not the compiled glue code, so gains are small. -## docs/Reference/Assert/index.md · after-remarks +As of late 2025 the LLVM backend was partially implemented and 32-bit only; full 64-bit coverage is planned for v1.0. -> [!NOTE] -> When a subroutine is annotated with `[TestCase]`, the twinBASIC IDE hides the inline **Run** action that normally appears above the `Sub` declaration. To run a parameterised test interactively, create a wrapper subroutine without `[TestCase]` that calls the test with the desired arguments. +### Performance relative to VBA and VB6 -_Source threads: 1194682995538407434 · confidence: medium_ -_Date range: 2024-01-10_ -_Reviewer note: Verify the [TestCase] inline-run behaviour against the current IDE release; this was observed in early 2024 and may have changed. Also check whether a Test Explorer panel has been added to the IDE since then, as the finding notes none existed at that time._ +Without LLVM, twinBASIC compiled code is faster than VBA and VB6 compiled to p-code. Results against VB6 native-compiled and speed-optimized executables are mixed and workload-dependent --- computation-heavy code (math, array operations) has been measured 25--30% faster in twinBASIC by independent users, while simple micro-benchmarks with default settings sometimes show VB6 native ahead. The twinBASIC maintainer has confirmed that the standard benchmark suite runs faster in a twinBASIC EXE than in VBA. + +_Source threads: 1392249968852471899 · confidence: high_ +_Date range: 2025-06-30 to 2025-12-01_ +_Reviewer note: LLVM 64-bit status and v1.0 timeline should be verified against the latest roadmap before publishing, as the thread data is from late 2025 and the situation may have changed._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1113070283004313701, 1380597011354484738, 1415716215204085864, 1220612763873972314] > [!NOTE] -> twinBASIC enables integer overflow checks by default (`True`), which differs from compiled VB6 executables. VB6 raises overflow errors only when running in the IDE; compiled VB6 executables do not check for integer overflow at runtime. Apply `[IntegerOverflowChecks(False)]` to a function to suppress overflow checking for that function and match compiled VB6 behavior. +> The `Console` class added to a new Console Application project is a minimal stub. It does not include a `Write` method by default. To use `Console.Write`, add the method manually by editing `Console.twin` in the project. -_Source threads: 1120535095279886416 · confidence: high_ -_Date range: 2023-10-22_ -_Reviewer note: This addition targets the existing `IntegerOverflowChecks` section (line 413) in docs/Reference/Attributes.md. The note should be inserted after the one-line description "Disables integer overflow checks. Used on performance-critical routines. The default value is **True**." Verify that the VB6 compiled-mode behavior is still accurate for current twinBASIC releases._ +_Source threads: 1109285180759347321 · confidence: high_ +_Date range: 2023-05-20 to 2023-05-23_ +_Reviewer note: Verify the current state of the Console.twin stub shipped with new Console Application projects -- the set of methods it provides may have changed since this Discord report._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1380597011354484738, 1415716215204085864, 1220612763873972314] > [!NOTE] -> The `[Description()]` value must be a single-line string. If the value contains newline characters (for example, produced by concatenating `vbCrLf` into the string), the IDE accepts the declaration without a compile error, but hovering over the member or invoking autocomplete on it produces a 'bad JSON received' error. The LSP response embeds the description text directly inside a JSON string, and a literal newline in that string breaks JSON parsing. Keep `[Description()]` values to a single line with no embedded newline characters. +> +> Prior to twinBASIC BETA 334, calling `SetConsoleMode` (or any function that calls it internally, such as `Console.ReadChar`) from code running inside the IDE returned 0 and set `Err.LastDllError` to 5 (`ERROR_ACCESS_DENIED`). The same code ran correctly in a compiled `.exe`. This discrepancy was a known IDE limitation and has been resolved as of BETA 334. -_Source threads: 1172641655631908884 · confidence: medium_ -_Date range: 2023-11-10_ -_Reviewer note: Verify whether this limitation has been fixed in recent IDE builds. The thread is from November 2023 and the behaviour may have changed. Anchor this note directly after the Description attribute's prose (after the 'helpstring attribute in the type library' sentence, before the next ## heading)._ +_Source threads: 1113070283004313701 · confidence: high_ +_Date range: 2023-05-30 to 2023-06-15_ +_Reviewer note: Confirm that BETA 334 is the correct fix version and that Console.ReadChar is the right example of an affected built-in function. The fix was described by WaynePhillipsEA on Discord._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1415716215204085864, 1220612763873972314] > [!NOTE] -> When typing `[CoClassId(...)]`, IntelliSense offers to auto-generate a random GUID --- the same prompt available for `[InterfaceId(...)]`. This behavior was introduced in BETA 438; earlier versions required the GUID to be generated and pasted manually. +> A **Standard EXE** that uses a `Sub Main` entry point instead of a startup form is not a Console App. The **Is Console App** setting in Project Settings is what makes a project a console application --- not the presence of a `Main` sub. To create a Standard EXE that starts from `Main` without a default form: start from the Standard EXE template, add a module (which provides the `Main` sub), then delete the default form. Starting from the Console App template and removing its console-specific code leaves the **Is Console App** flag set, which causes the Debug Console window to appear whenever a form editor is opened. -_Source threads: 1202939463630594079 · confidence: high_ -_Date range: 2024-02-02 to 2024-02-06_ -_Reviewer note: Insert this note within the `## CoClassId` section of Attributes.md, after the existing syntax/applicability description. Verify against the current IDE build that the GUID auto-generation prompt is still present for both [CoClassId] and [InterfaceId]._ +_Source threads: 1380597011354484738 · confidence: high_ +_Date range: 2025-06-06_ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -> [!NOTE] -> `[IgnoreWarnings]` applies only to diagnostic codes in the **TB0000--TB4999** range (warnings). Compilation errors are all reported under the single code **TB5000** and cannot be suppressed. Unique error IDs in the TB5000+ range are planned for a future release to allow associated help pages and easier searching. +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1380597011354484738, 1220612763873972314] -_Source threads: 1212815197501395054 · confidence: high_ -_Date range: 2024-02-29_ -_Reviewer note: Insert this NOTE immediately after the IgnoreWarnings syntax line (line 411 in Attributes.md), before the next section heading. Verify current status of unique TB5000+ IDs against the latest twinBASIC build notes._ +## Startup object ---- +A standard executable project requires one of two startup configurations, set in the project settings: -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +- **Sub Main** --- a `Public Sub Main()` procedure in a module. The IDE calls this procedure when the executable starts. Use this for non-GUI applications or when startup logic needs to run before any form is displayed. +- **Startup Form** --- a form designated as the startup object. The IDE displays that form when the executable starts, and the application ends when all forms are closed. -> [!NOTE] -> `[IntegerOverflowChecks(False)]` does not suppress overflow errors when the overflowing expression contains a division operation (e.g. `(6 + 52 / 4) * &H9E3779B9`). Division produces a `Double` intermediate, so the overflow is detected by the floating-point unit rather than the integer overflow checker. Apply `[FloatingPointErrorChecks(False)]` alongside `[IntegerOverflowChecks(False)]` to suppress it. Before BETA 473, `[FloatingPointErrorChecks(False)]` had no effect when all operands were compile-time constants; moving at least one operand into a variable was the workaround. This constant-folding limitation was resolved in BETA 473. +If neither is configured the project has no valid entry point and cannot run. -_Source threads: 1218599950359855186 · confidence: high_ -_Date range: 2024-03-16 to 2024-03-18_ -_Reviewer note: Insert immediately after the existing `IntegerOverflowChecks` description paragraph (after line 420 of Attributes.md). Verify the BETA 473 version number against the release notes._ +_Source threads: 1415716215204085864 · confidence: medium_ +_Date range: 2025-09-11_ +_Reviewer note: Basic project setup fact; verify exact UI label and setting location in the current IDE. The Project-Types.md page focuses on advanced project types; a general Startup Object section may fit better in Project-Configuration/index.md or a dedicated page._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -> [!NOTE] -> Applying `[IntegerOverflowChecks(False), FloatingPointErrorChecks(False)]` to a procedure does not necessarily prevent overflow or floating-point errors from firing at runtime inside the IDE. However, when both attributes are present, the IDE's **Resume Next** button in the error popup works correctly and execution continues normally. Without them, the IDE can stop responding on the error popup, requiring the process to be terminated externally. The source of such an overflow may be a code generation issue in a neighboring line rather than the line the error reports, making isolation by reproducing the values alone unreliable. (The underlying FPU code generation edge case was fixed in BETA 520.) - -_Source threads: 1230934350959345735 · confidence: high_ -_Date range: 2024-04-20 to 2024-04-24_ -_Reviewer note: Insert this note after the existing one-line description under the `IntegerOverflowChecks` section (line 420 in Attributes.md), before the next `##` heading. The note refers to both IntegerOverflowChecks and FloatingPointErrorChecks used together._ +## docs/Features/Project-Configuration/Project-Types.md · after-remarks [DUPLICATE? -- see also thread 1109285180759347321, 1113070283004313701, 1380597011354484738, 1415716215204085864] ---- +### Control Panel Applets -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +A Standard DLL project can produce a classic Windows Control Panel applet (`.cpl`) by exporting a function named `CPlApplet`. Classic `.cpl` applets are still recognized and loaded by Windows 10 and Windows 11. -> [!NOTE] -> The description text is rendered as markdown in the IDE's IntelliSense tooltip. Embedding `vbCrLf` in the string literal does not produce a visible line break. To force a line break, end the line with a trailing backslash before the string concatenation, for example: -> -> ```tb -> [Description("Line 1\" & vbCrLf & _ -> "Line 2")] -> ``` +When the applet's dialog uses ComCtl6-style controls but is loaded by a host process that does not provide a ComCtl6 activation context manifest (which `control.exe` does not), the DLL must call `CreateActCtx` and `ActivateActCtx` before creating each ComCtl6 window, then `DeactivateActCtx` afterward. All windows created while the context is active inherit the ComCtl6 theme. Resources within the DLL are loaded using the DLL's own module handle rather than the host's. -_Source threads: 1252891072774803487 · confidence: high_ -_Date range: 2024-06-19 to 2024-06-22_ -_Reviewer note: Verify the exact syntax for line breaks in Description attribute strings. The example in the detail uses a backslash before the closing quote of each segment; confirm this is correct twinBASIC syntax and that the trailing-backslash trick actually works as described. This note belongs under the Description attribute entry (the #description anchor), not at the top of the file._ +_Source threads: 1220612763873972314 · confidence: medium_ +_Date range: 2024-03-22_ +_Reviewer note: This is a Features page, not a Reference page. Verify that the Standard DLL section of Project-Types.md is the right home, or whether a dedicated .cpl tutorial page would be more appropriate. The ComCtl6 activation context pattern applies generally but is described here in the .cpl context._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/Features/Project-Configuration/Project-Types.md · after-remarks > [!NOTE] -> The placement of `[TypeHint]` differs depending on what is being annotated. For a **parameter**, place the attribute on the line immediately before the parameter declaration inside the procedure signature. For a **Property Get or Function return value**, place the attribute on the line immediately before the entire procedure declaration --- not inline with the return type. Example: -> -> ```tb -> ' TypeHint on a return value: attribute goes above the procedure -> [TypeHint(ScaleModeConstants)] -> Property Get ScaleMode() As Integer -> ScaleMode = m_ScaleMode -> End Property -> -> ' TypeHint on a parameter: attribute goes above the parameter -> Property Let ScaleMode( -> [TypeHint(ScaleModeConstants)] ByVal Value As Integer) -> m_ScaleMode = Value -> End Property -> ``` +> A **Standard EXE** project does not generate a type library. Only project types that expose COM-visible types, such as ActiveX DLL and ActiveX Control projects, produce one. -_Source threads: 1333587859474747413 · confidence: high_ -_Date range: 2025-02-03_ -_Reviewer note: This addition should be inserted into the TypeHint section (around line 633-640) of docs/Reference/Attributes.md, after the 'Applicable to:' line. Verify the code example compiles correctly in twinBASIC --- the thread confirms the behaviour but the exact syntax should be checked against the Attributes page style._ +_Source threads: 927834661298798602-2023-02 · confidence: high_ +_Date range: 2023-02-07_ +_Reviewer note: Core/Standard EXE unmapped; Project-Types.md chosen as nearest page. Source: BETA 244 only says no type library is generated for Standard EXE; the second sentence is inferred and should be verified or removed. Page may need a dedicated Standard EXE heading for placement._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/Features/Standard-Library/Unicode-Support.md · after-remarks -> [!NOTE] -> Applying `[OleAutomation(False)]` to an `Interface` declaration causes `RegisterTypeLib()` to skip registering that interface in the Windows registry. This only affects ActiveX DLL output that is registered; in a Standard EXE project the attribute has no observable effect. twinBASIC defaults interfaces to `[OleAutomation(True)]`, which differs from traditional MIDL/MKTYPLIB compilers that default to `False`. When defining COM interfaces intended for vtable binding only (not via `IDispatch`), `[OleAutomation(False)]` keeps the type library entry without registering the interface. +## Unicode characters in string literals -_Source threads: 1352268312117252126 · confidence: high_ -_Date range: 2025-06-05_ -_Reviewer note: Insert after the existing OleAutomation section body (line ~461 in Attributes.md). The section currently states only that the attribute 'Controls whether this attribute is applied in the typelibrary' and 'is set to True by default' -- this note adds the RegisterTypeLib and ActiveX DLL context._ +Because `.twin` files store source code as full Unicode, pasting code from a source that uses Unicode curly apostrophes (U+2018 / U+2019, the typographic single-quote pair) instead of the plain ASCII apostrophe (U+0027) produces string literals that contain non-ASCII characters. WMI queries and SQL `WHERE` clauses that rely on ASCII apostrophes as delimiters fail at runtime with an 'Invalid query' error when the literal contains these characters. + +The fix is to replace the Unicode apostrophes with standard ASCII apostrophes (U+0027) in the source. VB6 silently substituted a backtick in the same situation; twinBASIC preserves the original Unicode character, making the problem visible only at runtime. + +_Source threads: 1233612072835616871 · confidence: high_ +_Date range: 2024-04-27_ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Debug Console.md · after-remarks > [!NOTE] -> Within a `[RunAfterBuild]` subroutine, `App.LastBuildPath` returns the full path to the just-produced executable or DLL. This makes it straightforward to automate post-build tasks such as code signing or file copying. +> The IDE execution log may include messages of the form **IDE internal error: Uncaught ReferenceError: *name* is not defined** or **IDE internal error: Uncaught TypeError: Cannot set properties of null**. These are internal diagnostics intended for the IDE developer, not indicators of a problem in user code. As long as the IDE does not halt or freeze, they can be ignored. +> +> In very large projects an internal overflow in the IDE's error-reporting path can cause the line number shown for a genuine runtime error to be incorrect. The actual failing line may differ significantly from the reported location. When a runtime error occurs and the reported line appears unrelated to the surrounding code, treat the line number as unreliable and inspect a broader area---particularly any recently changed code---to locate the real fault. -_Source threads: 1079642242429489262 · confidence: high_ -_Date range: 2023-02-27_ -_Reviewer note: Insert immediately after the existing RunAfterBuild section description (currently lines 582-589 of docs/Reference/Attributes.md). The existing text ends with the `App.LastBuildPath` mention but is informal and contains a typo ('Tthere's'); this NOTE supplements the corrected prose that should replace it._ +_Source threads: 1312778660079730690 · confidence: high_ +_Date range: 2024-12-01 to 2024-12-02_ +_Reviewer note: Content is based on WaynePhillipsEA's direct explanation in the Discord thread. No .twin source to verify against --- this is IDE operational behavior. Confirm that the execution log shown in the thread (timestamped [SHELL] output) surfaces in the Debug Console rather than a separate IDE log pane before placing this addition here._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Editor.md · after-remarks [DUPLICATE? -- see also thread 1313837849690378280] > [!NOTE] -> Support for `[Description]` on **Const** declarations was added in BETA 268. Prior builds accepted the syntax but the description did not appear in IntelliSense tooltips. +> When a method call is inserted by IntelliSense tab-completion (a snippet insertion), typing `)` or `]` after the insertion adds a second closing character instead of moving past the one already inserted. The skip-over mechanism --- which normally lets `)` advance the cursor past an auto-inserted `)` --- only activates when Monaco itself recorded the corresponding `(` keystroke. Tab-completed snippets do not trigger that recording, so the mechanism does not activate. This is a Monaco editor limitation. Use the arrow key to move past the auto-inserted character, or delete the extra one with **Backspace**. -_Source threads: 1082767362253668474 · confidence: high_ -_Date range: 2023-03-07 to 2023-03-12_ -_Reviewer note: Insert inside the existing Description section (currently lines 263-270 of docs/Reference/Attributes.md), after the applicability line that lists Const. Verify the BETA 268 version number against release notes if available._ +_Source threads: 1200413549567156324 · confidence: high_ +_Date range: 2024-01-26 to 2024-02-10_ +_Reviewer note: Confirmed by the twinBASIC developer (WaynePhillipsEA) as a Monaco limitation as of February 2024. Verify whether a fix has been shipped in a later release before publishing._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Editor.md · after-remarks [DUPLICATE? -- see also thread 1200413549567156324] > [!NOTE] -> The twinBASIC default for `[OleAutomation]` is **True**, which is the opposite of the MIDL convention where `[oleautomation]` defaults to absent (equivalent to **False**). When importing a type library, interfaces that should not be OLE Automation compatible must be marked `[OleAutomation(False)]` explicitly, because omitting the attribute leaves them as **True** in twinBASIC. +> +> The code editor uses the Monaco editor engine, which may render certain fonts that are not strictly monospaced (such as MeowSans) with incorrect cursor or caret positioning. The same fonts render correctly in a native Windows text editor. To correct cursor alignment for such fonts, enable font ligatures in the IDE Options dialog (Tools > IDE Options..., Editor section, **Font Ligatures** toggle). This option was added in twinBASIC BETA builds from late December 2024 onwards. In older builds without this option, open `ide/main.js`, find the call `editor.updateOptions({fontFamily:n})`, and change it to `editor.updateOptions({fontFamily:n,fontLigatures:true})`. -_Source threads: 1083119492789448824 · confidence: high_ -_Date range: 2023-03-08 to 2023-03-12_ -_Reviewer note: Insert inside the existing OleAutomation section (currently lines 454-462 of docs/Reference/Attributes.md), after the existing 'This attribute is set to True by default' sentence._ +_Source threads: 1313837849690378280 · confidence: high_ +_Date range: 2024-12-04 to 2024-12-29_ +_Reviewer note: The Editor.md page currently lists editor options but does not include a Font Ligatures entry. Verify whether this option now appears in the IDE Options dialog and update the option list accordingly._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -The argument accepts a comma-separated list of enum type names to merge members from multiple enumerations into a single IntelliSense completion list. This is useful when a single numeric parameter draws values from two logically distinct enumerations that should not be merged into one physical **Enum**. +## docs/IDE/Editor.md · new-section -```tb -[TypeHint("SomeEnum, OtherEnum")] -Public Sub SetFlags(ByVal flags As Long) -``` +## Troubleshooting -IntelliSense will then show members from both `SomeEnum` and `OtherEnum` as candidates for the `flags` argument. +> [!NOTE] +> If code in a `.twin` file loses syntax colouring --- all text appears uncoloured --- the most common cause is that the background compiler process has crashed. The IDE continues to function but cannot produce colour information. To restore highlighting, use the **Restart the compiler** button on the toolbar. The Debug Console or the status bar at the bottom of the IDE may show additional information when this happens. If restarting the compiler does not help, some users have also resolved the issue by recreating the project file or moving it to a shorter file path. -_Source threads: 1103494252811526164 · confidence: high_ -_Date range: 2023-05-04 to 2023-05-12_ -_Reviewer note: Insert after the existing one-line description of TypeHint in the Attributes page (the current entry only says 'Allows populating Intellisense with an enum for types other than Long'). The multi-enum syntax was added in BETA 298._ +_Source threads: 1053718492647522314 · confidence: medium_ +_Date range: 2022-12-17_ +_Reviewer note: Verify that the 'Restart the compiler' toolbar button name matches the current IDE UI text. The claim about short file paths as an alternative fix came from user reports and may be version-specific._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Editor.md · after-remarks > [!NOTE] -> When the GUID string is removed from a `[ClassId("...")]` attribute (leaving the attribute empty or absent), the IDE displays a CodeLens entry directly above the attribute. Clicking it inserts a freshly generated GUID using `CoCreateGuid()`. This feature is available from BETA 323. The CodeLens appears only after the GUID value is removed; leaving an empty string `("")` in place may not trigger it. +> When **Auto Prettify Code** is ticked, the editor reformats each line as it is edited. Opening an **Enum** or **Type** block near the top of a module before typing its **End Enum** or **End Type** line can cause damage elsewhere in the file. The parser temporarily treats every line below as a member of that block, reports many syntax errors, and formats those lines as members. Calls such as `myfunction(1)` become `myfunction (1)`, and unwanted `()` pairs may appear at the end of the line being edited. To avoid this, type the closing **End Enum** or **End Type** line first, or check the code below the block for stray spaces after adding one. -_Source threads: 1113738858408988713 · confidence: high_ -_Date range: 2023-06-01 to 2023-06-02_ -_Reviewer note: Ideally place this note adjacent to the ClassId section in Attributes.md rather than as a free-standing after-remarks block; the reviewer should decide exact placement within the file._ +_Source threads: 1526407024529768509 · confidence: high_ +_Date range: 2026-07-14 to 2026-07-23_ +_Reviewer note: Finding had no symbol (package Core); placed on the IDE Editor page, after the Options list, because the behaviour belongs to the Auto Prettify Code option. The link between this behaviour and that specific option is inferred; confirm. Users also reported spurious parentheses after `Load FormX` and `[Object].Clear`, not included._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/FindReplace.md · after-remarks > [!NOTE] -> The `[Description]` value is stored as a COM typelib `helpstring`. The underlying limit (from MIDL error 2113/2114) is 255 characters; twinBASIC's effective limit is approximately 235 characters. Exceeding this length raises an "internal error: too long" message. Keep `[Description]` text within 230 characters to stay clear of the limit. +> The Find/Replace dialog retains a history of previously entered search strings. Pressing the up or down arrow key inside the **Find What** field cycles through earlier queries, avoiding the need to retype recent searches. -_Source threads: 1135126325783449711 · confidence: medium_ -_Date range: 2023-07-30_ -_Reviewer note: Verify the exact character limit against the current compiler behavior; the 235-character figure is approximate and based on a single report._ +_Source threads: 1478833214532620381 · confidence: medium_ +_Date range: 2026-03-04_ +_Reviewer note: The page-index does not include IDE pages. Reviewer should confirm the path docs/IDE/FindReplace.md and verify the feature is present in the current IDE build before publishing._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -Multiple enums can be combined in a single `[TypeHint]` by listing them separated by commas: `[TypeHint(EnumA, EnumB)]`. This causes IntelliSense to offer members from all listed enums for that parameter. The attribute works on `Declare` function parameters and `Sub`/`Function` parameters; UDT members are not currently supported. - -_Source threads: 1146837274357678203 · confidence: high_ -_Date range: 2023-08-31_ -_Reviewer note: Verify the multi-enum comma syntax and the UDT member exclusion against the current compiler source._ +## docs/IDE/History.md · new-section ---- +## Troubleshooting -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +If the History panel stops responding, or if switching panel layouts repeatedly produces errors such as "internal error: unable to close floating panel" or "couldn't close hidden panel", the IDE's stored panel layout may be corrupted in the Windows registry. -> [!NOTE] -> **`[DllExport]` on constants and executables.** The attribute applies to `Const` declarations as well as to procedures and variables. A practical use is exporting a `DWORD` constant named `NvOptimusEnablement` with a value of `1` from an x64 executable, which signals the GPU driver to activate the dedicated GPU for that process --- a requirement some GPU configurations have no reliable pure-code workaround for. `[DllExport]` also functions on executables, not only on DLLs. +The IDE stores panel layout state under: -```tb -[DllExport] -Public Const NvOptimusEnablement As Long = 1 +``` +HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE\IDESettings ``` -_Source threads: 1165150931554418698 · confidence: high_ -_Date range: 2023-10-21_ -_Reviewer note: Insert after the existing DllExport section (after the code block on line 304 and before the DLLStackCheck heading). Verify that the NvOptimusEnablement pattern actually requires DllExport from an EXE and is not a DLL-only requirement._ +Three values in that key control the layout: ---- +| Value | Purpose | +|-------|---------| +| `LAYOUTv2` | The active panel layout | +| `CUSTOM_PANEL_LAYOUTS` | All saved custom panel configurations | +| `LAYOUT` | Legacy layout entry | -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +Deleting all three values and restarting the IDE resets the layout to its defaults. The IDE regenerates these entries on start-up. Any custom layout previously exported as JSON from within the IDE can be re-imported afterward. -The attribute is used throughout the built-in packages to mark members that are declared in the type definition but have no working implementation yet. Code that references such a member compiles without an "unrecognized member" error, and the IDE surface (IntelliSense, type-library viewer) shows the member as present, but calling it at runtime produces no result or raises an error. The [**Printer**](../VB/Printer/) class uses this pattern for several of its members. +> [!NOTE] +> This issue is machine-specific and does not reproduce in clean environments. If it recurs, export any custom layouts as JSON before deleting the registry values so they can be restored. -_Source threads: 1167815153253892156 · confidence: high_ -_Date range: 2023-10-28_ -_Reviewer note: Insert as additional prose after the existing two-line Unimplemented section body. The Printer page already documents RightToLeft as not implemented; verify that other Printer members (Width, Height, FontName, hDC, CurrentX) are truly flagged [Unimplemented] in the .twin source rather than fully implemented._ +_Source threads: 1230091088560128061 · confidence: high_ +_Date range: 2024-04-17 to 2024-04-23_ +_Reviewer note: The History page is not in the page-index (package: tbIDE, symbol: null). The content concerns the IDE's panel layout registry, not the tbIDE addin SDK — docs/IDE/History.md is the closest existing page. Verify that the registry path is still current and that the value names (LAYOUTv2, CUSTOM_PANEL_LAYOUTS, LAYOUT) match the shipping IDE._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -The string is rendered as markdown in the IDE's IntelliSense popup and hover tooltip (the Monaco editor has built-in markdown support). Multi-line descriptions are built by concatenating fragments with `& vbCrLf &`; a markdown line break requires two or more trailing spaces before the line-feed constant. Only `vbCrLf` is supported for line endings inside attribute strings. Markdown headers (`###`), inline code (backtick), fenced code blocks (triple backtick with a language tag), bold, and italic are all supported. Emoji characters embedded in the string render correctly in the twinBASIC IDE but may display as garbled text in non-twinBASIC COM consumers whose type-library readers do not handle surrogate pairs. +## docs/IDE/Menu/Window.md · after-remarks [DUPLICATE? -- see also thread 1207651928356552704] -```tb -[Description("Returns the widget count. " & vbCrLf & _ - "### Notes" & vbCrLf & _ - "`Count` is zero-based.")] -Public Property Get Count() As Long -``` +> [!NOTE] +> Dragging a panel by its title bar detaches it into a floating window. To move a panel to a different position within the docked layout without detaching it, drag the six-dot gripper icon at the left end of the panel's title bar and drop it onto the target docked position. To return all panels to their original positions, use **Window** > **Panel Layouts** > **Default Built-in Layout**. -_Source threads: 1169508594341916712 · confidence: high_ -_Date range: 2023-11-02 to 2023-11-06_ -_Reviewer note: Insert as additional prose after the existing Description section body (after line 271). The current text ends after 'helpstring attribute in the type library (if applicable).' Append the new paragraph before the next heading._ +_Source threads: 1209855011530743908 · confidence: high_ +_Date range: 2024-02-21_ +_Reviewer note: Reviewer should confirm placement -- this note is most relevant to the Panel Layouts section of the Window menu page._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Menu/Window.md · after-remarks [DUPLICATE? -- see also thread 1209855011530743908] > [!NOTE] -> **`[EnumId]` on UDTs.** The attribute can also be applied to a [**Type** (UDT)](Type) definition, not only to an **Enum**. Assigning a GUID to a UDT allows it to be passed as a parameter across type-library boundaries --- without a GUID, passing a UDT defined in one type library as a parameter in a component from a different type library raises a compile error. The trade-off is that the type library then behaves like an ActiveX control for versioning: replacing it with a new build requires unregistering the old version first, overwriting the file, and re-registering. Simply overwriting the file (which works for plain TLBs) is no longer safe once a GUID has been assigned to any UDT in the library. +> The `tbEditor_DeleteLines` command deletes the current line (or all selected lines) in the code editor. Its default keyboard shortcut is Ctrl+Shift+Delete. This command was added in BETA 451. The shortcut can be viewed and reassigned through **Window** > **Keyboard Shortcuts** > **Manage Keyboard Shortcuts**. -_Source threads: 1173257226610290699 · confidence: high_ -_Date range: 2023-11-12 to 2023-11-14_ -_Reviewer note: Insert after the existing EnumId body (after line 348, before the EventInterfaceId heading). Also update the 'Applicable to' line from '[Enum]' to '[Enum], [Type (UDT)](Type)' if the .twin source confirms this._ +_Source threads: 1207651928356552704 · confidence: high_ +_Date range: 2024-02-15 to 2024-02-16_ +_Reviewer note: Reviewer should confirm placement -- this note belongs near the Keyboard Shortcuts section of the Window menu page, alongside the `tbEditor_DeleteLines` entry in the default shortcuts JSON._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -> [!NOTE] -> Although `[Unimplemented]` was not originally designed for general use, it is available in user code. A common pattern is to apply it to interface methods that must be declared to satisfy an interface contract but are intentionally left without an implementation body. Unlike `#If`/`#End If` conditional compilation, which removes code from the compilation entirely, `[Unimplemented]` keeps the method in the compiled output and type-checked --- so the compiler still catches downstream breakage if other code changes --- while issuing a warning at every call site. +## docs/IDE/Menu/Window.md · after-remarks -_Source threads: 1179991719551442975 · confidence: medium_ -_Date range: 2023-12-01 to 2023-12-03_ -_Reviewer note: Verify that [Unimplemented] is indeed documented as user-accessible in the current .twin sources; the finding notes it was 'not originally intended for general public use'. Placement: append this note immediately after the existing [Unimplemented] section body in Attributes.md._ +### Changing the colour of inline value hints +{: .no_toc } ---- +The dim text that IntelliSense shows after an enum member declaration (the member's value) is coloured by the theme property `InlineDecorationColor`. To change it, set the property in the theme file: -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +```css +InlineDecorationColor: red; +``` > [!NOTE] -> `Debug.Assert` is evaluated only when running inside the IDE debugger. All `Debug.Assert` calls are removed from compiled binaries and their conditions are never evaluated at runtime. For runtime assertion checks in compiled builds, use the [Assert package](../Assert/) or write an explicit error-raising guard. A project-level option to retain `Debug.Assert` in compiled binaries was under discussion as of early 2024 but was not yet implemented. +> Edit the theme file that belongs to the twinBASIC installation actually in use. Edits to a theme file of another installation have no effect. -_Source threads: 1206670811172306954 · confidence: high_ -_Date range: 2024-02-12_ -_Reviewer note: Placement: append immediately after the existing [DebugOnly] section body in Attributes.md, since that section already references Debug.Assert and Debug.Print. Alternatively, consider a dedicated Note block under the Assert package index page._ +One user reported that an edited and saved theme took effect only after twinBASIC was restarted, and that switching to another theme and back loaded the original version instead. If an edit does not appear, restart the IDE, or try **Reload from disk** in the **Theme** submenu. + +_Source threads: 1548997779148382328 · confidence: high_ +_Date range: 2026-09-14_ +_Reviewer note: Insert at the end of the Theme section. The InlineDecorationColor property was confirmed by Wayne Phillips; the restart behaviour is a single-user observation, and the suggestion that Reload from disk helps is an inference from the menu entry, not stated in the thread. Consider whether a dedicated theme-authoring page (or IDE-Features.md, Theme System) is a better home, and confirm the exact theme-file syntax (CSS-style) against a shipped theme._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/New Project.md · after-remarks [DUPLICATE? -- see also thread 1203088725605621801] > [!NOTE] -> A `[DefaultMember]` property that takes no parameters is evaluated automatically whenever an instance of the class appears in an expression, including when the instance is passed as an argument. This matches COM/VBA semantics and is intentional for compatibility with non-twinBASIC COM consumers. There is no per-parameter annotation to suppress this automatic evaluation. If automatic evaluation is not desired, the default member must accept at least one parameter, or the `[DefaultMember]` attribute must be omitted entirely. +> Importing a VB6 project that embeds the Windows Media Player ActiveX control (`WMPLibCtl.WindowsMediaPlayer`, from `wmp.dll`) fails at load time with a JSON deserialization error, even though the reference import step reports success. The control is embedded in `.ctl` form files using a `Begin WMPLibCtl.WindowsMediaPlayer` block, and twinBASIC cannot parse the control's serialized property data. The project will not load correctly until the Windows Media Player control is removed or replaced with an alternative. -_Source threads: 1358081642052190318 · confidence: high_ -_Date range: 2025-04-05 to 2025-04-06_ +_Source threads: 1109156485201661972 · confidence: medium_ +_Date range: 2023-05-19_ +_Reviewer note: Finding is tagged package=VB, symbol=null. No dedicated VB6-import page exists; placed on docs/IDE/New Project.md as the closest page covering 'Import from VBP...'. A more appropriate home would be a dedicated VB6 migration / known-limitations page if one is created. The JSON deserialization error was reported by a community user in May 2023 and may have been fixed since then -- verify against a current IDE build before publishing._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/New Project.md · after-remarks [DUPLICATE? -- see also thread 1109156485201661972] > [!NOTE] -> When `[DebugOnly]` is applied to a Sub or Function, the compiler removes both the procedure body and every call site that references it from the compiled EXE. Only the IDE (debug) run includes the procedure. This is distinct from `[Debuggable(False)]`, which suppresses breakpoints and stepping but does not strip code from the build. +> The New Projects dialog uses an invisible modal overlay that blocks all interaction with the main IDE window while the dialog is open. If the dialog opens positioned so that its close button is off-screen or otherwise inaccessible, the IDE can appear completely unresponsive. Press **Escape** to dismiss the dialog and restore access to the main window. +> +> Starting from BETA 438, the IDE includes a configurable option to display a real OS-native titlebar. Enabling it reduces the likelihood of this situation, because the OS titlebar remains draggable independently of any modal overlay. -_Source threads: 1398715673508843731 · confidence: high_ -_Date range: 2025-07-26_ -_Reviewer note: Insert immediately after the existing DebugOnly entry body (after line 231 in Attributes.md, under the ## DebugOnly section). The current description says 'Excludes calls to this procedure from the Build' --- this NOTE adds that the procedure itself is also excluded, not just the calls._ +_Source threads: 1203088725605621801 · confidence: high_ +_Date range: 2024-02-02 to 2024-02-06_ +_Reviewer note: Target page is docs/IDE/New Project.md, which is not in the page-index (the finding package is tbIDE with null symbol). The page content is minimal and has no existing remarks section -- reviewer should confirm placement and that BETA 438 native-titlebar option description is still accurate._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/New Project.md · after-remarks -> [!NOTE] -> The string value in `[Description("...")]` is rendered as markdown in the IDE tooltip, not as plain text. Embedding `vbCrLf` in the string does not produce visible line breaks. To produce multi-line or formatted IntelliSense tooltips, use markdown syntax within the string. Built-in functions use this pattern --- selecting 'Go to definition' on any built-in function in the IDE shows examples of how these strings are authored. +The list on the **Recent** tab is stored in the Windows registry, under `HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE\RecentlyOpened`. To remove many entries at once, close the IDE and delete the entries under that key with a registry editor such as RegEdit, instead of clicking the X beside each one. -_Source threads: 1396063342417674291 · confidence: high_ -_Date range: 2025-07-19_ -_Reviewer note: Insert immediately after the existing Description entry body (under ## Description in Attributes.md). The current entry says 'Provides a description in information popups in the IDE' without mentioning markdown rendering._ +_Source threads: 1511584056893112360 · confidence: high_ +_Date range: 2026-06-03_ +_Reviewer note: Registry path comes from a maintainer's Discord answer. Closing the IDE first is a precaution added by the drafter, not stated in the thread. Placed at the end of the Recent section._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] > [!NOTE] -> -> Square-bracket attributes apply to the next syntax element the parser encounters. Standard modules (`.BAS` files) have no explicit `Module` block --- the module container is implicit --- so there is no syntax element for an attribute to attach to before the first declaration. As a result, module-scoped settings that would be expressed as attributes in a Class or Form module must instead be expressed as `Option` statements (for example, `Option PtrSafe Off`) in a standard module. This limitation may change if the parser is updated to support attributes at the start of a module file before any declarations. +> Export can intermittently fail with a message such as "failed to create output file: `` (check permissions and storage space)" or "failed to clean directory", even when disk space and permissions are not the cause. The underlying reason is a Windows file-system behavior: when the IDE removes the export output folder before writing a fresh copy, Windows queues the deletion rather than completing it immediately. The folder still appears in the directory listing for an indeterminate period; any attempt to recreate, rename, or write into it during that window fails silently. Once Windows processes the deletion, the path becomes available again. Retrying the export immediately --- without making any other changes --- typically succeeds. -_Source threads: 1421815804172701708 · confidence: high_ -_Date range: 2025-10-01_ -_Reviewer note: Confirmed by WaynePhillipsEA. Verify that 'Option PtrSafe Off' is the correct syntax for the relevant option statement and that the Option page documents it, before adding this cross-reference. Also consider whether this note belongs on the Option page instead of (or in addition to) the Attributes page._ +_Source threads: 1065982376154501120 · confidence: high_ +_Date range: 2023-01-20 to 2023-02-22_ +_Reviewer note: This page (docs/IDE/Project Settings.md) is not in the page index --- it is an IDE guide page, not a Reference page. The finding's package is 'tbIDE' with null symbol; the Export section of Project Settings is the closest fit for this IDE-level gotcha. Verify that the Export section heading anchor is `## Export` (line 138) and that the NOTE sits inside that section. Also confirm the error message text against current IDE behaviour._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -Under the **ArrayBoundsChecks** section, after the existing description, insert: - -> [!NOTE] -> **[ArrayBoundsChecks(False)]** is not fully implemented in the current compiler. Suppression works on array write operations but may not suppress bounds checks on reads in all contexts --- for example, reading a known out-of-bounds element through `MsgBox` still raises a bounds error even when the attribute is present, whereas reading through `Debug.Print` succeeds. Do not rely on the attribute to eliminate all bounds-checking overhead until this is resolved. +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] > [!NOTE] -> With the current unoptimized code generator, **[ArrayBoundsChecks(False)]** may produce no measurable performance improvement. The bounds-checking code may still be emitted and simply not reached at runtime. Meaningful gains from disabling bounds checks require LLVM optimization, which is experimental and limited to procedures whose argument and variable types are numeric scalars only (no strings, objects, or dynamic arrays). Apply **[CompilerOptions("+llvm,+optimize")]** alongside this attribute to obtain the intended speedup. +> When the Available COM References list shows two entries with identical names and version numbers, this is not a bug. Each entry has a different GUID and corresponds to a distinct type library registered on the system (for example, two installations of the same library from different applications). Selecting each entry in turn and comparing the displayed file path or GUID identifies the specific library to reference. Checking both entries is unnecessary and may cause conflicts. -_Source threads: 1432054854016045096 · confidence: high_ -_Date range: 2025-11-28_ -_Reviewer note: Two separate limitations confirmed by the twinBASIC maintainer (WaynePhillipsEA). The read-vs-write asymmetry is a known bug; the LLVM dependency is by design. Verify both are still current before publishing._ +_Source threads: 1111079336079020102 · confidence: medium_ +_Date range: 2023-05-25_ +_Reviewer note: Target page is docs/IDE/Project Settings.md (Library References section), which is outside the page-index. The note should be inserted under the '## Library References' heading on that page, near the Available COM References screenshot. Verify the wording accurately describes the IDE's current References dialog behavior -- in particular that selecting a duplicate entry does display a file path or GUID that distinguishes the two libraries._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] - -Under the **UseGetLastError** section, after the existing description, insert: +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] > [!NOTE] -> Every `Declare` statement implicitly calls `GetLastError` after the API returns to populate `Err.LastDllError`. This hidden call adds overhead that is noticeable in tight loops. For APIs that do not use `GetLastError` --- for example, string comparison functions or functions returning NTSTATUS --- add **[UseGetLastError(False)]** to suppress the implicit call: +> When a type library carries the `TYPEFLAG_FCONTROL` flag on one of its interfaces---as the Microsoft Access type library does on `Access.Control`---twinBASIC treats the entire reference as a Components entry (equivalent to an ActiveX control registered via the Components list in VB6). Types from that reference are wrapped in `_ActiveXControlExtender` classes, which implement only the standard COM control interface and are marked non-extensible. Late-bound access to members that are not part of that base interface---such as `ControlSource`, `ControlType`, and other Access-specific control properties---fails with error 5216: `Unrecognized member "..." on type "_ActiveXControlExtender" [non-extensible object]`. > -> ```tb -> [UseGetLastError(False)] -> Public Declare PtrSafe Function CompareStringEx Lib "kernel32" ( _ -> ByVal lpLocaleName As LongPtr, ByVal dwCmpFlags As Long, _ -> ByVal lpString1 As LongPtr, ByVal cchCount1 As Long, _ -> ByVal lpString2 As LongPtr, ByVal cchCount2 As Long, _ -> ByVal lpVersionInformation As LongPtr, ByVal lpReserved As LongPtr, _ -> ByVal lParam As LongPtr) As Long -> ``` +> To restore normal late-bound access, open the project's Settings file and set the **Use ActiveX Controls** flag to **False** on the affected reference. After saving, twinBASIC treats the reference as a plain COM type library, and late-bound calls to interface members work as expected. > -> When working with APIs from a package reference that cannot be edited directly, copy the `Declare` into the project and add the attribute there. +> This is a known limitation. A future release will separate Components and References more precisely, matching VB6 behavior. -_Source threads: 1432054854016045096 · confidence: high_ -_Date range: 2025-12-06_ +_Source threads: 1114028559451828314 · confidence: high_ +_Date range: 2023-06-02_ +_Reviewer note: Verify the exact name of the 'Use ActiveX Controls' flag as it appears in the .twinproj / Settings JSON, and confirm the error number 5216 is still current. The finding comes from a 2023 Discord thread; the behavior or flag name may have changed. Target page (docs/IDE/Project Settings.md) is not in the Reference page-index --- this is the IDE section. Consider whether the note belongs in the Library References subsection of that page or needs a dedicated new paragraph there._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1205564031482593380, 1208711450240090122, 1339525725778345997, 1440729215682220074] > [!NOTE] -> A class's predeclared instance is not a compile-time constant. Because **Optional** parameter default values must resolve to a constant, writing `Optional ByVal Target As MyClass = MyClass` (using the predeclared instance as the default) is not valid. The standard workaround is to declare the parameter as `Optional ByVal Target As MyClass = Nothing` and test inside the procedure: -> -> ```tb -> Sub DoSomething(Optional ByVal Target As MyClass = Nothing) -> If Target Is Nothing Then Set Target = MyClass -> ' ... -> End Sub -> ``` +> When the references list is partially obscured --- for example, when a list item is clipped halfway by the bottom edge of the dialog --- clicking the topmost visible item may select the wrong entry. To avoid this, scroll or resize the dialog so that no item is partially clipped before clicking. This is a known bug; the references and packages lists are planned for an overhaul, at which point the issue is expected to be resolved. -_Source threads: 1291780674772275220 · confidence: medium_ -_Date range: 2024-10-04_ -_Reviewer note: This note should appear in the PredeclaredID section of Attributes.md, immediately after the existing two-sentence description. Confirm placement does not conflict with any existing notes in that section._ +_Source threads: 1126479798156394516 · confidence: high_ +_Date range: 2023-07-06 to 2023-07-11_ +_Reviewer note: Insertion point is the Library References section of docs/IDE/Project Settings.md. The page is not in the page-index (which covers only docs/Reference/ and a few top-level pages), but was identified by reading the IDE menu pages. Verify that the workaround description still applies to the current IDE version before publishing._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1208711450240090122, 1339525725778345997, 1440729215682220074] > [!NOTE] -> Applying `[Debuggable(False)]` to a procedure or module causes the debugger to skip over that code when stepping through with F8. This is useful when a referenced package contains source code that would otherwise be entered during step-through. If the source belongs to a read-only package that cannot be edited from within the consuming project, the package author must set this attribute in their own project before building the package. +> The icon shown in Windows Explorer and the taskbar for a compiled executable comes from the `Resources\ICON` folder inside the project, not from the **Icon Form** setting. twinBASIC selects the icon file in that folder whose name sorts first alphabetically. To control the application icon, add the desired `.ico` file to the `Resources\ICON` folder and name it so that it sorts before any other icon files in that folder. If the folder is empty, the compiled executable uses a generic icon. -_Source threads: 1357558567447695391 · confidence: high_ -_Date range: 2025-04-04 to 2025-04-07_ -_Reviewer note: Insert after the existing [Debuggable] attribute entry (around the 'When false, turns of breakpoints and stepping' line). The note about a per-reference consumer-side override switch (planned but not yet available as of April 2025) was omitted as it may become stale quickly; re-add when the feature ships._ +_Source threads: 1205564031482593380 · confidence: medium_ +_Date range: 2024-02-09 to 2024-02-10_ +_Reviewer note: Verify against current IDE behavior -- the claim is that the icon is taken from Resources\ICON (alphabetically first), not from the Icon Form project property. Needs confirmation against recent IDE release._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1339525725778345997, 1440729215682220074] > [!NOTE] -> Both `[ArrayBoundsChecks]` and `[IntegerOverflowChecks]` can be applied to a single procedure as well as to an entire class or module. Prefixing one performance-critical function disables the corresponding runtime check for that function only: -> -> ```tb -> [ArrayBoundsChecks(False), IntegerOverflowChecks(False)] -> Private Sub CriticalInnerLoop(arr() As Long, ByVal n As Long) -> Dim i As Long -> For i = 0 To n - 1 -> arr(i) = arr(i) + 1 -> Next i -> End Sub -> ``` +> When a **Package** project type is built, the output filename includes a `_win32` or `_win64` suffix even though a `.twin` package file is architecture-neutral (the consuming project determines the target architecture). This suffix comes from the default build path template, which includes an `_${Architecture}` token. To produce a filename without the suffix, set a custom **Build Output Path** in the project settings that omits the `_${Architecture}` token. -_Source threads: 1382015106380075078 · confidence: medium_ -_Date range: 2025-06-11_ -_Reviewer note: This note should be inserted under both the ArrayBoundsChecks and IntegerOverflowChecks sections (or added once after IntegerOverflowChecks with a cross-reference). The existing Attributes.md text already states both attributes are applicable to procedure scope, so this is a clarifying example rather than new information. Place it under the IntegerOverflowChecks section._ +_Source threads: 1208711450240090122 · confidence: medium_ +_Date range: 2024-02-18_ +_Reviewer note: Reviewer should confirm placement -- this note is most relevant to the Build Output Path section of Project Settings. Verify whether the behavior has changed in recent IDE releases._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1440729215682220074] -## NonBrowsable (optional Bool) -{: #nonbrowsable } +> [!NOTE] +> **Import from file** embeds the selected binary (e.g. an `.ocx` file) inside the `.twinproj` file, which can increase the project file size from a few kilobytes to several megabytes. To keep the project file small, use **Add reference** (the available references list) instead --- that stores only a path to the library. When a twinBASIC-native package is available for the library (e.g. `VBCCR18`), prefer the native package: it supports both 32-bit and 64-bit targets and does not embed a binary. **Import from file** is generally not recommended for OCX or DLL files. -Syntax: **[NonBrowsable** [ **(** **True** \| **False** **)** ] **]** +_Source threads: 1339525725778345997 · confidence: high_ +_Date range: 2025-02-13_ +_Reviewer note: Insert under the 'Library References' section of the Project Settings page (after line 34 in the current file). Verify the UI labels ('Import from file' and 'Add reference') match what the IDE actually shows._ -Applicable to: [**Property**](Property) procedures (**Property Let**, **Property Get**, **Property Set**) +--- -Hides the property from the form designer property browser, but leaves it visible in other browsing contexts such as the Object Browser. This is the twinBASIC equivalent of VBA/Access run-time-only properties that do not appear at design time. +## docs/IDE/Project Settings.md · after-remarks [DUPLICATE? -- see also thread 1065982376154501120, 1111079336079020102, 1114028559451828314, 1126479798156394516, 1205564031482593380, 1208711450240090122, 1339525725778345997] -To hide a property from all browsing contexts, use [**Hidden**](#hidden) instead. +> [!NOTE] +> When opening a project created with an older version of twinBASIC (before version 9), the IDE may display an "Unable to Load Library" dialog whose title bar shows a GUID rather than a human-readable name. That GUID is the type library ID of the `twinBASIC - WindowsControls DESIGNER Package`, which was a built-in package removed in a later release. To resolve the error: dismiss the dialog, open **Project Settings**, locate the reference entry for that GUID in the **Library References** list, untick it, and save the project. -_Source threads: 1387416025892393042 · confidence: high_ -_Date range: 2025-06-25_ -_Reviewer note: The existing [Hidden] entry in Attributes.md lists applicability as Class/CoClass/Interface only. The Discord finding states [Hidden] on a Property Let hides it from all browsing contexts. Verify whether the applicability of [Hidden] should be expanded to cover Property procedures, and whether [NonBrowsable] is implemented and behaves as described. Both attributes need to be checked against the .twin source._ +_Source threads: 1440729215682220074 · confidence: high_ +_Date range: 2025-11-19_ +_Reviewer note: Page is docs/IDE/Project Settings.md -- not in page-index. Reviewer should confirm placement under the Library References section and verify the package name and tB version number ("before version 9") against the changelog._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks -### Example: detecting build completion +> [!NOTE] +> A built program that Windows refuses to start with *This app can't run on your PC* usually has one of two causes. First, a manifest imported from a VB6 project keeps `processorArchitecture="X86"`, which a 64-bit build cannot use; change it to `*`. Second, *Target OS Version* is higher than the Windows version that runs the program: a project set to Windows 10 does not run on Windows 7. When both are correct, delete the manifest and create it again. If the program still fails to start, check whether security software or file permissions on that machine block it. -Because the IDE does not produce a visible alert when a build fails with the DEBUG CONSOLE panel closed, a `[RunAfterBuild]` handler can serve as a programmatic signal. The handler is called after every build attempt --- including failed ones --- so comparing the output file's modification timestamp to a known good time detects whether a new binary was produced: +_Source threads: 1540209036325298176 · confidence: medium_ +_Date range: 2026-08-21 to 2026-08-22_ +_Reviewer note: Place after the Target OS Version section. The thread's title also mentions an 'Access is denied' message that the draft does not reproduce. The diagnosis of each cause is by the thread's participants, not confirmed against the compiler._ -```tb -[RunAfterBuild] -Public Sub AfterBuild() - If FileDateTime(App.LastBuildPath) >= Now - TimeSerial(0, 0, 5) Then - MsgBox "Build succeeded: " & App.LastBuildPath - Else - MsgBox "Build may have failed --- check the Debug Console." - End If -End Sub -``` +--- + +## docs/IDE/Project Settings.md · after-remarks > [!NOTE] -> If the DEBUG CONSOLE panel is closed when a build fails, the IDE returns silently to its idle state with no dialog or notification. Keep the DEBUG CONSOLE open during builds, or use a `[RunAfterBuild]` handler to check the result programmatically. +> Compression does not work together with an overridden entry point. A build that sets a custom entry point, such as a `DllMain` in a Standard DLL project, reports "Build Error: Internal error, compression". Untick the three compression boxes in [Feature Flags](#feature-flags) --- **Compress Runtime Class Dispatch Info**, **Compress Runtime Error Tables** and **Compress Misc Data** --- to remove the error. In the reported case, the built DLL's entry point was still not the custom procedure, so custom entry points did not work completely at that time. -_Source threads: 1414587677574959165 · confidence: medium_ -_Date range: 2025-09-08 to 2025-09-09_ -_Reviewer note: The timestamp comparison heuristic in the example is illustrative; verify whether `App.LastBuildPath` and `FileDateTime` behave as expected immediately after a build. Confirm that `[RunAfterBuild]` fires even on failed builds._ +_Source threads: 1550752244469735475 · confidence: high_ +_Date range: 2026-09-19_ +_Reviewer note: Insert at the end of the Override Entry Point section. The maintainer said to turn off 'the compression options among the feature flags'; the three names are taken from the existing Feature Flags - continued list, so confirm that all three are needed. The final sentence records an unresolved defect (entry point at 0x1000, 'entry point not found') and may be dated quickly._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks > [!NOTE] -> The function runs in the same process as the build, with full access to the language and project code. Common uses include invoking `signtool` to sign the output binary, running `git` commands to tag the build, or copying the output file to a deployment directory. `App.LastBuildPath` returns the path of the built output. +> The 32-bit compiler is limited to 4 GB of address space, so a very large project, such as an imported VB6 project of over a million lines, can fail with "INTERNAL ERROR: OUT OF MEMORY". The limit is address space, not physical memory, so adding RAM does not help. Two ways to reduce the compiler's memory use are to untick the features the project does not use in Feature Flags, and to split the program into smaller projects. A 64-bit compiler is planned. -_Source threads: 1431348682271363182 · confidence: medium_ -_Date range: 2025-10-24_ -_Reviewer note: Insert after the existing RunAfterBuild section body (around line 589 of docs/Reference/Attributes.md). Verify that App.LastBuildPath is the correct property name and that the function runs in-process (not as a separate invocation of the IDE)._ +_Source threads: 1551873936898261022 · confidence: high_ +_Date range: 2026-09-22_ +_Reviewer note: Placed after the Feature Flags section. It may fit better in a compiler-limits or FAQ page. The claim that the compiler is x86 Large Address Aware comes from one process sample in the thread; the maintainer's 20-30% reduction and the 64-bit compiler plan are stated intentions and are worded loosely. Suggestions to disable features and split the program came from other users, not the maintainer._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks > [!NOTE] -> The three-argument (table, name, value) form of **[PopulateFrom]** requires the JSON source to be an object containing a named array of objects (e.g. `{ "events": [{"id": 12, ...}] }`). When the JSON source is a plain list of objects (e.g. `[{"id": 12, ...}]`) --- the default format for a twinBASIC STRING resource --- neither the three-argument form (raises TB5083: *JSON: accessor is invalid*) nor the two-argument form (silently produces an empty enum) works correctly. The workaround is to add the enum member IDs manually. +> The LLVM compiler did not support this option in BETA 984 to 987. A project with it on could report "a feature used in your code is not yet supported with the LLVM compiler" and produce a DLL or OCX that crashed when the affected procedure was called. BETA 988 fixes it. See [Language support](../../../LLVM/Getting-Started#language-support) in the LLVM section. -_Source threads: 1455320119239643390 · confidence: medium_ -_Date range: 2025-12-29_ -_Reviewer note: Insert in the PopulateFrom section of Attributes.md, after the existing JSON structure example. Confirm whether a future fix is planned or if the limitation is by design._ +_Source threads: 1553387151842877491 · confidence: high_ +_Date range: 2026-09-26 to 2026-09-28_ +_Reviewer note: Place at the end of the 'Sanitize Booleans' section, before the 'Constant Function Folding' heading. Overlaps with the note added to the LLVM Getting Started page; keep only one if the reviewer prefers less duplication._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks + +When two referenced packages define the same name, an unqualified use of that name resolves to the package that is higher in the priority order. A use of the other package's name must be prefixed with the package name. For example, WinDevLib and a WinRT package can each define `IPersistFile` with a different signature. + +To change the order, use the arrow buttons in the References dialog, as in VB6. Putting the package with fewer clashing names first keeps the number of prefixes small. > [!NOTE] -> A common interop pattern for Windows APIs that return a variable-length array embedded in a struct (for example, `PROCESS_HANDLE_SNAPSHOT_INFORMATION.Handles`) is to declare the fixed-size trailing member with a nominal bound of `(0 To 0)`, then apply `[ArrayBoundsChecks(False)]` on the procedure that reads from it. With bounds checking disabled, indexing beyond the declared dimension is safe as long as the index stays within the actual runtime length reported by the API. +> Updating a package removes it and adds it again, so it may not return to its earlier position in the order. Check the order after each update. Diagnostics does not report the case where the order silently changes which type a name refers to. -_Source threads: 1459605334502150206 · confidence: high_ -_Date range: 2026-01-10_ -_Reviewer note: Insert after the existing body text of the ## ArrayBoundsChecks section (around line 50 of Attributes.md). Verify the pattern against the PROCESS_HANDLE_SNAPSHOT_INFORMATION example in the twinBASIC source or sample projects before publishing._ +_Source threads: 1125359845936205844 · confidence: medium_ +_Date range: 2026-06-17 to 2026-06-18_ +_Reviewer note: The thread says an updated package lands at the end of the resolve chain, but Features/Packages/Updating a package.md says the entry moves to the top, below the built-in packages. Reconcile before publishing; the draft avoids stating the new position. The exact caption of the arrow buttons in the Library References dialog is not confirmed._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks -Only binds this name to a callsite when the caller uses the dollar-sign string-suffix form of the name. For example, a function named `Error` decorated with `[BindOnlyIfStringSuffix]` binds to `Error$()` but not to `Error()`. Used in the twinBASIC runtime library (Conversion module) to correctly dispatch the `Error$` variant. Complements [BindOnlyIfNoArguments](#bindonlyifnoarguments). +With **Native Subsystem** set to **Yes**, the linker reports any DLL import that does not come from NTOSKRNL, [**UseGetLastError**](../Reference/Attributes#usegetlasterror) defaults to **False**, and the built file gets the extension **.sys**. -_Source threads: 1462420288280068323 · confidence: high_ -_Date range: 2026-01-18 to 2026-01-22_ -_Reviewer note: This text should replace or extend the currently empty body of the ## BindOnlyIfStringSuffix section in Attributes.md (the section exists with syntax and applicability lines but no explanatory prose). Verify usage against the Conversion module .twin source._ +_Source threads: 927834661298798602-2022-07 · confidence: high_ +_Date range: 2022-07-23 to 2022-07-27_ +_Reviewer note: From BETA 82-86. Also mentioned: 'Runtime Binding Of DLL Declares (ADVANCED)', which binds DLL declares through the import address table; check whether that setting is documented and add it. Insert after the Native Subsystem paragraph; verify the Attributes link anchor._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks -Makes the class available in the twinBASIC IDE toolbox and gives it full ActiveX control behavior (Extender, siting, container notification). A class that compiles as an OCX but omits `[COMControl]` registers and instantiates with **New** as an ordinary COM object, but does not appear in the toolbox and cannot be sited on a form at design time. +> [!NOTE] +> In a native-subsystem (kernel-mode) build, standard assignments of the **String** type do not call **SysAllocString** or **SysFreeString**. String literals can therefore be used in kernel-mode code. -_Source threads: 1491119090306256906 · confidence: medium_ -_Date range: 2026-04-07 to 2026-04-13_ -_Reviewer note: This draft is a description body for the sparse [COMControl] entry in Attributes.md (currently only a syntax line with no prose). Reviewer should verify the exact semantics --- in particular whether [COMControl] applies to Class, CoClass, or Interface (the current page says Interface; the finding implies it applies to a class compiled into an OCX). Confirm against the .twin source._ +_Source threads: 927834661298798602-2022-08 · confidence: high_ +_Date range: 2022-08-23_ +_Reviewer note: From BETA 97. Insert under the Native Subsystem heading (around line 104). Verify the scope of 'standard assignments' against the compiler; non-assignment operations may still allocate._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks -Setting the attribute to **False** avoids the cost of capturing the last-error value after every call, so it suits APIs for which the last error is not meaningful. WinDevLib applies `[UseGetLastError(False)]` to its declarations for this reason. +The IDE Add menu includes **Add Resource: Visual Styles Manifest** and **Add Resource: String Table**. Global find can search inside referenced packages and offers **Match case** and **Match whole word only** options. An experimental **View as JSON** command for packages gives an outline for generating documentation. -_Source threads: 1125359845936205844 · confidence: low_ -_Date range: 2026-05-16_ -_Reviewer note: Insert at the end of the UseGetLastError entry. Based on a single remark in the thread; the size of the performance gain is not measured._ +_Source threads: 927834661298798602-2023-05 · confidence: high_ +_Date range: 2023-05-19 to 2023-05-30_ +_Reviewer note: IDE feature notes with no clear target page; placement is a guess. Register DLL after build and description variables are already documented in Project Settings.md. CDecl on internal procedures and console window visibility are already covered or unplaced: CDecl is in Features/Advanced/API-Declarations.md; console-window finding (medium confidence) not drafted._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1222086643737956494, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks -> [!NOTE] -> -> **[TypeHint]** accepts a comma-separated list of enum type names, not just a single type. This is useful for Win32 API parameters that combine flags from several independent enumerations --- for example, a `dwDesiredAccess` parameter that accepts members from both a generic-rights enum and a file-specific-rights enum. All listed enumerations appear together in the Intellisense popup. -> -> ```tb -> Public Declare PtrSafe Function CreateFileW Lib "kernel32" ( _ -> ByVal lpFileName As LongPtr, _ -> [TypeHint(GenericRights, StandardAccessTypes, FileAccessRights)] _ -> ByVal dwDesiredAccess As Long, _ -> ByVal dwShareMode As Long, _ -> ByVal lpSecurityAttributes As LongPtr, _ -> ByVal dwCreationDisposition As Long, _ -> ByVal dwFlagsAndAttributes As Long, _ -> ByVal hTemplateFile As LongPtr) As LongPtr -> ``` +> [!TIP] +> An ActiveX control that runs in a host which caches type library information, such as a UserForm that stores it in an `.EXD` file, should have its type library version raised for each release, so that the host refreshes the cached copy. Setting the auto-increment option to **Major** or **Minor** does this on every build. The ActiveX Control sample project has auto-increment enabled. -_Source threads: 1106637635818094733 · confidence: high_ -_Date range: 2023-05-12 to 2023-05-24_ -_Reviewer note: This addition targets the `## TypeHint` section in Attributes.md (around line 633). Confirm that the multiple-enum form is in the current compiler --- the thread is from 2023 but the feature may have been present since then._ +_Source threads: 927834661298798602-2024-03 · confidence: high_ +_Date range: 2024-03-12_ +_Reviewer note: Intended for the TypeLib version section (typelib-auto-increment). From BETA 469. The sample's exact auto-increment value should be verified._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1292578507674746951, 1427261753544409168] +## docs/IDE/Project Settings.md · after-remarks > [!NOTE] -> twinBASIC's linker writes `Windows NT 4.0` into the OS version and Subsystem version fields of the `IMAGE_OPTIONAL_HEADER` in compiled executables, DLLs, and OCX files. Most applications are unaffected, but projects that require a specific OS or subsystem version in the PE header --- such as kernel drivers --- must patch these fields after the build. A `[RunAfterBuild]`-decorated procedure runs automatically after each successful build (see [RunAfterBuild](#runafterbuild)) and receives the output path through `App.LastBuildPath`, making it straightforward to apply any post-build patch without a separate build script. - -_Source threads: 1222086643737956494 · confidence: medium_ -_Date range: 2024-03-26_ -_Reviewer note: Verify the exact OS/Subsystem version string written by the twinBASIC linker, and confirm that App.LastBuildPath is the correct API for accessing the build output path from a [RunAfterBuild] procedure. The original workaround module is at https://github.com/fafalone/SetPEImageProps._ +> When a VBP project is imported and the VBP stores an executable name (`ExeName32`), the import uses it for the build path, which becomes `${SourcePath}\` followed by that name, with no `Build` folder. `App.Path` in the imported project is the folder that holds the VBP. ---- +> [!NOTE] +> The build reports an error in the DEBUG CONSOLE when the project's manifest file holds a `processorArchitecture` value that does not match the build target, because an executable with such a manifest would fail to start. -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1427261753544409168] +_Source threads: 927834661298798602-2024-04 · confidence: high_ +_Date range: 2024-04-15 to 2024-04-25_ +_Reviewer note: BETA 517/521 (VBP import) and BETA 505 (manifest check). Placement after Build Output Path; the manifest note may belong near the manifest setting. Verify against the compiler._ -One common use for **[RunAfterBuild]** is to merge shell extension registration data immediately after compilation: +--- -```tb -[RunAfterBuild] -Public Sub RegisterExtension() - ' Merge the .reg file that registers this control as a shell extension. - Shell "reg import """ & App.LastBuildPath & "\\..\\ QuickRename.reg""" -End Sub -``` +## docs/IDE/Project Settings.md · after-remarks -This pattern applies to any project that needs post-build steps: COM registration, file copying, or code signing. `App.LastBuildPath` holds the path to the compiled binary, so the sub does not need to hard-code a build output directory. +> [!NOTE] +> **Trace Procedure Arguments** logs only arguments of simple types such as **Byte**, **Integer**, **Long**, **String** and **Variant**. Arrays and user-defined types are not logged. The return value of a function in a standard module is logged only when it is a **Variant** or **Decimal**. Entries named `{inline_execution}.{temp_evaluation_proc}` come from the evaluation of constant expressions and are normal. +> +> Every flag slows the program, and some produce very large logs. When hunting a crash, tick them all. If the program dies from a native exception, the log ends abruptly, and the last entry shows where the crash happened. The trace log covers all referenced packages as well as the project itself. -_Source threads: 1292578507674746951 · confidence: high_ -_Date range: 2024-10-06_ -_Reviewer note: Draft inserts after the existing one-line description of [RunAfterBuild] in Attributes.md (around line 589). Verify the App.LastBuildPath usage is syntactically correct for a shell call and that the surrounding code example matches the site's tb code-fence convention._ +_Source threads: 1215336829385973820-2024-03 · confidence: high_ +_Date range: 2024-03-07 to 2025-01-03_ +_Reviewer note: Check against the compiler source. The page lists eleven flags; the thread described seven. The ${SESSIONID} finding is already stated on the page and was dropped._ --- -## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951] +## docs/IDE/Toolbar.md · after-remarks > [!NOTE] -> -> `[MustBeQualified]` can be applied to a **Module** (forcing callers to qualify member references with the module name) but cannot be applied to interfaces. Interface methods have no module scope, so this attribute has no equivalent for them. In large Win32 API definition libraries that define many interfaces, members from those interfaces will appear unqualified in Intellisense and cannot be suppressed with this attribute. +> When a transient runtime error occurs during execution, restarting the entire IDE is not necessary. The **Restart Compiler** button on the toolbar --- the circular-arrow icon next to the architecture dropdown --- resets the compiler process and usually clears the error state without closing and reopening the IDE. -_Source threads: 1427261753544409168 · confidence: medium_ -_Date range: 2025-10-24_ -_Reviewer note: The existing MustBeQualified entry says 'Applicable to: procedure' but the finding describes module-level use. Reviewer should verify: (1) whether the attribute also applies to Module declarations (not just individual procedures), and (2) confirm the interface limitation is accurate in the current compiler._ +_Source threads: 1395281720277991444 · confidence: medium_ +_Date range: 2025-07-18_ +_Reviewer note: Toolbar.md is a sparse list-based page with no prose sections. Verify whether a prose note fits here or whether it belongs in docs/Miscellaneous/FAQs.md instead._ --- -## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1432054854016045096, 1445969212698005648] +## docs/IDE/Toolbar.md · after-remarks -When a DLL is placed next to the project file, the runtime may not locate it because Windows does not search the application directory in all circumstances. Placing `[SetDllDirectory(True)]` on the first **Declare** from that DLL instructs the loader to include the application path in its search. The attribute only needs to appear on one declaration per DLL; subsequent declarations in the same module do not need it. +> [!NOTE] +> A *Compiler crash loop* message means the compiler keeps crashing and restarting, and its progress bar loops near 5%. A single compiler restart is normal: changing project settings restarts the compiler by design. In BETA 985 to 991 the loop had two known causes. One was a hard crash while the [Project Explorer](Project%20Explorer) was showing the object view instead of the file view. The other was a fault in error reporting, which appeared when the [Debug Console](Debug%20Console) had errors to show, for example after the VBA reference was removed. BETA 992 fixes both for the reporters. On one machine, restarting Windows cleared the loop, which is a workaround and not a fix. To recover a project meanwhile, use **safeMode**. -```tb -[ SetDllDirectory (True) ] -Public Declare Sub sqlite3_open Lib "sqlite.dll" (ByVal FileName As String, ByRef handle As Long) -Public Declare Sub sqlite3_close Lib "sqlite.dll" (ByVal handle As Long) -``` +_Source threads: 1553605375423553679 · confidence: high_ +_Date range: 2026-09-25 to 2026-09-29_ +_Reviewer note: Place after the paragraph about **safeMode** at the end of the page. Verify the relative links to Project Explorer and Debug Console resolve (the pages use spaces in their filenames; use their permalinks instead if the build's link check objects). The two causes are from developer statements in the thread; the 'object view' name for the Project Explorer mode should be checked against the IDE, since the Project Explorer page calls the toggle 'Toggle file view'._ -In VB6, the equivalent requires declaring and calling the Win32 `SetDefaultDllDirectories` API with the `LOAD_LIBRARY_SEARCH_APPLICATION_DIR` flag before the first call into the DLL. +--- -_Source threads: 1140151535217680436 · confidence: high_ -_Date range: 2023-08-13_ -_Reviewer note: Insert this example block immediately after the SetDllDirectory attribute description at the #setdlldirectory anchor in Attributes.md. Verify the 'only needs to appear on one declaration per DLL' claim against the .twin source or compiler behaviour._ +## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1336104425520893972, 1118376310725673091] ---- +> [!NOTE] +> Panel and window layout state is stored independently of project files. A corrupt or incompatible saved layout therefore affects all projects, not only the one that was open when the problem appeared. To recover from a broken layout, delete the saved layout file and restore the default layout from within the IDE. Opening a different project does not reset the layout. -## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1140151535217680436, 1445969212698005648] +_Source threads: 1200411286937276508 · confidence: high_ +_Date range: 2024-01-26_ +_Reviewer note: The exact file path for the saved layout is not documented in the thread. Verify the location (likely a per-user app-data file) and consider adding it to the note so users can find it. The IDE index page (docs/IDE/index.md) is not in the page-index --- this is an IDE guide page, not a Reference page._ -Under the **ArrayBoundsChecks** section, add an example block: +--- -### Win32 flexible array member pattern +## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1200411286937276508, 1118376310725673091] -Some Win32 structures end with a variable-length trailing array (a C-style flexible array member). Declare the field with a single-element array inside the UDT, then apply **[ArrayBoundsChecks(False)]** to any procedure that reads or writes elements beyond index 0: +> [!IMPORTANT] +> The twinBASIC IDE uses loopback sockets (127.0.0.1) for all communication between its frontend and backend compiler processes. Any code that installs Windows Filtering Platform (WFP) rules or other network-filter mechanisms must explicitly exempt the loopback address range (127.0.0.0/8) from blocking. A WFP filter that blocks outbound traffic to non-LAN destinations but omits 127.0.0.0/8 from its exclusion list will sever the IDE's internal channels, causing every open twinBASIC instance to enter a crash loop. The fix is to add a filter condition on `FWPM_CONDITION_IP_LOCAL_ADDRESS` excluding 127.0.0.0/8 in addition to any private-range exclusions on `FWPM_CONDITION_IP_REMOTE_ADDRESS`. This loopback architecture is planned to change in a future release. -```tb -' UDT that mirrors a Win32 structure with a trailing array -Private Type WLAN_AVAILABLE_NETWORK_LIST_V2_sa - NumberOfItems As Long - Index As Long - Network(0) As WLAN_AVAILABLE_NETWORK_V2 -End Type +_Source threads: 1336104425520893972 · confidence: high_ +_Date range: 2025-02-03 to 2025-02-05_ +_Reviewer note: The finding was tagged package=Core, symbol=null, which maps to UNMAPPED in the page index. Placed here on docs/IDE/index.md as this is an IDE-level architectural warning. Verify that the IDE index page is the appropriate home, or consider a dedicated troubleshooting page if one is added in future._ -' Access the trailing elements through a pointer cast -[ArrayBoundsChecks(False)] -Private Sub ParseNetworkList(ByVal ptr As LongPtr) - With CType(Of WLAN_AVAILABLE_NETWORK_LIST_V2_sa)(ptr) - Dim i As Long - For i = 0 To .NumberOfItems - 1 - Debug.Print .Network(i).dot11Ssid.ucSSID - Next i - End With -End Sub -``` +--- -Placing the attribute on the UDT field itself is not yet supported; it must be placed on the calling procedure. +## docs/IDE/index.md · after-remarks [DUPLICATE? -- see also thread 1200411286937276508, 1336104425520893972] -_Source threads: 1432054854016045096 · confidence: high_ -_Date range: 2025-10-26_ -_Reviewer note: The field-level attribute placement was a feature request acknowledged by WaynePhillipsEA; confirm whether it has since been implemented before publishing this note about the limitation._ +> [!NOTE] +> The IDE stores all its settings in the Windows registry under `HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE`. Because settings are stored in the registry rather than in the installation folder, installing a new release to a fresh directory preserves existing settings automatically. To reset the IDE to its defaults, delete or rename this registry key. + +_Source threads: 1118376310725673091 · confidence: high_ +_Date range: 2023-06-14_ +_Reviewer note: The IDE section pages (docs/IDE/) are not in the page-index. This addition targets docs/IDE/index.md as the general IDE overview page. Reviewer should confirm placement --- the Project Settings page (docs/IDE/Project Settings.md) covers per-project settings, not IDE-level settings, so the index page is the better fit._ --- -## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1140151535217680436, 1432054854016045096] +## docs/IDE/index.md · new-section -### Example: reading variable-length C-style arrays +## Requirements -Win32 APIs often return structures whose last field is a flexible array --- a field declared with a fixed size of 1 in the type definition but allocated larger at run time. `[ArrayBoundsChecks(False)]` makes it possible to traverse the full array without a runtime bounds error. +### Microsoft Edge WebView2 Runtime -Declare the UDT with the last field sized to 1, allocate a **Byte** array large enough for the full data, overlay the UDT on the buffer using `CType(Of ...)`, then iterate: +The twinBASIC IDE requires the Microsoft Edge WebView2 Runtime to be installed on the development machine. Without it, opening certain project types (such as Standard EXE) can produce an "Unable to Load Library" error and a non-functional designer. -```tb -[ArrayBoundsChecks(False)] -Private Sub ReadHandles() - Dim buf() As Byte - ' --- allocate buf and fill it via the API --- - With CType(Of SYSTEM_HANDLE_INFORMATION_EX)(VarPtr(buf(0))) - Dim i As Long - For i = 0 To .NumberOfHandles - 1 - Debug.Print .Handles(i).HandleValue - Next i - End With -End Sub -``` +Download the x86 (32-bit) Evergreen Standalone Installer from the [Microsoft Edge WebView2 download page](https://developer.microsoft.com/en-us/microsoft-edge/webview2/) and run it on the development machine. -An alternative that avoids a fixed-size array declaration entirely: allocate via `CoTaskMemAlloc`, declare the last field as a plain non-array type (for example, `FirstHandle As SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX`), and compute element addresses manually: +> [!NOTE] +> This is a development machine requirement only. Executables compiled with twinBASIC do not require the WebView2 Runtime on end-user machines. -```tb -Dim pEntry As LongPtr = VarPtr(info.FirstHandle) + i * LenB(Of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) -Dim entry As SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX -entry = CType(Of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)(pEntry) -``` +### Extracting the distribution ZIP -Both patterns require `[ArrayBoundsChecks(False)]` (first) or no array at all (second). Apply `[ArrayBoundsChecks(False)]` at the narrowest scope --- a single procedure rather than a whole module --- to limit exposure. +Before extracting the twinBASIC distribution ZIP, remove the Windows zone identifier ("Mark of the Web") from the file: right-click the ZIP in File Explorer, open **Properties**, and click **Unblock** if the button is present. Extracting without doing this can cause the IDE to treat the extracted files as untrusted, resulting in library-load failures. -_Source threads: 1445969212698005648 · confidence: high_ -_Date range: 2025-12-04_ -_Reviewer note: This example uses hypothetical Win32 type names for illustration. A reviewer should verify the exact struct names match the WinDevLib or twinBASIC definitions before publishing, and confirm both code paths compile correctly._ +Extract to a conventional location such as a subfolder of Downloads, Documents, or a dedicated tools directory. Extracting directly into a special shell folder (for example the user profile root `C:\Users\`) can produce a broken installation in which packages such as WinNativeFormsBase fail to load, even when the file counts appear correct. + +_Source threads: 1111043339987468380 · confidence: medium_ +_Date range: 2023-05-24 to 2023-05-26_ +_Reviewer note: Target page docs/IDE/index.md is not in the page-index (package 'Core', symbol null has no page-index match). Placement here is a best-effort choice -- the IDE index page is the closest existing page covering the IDE as a whole. A dedicated installation/setup page may be a better long-term home. The WebView2 Runtime finding (high confidence, 2023-05-24--25) and the two ZIP-extraction findings (medium confidence, 2023-05-25--26) are merged into one section. Verify: (1) that the IDE still requires the 32-bit Evergreen installer rather than the Evergreen bootstrapper or a fixed-version installer; (2) whether the user-profile-root extraction issue still reproduces on current builds; (3) whether 'Unblock' behavior differs between Windows 10 and 11._ --- -## docs/Reference/Attributes.md · after-remarks +## docs/IDE/tbForm.md · after-remarks > [!NOTE] -> Before BETA 984, an array declared with a negative lower bound, such as `Public Keyb(-3 To 255) As Boolean`, crashed with an access violation on x64 when [**ArrayBoundsChecks**](#arrayboundschecks) was disabled and an element at a negative index was read or written, even though the index was in bounds. The problem is fixed in BETA 984. On earlier builds, keep bounds checking enabled for code that uses such arrays. +> If the form designer fails to finish loading and stays at a fixed percentage (for example, 15%) without progressing, the most likely cause is that the IDE toolbar panel has been closed. Go to **Window** > **Panel Layouts** > **Default Built-in Layout** to restore the default panel arrangement, then restart the IDE. An anti-virus or firewall blocking the WebSocket connection between the IDE and the compiler can also prevent the designer from loading. This issue was resolved in BETA 449. -_Source threads: 1514851886656917664 · confidence: high_ -_Date range: 2026-06-12 to 2026-09-24_ -_Reviewer note: Insert directly after the ArrayBoundsChecks description. Fix version (BETA 984) comes from the thread; confirm against the release notes._ +_Source threads: 1205932211954786425 · confidence: high_ +_Date range: 2024-02-10 to 2024-02-11_ --- -## docs/Reference/Attributes.md · after-remarks +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] -> [!NOTE] -> Use an empty string, `[CompilerOptions("")]`, to turn LLVM off for one procedure. A string such as `"-llvm"` can appear to work, but it is not a documented form. The compiler seems to ignore option words it does not recognize, so a later version that validates the string more strictly could reject it. +
+I get "Internal Error 33330: IDE was unable to read builtin package file data" when adding a package reference. -_Source threads: 1487268961061044338 · confidence: medium_ -_Date range: 2026-09-25_ -_Reviewer note: Place after the list of compiler options in the CompilerOptions section. The exact parsing rules were not confirmed, so the draft says only that the form is undocumented._ +This error occurs when twinBASIC is extracted on top of a previous installation rather than into a clean folder. The IDE cannot read its built-in package data if stale files from an older release are present alongside the new ones. ---- +To resolve it, extract the new release into an empty folder. Delete the contents of the old installation folder first, or use a different folder entirely. See [How do I install twinBASIC](#installation) for the recommended extraction steps. -## docs/Reference/Attributes.md · after-remarks +The error affects all built-in package references and `.twinpack` files, not only the one that was being added when it first appeared. -> [!NOTE] -> The attribute is not accepted on a [**Type**](Type) or on a procedure written inside a **Type**: the compiler reports it as invalid syntax. Warnings raised by a UDT's member procedures cannot be suppressed at that level. Apply the attribute to the enclosing class or module instead, or change the warning's level in the project settings. +
-_Source threads: 1542590154810851338 · confidence: low_ -_Date range: 2026-08-27_ -_Reviewer note: Based on a feature request open on 2026-08-27; probe the current build (scripts/tbbuild.mjs) to confirm the attribute is still refused on a Type and on a Type's member procedure, and that the suggested workaround (module-level attribute) works. Drop the note if a later build accepts it. Insert after the IgnoreWarnings code sample and its Applicable-to line._ +_Source threads: 1241295009110687764 · confidence: high_ +_Date range: 2024-05-18_ +_Reviewer note: This finding maps to the Installation section of docs/Miscellaneous/FAQs.md (not in the page-index). Insert the new
block after the existing #installation entry (around line 188) and before #installation-size. The existing installation entry already mentions extracting to an empty folder but only vaguely; this new entry gives the specific error number (33330) and symptom (package load failure) so users can find it by searching. Confirmed by WaynePhillipsEA in the Discord thread._ --- -## docs/Reference/Attributes.md · after-remarks - -A referenced twinBASIC package behaves like source code added to the referencing project, not like a compiled boundary. Scope takes hard effect only where the package contents end up in an ActiveX DLL or control. Two consequences apply when a package tries to hide its internals: - -- **Friend** members of package classes appear in IntelliSense in the referencing project. -- The public members of a **Private** class or standard module are still exposed to the referencing project, but must be qualified with the class or module name to be called. +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] -> [!NOTE] -> **MustBeQualified** on a module is meant to hide its members unless they are qualified with the module name, but it was reported not to work reliably at the time of writing, and a bug report was filed. A class with a predeclared instance is an alternative to a standard module. When internals must be fully hidden, ship them as an ActiveX DLL and reference that from twinBASIC. +Installing the specific updates KB4490628 and KB4516065 (with a reboot between them) has been reported to resolve this requirement on Windows 7 SP1. After applying all available updates, the entry-point error should no longer appear; if WebView2 is absent or at the wrong version for Windows 7, the next error on launch will be about WebView2 instead --- see [How do I install WebView2 on Windows 7 or Windows 8.1?](#webview2-windows7) for the correct procedure. -_Source threads: 1516861763042279534 · confidence: medium_ -_Date range: 2026-06-17 to 2026-09-22_ -_Reviewer note: Existing MustBeQualified section says 'Applicable to: procedure' and has no description, while the thread discusses it on a module (and the Assert package tags members). Verify applicability against the .twin sources and by probe, and confirm the reported bug before keeping the 'unreliable' note. Whether [NonBrowsable] hides Friend members in a package was unconfirmed and is omitted. Insert after the MustBeQualified heading's applicability line._ +_Source threads: 1332299294006448170 · confidence: high_ +_Date range: 2025-01-24 to 2025-01-26_ +_Reviewer note: This text should be appended to the body of the existing 'invalid-entry-point' FAQ entry (around line 159-163 of FAQs.md), which currently ends after advising the user to run Windows Update. The KB numbers (KB4490628, KB4516065) came from a community report and should be verified before publication._ --- -## docs/Reference/Attributes.md · example +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] -A class tagged **AppObject** exposes its **Public** members as globals. Its `Class_Initialize` procedure runs when one of those members is first used, not when the program starts. That makes the class suitable for one-time initialization that depends on run-time state. The following class resolves an OpenGL extension function once a GL context exists. A module-level variable cannot do this, because module-level initialization runs before the context exists, and **wglGetProcAddress** then returns Null. +After importing, the recommended first step is to save the project in the same directory as the original VBP so that relative paths to referenced files remain intact, then run or debug the project to identify any remaining issues. -```tb -[AppObject] -Class WdlGLInit - Public glArrayElementEXT As PFNGLARRAYELEMENTEXTPROC +_Source threads: 1083431012417155154 · confidence: medium_ +_Date range: 2023-05-27_ +_Reviewer note: This workflow tip belongs in the 'How do I import my VB6 project into twinBASIC?' FAQ entry, appended after the existing content. The surrounding FAQ entry already notes that resource files are imported automatically when selecting the .vbp; this sentence adds the save-in-place step. Verify that the "save in same directory" advice is still current best practice._ - Private Sub Class_Initialize() - glArrayElementEXT = wglGetProcAddress("glArrayElementEXT") - End Sub -End Class -``` +--- -A project can contain more than one **AppObject** class. +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1181674508449480815, 1330341380496560128, 1331867785944371301, 1396409493981626409] > [!NOTE] -> If a global of the same name is still defined elsewhere in the project, such as a leftover **Public** variable in a module, unqualified references bind to that global first. The **AppObject** member is then never used, and its value stays 0. +> To convert an imported VB6 project into a twinBASIC package, open the `.vbp` file in twinBASIC as usual, then open the project Settings and change the project type from **Standard EXE** to **Package**. After saving the settings the compiler restarts and the Package Manager pane becomes visible in the IDE. -_Source threads: 1125359845936205844 · confidence: high_ -_Date range: 2026-01-09_ -_Reviewer note: The Attributes page lists AppObject as applicable to CoClass only, while the thread applies it to a Class. Verify against the compiler and adjust the 'Applicable to' line if a Class is valid. The class and type names in the sample are adapted from WinDevLib; the sample is not compiled (it needs the OpenGL declarations)._ +_Source threads: 1180541541945712700 · confidence: high_ +_Date range: 2023-12-02_ +_Reviewer note: Insert near the existing `#vb6-import` FAQ entry. Confirmed by WaynePhillipsEA in the thread._ --- -## docs/Reference/CEF/index.md · after-remarks +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1330341380496560128, 1331867785944371301, 1396409493981626409] > [!NOTE] -> The CEF runtime files have no system-wide installer and do not register in a shared location. Both the 32-bit and the 64-bit runtime distributions can be present on the same machine at the same time, each in its own folder. A project targeting both architectures places the appropriate runtime files alongside each compiled binary. +> Windows 95 and Windows NT 4.0 are not supported targets. The runtime startup routine calls APIs absent from those platforms---for example, `GetVolumePathNameW` is not part of the Windows 95 / NT 4.0 kernel32---so produced binaries will fail to launch. The minimum version currently confirmed to work is Windows XP; the intended minimum target is Windows 2000. -_Source threads: 1503736509713481799 · confidence: high_ -_Date range: 2026-05-12_ -_Reviewer note: Place this note in or near the 'Runtime files' / 'Installing runtime files' subsection rather than at the top of the page._ +_Source threads: 1181674508449480815 · confidence: high_ +_Date range: 2023-12-05 to 2023-12-08_ +_Reviewer note: Insert into or alongside the existing `#runtime-requirements` FAQ entry, which already states 'minimum supported Windows version is Windows XP'. The WinMain entry-point override (for bypassing the startup routine) was described in the thread as unsupported for general use and intended only for kernel-mode driver targets --- omitted here to avoid encouraging an unsupported path. Verify that Windows 2000 status has not changed since the thread date._ --- -## docs/Reference/Compiler Constants.md · after-remarks +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1331867785944371301, 1396409493981626409] -## Target OS and compiler constants +> [!NOTE] +> Individual `.frm` form files cannot be added to an existing twinBASIC project after the initial import. To incorporate additional VB6 forms, start a new project that includes all required forms from the beginning. `.bas` module files and `.cls` class files can be imported individually into an existing project at any time. -The project's **Target OS** setting controls the minimum OS version recorded in the compiled PE header and gates certain runtime features --- for example, control transparency requires targeting Windows 8 or later. As of BETA 844, the Target OS value is not exposed as a conditional compilation constant, so code cannot branch on the target OS at compile time using `#If`. +_Source threads: 1330341380496560128 · confidence: medium_ +_Date range: 2025-01-19_ +_Reviewer note: This note should be added inside the `vb6-import` `
` block in the FAQ, after the existing NOTE callout about .bas/.cls import. Verify that the one-way form conversion restriction still applies in current builds._ -> [!NOTE] -> x64 (AMD64) builds cannot target Windows 2000, which never shipped a consumer 64-bit edition. The practical minimum for x64 builds is Windows XP x64 Edition. Only 32-bit twinBASIC builds can target Windows 2000. This is an OS-level constraint, not a twinBASIC limitation. +--- -_Source threads: 1389961632846843988 · confidence: high_ -_Date range: 2025-07-04 to 2025-07-06_ -_Reviewer note: Verify whether a Target OS compiler constant has been added since BETA 844 (July 2025). The page uses a non-standard format (## headings without frontmatter sections) --- match the existing style when inserting._ - ---- - -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1396409493981626409] > [!NOTE] -> -> A procedure used as an **AddressOf** callback target does not need to be **Public**. Declaring it **Private** restricts its visibility within the module while still allowing it to be invoked by the Windows API through the function pointer. +> Windows XP is not supported. The twinBASIC IDE requires Windows 7 or later because the IDE depends on WebView2, which does not run on Windows XP. Both 32-bit and 64-bit editions of Windows 7 are supported (fully updated). See the entry above for the update requirement on Windows 7. -_Source threads: 1091707854488096851 · confidence: low_ -_Date range: 2023-04-03_ -_Reviewer note: Source finding is low-confidence and mentions SetTimer/KillTimer specifically (VBA package, null symbol). Mapped to AddressOf as the closest reference page. Verify against the twinBASIC compiler behaviour --- in classic VBA, AddressOf targets must be in a standard Module and Public; confirm that Private callbacks work correctly in twinBASIC's implementation before publishing._ +_Source threads: 1331867785944371301 · confidence: high_ +_Date range: 2025-01-23_ +_Reviewer note: Insert inside or directly after the `system-requirements` `
` block, as a clarification that Windows XP is out of scope. The block already states 'Windows 7 through Windows 11' but does not explicitly call out XP. Note that the `runtime-requirements` entry separately states that *compiled EXEs* support Windows XP as a minimum --- this addition is only about the IDE._ --- -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] +## docs/Miscellaneous/FAQs.md · after-remarks [DUPLICATE? -- see also thread 1241295009110687764, 1332299294006448170, 1083431012417155154, 1180541541945712700, 1181674508449480815, 1330341380496560128, 1331867785944371301] > [!NOTE] -> When using Tab to autocomplete a function name after the **AddressOf** operator in the twinBASIC IDE, the editor may incorrectly append empty parentheses to the function name, producing `AddressOf FunctionName()` instead of the correct `AddressOf FunctionName`. The trailing parentheses are invalid in this position and must be deleted manually. +> twinBASIC produces native machine code comparable to a C++ or VB6 native-compilation build. Tools designed to decompile VB6 p-code or .NET IL --- such as VB6 Decompiler or .NET reverse-engineering utilities --- cannot be applied to twinBASIC executables or DLLs. Analyzing a twinBASIC binary requires native-code tools such as x64dbg or IDA. -_Source threads: 1175257978023182426 · confidence: medium_ -_Date range: 2023-11-18_ -_Reviewer note: IDE editor bug reported in beta 423. Verify whether this has been fixed in a later release before publishing._ +_Source threads: 1396409493981626409 · confidence: high_ +_Date range: 2025-07-20 to 2025-07-27_ +_Reviewer note: FAQs.md is not in the page-index. The note fits best in the 'Using twinBASIC' section, potentially as a new FAQ entry about reverse engineering / decompilation. Reviewer should decide whether to add a new
entry or fold this into the existing backwards-compatibility FAQ._ --- -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] - -> [!NOTE] -> Applying **AddressOf** to a procedure obtained through a **Declare** statement introduces compiler-generated indirection on top of the raw entry point. The address produced is not the same as the pointer returned by `GetProcAddress` for the same function. When this indirection causes a delegate invocation to behave differently from a direct call---for example, a caller-allocated buffer pattern that succeeds as a direct call but silently returns an empty result through the delegate---obtain the pointer explicitly with `GetProcAddress` and cast it to the target delegate type: -> -> ```tb -> Dim lpfn As LongPtr = GetProcAddress(hLib, "FunctionName") -> Dim d As YourDelegate = CType(Of YourDelegate)(lpfn) -> ``` -> -> This produces a clean function pointer with no extra indirection, and the delegate invocation then behaves identically to a direct call. +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1202802099356966962, 1332299294006448170, 1447957791770677382] -_Source threads: 1499900664275275969 · confidence: high_ -_Date range: 2026-05-01 to 2026-05-02_ +
+The IDE reports "IDE was unable to read builtin package file data" (Internal Error 33330). ---- +This error occurs when a new twinBASIC ZIP is extracted into a folder that already contains an older installation. A slight change in the folder structure between releases means the mixed old and new files cause the error. -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1291033244753727490, 1253786438454214788, 1156840378213867620] +To fix it: delete the old twinBASIC folder completely, then extract the new ZIP into a fresh, empty directory. If the error persists after a clean extraction, check whether your antivirus software has quarantined any of the twinBASIC files --- see [The IDE reports missing files](#missing-files) for steps to resolve that. -> [!NOTE] -> When `AddressOf` targets a class member (e.g. `AddressOf myInstance.Func`), twinBASIC generates an on-the-fly stub that captures the specific object instance and dispatches through it. The returned pointer is therefore unique per instance---it is **not** the address of the function as it appears in the vtable. Call the returned pointer as a callback and the implicit `Me` argument is populated automatically for that object. To obtain the raw vtable entry address instead, use pointer arithmetic on `ObjPtr`; that is only necessary for low-level COM work and is not required for ordinary callback use. +
-_Source threads: 1136660342739566635 · confidence: high_ -_Date range: 2023-08-03 to 2023-08-16_ +_Source threads: 1169313912823496866 · confidence: high_ +_Date range: 2023-11-01 to 2023-11-10_ +_Reviewer note: This entry belongs in the Installation section of FAQs.md, ideally placed after the existing 'missing-files' entry (around line 177) since it references that entry. The existing 'installation' FAQ (line 180) already advises extracting to an empty folder but does not describe this specific error code. Confirm the exact placement within the Installation section._ --- -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1253786438454214788, 1156840378213867620] +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1332299294006448170, 1447957791770677382] -> [!NOTE] -> **AddressOf** does not return the raw machine address of a procedure. twinBASIC creates a stub thunk behind the scenes so that calling conventions and the implicit object pointer are handled correctly when the callback is invoked. VB6 code that identifies a callback by ordinal position rather than by name --- for example, passing the integer `1` to mean "the last Sub in the file" --- cannot be ported mechanically: that pattern relied on VB6's internal vtable numbering, which twinBASIC does not reproduce. Replace the ordinal reference with an explicit **AddressOf** *ProcedureName* expression, where *ProcedureName* is the actual name of the target procedure. In VB6 projects using this pattern the target was typically the last Sub or Function declared in the module. +
+My VB6 import stalls partway through on a large project. -_Source threads: 1291033244753727490 · confidence: high_ -_Date range: 2024-10-02_ +When converting a large VB6 project, the import progress bar may appear to complete, the Project Explorer fills briefly, then clears---after which a second progress bar appears but never advances. The IDE remains open but unresponsive. This is a symptom of the compiler process running out of virtual address space. ---- +On 64-bit Windows, a standard Win32 process has a 2 GB virtual address space limit. Known memory leaks in the compiler (present during the BETA period) can push memory use above 1.8 GB for large projects, which is enough to cause a crash at that limit. -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1156840378213867620] +The workaround is to enable the **Large Address Aware (LAA)** flag, which raises the virtual address limit to 4 GB for a 32-bit process running on 64-bit Windows. Enable it in Project Settings before retrying the import. > [!NOTE] -> -> In VB6, **AddressOf** is restricted to procedures in standard modules (BAS files). Using it inside a class module or form requires self-subclassing workarounds such as a separate forwarding module. In twinBASIC that restriction does not apply --- **AddressOf** accepts any method regardless of its containing module type, making patterns like Win32 `SetWindowSubclass` straightforward to implement directly on a class. +> This workaround addresses the symptom. The underlying memory leaks are a known issue being resolved in later releases. Once the leaks are fixed, LAA will no longer be required for import. -_Source threads: 1253786438454214788 · confidence: high_ -_Date range: 2024-06-25_ -_Reviewer note: The existing twinBASIC enhancements section already documents that AddressOf works on class, form, and UserControl members. This note adds the VB6 restriction context. Check whether it duplicates or enriches the existing text before inserting, and consider integrating into the existing bullet point instead._ +
+ +_Source threads: 1202802099356966962 · confidence: high_ +_Date range: 2024-02-02 to 2024-02-07_ +_Reviewer note: Insertion point: after the closing
of the 'vb6-import' entry (line 246 in the current file) and before the 'unrecognized-variables' entry. The LAA setting referenced here appears in docs/IDE/Project Settings.md at the 'Large Address Aware (LAA)' heading. Verify the current compiler memory situation -- the thread is from February 2024 (BETA) and the leaks may have been partially addressed since then; if so, soften the NOTE or remove it._ --- -## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788] +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1202802099356966962, 1447957791770677382] -> [!NOTE] -> **64-bit only:** In VBA 64-bit (and twinBASIC running in 64-bit mode against a VBA-hosted runtime), the addresses of procedures declared in a standard module are uninitialized when first queried via **AddressOf** --- until those procedures have been called at least once from within Basic code. If a Windows API (for example, one that invokes a dialog callback or a WndProc) calls through the address before any Basic code has invoked the procedure, the address is not valid and the program crashes. -> -> The workaround is to add a public no-op `Sub` to the same module as the callbacks or WndProcs, then call it from early initialization code (for example, `Class_Initialize`) before passing any **AddressOf** addresses to external APIs. This forces the runtime to initialize the procedure addresses. -> -> This issue affects any 64-bit project that passes module-level function addresses as callbacks or WndProcs to Windows APIs. +
+How do I install WebView2 on Windows 7 or Windows 8.1? -_Source threads: 1156840378213867620 · confidence: high_ -_Date range: 2023-09-28_ -_Reviewer note: Verify whether this 64-bit uninitialized-address behavior still occurs in current twinBASIC builds, or whether it has been fixed. The finding was reported against VBA 64-bit hosted code; behavior in native twinBASIC 64-bit may differ._ +Microsoft dropped Windows 7 and Windows 8.1 support from the WebView2 evergreen installer after version 109. Running any newer evergreen installer on those systems produces an "installer failed to start" error. Version 109 (109.0.1518.140) is the last release to support these operating systems and must be installed instead. ---- +Because version 109 is no longer distributed as a standalone download, the procedure to install it is: -## docs/Reference/Core/AddressOf.md · after-remarks +1. Open the [Microsoft Update Catalog](https://www.catalog.update.microsoft.com/Search.aspx?q=webview2%20109) and search for **webview2 109**. +2. Download the package for your architecture. +3. The downloaded file cannot be run directly --- it must first be extracted using a tool such as 7-Zip. +4. After extraction, locate the file named `MicrosoftEdge_X64_109.0.1518.140.exe.{GUID}` (the exact GUID varies). Rename it by removing the `.{GUID}` suffix so that the name ends in `.exe`. +5. Running the renamed executable without arguments installs the full Edge browser. To install only the WebView2 runtime that twinBASIC requires, pass the following flags: -> [!NOTE] -> A callback installed with **AddressOf** can also serve as a vectored exception handler, registered through the `AddVectoredExceptionHandler` API. Such a handler can recover from a CPU exception, such as an access violation, by advancing the instruction pointer in the `CONTEXT` structure (`Eip` in a 32-bit process, `Rip` in a 64-bit process). One report says this protection works only in a compiled EXE and does not work while the project runs under the IDE debugger, so test any handler of this kind in a compiled build. + ``` + MicrosoftEdge_X64_109.0.1518.140.exe --msedgewebview --system-level + ``` -_Source threads: 1507659583437668512 · confidence: medium_ -_Date range: 2026-05-23_ -_Reviewer note: Single-source claim from a Discord show-and-tell; the author corrected an earlier statement that it also worked in the IDE. Verify by probe (tbrun) that a VEH handler registered via AddressOf fails to catch access violations in the IDE debug run but works in a compiled EXE. Placed on AddressOf because the finding named it (package VBA, symbol AddressOf); the page index has AddressOf only under Core._ +After the runtime installs, relaunch the twinBASIC IDE. If the IDE still reports a missing WebView2 installation, verify that the correct architecture was downloaded and that the `--system-level` flag was included. + +
+ +_Source threads: 1332299294006448170 · confidence: high_ +_Date range: 2025-01-26 to 2025-03-07_ +_Reviewer note: This new FAQ entry should be placed in the Installation section, immediately after the existing 'system-requirements' entry (around line 156 of FAQs.md). The x64 filename in the extract step should be verified against the actual catalog download --- the 32-bit variant uses the x86 filename instead._ --- -## docs/Reference/Core/Alias.md · after-remarks [DUPLICATE? -- see also thread 1459159860875624595] +## docs/Miscellaneous/FAQs.md · new-section [DUPLICATE? -- see also thread 1169313912823496866, 1202802099356966962, 1332299294006448170] -> [!NOTE] -> -> **POINTAPI compatibility change (BETA 947 / v9.2.634.0):** `POINTAPI` was changed from a standalone UDT to a type alias (`Public Alias POINTAPI As POINT`). Because type aliases are interchangeable for simple assignments and scalar parameters, most code continues to work; however, passing a `POINTAPI()` array to an API parameter declared as `POINT()` raised Runtime Error 5 until BETA 953, where the underlying mismatch was corrected. If porting code that uses `POINTAPI` arrays, prefer declaring both the array variable and the API parameter with the same type name (`POINT` or `POINTAPI`) to avoid the issue on intermediate builds. +
+Double-clicking a .twinproj file in Explorer does not open twinBASIC. -_Source threads: 1455209665733464247 · confidence: high_ -_Date range: 2025-12-29 to 2026-01-17_ +Each time `twinbasic.exe` is launched, it registers itself in the Windows registry as the application associated with `.twinproj` files. Because twinBASIC has no installer, if the IDE executable is deleted or moved (for example when removing an older BETA release) before the replacement version has been launched at least once, the `.twinproj` file association points to a non-existent path. Double-clicking a `.twinproj` file in Explorer will then fail to open twinBASIC. + +The fix is to launch the new `twinbasic.exe` directly from its folder before removing the old version, so that the registry entry is updated to the new path. + +
+ +_Source threads: 1447957791770677382 · confidence: high_ +_Date range: 2025-12-09 to 2025-12-10_ +_Reviewer note: This entry belongs in the Installation section of the FAQs page, after the existing entries for missing files and the installation procedure. The page uses
/ HTML elements; match that formatting exactly._ --- -## docs/Reference/Core/Alias.md · after-remarks [DUPLICATE? -- see also thread 1455209665733464247] +## docs/Miscellaneous/FAQs.md · new-section -> [!NOTE] -> The twinBASIC IDE automatically resolves alias chains when the underlying type is itself an alias. Writing `Public Alias MyPtr As LongPtr`, for example, causes the editor to rewrite `LongPtr` to `Long` or `LongLong` depending on the target architecture, because `LongPtr` is itself an alias. This behavior applies to any alias-of-alias, not only `LongPtr`. The rewrite is silent and occurs as soon as the source file is edited. The issue was a regression in BETA 950 and was fixed in BETA 951. +
+The IDE window opens off-screen after I disconnect a monitor. How do I get it back? -_Source threads: 1459159860875624595 · confidence: high_ -_Date range: 2026-01-09 to 2026-01-16_ +If the IDE was last used on an extra monitor that is no longer connected, its window can open outside the visible area of the screen. Right-click the IDE's title bar or its taskbar button and choose **Move**, then use the arrow keys or the mouse to bring the window back onto the screen. + +
+ +_Source threads: 1363863830437822484 · confidence: low_ +_Date range: 2025-04-21 to 2026-08-21_ +_Reviewer note: Add under 'Using twinBASIC' in the FAQs. Low confidence: a user reported it still happening in August 2026, so it may be fixed or not; the suggestion of a default window.left=0 setting was not confirmed to exist. Verify the Move steps on Windows 10 and 11._ --- -## docs/Reference/Core/Alias.md · after-remarks +## docs/Miscellaneous/FAQs.md · after-remarks -> [!NOTE] -> In builds around BETA 983 and 984, a function whose return type is an alias of an alias of a user-defined **Type** larger than 8 bytes crashed the compiler at project start, even if nothing called the function. Returning the base type, or an alias one level deep, avoids the crash. One user reported this, and no fix was confirmed. +If the extraction fails or a file cannot be replaced, a `twinBASIC_win64.exe` process may still be running from the previous session. It is the LLVM cache process, which keeps running after the IDE closes. End it in Task Manager, or see [General LLVM options](LLVM/Getting-Started#general-llvm-options) to turn the behaviour off. -_Source threads: 1548875667045621801 · confidence: medium_ -_Date range: 2026-09-14_ -_Reviewer note: Single unconfirmed report of a compiler defect. Consider moving it to BUGS-TO-REPORT.md instead, or dropping it once a later build is tested with the reproduction (Type over 8 bytes; Alias aliasA As myType; Alias aliasB As aliasA; Function test() As aliasB)._ +_Source threads: 1553387151842877491 · confidence: medium_ +_Date range: 2026-09-26 to 2026-09-27_ +_Reviewer note: Place at the end of the 'How do I install twinBASIC?' FAQ entry, after the sentence about not overwriting a previous version. The thread's community advice (extract into a new folder or empty the old one, because overwriting old betas has caused hard-to-trace bugs) is already on that page. Verify the relative link resolves from /FAQ._ --- -## docs/Reference/Core/Alias.md · after-remarks +## docs/Miscellaneous/FAQs.md · after-remarks -> [!IMPORTANT] -> In some recent BETA builds, an **Alias** whose target is another **Alias** crashes the program at startup when a function returns it. Any code that refers to such a function is enough, even if the function is never called. For example, with `Public Alias D2D_COLOR_F As D3DCOLORVALUE` and `Public Alias D2D1_COLOR_F As D2D_COLOR_F`, code that uses a function returning `D2D1_COLOR_F` crashes at startup. As a workaround, make each alias refer directly to a real **Type** or intrinsic type, so that no alias has another alias as its target. +
+Can a twinBASIC project be saved back to VB6 files? -An alias of a pointer type is not supported. A **ByRef** parameter that is an array of an alias type also had a bug in some builds around January 2026. +Only in part. Code kept in `.bas` and `.cls` files can be shared with VB6, at the cost of the new language features and Unicode support. The `.frm` and `.ctl` formats cannot hold the many form and control properties and attributes that are specific to twinBASIC, so a form cannot be taken back to VB6, and migration from VB6 is in practice one-way. The `.twinproj` file is packed JSON, so scripts can unpack and repack it for editing with external tools. -_Source threads: 1125359845936205844 · confidence: medium_ -_Date range: 2026-01-13 to 2026-09-14_ -_Reviewer note: The page states that an Alias may target another Alias, so this is a compiler bug that may be fixed in a later BETA; check the current build and remove or date the note accordingly. The thread also says WinDevLib's alias use needs BETA 923 or newer. The pointer-type and array-parameter remarks are from single reports and are unverified._ +
+ +_Source threads: 1537389045099921470 · confidence: medium_ +_Date range: 2026-08-13 to 2026-08-14_ +_Reviewer note: Match the FAQ's existing details markup and choose a fitting position; verify the .twinproj JSON claim._ --- -## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1157647794484543589, 1481282096877146244, 1458787436607045693, 1459196491380818071, 1485008719719698604] +## docs/Miscellaneous/FAQs.md · after-remarks -> [!NOTE] -> The compiler unconditionally generates implementations of the three core `IUnknown` members --- `AddRef`, `Release`, and `QueryInterface` --- for every **Class**. `AddRef` and `Release` manage the object's reference count and release all associated memory when the count reaches zero; the compiler enforces this by raising a "duplicate implementation of member" error if user code defines `AddRef` or `Release`. `IDispatch` members, by contrast, can be manually implemented. If custom `IUnknown` forwarding is required (for example, to expose a vtable-based interface that includes `IUnknown` members at known offsets), the only supported path is vtable patching at runtime. +twinBASIC cannot replace the VBA editor inside Office. As an alternative, a twinBASIC program can reference an Office application's object model (Excel, Access and so on) and automate it from outside; such a program can still show a full application window of its own when needed. -_Source threads: 1060905587577208863 · confidence: high_ -_Date range: 2023-01-06_ -_Reviewer note: Verify against the compiler source or a test project that AddRef/Release genuinely raise a compile error, and that QueryInterface does not (the source described that as a bug rather than a supported pattern)._ +_Source threads: 1120445096865828865 · confidence: medium_ +_Date range: 2023-06-19 to 2026-10-20_ +_Reviewer note: Place in the addins FAQ entry, after the paragraph on creating addins for VBA. Thread says only a proof of concept existed for hooking into the Office editor; verify the current status with the maintainer._ --- -## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1481282096877146244, 1458787436607045693, 1459196491380818071, 1485008719719698604] +## docs/Reference/Assert/index.md · after-remarks > [!NOTE] -> twinBASIC dispatches class method calls through the native COM vtable function pointer, as the COM specification requires. VBA64 (Office 2010 with service packs and later) does not: when it detects that the target object is owned by its own runtime, it bypasses the vtable and jumps directly to the p-code stream. As a result, replacing a vtable slot with a pointer to custom executable memory works correctly in twinBASIC but has no effect in VBA64 --- and may crash it, because the VBA64 runtime navigates from the original vtable function pointer to the p-code stream and expects the pointer to be intact. This optimization was absent in Office 2010 RTM, where native vtable calls were used and vtable slot replacement worked. +> When a subroutine is annotated with `[TestCase]`, the twinBASIC IDE hides the inline **Run** action that normally appears above the `Sub` declaration. To run a parameterised test interactively, create a wrapper subroutine without `[TestCase]` that calls the test with the desired arguments. -_Source threads: 1157647794484543589 · confidence: high_ -_Date range: 2023-09-30 to 2023-10-01_ -_Reviewer note: Placement on the Class page is reasonable but this could alternatively live on a dedicated low-level/interop features page if one is added. The p-code navigation detail (vtable pointer as a key to locate the p-code stream) is confirmed by WaynePhillipsEA (twinBASIC author) in the thread._ +_Source threads: 1194682995538407434 · confidence: medium_ +_Date range: 2024-01-10_ +_Reviewer note: Verify the [TestCase] inline-run behaviour against the current IDE release; this was observed in early 2024 and may have changed. Also check whether a Test Explorer panel has been added to the IDE since then, as the finding notes none existed at that time._ --- -## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1458787436607045693, 1459196491380818071, 1485008719719698604] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> A **Public** array member of a class is automatically converted by the compiler into a **Property Get** / **Property Let** pair. The **Property Get** returns a *copy* of the array, not a reference to the storage inside the object. As a result, writing to an element through the returned copy --- or calling **ReDim** on it --- has no effect on the object's own data, and no compile-time error is raised. For example, `Set obj.Children(1) = node` silently discards the assignment, and `ReDim obj.arr(4)` raises a run-time error because `obj.arr` evaluates to a copy, not the original storage. +> twinBASIC enables integer overflow checks by default (`True`), which differs from compiled VB6 executables. VB6 raises overflow errors only when running in the IDE; compiled VB6 executables do not check for integer overflow at runtime. Apply `[IntegerOverflowChecks(False)]` to a function to suppress overflow checking for that function and match compiled VB6 behavior. -To expose a mutable array from a class, declare the field **Private** (or **Protected**) and provide dedicated mutation methods: +_Source threads: 1120535095279886416 · confidence: high_ +_Date range: 2023-10-22_ +_Reviewer note: This addition targets the existing `IntegerOverflowChecks` section (line 413) in docs/Reference/Attributes.md. The note should be inserted after the one-line description "Disables integer overflow checks. Used on performance-critical routines. The default value is **True**." Verify that the VB6 compiled-mode behavior is still accurate for current twinBASIC releases._ -```tb -Private Children() As Node +--- -Public Sub SetChild(index As Long, n As Node) - Set Children(index) = n -End Sub -``` +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -Arrays inherited from a base class are not affected --- they behave as direct field accessors. +> [!NOTE] +> The `[Description()]` value must be a single-line string. If the value contains newline characters (for example, produced by concatenating `vbCrLf` into the string), the IDE accepts the declaration without a compile error, but hovering over the member or invoking autocomplete on it produces a 'bad JSON received' error. The LSP response embeds the description text directly inside a JSON string, and a literal newline in that string breaks JSON parsing. Keep `[Description()]` values to a single line with no embedded newline characters. -_Source threads: 1481282096877146244 · confidence: high_ -_Date range: 2026-03-11_ -_Reviewer note: Verify that the base-class exception (inherited arrays not subject to the copy behavior) is still accurate in the current compiler._ +_Source threads: 1172641655631908884 · confidence: medium_ +_Date range: 2023-11-10_ +_Reviewer note: Verify whether this limitation has been fixed in recent IDE builds. The thread is from November 2023 and the behaviour may have changed. Anchor this note directly after the Description attribute's prose (after the 'helpstring attribute in the type library' sentence, before the next ## heading)._ --- -## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1459196491380818071, 1485008719719698604] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Calling any method on a **Nothing** reference raises a run-time error at the call site --- the method body is never entered. All class methods are virtual (vtable) calls, and the runtime checks the instance before dispatching. A method body that reads `VarPtr(Me) = 0` to detect a **Nothing** caller will therefore never execute. -> -> Two workarounds are available: -> - Declare the method as `Protected` (which removes its vtable entry) and call it through [`UnprotectedAccess`](../../Modules/HiddenModule/UnprotectedAccess) at the call site. -> - Wait for proper static (`Shared`) class members, which are not yet implemented. -> -> Note also that `IUnknown::AddRef` and `Release` are called implicitly on the `Me` argument in member prologue and epilogue, so additional issues arise if a null instance reaches the method body by any other means. +> When typing `[CoClassId(...)]`, IntelliSense offers to auto-generate a random GUID --- the same prompt available for `[InterfaceId(...)]`. This behavior was introduced in BETA 438; earlier versions required the GUID to be generated and pasted manually. -_Source threads: 1458787436607045693 · confidence: high_ -_Date range: 2026-01-08_ +_Source threads: 1202939463630594079 · confidence: high_ +_Date range: 2024-02-02 to 2024-02-06_ +_Reviewer note: Insert this note within the `## CoClassId` section of Attributes.md, after the existing syntax/applicability description. Verify against the current IDE build that the GUID auto-generation prompt is still present for both [CoClassId] and [InterfaceId]._ --- -## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1458787436607045693, 1485008719719698604] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> A `.cls` file can contain only one class definition, matching VB6 behaviour. A `.twin` file may contain multiple **Class** blocks --- along with [**Interface**](Interface), [**CoClass**](CoClass), [**Module**](Module), and [**Alias**](Alias) declarations --- in the same file. Classes defined in a `.twin` file are full, normal classes, accessible from anywhere in the project. +> `[IgnoreWarnings]` applies only to diagnostic codes in the **TB0000--TB4999** range (warnings). Compilation errors are all reported under the single code **TB5000** and cannot be suppressed. Unique error IDs in the TB5000+ range are planned for a future release to allow associated help pages and easier searching. -_Source threads: 1459196491380818071 · confidence: high_ -_Date range: 2026-01-09 to 2026-01-10_ -_Reviewer note: The existing Class.md body already notes that .twin files may share a file with Interface/CoClass/Module, but does not explicitly say multiple Class blocks are allowed in one .twin file. Verify this and merge with the existing paragraph rather than duplicating it._ +_Source threads: 1212815197501395054 · confidence: high_ +_Date range: 2024-02-29_ +_Reviewer note: Insert this NOTE immediately after the IgnoreWarnings syntax line (line 411 in Attributes.md), before the next section heading. Verify current status of unique TB5000+ IDs against the latest twinBASIC build notes._ --- -## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1458787436607045693, 1459196491380818071] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Generic classes do not support inheritance. A class declared with a type parameter (e.g. `Class Derived(Of T)`) cannot use `Inherits`. Attempting to do so does not produce an immediate compile error, but accessing any inherited `Protected` member from within the generic derived class produces error TB5079 "Unrecognized symbol" when the generic class is instantiated. `Public` members of the base class may appear to work, which can mask the underlying lack of support. The limitation only surfaces at the point where the first instance of the generic class is created. Inheritance support for generic classes is planned for a future release. +> `[IntegerOverflowChecks(False)]` does not suppress overflow errors when the overflowing expression contains a division operation (e.g. `(6 + 52 / 4) * &H9E3779B9`). Division produces a `Double` intermediate, so the overflow is detected by the floating-point unit rather than the integer overflow checker. Apply `[FloatingPointErrorChecks(False)]` alongside `[IntegerOverflowChecks(False)]` to suppress it. Before BETA 473, `[FloatingPointErrorChecks(False)]` had no effect when all operands were compile-time constants; moving at least one operand into a variable was the workaround. This constant-folding limitation was resolved in BETA 473. -_Source threads: 1485008719719698604 · confidence: high_ -_Date range: 2026-03-21 to 2026-03-23_ -_Reviewer note: Verify against current compiler behaviour -- specifically whether TB5079 is the exact error number and whether Public member access truly works when Protected fails._ +_Source threads: 1218599950359855186 · confidence: high_ +_Date range: 2024-03-16 to 2024-03-18_ +_Reviewer note: Insert immediately after the existing `IntegerOverflowChecks` description paragraph (after line 420 of Attributes.md). Verify the BETA 473 version number against the release notes._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> In early twinBASIC releases, a `Const` declaration that referenced an enum member defined in a separate standard module (for example, `Private Const MyConst As Long = MyEnum.MyEnumValue`) could fail to compile when the compiler processed the class module before the standard module declaring the enum. The failure was evaluation-order-dependent: adding, removing, or reordering modules in the project could change whether it compiled. This was fixed by the twinBASIC maintainer shortly after December 2022. +> Applying `[IntegerOverflowChecks(False), FloatingPointErrorChecks(False)]` to a procedure does not necessarily prevent overflow or floating-point errors from firing at runtime inside the IDE. However, when both attributes are present, the IDE's **Resume Next** button in the error popup works correctly and execution continues normally. Without them, the IDE can stop responding on the error popup, requiring the process to be terminated externally. The source of such an overflow may be a code generation issue in a neighboring line rather than the line the error reports, making isolation by reproducing the values alone unreliable. (The underlying FPU code generation edge case was fixed in BETA 520.) -_Source threads: 1053323243085385828 · confidence: high_ -_Date range: 2022-12-16 to 2023-01-07_ -_Reviewer note: Describes a now-fixed compiler bug. Verify against release notes around January 2023 to confirm the fix landed and the version number, if it should be cited._ +_Source threads: 1230934350959345735 · confidence: high_ +_Date range: 2024-04-20 to 2024-04-24_ +_Reviewer note: Insert this note after the existing one-line description under the `IntegerOverflowChecks` section (line 420 in Attributes.md), before the next `##` heading. The note refers to both IntegerOverflowChecks and FloatingPointErrorChecks used together._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> twinBASIC warns when a hex literal is implicitly coerced from **Integer** to **Long** and sign-extended --- for example, `&H8000` becoming `&HFFFF8000`. This warning is suppressed when the hex value appears in a **Const** declaration, because the constant retains its **Integer** representation and no coercion occurs at that point. The warning fires only at the site of the actual coercion to **Long**, not at the declaration. +> The description text is rendered as markdown in the IDE's IntelliSense tooltip. Embedding `vbCrLf` in the string literal does not produce a visible line break. To force a line break, end the line with a trailing backslash before the string concatenation, for example: +> +> ```tb +> [Description("Line 1\" & vbCrLf & _ +> "Line 2")] +> ``` -_Source threads: 1071216635966402610 · confidence: high_ -_Date range: 2023-02-04 to 2023-02-07_ +_Source threads: 1252891072774803487 · confidence: high_ +_Date range: 2024-06-19 to 2024-06-22_ +_Reviewer note: Verify the exact syntax for line breaks in Description attribute strings. The example in the detail uses a backslash before the closing quote of each segment; confirm this is correct twinBASIC syntax and that the trailing-backslash trick actually works as described. This note belongs under the Description attribute entry (the #description anchor), not at the top of the file._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> A `Public Const` expression that references an enum member defined in another module may produce an "Error evaluating constant expression" compile error if the module containing the enum sorts alphabetically after the module containing the constant. The compiler evaluates constant expressions during an early pass; if the enum's defining module has not yet been processed, the value is not known at that point. To avoid this, either place the enum and the constants that depend on it in the same module, or name the module containing the enum so that it sorts alphabetically before the module that references it. +> The placement of `[TypeHint]` differs depending on what is being annotated. For a **parameter**, place the attribute on the line immediately before the parameter declaration inside the procedure signature. For a **Property Get or Function return value**, place the attribute on the line immediately before the entire procedure declaration --- not inline with the return type. Example: +> +> ```tb +> ' TypeHint on a return value: attribute goes above the procedure +> [TypeHint(ScaleModeConstants)] +> Property Get ScaleMode() As Integer +> ScaleMode = m_ScaleMode +> End Property +> +> ' TypeHint on a parameter: attribute goes above the parameter +> Property Let ScaleMode( +> [TypeHint(ScaleModeConstants)] ByVal Value As Integer) +> m_ScaleMode = Value +> End Property +> ``` -_Source threads: 1082234628385034240 · confidence: medium_ -_Date range: 2023-03-06_ -_Reviewer note: Reported behavior included non-deterministic failure across IDE restarts, suggesting the module load order may not be strictly alphabetical in all cases. Verify whether this has been fixed in a later twinBASIC build before publishing._ +_Source threads: 1333587859474747413 · confidence: high_ +_Date range: 2025-02-03_ +_Reviewer note: This addition should be inserted into the TypeHint section (around line 633-640) of docs/Reference/Attributes.md, after the 'Applicable to:' line. Verify the code example compiles correctly in twinBASIC --- the thread confirms the behaviour but the exact syntax should be checked against the Attributes page style._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Before twinBASIC BETA 298, a literal value that exceeded the range of its type-declaration character suffix (for example, `&H10012002%`, where `%` forces **Integer** type) was silently truncated rather than reported as a compiler error. The same code raises an error at edit time in VBA and VBx. The truncation affected both **Const** declarations and variable assignments. The bug was fixed in BETA 298; such out-of-range literals now produce a compiler error as expected. +> **[OleAutomation]** sets the `TYPEFLAG_FOLEAUTOMATION` flag in the type library. The flag matters only when the type library is registered with `RegisterTypeLib`, which skips interfaces that lack it. It therefore affects ActiveX DLLs and has no effect in a Standard EXE. Unlike MIDL and mktyplib, where the default is **False**, twinBASIC defaults to **True**. -_Source threads: 1096942103180869632 · confidence: high_ -_Date range: 2023-04-15 to 2023-05-12_ +_Source threads: 1352268312117252126 · confidence: high_ +_Date range: 2025-06-05_ +_Reviewer note: Intended for the OleAutomation section of Attributes.md; the page index has no entry for it. The default (True) statement already exists on the page; consider trimming._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> A self-referential `Const` declaration --- one where the constant name appears on the right-hand side of its own initializer, for example `TVN_BEGINRDRAG = TVN_BEGINRDRAG` instead of the intended `TVN_BEGINRDRAG = TVN_BEGINRDRAGW` --- causes the twinBASIC IDE syntax highlighter to enter an endless loop. The symptom is that all syntax coloring stops working and every token renders in a single color; the broken state persists after restarting the compiler. Correcting the self-referential assignment restores normal highlighting. This bug was fixed in BETA 312. +> Within a `[RunAfterBuild]` subroutine, `App.LastBuildPath` returns the full path to the just-produced executable or DLL. This makes it straightforward to automate post-build tasks such as code signing or file copying. -_Source threads: 1110426304710459442 · confidence: high_ -_Date range: 2023-05-23_ +_Source threads: 1079642242429489262 · confidence: high_ +_Date range: 2023-02-27_ +_Reviewer note: Insert immediately after the existing RunAfterBuild section description (currently lines 582-589 of docs/Reference/Attributes.md). The existing text ends with the `App.LastBuildPath` mention but is informal and contains a typo ('Tthere's'); this NOTE supplements the corrected prose that should replace it._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Constants whose *expression* references a constant declared in a different module may intermittently fail to evaluate on compiler startup, producing a "cannot evaluate constant expression" error. The failure is non-deterministic: restarting the compiler without changing any source can alternate between reporting the error and not. A reliable workaround is to declare a private copy of the base constant in the same module that uses it, eliminating the cross-module dependency. +> Support for `[Description]` on **Const** declarations was added in BETA 268. Prior builds accepted the syntax but the description did not appear in IntelliSense tooltips. -_Source threads: 1201555639005106257 · confidence: high_ -_Date range: 2024-01-29 to 2026-02-05_ +_Source threads: 1082767362253668474 · confidence: high_ +_Date range: 2023-03-07 to 2023-03-12_ +_Reviewer note: Insert inside the existing Description section (currently lines 263-270 of docs/Reference/Attributes.md), after the applicability line that lists Const. Verify the BETA 268 version number against release notes if available._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1487572362898444458, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> **Hexadecimal literals and type suffixes.** A hexadecimal literal is typed by its magnitude. The literal `&H8000` fits in a 16-bit **Integer** and is therefore interpreted as **Integer** `-32768`, not **Long** `32768`. Declaring `Const SOMECONSTANT As Long = &H8000` triggers warning TB0012 (*Possible misuse of hexadecimal literal notation*) because the literal is being widened --- a sign that the value is almost certainly wrong. The correct form is `&H8000&`, where the trailing ampersand forces the literal to **Long** type, giving the value `32768`. -> -> The practical consequence is that comparisons or flag checks using the unqualified form can evaluate to unexpected results --- for example, `&H8000 >= &H100` evaluates to `False` because `&H8000` is `-32768` as an **Integer**. +> The twinBASIC default for `[OleAutomation]` is **True**, which is the opposite of the MIDL convention where `[oleautomation]` defaults to absent (equivalent to **False**). When importing a type library, interfaces that should not be OLE Automation compatible must be marked `[OleAutomation(False)]` explicitly, because omitting the attribute leaves them as **True** in twinBASIC. -_Source threads: 1382265105621979166 · confidence: high_ -_Date range: 2025-06-11 to 2025-06-25_ +_Source threads: 1083119492789448824 · confidence: high_ +_Date range: 2023-03-08 to 2023-03-12_ +_Reviewer note: Insert inside the existing OleAutomation section (currently lines 454-462 of docs/Reference/Attributes.md), after the existing 'This attribute is set to True by default' sentence._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1069548868200583168, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -> [!NOTE] -> Certain extreme numeric literals require special handling when used in **Const** declarations: -> -> **Currency.** The `@` suffix is required to tell the compiler that the literal is **Currency**. The accepted minimum value constant is `-922337203685477.5807@`; the literal `-922337203685477.5808@` is rejected by the compiler. This matches VB6 behavior. -> -> **LongLong.** The minimum **LongLong** value cannot be written as a single negative literal. Use the expression form instead: -> ```tb -Public Const MINLONGLONG As LongLong = (-9223372036854775807^ - 1) -``` -> or a string literal: `Public Const MINLONGLONG As LongLong = "-9223372036854775808"`. -> -> **Decimal.** There is no type-suffix character for **Decimal**, so extreme-range constants must be written using `CDec` with a string argument: -> ```tb -Public Const G_MAX_DECIMAL As Decimal = CDec("79228162514264337593543950335") -Public Const G_MIN_DECIMAL As Decimal = CDec("-79228162514264337593543950335") +The argument accepts a comma-separated list of enum type names to merge members from multiple enumerations into a single IntelliSense completion list. This is useful when a single numeric parameter draws values from two logically distinct enumerations that should not be merged into one physical **Enum**. + +```tb +[TypeHint("SomeEnum, OtherEnum")] +Public Sub SetFlags(ByVal flags As Long) ``` -_Source threads: 1487572362898444458 · confidence: high_ -_Date range: 2026-03-28 to 2026-03-30_ +IntelliSense will then show members from both `SomeEnum` and `OtherEnum` as candidates for the `flags` argument. + +_Source threads: 1103494252811526164 · confidence: high_ +_Date range: 2023-05-04 to 2023-05-12_ +_Reviewer note: Insert after the existing one-line description of TypeHint in the Attributes page (the current entry only says 'Allows populating Intellisense with an enum for types other than Long'). The multi-enum syntax was added in BETA 298._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1466129455276757117] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Enum types cannot be used as the declared type of a **Const**. A declaration such as `Const x As SomeEnum = SomeEnum.Value` raises a compile-time error (`Expected type name`). This restriction is inherited from VB6. Because all enum values are backed by **Long**, the workaround is to omit the **As** *type* clause and let the constant take on the type of its initializer expression, or to declare it explicitly **As Long**. +> When the GUID string is removed from a `[ClassId("...")]` attribute (leaving the attribute empty or absent), the IDE displays a CodeLens entry directly above the attribute. Clicking it inserts a freshly generated GUID using `CoCreateGuid()`. This feature is available from BETA 323. The CodeLens appears only after the GUID value is removed; leaving an empty string `("")` in place may not trigger it. -_Source threads: 1069548868200583168 · confidence: high_ -_Date range: 2023-01-30_ +_Source threads: 1113738858408988713 · confidence: high_ +_Date range: 2023-06-01 to 2023-06-02_ +_Reviewer note: Ideally place this note adjacent to the ClassId section in Attributes.md rather than as a free-standing after-remarks block; the reviewer should decide exact placement within the file._ --- -## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> A hex literal without a type suffix is assigned the type **Integer** by the compiler (for compatibility with VBA and VB6). A value such as `&H8000` is therefore an **Integer** constant whose high bit is set. When the constant is used in a **Long** context, the runtime sign-extends the **Integer** value, so `&H8000` becomes `&HFFFF8000` rather than `&H00008000`. Declaring the constant `As Long` does not change this --- the type of the *literal* itself determines the intermediate representation. The correct fix is to append the **Long** type-declaration character to the literal: `&H8000&`. This affects any hex constant whose value is `>= &H8000`. +> The `[Description]` value is stored as a COM typelib `helpstring`. The underlying limit (from MIDL error 2113/2114) is 255 characters; twinBASIC's effective limit is approximately 235 characters. Exceeding this length raises an "internal error: too long" message. Keep `[Description]` text within 230 characters to stay clear of the limit. -_Source threads: 1466129455276757117 · confidence: high_ -_Date range: 2026-01-28_ +_Source threads: 1135126325783449711 · confidence: medium_ +_Date range: 2023-07-30_ +_Reviewer note: Verify the exact character limit against the current compiler behavior; the 235-character figure is approximate and based on a single report._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -> [!WARNING] -> -> **`RtlInitUnicodeString` does not allocate memory.** When porting VB6 code that uses Windows NT Native API `UNICODE_STRING` structs, verify that every call to `RtlFreeUnicodeString` corresponds to an API that explicitly heap-allocates the string buffer --- such as `RtlDuplicateUnicodeString`. `RtlInitUnicodeString` only stores the length and address of an existing buffer into the `UNICODE_STRING` struct; it performs no heap allocation. Calling `RtlFreeUnicodeString` on a `UNICODE_STRING` that was initialized with `RtlInitUnicodeString` is incorrect and causes a hard crash in twinBASIC. The same misuse may appear to succeed silently in VB6 due to runtime memory layout differences. +Multiple enums can be combined in a single `[TypeHint]` by listing them separated by commas: `[TypeHint(EnumA, EnumB)]`. This causes IntelliSense to offer members from all listed enums for that parameter. The attribute works on `Declare` function parameters and `Sub`/`Function` parameters; UDT members are not currently supported. -_Source threads: 1053654998556348457 · confidence: high_ -_Date range: 2022-12-17 to 2023-01-07_ -_Reviewer note: This is a Win32 NT Native API porting gotcha (ntdll.dll: RtlInitUnicodeString / RtlFreeUnicodeString / RtlDuplicateUnicodeString). The finding was confirmed by Wayne Phillips as an API usage error, not a twinBASIC bug. Placement on the Declare page is reasonable but the site has no dedicated Win32 API porting guide; a reviewer may prefer to place this on the Features/Advanced/API-Declarations page instead._ +_Source threads: 1146837274357678203 · confidence: high_ +_Date range: 2023-08-31_ +_Reviewer note: Verify the multi-enum comma syntax and the UDT member exclusion against the current compiler source._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> When a declared function's output parameter is typed `ByRef pUnk As IUnknown` and the caller passes a variable of a more specific interface type (for example, `IPicture`), the compiler inserts an implicit **QueryInterface** call at the callsite after the function returns. If **QueryInterface** succeeds the variable is populated with the correct interface pointer. If it fails --- for example, the returned object does not implement the requested interface --- a runtime error such as "No such interface supported" or "Type mismatch" is raised. This behavior is identical in VB6 and twinBASIC. A practical consequence is that when calling an API such as `OleCreatePictureIndirect`, passing `IID_IUnknown` as the `riid` argument is sufficient: the implicit QI at the callsite performs the cast to the caller's declared type, so supplying `IID_IPicture` explicitly is redundant. +> **`[DllExport]` on constants and executables.** The attribute applies to `Const` declarations as well as to procedures and variables. A practical use is exporting a `DWORD` constant named `NvOptimusEnablement` with a value of `1` from an x64 executable, which signals the GPU driver to activate the dedicated GPU for that process --- a requirement some GPU configurations have no reliable pure-code workaround for. `[DllExport]` also functions on executables, not only on DLLs. -_Source threads: 1053698170846183434 · confidence: medium_ -_Date range: 2022-12-17_ +```tb +[DllExport] +Public Const NvOptimusEnablement As Long = 1 +``` + +_Source threads: 1165150931554418698 · confidence: high_ +_Date range: 2023-10-21_ +_Reviewer note: Insert after the existing DllExport section (after the code block on line 304 and before the DLLStackCheck heading). Verify that the NvOptimusEnablement pattern actually requires DllExport from an EXE and is not a DLL-only requirement._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -> [!NOTE] -> -> Prior to BETA 269, a **Public Declare** and a **Public Enum** with the same base name (compared case-insensitively --- for example, `ShowWindow` and `SHOWWINDOW`) in separate modules within the same project caused an ambiguity error. The compiler's symbol resolver is context-free: it looks up a name in the symbol table without examining how the name is used at the call site (whether it is followed by a dot, appears as a type annotation, or is being called as a procedure), so it cannot distinguish between a callable declaration and a data type of the same name when both are in scope. This was fixed in BETA 269. In earlier versions the workaround was to place the **Declare** and the conflicting **Enum** in the same module, or to qualify the symbol with its module name. +The attribute is used throughout the built-in packages to mark members that are declared in the type definition but have no working implementation yet. Code that references such a member compiles without an "unrecognized member" error, and the IDE surface (IntelliSense, type-library viewer) shows the member as present, but calling it at runtime produces no result or raises an error. The [**Printer**](../VB/Printer/) class uses this pattern for several of its members. -_Source threads: 1084723164296261672 · confidence: high_ -_Date range: 2023-03-13_ +_Source threads: 1167815153253892156 · confidence: high_ +_Date range: 2023-10-28_ +_Reviewer note: Insert as additional prose after the existing two-line Unimplemented section body. The Printer page already documents RightToLeft as not implemented; verify that other Printer members (Width, Height, FontName, hDC, CurrentX) are truly flagged [Unimplemented] in the .twin source rather than fully implemented._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -> [!NOTE] -> -> When a standard DLL function returns an array of UDTs directly as its return value, the caller may crash. The cause is a lifetime management issue at the DLL boundary: the array may be released before the caller has finished with it. A reliable workaround is to store the array in a `Private` module-level variable inside the DLL module, then return that variable. The module-level variable extends the array's lifetime beyond the function frame and prevents the premature deallocation. +The string is rendered as markdown in the IDE's IntelliSense popup and hover tooltip (the Monaco editor has built-in markdown support). Multi-line descriptions are built by concatenating fragments with `& vbCrLf &`; a markdown line break requires two or more trailing spaces before the line-feed constant. Only `vbCrLf` is supported for line endings inside attribute strings. Markdown headers (`###`), inline code (backtick), fenced code blocks (triple backtick with a language tag), bold, and italic are all supported. Emoji characters embedded in the string render correctly in the twinBASIC IDE but may display as garbled text in non-twinBASIC COM consumers whose type-library readers do not handle surrogate pairs. -_Source threads: 1099208311313813534 · confidence: medium_ -_Date range: 2023-04-22_ -_Reviewer note: Needs verification against the twinBASIC runtime source to confirm the root cause (array released too soon at DLL boundary). The workaround (store in module-level variable) was reported as effective in practice, but the underlying cause has not been confirmed from first principles._ +```tb +[Description("Returns the widget count. " & vbCrLf & _ + "### Notes" & vbCrLf & _ + "`Count` is zero-based.")] +Public Property Get Count() As Long +``` + +_Source threads: 1169508594341916712 · confidence: high_ +_Date range: 2023-11-02 to 2023-11-06_ +_Reviewer note: Insert as additional prose after the existing Description section body (after line 271). The current text ends after 'helpstring attribute in the type library (if applicable).' Append the new paragraph before the next heading._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> When writing a COM interface method that takes an optional output pointer (such as a D2D1\_TAG-style out-parameter), declaring the parameter as `ByRef tag As LongLong` and calling it with `ByVal 0` does not pass a null pointer on 32-bit (x86) builds. The `ByVal` modifier at the call site promotes the argument to a 64-bit value, so two 64-bit zero pushes land on the stack instead of two 32-bit null pointer pushes. The call returns with the stack misaligned, causing an access violation. Declare the parameter as `ByRef tag As Any` (the convention used by oleexp/olelib type libraries) so that `ByVal 0` correctly passes a 32-bit null pointer. +> **`[EnumId]` on UDTs.** The attribute can also be applied to a [**Type** (UDT)](Type) definition, not only to an **Enum**. Assigning a GUID to a UDT allows it to be passed as a parameter across type-library boundaries --- without a GUID, passing a UDT defined in one type library as a parameter in a component from a different type library raises a compile error. The trade-off is that the type library then behaves like an ActiveX control for versioning: replacing it with a new build requires unregistering the old version first, overwriting the file, and re-registering. Simply overwriting the file (which works for plain TLBs) is no longer safe once a GUID has been assigned to any UDT in the library. -_Source threads: 1101704869804511265 · confidence: high_ -_Date range: 2023-05-11 to 2023-05-12_ -_Reviewer note: This applies specifically to COM interface method declarations (Interface blocks), not only to Declare statements. If a dedicated page for COM interface method argument conventions exists or is added, consider moving this note there. Verify the claim against the twinBASIC compiler behavior on x86 targets._ +_Source threads: 1173257226610290699 · confidence: high_ +_Date range: 2023-11-12 to 2023-11-14_ +_Reviewer note: Insert after the existing EnumId body (after line 348, before the EventInterfaceId heading). Also update the 'Applicable to' line from '[Enum]' to '[Enum], [Type (UDT)](Type)' if the .twin source confirms this._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> When an ANSI Win32 API is declared and a UDT parameter contains a fixed-length string field (for example, `dmDeviceName As String * CCHDEVICENAME`), the compiler generates implicit ANSI-conversion procedures for the UDT. Before a compiler fix issued in a subsequent BETA release, those generated procedures did not explicitly return `S_OK` on success, leaving the return value of the procedure's final instruction (a pointer) in `EAX`/`RAX` to be interpreted as an `HRESULT`. On x86 this rarely caused a problem because pointer values seldom set the `0x80000000` failure bit. On x64, the larger address space means pointers frequently have that bit set, so the implicit conversion is treated as failed and the API call is aborted intermittently. -> -> Two workarounds are available: -> -> 1. Replace the fixed-length string field with a `Byte` array of the same length (for example, `dmDeviceName(CCHDEVICENAME - 1) As Byte`). -> 2. Switch to the Unicode variant of the API using `DeclareWide`, replacing `String` fields with `String` and `Integer` arrays in place of `Byte` arrays for wide-char fields. +> Although `[Unimplemented]` was not originally designed for general use, it is available in user code. A common pattern is to apply it to interface methods that must be declared to satisfy an interface contract but are intentionally left without an implementation body. Unlike `#If`/`#End If` conditional compilation, which removes code from the compilation entirely, `[Unimplemented]` keeps the method in the compiled output and type-checked --- so the compiler still catches downstream breakage if other code changes --- while issuing a warning at every call site. -_Source threads: 1107676681449254952 · confidence: high_ -_Date range: 2023-05-18_ -_Reviewer note: Verify the exact BETA release version in which the compiler fix shipped. The finding says 'a subsequent BETA release' without a specific number. Also confirm that DeclareWide is the correct twinBASIC keyword for the Unicode workaround._ +_Source threads: 1179991719551442975 · confidence: medium_ +_Date range: 2023-12-01 to 2023-12-03_ +_Reviewer note: Verify that [Unimplemented] is indeed documented as user-accessible in the current .twin sources; the finding notes it was 'not originally intended for general public use'. Placement: append this note immediately after the existing [Unimplemented] section body in Attributes.md._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> In versions of twinBASIC before BETA 331, passing a `ByVal` expression as a named argument to a **Declare**-imported procedure caused a syntax error. For example, `lpOutput:=ByVal 0` was rejected. The workaround was to wrap the expression in parentheses: `lpOutput:=(ByVal 0)`. VB6 accepted the bare form without parentheses. This restriction was removed in BETA 331. +> `Debug.Assert` is evaluated only when running inside the IDE debugger. All `Debug.Assert` calls are removed from compiled binaries and their conditions are never evaluated at runtime. For runtime assertion checks in compiled builds, use the [Assert package](../Assert/) or write an explicit error-raising guard. A project-level option to retain `Debug.Assert` in compiled binaries was under discussion as of early 2024 but was not yet implemented. -_Source threads: 1117974216810188920 · confidence: high_ -_Date range: 2023-06-13_ -_Reviewer note: Verify the exact BETA 331 fix version against the twinBASIC changelog. The Discord thread attributed the fix to BETA 331 and confirmed it was a named-argument-only issue, but the release number should be confirmed before publishing._ +_Source threads: 1206670811172306954 · confidence: high_ +_Date range: 2024-02-12_ +_Reviewer note: Placement: append immediately after the existing [DebugOnly] section body in Attributes.md, since that section already references Debug.Assert and Debug.Print. Alternatively, consider a dedicated Note block under the Assert package index page._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> When a `Declare` statement loads a DLL from the application's own folder, the DLL may fail to load on subsequent IDE sessions even though it loads correctly on the first run after import. The DLL search path used during the initial session does not persist once the IDE is closed and reopened. Add the `[SetDllDirectory(True)]` attribute to the `Declare` statement to include the application's directory in the DLL search path: -> -> ```tb -> [SetDllDirectory(True)] -> Public Declare PtrSafe Sub sqlite3_open Lib "sqlite.dll" (ByVal FileName As String, ByRef handle As LongPtr) -> ``` -> -> If the DLL is installed into a Windows system directory (`System32` or `SysWOW64`), the attribute is not needed. +> A `[DefaultMember]` property that takes no parameters is evaluated automatically whenever an instance of the class appears in an expression, including when the instance is passed as an argument. This matches COM/VBA semantics and is intentional for compatibility with non-twinBASIC COM consumers. There is no per-parameter annotation to suppress this automatic evaluation. If automatic evaluation is not desired, the default member must accept at least one parameter, or the `[DefaultMember]` attribute must be omitted entirely. -_Source threads: 1139937031775072307 · confidence: medium_ -_Date range: 2023-08-13 to 2023-08-14_ -_Reviewer note: Verify that [SetDllDirectory(True)] on a Declare statement is the canonical fix for this behavior and that the description in docs/Reference/Attributes.md (which says it 'allows searching the app path for the DLLs in the base app's declare statements') matches the behavior reported. The example uses PtrSafe for correctness on 64-bit; reviewer should confirm the attribute works on both 32-bit and 64-bit targets._ +_Source threads: 1358081642052190318 · confidence: high_ +_Date range: 2025-04-05 to 2025-04-06_ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> twinBASIC supports the `_cdecl` calling convention natively in **Declare** statements. VB6 cannot call `_cdecl` DLL functions directly and requires a separate stdcall wrapper DLL instead. In twinBASIC, a direct **Declare** with the **CDecl** keyword replaces any such wrapper. See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#cdecl-support) for syntax and examples. +> When `[DebugOnly]` is applied to a Sub or Function, the compiler removes both the procedure body and every call site that references it from the compiled EXE. Only the IDE (debug) run includes the procedure. This is distinct from `[Debuggable(False)]`, which suppresses breakpoints and stepping but does not strip code from the build. -_Source threads: 1140151535217680436 · confidence: high_ -_Date range: 2023-08-13_ +_Source threads: 1398715673508843731 · confidence: high_ +_Date range: 2025-07-26_ +_Reviewer note: Insert immediately after the existing DebugOnly entry body (after line 231 in Attributes.md, under the ## DebugOnly section). The current description says 'Excludes calls to this procedure from the Build' --- this NOTE adds that the procedure itself is also excluded, not just the calls._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Specifying a relative subfolder path in *libname* --- for example `Lib "subfolder\anyname.dll"` --- results in an "Error Loading DLL" at runtime. twinBASIC does not resolve the subfolder relative to the executable's location. The workaround is to call the Win32 `SetDllDirectory` API with the target directory before the first call that loads the library, or to use an absolute path in *libname*. The file extension does not have to be `.dll`; other extensions such as `.dat` are accepted provided the folder path is resolved correctly. +> The string value in `[Description("...")]` is rendered as markdown in the IDE tooltip, not as plain text. Embedding `vbCrLf` in the string does not produce visible line breaks. To produce multi-line or formatted IntelliSense tooltips, use markdown syntax within the string. Built-in functions use this pattern --- selecting 'Go to definition' on any built-in function in the IDE shows examples of how these strings are authored. -_Source threads: 1148585708362149899 · confidence: medium_ -_Date range: 2023-09-05_ -_Reviewer note: Verify whether this limitation is still present in current builds, and whether there is a [SetDllDirectory] attribute documented on the Attributes page that already covers this workaround._ +_Source threads: 1396063342417674291 · confidence: high_ +_Date range: 2025-07-19_ +_Reviewer note: Insert immediately after the existing Description entry body (under ## Description in Attributes.md). The current entry says 'Provides a description in information popups in the IDE' without mentioning markdown rendering._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> **MIDL type library type mapping** -- When a type library (TLB) compiled with MIDL is imported into twinBASIC, the MIDL pointer-width types do not map to twinBASIC's intrinsic **LongPtr** as they do conceptually in C: > -> - `LONG_PTR` always maps to a 64-bit integer regardless of whether the TLB was compiled with `/env win32` or `/env win64`. -> - `__int3264` maps to **LongLong** in both 32-bit and 64-bit configurations. -> - `size_t` causes a parse error because it maps to an unsigned 64-bit value that twinBASIC cannot represent. -> -> As a result, no MIDL type currently maps to twinBASIC's intrinsic **LongPtr** when imported through a TLB. -> -> **Workaround 1 -- shared TLB:** Add `typedef [public] long LongPtr;` to the MIDL source. VB6 treats it as **Long** (4 bytes). twinBASIC gives its own intrinsic **LongPtr** priority over the TLB-defined alias when the name is used unqualified; if the type is referenced with its library qualifier (e.g. `mytypelib.LongPtr`), twinBASIC resolves it to the TLB's 4-byte **Long** definition instead. -> -> **Workaround 2 -- `void*` as `As Any`:** Arguments declared as `void*` in MIDL map to **As Any** in twinBASIC, independent of the TLB's compilation target. Passing a pointer `ByVal` with **As Any** works in both 32-bit and 64-bit builds. This requires callers to write `ByVal` explicitly on each call site, which is error-prone, and `void*` cannot be used inside UDTs --- it is unsupported in both VB6 and twinBASIC for that context. +> Square-bracket attributes apply to the next syntax element the parser encounters. Standard modules (`.BAS` files) have no explicit `Module` block --- the module container is implicit --- so there is no syntax element for an attribute to attach to before the first declaration. As a result, module-scoped settings that would be expressed as attributes in a Class or Form module must instead be expressed as `Option` statements (for example, `Option PtrSafe Off`) in a standard module. This limitation may change if the parser is updated to support attributes at the start of a module file before any declarations. -_Source threads: 1159270753468952576 · confidence: medium_ -_Date range: 2023-10-04 to 2023-10-05_ -_Reviewer note: Verify current behavior against twinBASIC source or a test TLB: in particular, confirm that LONG_PTR still maps to 64-bit rather than LongPtr, and that the void*/As Any behavior is still architecture-independent. The thread reports Beta 399 behavior; check whether any of these mappings have changed in later builds._ +_Source threads: 1421815804172701708 · confidence: high_ +_Date range: 2025-10-01_ +_Reviewer note: Confirmed by WaynePhillipsEA. Verify that 'Option PtrSafe Off' is the correct syntax for the relevant option statement and that the Option page documents it, before adding this cross-reference. Also consider whether this note belongs on the Option page instead of (or in addition to) the Attributes page._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] - -> [!NOTE] -> -> `[PreserveSig(False)]` is supported on **Declare** statements in twinBASIC. When translating a type library whose COM methods return an HRESULT that should be hidden from tB callers, `[PreserveSig(False)]` is the correct annotation --- the compiler transforms the call so that the HRESULT is checked automatically and the last out-parameter is promoted to the function's return value. An earlier version of the twinBASIC typelib viewer incorrectly emitted a warning comment instead of the `[PreserveSig(False)]` attribute for such declarations; this was corrected in BETA 553. - -_Source threads: 1248884196575809578 · confidence: high_ -_Date range: 2024-06-08_ +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] ---- +Under the **ArrayBoundsChecks** section, after the existing description, insert: -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +> [!NOTE] +> **[ArrayBoundsChecks(False)]** is not fully implemented in the current compiler. Suppression works on array write operations but may not suppress bounds checks on reads in all contexts --- for example, reading a known out-of-bounds element through `MsgBox` still raises a bounds error even when the attribute is present, whereas reading through `Debug.Print` succeeds. Do not rely on the attribute to eliminate all bounds-checking overhead until this is resolved. > [!NOTE] -> When a `Declare` statement passes a SafeArray pointer to a Windows API function such as `SafeArrayLock` or `SafeArrayUnlock`, declare the pointer parameter **ByVal**, not **ByRef**. Declaring it **ByRef** passes the address of the local variable that holds the pointer --- a double indirection --- which corrupts the lock and unlock operations. The symptom is that a class constructor that calls these functions appears to finish correctly (the instance looks valid inside the constructor body) but the caller receives **Nothing** or an instance in an incorrect state. +> With the current unoptimized code generator, **[ArrayBoundsChecks(False)]** may produce no measurable performance improvement. The bounds-checking code may still be emitted and simply not reached at runtime. Meaningful gains from disabling bounds checks require LLVM optimization, which is experimental and limited to procedures whose argument and variable types are numeric scalars only (no strings, objects, or dynamic arrays). Apply **[CompilerOptions("+llvm,+optimize")]** alongside this attribute to obtain the intended speedup. -_Source threads: 1270386953115930828 · confidence: medium_ -_Date range: 2024-08-06 to 2024-08-07_ -_Reviewer note: Verify against the SafeArrayLock / SafeArrayUnlock Win32 API signatures to confirm ByVal is correct for the SAFEARRAY* parameter. The finding is self-reported by the user after fixing their own bug; no maintainer confirmation in the thread._ +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-11-28_ +_Reviewer note: Two separate limitations confirmed by the twinBASIC maintainer (WaynePhillipsEA). The read-vs-write asymmetry is a known bug; the LLVM dependency is by design. Verify both are still current before publishing._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] + +Under the **UseGetLastError** section, after the existing description, insert: > [!NOTE] +> Every `Declare` statement implicitly calls `GetLastError` after the API returns to populate `Err.LastDllError`. This hidden call adds overhead that is noticeable in tight loops. For APIs that do not use `GetLastError` --- for example, string comparison functions or functions returning NTSTATUS --- add **[UseGetLastError(False)]** to suppress the implicit call: > -> twinBASIC automatically redirects `Declare` statements that target `msvbvm60` by exported name to its own runtime equivalents, so VB6-compatible code runs without requiring `msvbvm60.dll` on disk. However, this redirect applies only when the `Alias` clause specifies an exported name. If the alias is an ordinal reference --- for example `Alias "#307"` --- twinBASIC does not recognise the declaration as a redirected call and will attempt to load the real `msvbvm60.dll` at runtime. VB6 code in the wild sometimes imports `msvbvm60` functions by ordinal (for example `PutMem4 Lib "msvbvm60" Alias "#307"`), so this limitation can affect VB6 migration projects. +> ```tb +> [UseGetLastError(False)] +> Public Declare PtrSafe Function CompareStringEx Lib "kernel32" ( _ +> ByVal lpLocaleName As LongPtr, ByVal dwCmpFlags As Long, _ +> ByVal lpString1 As LongPtr, ByVal cchCount1 As Long, _ +> ByVal lpString2 As LongPtr, ByVal cchCount2 As Long, _ +> ByVal lpVersionInformation As LongPtr, ByVal lpReserved As LongPtr, _ +> ByVal lParam As LongPtr) As Long +> ``` +> +> When working with APIs from a package reference that cannot be edited directly, copy the `Declare` into the project and add the attribute there. -_Source threads: 1295648317820108831 · confidence: high_ -_Date range: 2024-10-15 to 2024-10-21_ -_Reviewer note: A twinBASIC maintainer (WaynePhillipsEA) described this as a bug that should be straightforward to fix. Verify whether this has been fixed in a later release before publishing; if fixed, the note may be removed or converted to a historical remark._ +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-12-06_ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] +> A class's predeclared instance is not a compile-time constant. Because **Optional** parameter default values must resolve to a constant, writing `Optional ByVal Target As MyClass = MyClass` (using the predeclared instance as the default) is not valid. The standard workaround is to declare the parameter as `Optional ByVal Target As MyClass = Nothing` and test inside the procedure: > -> On Windows Vista and Windows 7, including the `.dll` extension in the *libname* string (for example, `"cabinet.dll"`) causes a DLL search path error at runtime, preventing the DLL from loading. Omit the extension (use `"cabinet"` rather than `"cabinet.dll"`) when targeting those OS versions. This restriction does not apply to Windows XP, Windows 10, or Windows 11. The same issue can affect other DLLs listed in the import address table (IAT). Note that the failure occurs only in compiled executables; running from the twinBASIC IDE is not affected. +> ```tb +> Sub DoSomething(Optional ByVal Target As MyClass = Nothing) +> If Target Is Nothing Then Set Target = MyClass +> ' ... +> End Sub +> ``` -_Source threads: 1310261549574656001 · confidence: high_ -_Date range: 2024-11-24 to 2024-12-14_ +_Source threads: 1291780674772275220 · confidence: medium_ +_Date range: 2024-10-04_ +_Reviewer note: This note should appear in the PredeclaredID section of Attributes.md, immediately after the existing two-sentence description. Confirm placement does not conflict with any existing notes in that section._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> **ByVal with object variables differs from VB6.** In VB6, passing an object variable `ByVal` to a `Declare`-style API (for example a `CopyMemory` wrapper) treats the argument as the address of the reference slot --- equivalent to `ByVal VarPtr(o)`. In twinBASIC the same call behaves as `ByVal ObjPtr(o)`, dereferencing the object pointer first. Low-level object-pointer code that relies on `ByVal ` without an explicit pointer function will therefore produce different results between the two compilers. To be explicit: use `ByVal ObjPtr(o)` when the API needs the vtable/object address, and `ByVal VarPtr(o)` when it needs the address of the reference variable itself. +> Applying `[Debuggable(False)]` to a procedure or module causes the debugger to skip over that code when stepping through with F8. This is useful when a referenced package contains source code that would otherwise be entered during step-through. If the source belongs to a read-only package that cannot be edited from within the consuming project, the package author must set this attribute in their own project before building the package. -_Source threads: 1356284960495108156 · confidence: medium_ -_Date range: 2025-03-31_ -_Reviewer note: Verify against twinBASIC compiler source or a reproducible test case before publishing. The distinction between ObjPtr and VarPtr semantics for ByVal object arguments is subtle and this finding is medium-confidence._ +_Source threads: 1357558567447695391 · confidence: high_ +_Date range: 2025-04-04 to 2025-04-07_ +_Reviewer note: Insert after the existing [Debuggable] attribute entry (around the 'When false, turns of breakpoints and stepping' line). The note about a per-reference consumer-side override switch (planned but not yet available as of April 2025) was omitted as it may become stale quickly; re-add when the feature ships._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> From BETA 797 onward, `ByRef ParamArray args As Any()` is supported in **Declare** (without **CDecl**) for `__stdcall` functions that accept a `va_list`-style pointer. Before BETA 797, using this pattern without **CDecl** raised a compiler error requiring **Variant**; the workaround was to add **CDecl**, which caused a calling-convention mismatch for true stdcall functions. +> Both `[ArrayBoundsChecks]` and `[IntegerOverflowChecks]` can be applied to a single procedure as well as to an entire class or module. Prefixing one performance-critical function disables the corresponding runtime check for that function only: > -> **CDecl** remains required for genuinely variadic functions that use the C `...` ellipsis calling convention. +> ```tb +> [ArrayBoundsChecks(False), IntegerOverflowChecks(False)] +> Private Sub CriticalInnerLoop(arr() As Long, ByVal n As Long) +> Dim i As Long +> For i = 0 To n - 1 +> arr(i) = arr(i) + 1 +> Next i +> End Sub +> ``` -_Source threads: 1378220872405487636 · confidence: high_ -_Date range: 2025-05-31 to 2025-06-08_ -_Reviewer note: Verify that the fix landed in exactly BETA 797 and that the description of `ByRef ParamArray args As Any()` being valid for stdcall va_list-style declarations is accurate against the twinBASIC compiler source._ +_Source threads: 1382015106380075078 · confidence: medium_ +_Date range: 2025-06-11_ +_Reviewer note: This note should be inserted under both the ArrayBoundsChecks and IntegerOverflowChecks sections (or added once after IntegerOverflowChecks with a cross-reference). The existing Attributes.md text already states both attributes are applicable to procedure scope, so this is a clarifying example rather than new information. Place it under the IntegerOverflowChecks section._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -> [!NOTE] -> -> twinBASIC does not silently coerce `Object` variables to COM interface types such as `IUnknown` when passing them to **Declare** functions. Passing an `Object` variable to a parameter declared `As IUnknown` (or any other specific COM interface) causes an access violation (&HC0000005) at run time. VB6 accepted this by performing a `QueryInterface` coercion automatically. In twinBASIC the types must match exactly: declare a local variable `As IUnknown`, obtain the reference into that variable, then pass it to the API call. If the calling code needs an `Object` afterwards, assign the result separately with `Set objVar = ppUnk`. +## NonBrowsable (optional Bool) +{: #nonbrowsable } -_Source threads: 1444009884637986966 · confidence: high_ -_Date range: 2025-11-28_ +Syntax: **[NonBrowsable** [ **(** **True** \| **False** **)** ] **]** ---- +Applicable to: [**Property**](Property) procedures (**Property Let**, **Property Get**, **Property Set**) -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +Hides the property from the form designer property browser, but leaves it visible in other browsing contexts such as the Object Browser. This is the twinBASIC equivalent of VBA/Access run-time-only properties that do not appear at design time. -> [!NOTE] -> When the `[ComExport]` attribute is applied to a `Declare` function whose return type is a type alias, parameter types were correctly exposed under the alias name in the exported type information, but the return type was resolved to its underlying primitive (for example, a `WinRT.LongPtr` return type appeared as `Long`). Interface methods with alias return types were not affected. Fixed in BETA 957. +To hide a property from all browsing contexts, use [**Hidden**](#hidden) instead. -_Source threads: 1463135813574594730 · confidence: high_ -_Date range: 2026-01-20 to 2026-01-21_ +_Source threads: 1387416025892393042 · confidence: high_ +_Date range: 2025-06-25_ +_Reviewer note: The existing [Hidden] entry in Attributes.md lists applicability as Class/CoClass/Interface only. The Discord finding states [Hidden] on a Property Let hides it from all browsing contexts. Verify whether the applicability of [Hidden] should be expanded to cover Property procedures, and whether [NonBrowsable] is implemented and behaves as described. Both attributes need to be checked against the .twin source._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] - -> [!NOTE] -> When a **Declare** parameter is typed **As Any** and represents a null pointer, pass `vbNullPtr` rather than the integer literal `0`. Passing `0` only guarantees that the lower 16 bits of the argument slot are zero; the current twinBASIC compiler happens to zero-extend correctly, but an optimizing backend (such as the planned LLVM backend) may leave upper bits non-zero if it determines only the low 16 bits need satisfying. `vbNullPtr` is a native-pointer-sized zero and is always safe. +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -_Source threads: 1505726092479041556 · confidence: high_ -_Date range: 2026-05-18_ +### Example: detecting build completion ---- +Because the IDE does not produce a visible alert when a build fails with the DEBUG CONSOLE panel closed, a `[RunAfterBuild]` handler can serve as a programmatic signal. The handler is called after every build attempt --- including failed ones --- so comparing the output file's modification timestamp to a known good time detects whether a new binary was produced: -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +```tb +[RunAfterBuild] +Public Sub AfterBuild() + If FileDateTime(App.LastBuildPath) >= Now - TimeSerial(0, 0, 5) Then + MsgBox "Build succeeded: " & App.LastBuildPath + Else + MsgBox "Build may have failed --- check the Debug Console." + End If +End Sub +``` > [!NOTE] -> **olepro32.dll** has no 64-bit version. When compiling in 64-bit mode, twinBASIC (as of BETA 311) automatically redirects any **Declare** that targets **olepro32.dll** to **oleaut32.dll**, where MSDN has long documented the same functions (such as `OleCreatePictureIndirect`). Existing VB6-derived code that declares these functions against **olepro32.dll** compiles and runs correctly in 64-bit mode without manual changes. +> If the DEBUG CONSOLE panel is closed when a build fails, the IDE returns silently to its idle state with no dialog or notification. Keep the DEBUG CONSOLE open during builds, or use a `[RunAfterBuild]` handler to check the result programmatically. -_Source threads: 1110121787745894400 · confidence: high_ -_Date range: 2023-05-22_ +_Source threads: 1414587677574959165 · confidence: medium_ +_Date range: 2025-09-08 to 2025-09-09_ +_Reviewer note: The timestamp comparison heuristic in the example is illustrative; verify whether `App.LastBuildPath` and `FileDateTime` behave as expected immediately after a build. Confirm that `[RunAfterBuild]` fires even on failed builds._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> As of October 2025, the **PtrSafe** keyword is optional in twinBASIC for both 32-bit and 64-bit targets. A **Declare** statement without **PtrSafe** compiles for both architectures. The keyword is still accepted and still signals VBA7 x64 intent, but its absence is no longer a compile error on any target. Note that omitting **PtrSafe** while using 32-bit-only **Long** for pointer parameters still produces incorrect behaviour on x64 at runtime --- the compiler does not validate pointer-size correctness when **PtrSafe** is absent. -> -> **LongPtr** may be used in a **Declare** statement that does not carry **PtrSafe**. The requirement that **LongPtr** implies **PtrSafe** is a misconception: in 32-bit VBA7 (and in 32-bit or 64-bit twinBASIC), **LongPtr** is valid in non-PtrSafe declares. **PtrSafe** was only ever required by 64-bit VBA7; twinBASIC now makes it optional for all targets. +> The function runs in the same process as the build, with full access to the language and project code. Common uses include invoking `signtool` to sign the output binary, running `git` commands to tag the build, or copying the output file to a deployment directory. `App.LastBuildPath` returns the path of the built output. -_Source threads: 1421815804172701708 · confidence: high_ -_Date range: 2025-09-28 to 2025-10-15_ -_Reviewer note: The existing Declare page documents PtrSafe as "required in 64-bits" in its parameter table. That description should be updated to reflect that PtrSafe is now optional in twinBASIC (confirmed by maintainer WaynePhillipsEA around October 2025). The two NOTE callouts near the top of the page also assert that PtrSafe is required for x64 builds and should be reviewed for accuracy against the current compiler behaviour._ +_Source threads: 1431348682271363182 · confidence: medium_ +_Date range: 2025-10-24_ +_Reviewer note: Insert after the existing RunAfterBuild section body (around line 589 of docs/Reference/Attributes.md). Verify that App.LastBuildPath is the correct property name and that the function runs in-process (not as a separate invocation of the IDE)._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> -> When calling a Win32 API that accepts a `POINT` structure by value, passing two separate **Long** parameters (one for `x`, one for `y`) does not work reliably on x64. On 64-bit targets the first four arguments are passed in registers rather than on the stack, so a two-**Long** layout does not match what the API expects. A cross-bitness workaround is to declare the parameter as **ByVal Currency**: a **Currency** value is 8 bytes --- the same size as a 64-bit **POINT** --- and is passed in a single slot, matching the x64 calling convention. This approach works on both 32-bit and 64-bit twinBASIC targets without conditional compilation. +> The three-argument (table, name, value) form of **[PopulateFrom]** requires the JSON source to be an object containing a named array of objects (e.g. `{ "events": [{"id": 12, ...}] }`). When the JSON source is a plain list of objects (e.g. `[{"id": 12, ...}]`) --- the default format for a twinBASIC STRING resource --- neither the three-argument form (raises TB5083: *JSON: accessor is invalid*) nor the two-argument form (silently produces an empty enum) works correctly. The workaround is to add the enum member IDs manually. -_Source threads: 1421815804172701708 · confidence: medium_ -_Date range: 2025-09-30_ -_Reviewer note: Community-sourced workaround (wqweto). Verify that passing ByVal Currency for a POINT-by-value parameter produces the correct register layout on x64 before publishing. Consider whether an illustrative code example would be helpful here._ +_Source threads: 1455320119239643390 · confidence: medium_ +_Date range: 2025-12-29_ +_Reviewer note: Insert in the PopulateFrom section of Attributes.md, after the existing JSON structure example. Confirm whether a future fix is planned or if the limitation is by design._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Overloaded `Declare` statements --- for example, one overload taking `LongPtr` parameters and another taking `String` parameters for the same API --- cannot be disambiguated by the compiler when the declarations appear inside a **Class** or **Form** module. The compiler reports "Unable to disambiguate between overloaded methods" in this case. The same declarations work correctly when placed in a standard **Module**. As a workaround, move overloaded API declarations to a standard module and call them from the class or form. +> A common interop pattern for Windows APIs that return a variable-length array embedded in a struct (for example, `PROCESS_HANDLE_SNAPSHOT_INFORMATION.Handles`) is to declare the fixed-size trailing member with a nominal bound of `(0 To 0)`, then apply `[ArrayBoundsChecks(False)]` on the procedure that reads from it. With bounds checking disabled, indexing beyond the declared dimension is safe as long as the index stays within the actual runtime length reported by the API. -_Source threads: 1432054854016045096 · confidence: high_ -_Date range: 2025-12-06_ -_Reviewer note: Reported as GitHub issue #2102. Verify whether it has been fixed before publishing._ +_Source threads: 1459605334502150206 · confidence: high_ +_Date range: 2026-01-10_ +_Reviewer note: Insert after the existing body text of the ## ArrayBoundsChecks section (around line 50 of Attributes.md). Verify the pattern against the PROCESS_HANDLE_SNAPSHOT_INFORMATION example in the twinBASIC source or sample projects before publishing._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -> [!NOTE] -> -> Some Win32 ANSI APIs that accept a null or empty buffer to probe the required size may return a byte count rather than a character count. `GetUserNameA` (in `Advapi32.dll`) is a known case: calling it with a null buffer sets the output length to N\*2 instead of N. The practical remedy is to pre-allocate a buffer large enough for the maximum possible value (for example, UNLEN\+1 = 257 characters for user names) rather than relying on a probe call to size the buffer. The `GetUserNameW` (Unicode) variant and similar ANSI APIs such as `GetComputerNameA` do not exhibit this behavior. +Only binds this name to a callsite when the caller uses the dollar-sign string-suffix form of the name. For example, a function named `Error` decorated with `[BindOnlyIfStringSuffix]` binds to `Error$()` but not to `Error()`. Used in the twinBASIC runtime library (Conversion module) to correctly dispatch the `Error$` variant. Complements [BindOnlyIfNoArguments](#bindonlyifnoarguments). -_Source threads: 1187218188728008785 · confidence: medium_ -_Date range: 2023-12-21 to 2023-12-26_ -_Reviewer note: This is a Windows API quirk, not a twinBASIC-specific behavior. Verify whether this note is within scope for Declare.md (which currently focuses on syntax) or whether it belongs in a different location such as a Win32 API usage guide._ +_Source threads: 1462420288280068323 · confidence: high_ +_Date range: 2026-01-18 to 2026-01-22_ +_Reviewer note: This text should replace or extend the currently empty body of the ## BindOnlyIfStringSuffix section in Attributes.md (the section exists with syntax and applicability lines but no explanatory prose). Verify usage against the Conversion module .twin source._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] - -> [!NOTE] -> The `Null` keyword is only valid with **Variant** typed values. Assigning `Null` to a `LongPtr` or any non-Variant numeric type raises run-time error 'Invalid use of Null'. To pass a null pointer to a Win32 API declared with a numeric pointer parameter, use the value `0` (or `ByVal 0` when the parameter is `ByRef`). - -> [!NOTE] -> twinBASIC introduces **vbNullPtr** as a special value that can be passed in place of a typed UDT argument when the underlying Win32 API accepts NULL for that parameter. VB6 required such optional struct arguments to be declared `As Any` and called with `ByVal 0&`; twinBASIC lets the declaration use the real UDT type and the caller substitutes **vbNullPtr** to pass the equivalent of a C NULL pointer. - -```tb -' Win32 declaration using the real UDT type: -Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileW" ( _ - ByVal lpFileName As LongPtr, _ - ByVal dwDesiredAccess As Long, _ - ByVal dwShareMode As Long, _ - lpSecurityAttributes As SECURITY_ATTRIBUTES, _ - ByVal dwCreationDisposition As Long, _ - ByVal dwFlagsAndAttributes As Long, _ - ByVal hTemplateFile As LongPtr) As LongPtr - -' Pass vbNullPtr where the API accepts NULL: -Dim hFile As LongPtr -hFile = CreateFile(StrPtr("C:\test.txt"), GENERIC_READ, FILE_SHARE_READ, _ - vbNullPtr, OPEN_EXISTING, 0, 0) -``` - -### String arguments and DeclareWide - -Win32 API declarations in twinBASIC follow one of three patterns depending on how string arguments are handled: - -- **ANSI variant** (e.g. `MessageBoxA`) --- string parameters declared `As String`; string literals or **String** variables are accepted directly. -- **Wide variant** (e.g. `MessageBoxW`) --- string parameters declared `As LongPtr`; the caller must pass `StrPtr(s)` to obtain the address of the string's character buffer. -- **Alias variant** (e.g. `MessageBox`) --- declared using twinBASIC's `DeclareWide` feature; accepts bare **String** arguments and internally calls the W API. This is the simplest form for Unicode-correct calls. +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] -Passing a **String** variable where a `LongPtr` is expected results in run-time error 13 (Type Mismatch). +Makes the class available in the twinBASIC IDE toolbox and gives it full ActiveX control behavior (Extender, siting, container notification). A class that compiles as an OCX but omits `[COMControl]` registers and instantiates with **New** as an ordinary COM object, but does not appear in the toolbox and cannot be sited on a form at design time. -_Source threads: 1195546112459821176 · confidence: high_ -_Date range: 2024-01-13_ -_Reviewer note: Verify the vbNullPtr feature is documented in the .twin sources. The CreateFile example is illustrative; reviewer should check it compiles correctly with WinDevLib-style declarations. DeclareWide description should be cross-checked against docs/Features/Advanced/API-Declarations._ +_Source threads: 1491119090306256906 · confidence: medium_ +_Date range: 2026-04-07 to 2026-04-13_ +_Reviewer note: This draft is a description body for the sparse [COMControl] entry in Attributes.md (currently only a syntax line with no prose). Reviewer should verify the exact semantics --- in particular whether [COMControl] applies to Class, CoClass, or Interface (the current page says Interface; the finding implies it applies to a class compiled into an OCX). Confirm against the .twin source._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] - -> [!NOTE] -> -> twinBASIC (like VB6) cannot consume .NET managed assemblies. Only traditional COM/ActiveX DLLs and standard Win32 DLLs are supported. Importing a .NET assembly through the **Project > References** dialog will add it to the Imported Type Libraries node but its classes will not be available for use. To call into a .NET library from twinBASIC, the library must expose a COM-callable wrapper. +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1106637635818094733, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] +> The instance behind an **[AppObject]** class is created when one of its members is first used, not at program start-up, and **Class_Initialize** runs at that point. A project can contain several **[AppObject]** classes. This suits global function-pointer variables that are filled in at run time, such as those obtained through `wglGetProcAddress`, which returns valid pointers only once an OpenGL context exists. > -> twinBASIC supports the `CDecl` calling convention natively in **Declare** statements. Append **CDecl** after the procedure name in the declaration to call C-exported functions that use the `cdecl` convention rather than `stdcall`. In VB6, `cdecl` support requires a third-party patch and does not work in the IDE. See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#cdecl-support) for syntax and examples. +> If a global variable with the same name is declared elsewhere, the bare name binds to that variable first and the **[AppObject]** member is ignored, so the value stays 0. -_Source threads: 1253786438454214788 · confidence: high_ -_Date range: 2024-06-21 to 2024-06-24_ -_Reviewer note: Two separate notes merged here. (1) The .NET-assembly restriction should be verified against any twinBASIC roadmap items --- a COM-callable-wrapper requirement is the current state as of the Discord thread. (2) The CDecl note references the Features/Advanced/API-Declarations page; confirm the anchor #cdecl-support resolves correctly._ +_Source threads: 1125359845936205844 · confidence: high_ +_Date range: 2026-01-09_ +_Reviewer note: Placed under the AppObject section. Verify lazy initialisation and name-shadowing order against the compiler._ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1222086643737956494, 1292578507674746951, 1427261753544409168] > [!NOTE] -> Type libraries that follow the Windows SDK definition closely (such as WinDevLib) declare pointer-to-buffer and pointer-to-UDT parameters as `ByRef ` rather than `ByVal LongPtr`. At the call site, pass the variable or array element directly --- for example, `Data(0)` rather than `VarPtr(Data(0))`. For an optional pointer that should be null, pass `vbNullPtr` (without `ByVal`) rather than `ByVal 0`, which causes a type-coercion error. This differs from declarations that use `ByVal LongPtr` throughout, where `VarPtr()` is needed at every call site. Because twinBASIC allows substituting a `LongPtr` for a UDT argument, a library can retain the real SDK types for all pointer parameters while still accepting a `LongPtr` at the call site when needed. - +> +> **[TypeHint]** accepts a comma-separated list of enum type names, not just a single type. This is useful for Win32 API parameters that combine flags from several independent enumerations --- for example, a `dwDesiredAccess` parameter that accepts members from both a generic-rights enum and a file-specific-rights enum. All listed enumerations appear together in the Intellisense popup. +> +> ```tb +> Public Declare PtrSafe Function CreateFileW Lib "kernel32" ( _ +> ByVal lpFileName As LongPtr, _ +> [TypeHint(GenericRights, StandardAccessTypes, FileAccessRights)] _ +> ByVal dwDesiredAccess As Long, _ +> ByVal dwShareMode As Long, _ +> ByVal lpSecurityAttributes As LongPtr, _ +> ByVal dwCreationDisposition As Long, _ +> ByVal dwFlagsAndAttributes As Long, _ +> ByVal hTemplateFile As LongPtr) As LongPtr +> ``` + +_Source threads: 1106637635818094733 · confidence: high_ +_Date range: 2023-05-12 to 2023-05-24_ +_Reviewer note: This addition targets the `## TypeHint` section in Attributes.md (around line 633). Confirm that the multiple-enum form is in the current compiler --- the thread is from 2023 but the feature may have been present since then._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1292578507674746951, 1427261753544409168] + +> [!NOTE] +> twinBASIC's linker writes `Windows NT 4.0` into the OS version and Subsystem version fields of the `IMAGE_OPTIONAL_HEADER` in compiled executables, DLLs, and OCX files. Most applications are unaffected, but projects that require a specific OS or subsystem version in the PE header --- such as kernel drivers --- must patch these fields after the build. A `[RunAfterBuild]`-decorated procedure runs automatically after each successful build (see [RunAfterBuild](#runafterbuild)) and receives the output path through `App.LastBuildPath`, making it straightforward to apply any post-build patch without a separate build script. + +_Source threads: 1222086643737956494 · confidence: medium_ +_Date range: 2024-03-26_ +_Reviewer note: Verify the exact OS/Subsystem version string written by the twinBASIC linker, and confirm that App.LastBuildPath is the correct API for accessing the build output path from a [RunAfterBuild] procedure. The original workaround module is at https://github.com/fafalone/SetPEImageProps._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1427261753544409168] + +One common use for **[RunAfterBuild]** is to merge shell extension registration data immediately after compilation: + +```tb +[RunAfterBuild] +Public Sub RegisterExtension() + ' Merge the .reg file that registers this control as a shell extension. + Shell "reg import """ & App.LastBuildPath & "\\..\\ QuickRename.reg""" +End Sub +``` + +This pattern applies to any project that needs post-build steps: COM registration, file copying, or code signing. `App.LastBuildPath` holds the path to the compiled binary, so the sub does not need to hard-code a build output directory. + +_Source threads: 1292578507674746951 · confidence: high_ +_Date range: 2024-10-06_ +_Reviewer note: Draft inserts after the existing one-line description of [RunAfterBuild] in Attributes.md (around line 589). Verify the App.LastBuildPath usage is syntactically correct for a shell call and that the surrounding code example matches the site's tb code-fence convention._ + +--- + +## docs/Reference/Attributes.md · after-remarks [DUPLICATE? -- see also thread 1120535095279886416, 1172641655631908884, 1202939463630594079, 1212815197501395054, 1218599950359855186, 1230934350959345735, 1252891072774803487, 1333587859474747413, 1352268312117252126, 1079642242429489262, 1082767362253668474, 1083119492789448824, 1103494252811526164, 1113738858408988713, 1135126325783449711, 1146837274357678203, 1165150931554418698, 1167815153253892156, 1169508594341916712, 1173257226610290699, 1179991719551442975, 1206670811172306954, 1358081642052190318, 1398715673508843731, 1396063342417674291, 1421815804172701708, 1432054854016045096, 1432054854016045096, 1291780674772275220, 1357558567447695391, 1382015106380075078, 1387416025892393042, 1414587677574959165, 1431348682271363182, 1455320119239643390, 1459605334502150206, 1462420288280068323, 1491119090306256906, 1125359845936205844, 1106637635818094733, 1222086643737956494, 1292578507674746951] + +> [!NOTE] +> +> `[MustBeQualified]` can be applied to a **Module** (forcing callers to qualify member references with the module name) but cannot be applied to interfaces. Interface methods have no module scope, so this attribute has no equivalent for them. In large Win32 API definition libraries that define many interfaces, members from those interfaces will appear unqualified in Intellisense and cannot be suppressed with this attribute. + +_Source threads: 1427261753544409168 · confidence: medium_ +_Date range: 2025-10-24_ +_Reviewer note: The existing MustBeQualified entry says 'Applicable to: procedure' but the finding describes module-level use. Reviewer should verify: (1) whether the attribute also applies to Module declarations (not just individual procedures), and (2) confirm the interface limitation is accurate in the current compiler._ + +--- + +## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1432054854016045096, 1445969212698005648] + +When a DLL is placed next to the project file, the runtime may not locate it because Windows does not search the application directory in all circumstances. Placing `[SetDllDirectory(True)]` on the first **Declare** from that DLL instructs the loader to include the application path in its search. The attribute only needs to appear on one declaration per DLL; subsequent declarations in the same module do not need it. + +```tb +[ SetDllDirectory (True) ] +Public Declare Sub sqlite3_open Lib "sqlite.dll" (ByVal FileName As String, ByRef handle As Long) +Public Declare Sub sqlite3_close Lib "sqlite.dll" (ByVal handle As Long) +``` + +In VB6, the equivalent requires declaring and calling the Win32 `SetDefaultDllDirectories` API with the `LOAD_LIBRARY_SEARCH_APPLICATION_DIR` flag before the first call into the DLL. + +_Source threads: 1140151535217680436 · confidence: high_ +_Date range: 2023-08-13_ +_Reviewer note: Insert this example block immediately after the SetDllDirectory attribute description at the #setdlldirectory anchor in Attributes.md. Verify the 'only needs to appear on one declaration per DLL' claim against the .twin source or compiler behaviour._ + +--- + +## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1140151535217680436, 1445969212698005648] + +Under the **ArrayBoundsChecks** section, add an example block: + +### Win32 flexible array member pattern + +Some Win32 structures end with a variable-length trailing array (a C-style flexible array member). Declare the field with a single-element array inside the UDT, then apply **[ArrayBoundsChecks(False)]** to any procedure that reads or writes elements beyond index 0: + +```tb +' UDT that mirrors a Win32 structure with a trailing array +Private Type WLAN_AVAILABLE_NETWORK_LIST_V2_sa + NumberOfItems As Long + Index As Long + Network(0) As WLAN_AVAILABLE_NETWORK_V2 +End Type + +' Access the trailing elements through a pointer cast +[ArrayBoundsChecks(False)] +Private Sub ParseNetworkList(ByVal ptr As LongPtr) + With CType(Of WLAN_AVAILABLE_NETWORK_LIST_V2_sa)(ptr) + Dim i As Long + For i = 0 To .NumberOfItems - 1 + Debug.Print .Network(i).dot11Ssid.ucSSID + Next i + End With +End Sub +``` + +Placing the attribute on the UDT field itself is not yet supported; it must be placed on the calling procedure. + +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-10-26_ +_Reviewer note: The field-level attribute placement was a feature request acknowledged by WaynePhillipsEA; confirm whether it has since been implemented before publishing this note about the limitation._ + +--- + +## docs/Reference/Attributes.md · example [DUPLICATE? -- see also thread 1140151535217680436, 1432054854016045096] + +### Example: reading variable-length C-style arrays + +Win32 APIs often return structures whose last field is a flexible array --- a field declared with a fixed size of 1 in the type definition but allocated larger at run time. `[ArrayBoundsChecks(False)]` makes it possible to traverse the full array without a runtime bounds error. + +Declare the UDT with the last field sized to 1, allocate a **Byte** array large enough for the full data, overlay the UDT on the buffer using `CType(Of ...)`, then iterate: + +```tb +[ArrayBoundsChecks(False)] +Private Sub ReadHandles() + Dim buf() As Byte + ' --- allocate buf and fill it via the API --- + With CType(Of SYSTEM_HANDLE_INFORMATION_EX)(VarPtr(buf(0))) + Dim i As Long + For i = 0 To .NumberOfHandles - 1 + Debug.Print .Handles(i).HandleValue + Next i + End With +End Sub +``` + +An alternative that avoids a fixed-size array declaration entirely: allocate via `CoTaskMemAlloc`, declare the last field as a plain non-array type (for example, `FirstHandle As SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX`), and compute element addresses manually: + +```tb +Dim pEntry As LongPtr = VarPtr(info.FirstHandle) + i * LenB(Of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX) +Dim entry As SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX +entry = CType(Of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)(pEntry) +``` + +Both patterns require `[ArrayBoundsChecks(False)]` (first) or no array at all (second). Apply `[ArrayBoundsChecks(False)]` at the narrowest scope --- a single procedure rather than a whole module --- to limit exposure. + +_Source threads: 1445969212698005648 · confidence: high_ +_Date range: 2025-12-04_ +_Reviewer note: This example uses hypothetical Win32 type names for illustration. A reviewer should verify the exact struct names match the WinDevLib or twinBASIC definitions before publishing, and confirm both code paths compile correctly._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> Before BETA 984, an array declared with a negative lower bound, such as `Public Keyb(-3 To 255) As Boolean`, crashed with an access violation on x64 when [**ArrayBoundsChecks**](#arrayboundschecks) was disabled and an element at a negative index was read or written, even though the index was in bounds. The problem is fixed in BETA 984. On earlier builds, keep bounds checking enabled for code that uses such arrays. + +_Source threads: 1514851886656917664 · confidence: high_ +_Date range: 2026-06-12 to 2026-09-24_ +_Reviewer note: Insert directly after the ArrayBoundsChecks description. Fix version (BETA 984) comes from the thread; confirm against the release notes._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> Use an empty string, `[CompilerOptions("")]`, to turn LLVM off for one procedure. A string such as `"-llvm"` can appear to work, but it is not a documented form. The compiler seems to ignore option words it does not recognize, so a later version that validates the string more strictly could reject it. + +_Source threads: 1487268961061044338 · confidence: medium_ +_Date range: 2026-09-25_ +_Reviewer note: Place after the list of compiler options in the CompilerOptions section. The exact parsing rules were not confirmed, so the draft says only that the form is undocumented._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> The attribute is not accepted on a [**Type**](Type) or on a procedure written inside a **Type**: the compiler reports it as invalid syntax. Warnings raised by a UDT's member procedures cannot be suppressed at that level. Apply the attribute to the enclosing class or module instead, or change the warning's level in the project settings. + +_Source threads: 1542590154810851338 · confidence: low_ +_Date range: 2026-08-27_ +_Reviewer note: Based on a feature request open on 2026-08-27; probe the current build (scripts/tbbuild.mjs) to confirm the attribute is still refused on a Type and on a Type's member procedure, and that the suggested workaround (module-level attribute) works. Drop the note if a later build accepts it. Insert after the IgnoreWarnings code sample and its Applicable-to line._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> **[Restricted]** on a **Friend** member of a class hides it from the IntelliSense of a project that references the package, where **[NonBrowsable]** and **[Hidden]** do not. The member is hidden only for objects created explicitly (`Dim c As MyClass: Set c = New MyClass`). A variable declared with auto-instancing (`Dim c As New MyClass`) still shows it. + +_Source threads: 1516861763042279534 · confidence: low_ +_Date range: 2026-06-18 to 2026-09-30_ +_Reviewer note: Based on user experiments in Discord; verify in the IDE. Thread also reports [MustBeQualified] on a module broken in June 2026 with an open bug; not included._ + +--- + +## docs/Reference/Attributes.md · example + +A class tagged **AppObject** exposes its **Public** members as globals. Its `Class_Initialize` procedure runs when one of those members is first used, not when the program starts. That makes the class suitable for one-time initialization that depends on run-time state. The following class resolves an OpenGL extension function once a GL context exists. A module-level variable cannot do this, because module-level initialization runs before the context exists, and **wglGetProcAddress** then returns Null. + +```tb +[AppObject] +Class WdlGLInit + Public glArrayElementEXT As PFNGLARRAYELEMENTEXTPROC + + Private Sub Class_Initialize() + glArrayElementEXT = wglGetProcAddress("glArrayElementEXT") + End Sub +End Class +``` + +A project can contain more than one **AppObject** class. + +> [!NOTE] +> If a global of the same name is still defined elsewhere in the project, such as a leftover **Public** variable in a module, unqualified references bind to that global first. The **AppObject** member is then never used, and its value stays 0. + +_Source threads: 1125359845936205844 · confidence: high_ +_Date range: 2026-01-09_ +_Reviewer note: The Attributes page lists AppObject as applicable to CoClass only, while the thread applies it to a Class. Verify against the compiler and adjust the 'Applicable to' line if a Class is valid. The class and type names in the sample are adapted from WinDevLib; the sample is not compiled (it needs the OpenGL declarations)._ + +--- + +## docs/Reference/Attributes.md · example + +On a **Declare** or constant in a standard module, **[ComExport]** places the entry in a module (`TKIND_MODULE`) of the ActiveX DLL's type library. When the **Declare** has an alias or an ordinal, the declared name is exported and the alias becomes the entry point. User-defined types used by an exported **Declare** are exported as well. + +**[ComExport]** cannot export regular code, but a **Declare** can point at the project's own DLL, whose function is exported with [**DllExport**](#dllexport): + +```tb +' Module A +[ComExport] +Public Declare PtrSafe Function FooBar Lib "mydll.dll" () As Long + +' Module B +[DllExport] +Public Function FooBar() As Long + FooBar = 42 +End Function +``` + +> [!NOTE] +> VBA and VB6 resolve the **Lib** file when the type library is loaded. A bare file name that the search path cannot resolve produces *File not found*; use a full path or place the DLL in the system folder. An empty **Lib** string does not work from VBA. + +_Source threads: 1352268312117252126 · confidence: high_ +_Date range: 2025-06-05 to 2025-06-10_ +_Reviewer note: Page currently says the effect of ComExport is not established (HTML comment); verify against a built DLL. Place after the ComExport section._ + +--- + +## docs/Reference/Attributes.md · example + +The attribute suits code ported from C or C++ in which integer arithmetic is expected to wrap silently, such as hash and checksum routines. Combined with the native [**<<**](Core/LeftShift) and [**>>**](Core/RightShift) operators in place of helper functions that emulate rotation and wrapping addition, bit-twiddling code such as an MD5 implementation runs many times faster than a direct port of VB6-style helper code. + +```tb +[IntegerOverflowChecks(False)] +Private Function RotateLeft(ByVal value As Long, ByVal count As Long) As Long + RotateLeft = (value << count) Or ((value >> (32 - count)) And ((1& << count) - 1)) +End Function +``` + +> [!NOTE] +> The attribute applies to a whole class, module or procedure. It cannot be applied to a single variable or expression. + +_Source threads: 927641929905209405-2022-10 · confidence: high_ +_Date range: 2022-10-18 to 2022-10-19_ +_Reviewer note: Both findings come from BETA 152 discussion. Page already documents the attribute itself, so only the porting/performance example is added. The RotateLeft sample was composed by the drafter, not taken from the thread; verify it compiles and that >> is arithmetic vs logical (RightShift page) before accepting, since a sign-extended >> would need masking. The thread's timings (62s vs 266s vs 447s for MD5, about 9x) are omitted as anecdotal._ + +--- + +## docs/Reference/Attributes.md · example + +Both checks can be switched off for a whole module with one attribute line. Skipping the checks speeds up tight array loops such as hashing routines and prime sieves, with typical gains of about 40% to 60% on hashing code. The compiler generates unoptimized code, so removing the per-access checks is a worthwhile source-level tuning step. + +```tb +[ArrayBoundsChecks(False), IntegerOverflowChecks(False)] +Module Sieve + Public Function CountPrimes(ByVal Limit As Long) As Long + Dim IsComposite() As Boolean + ReDim IsComposite(0 To Limit) + Dim i As Long, j As Long, n As Long + For i = 2 To Limit + If Not IsComposite(i) Then + n += 1 + For j = i * 2 To Limit Step i + IsComposite(j) = True + Next j + End If + Next i + Return n + End Function +End Module +``` + +> [!NOTE] +> With the checks off, an out-of-range index reads or writes memory outside the array instead of raising a run-time error. Disable the checks only in code whose indexes are known to be valid. + +_Source threads: 927641929905209405-2022-11 · confidence: high_ +_Date range: 2022-11-03 to 2023-02-17_ +_Reviewer note: Source: BETA 168 (ArrayBoundsChecks added). Benchmarks: MD5 +58%, SHA-1 +52%, SHA-2 +46%; sieve 593 to 993 passes (x64) with both attributes. Verify the example compiles and the out-of-range note against actual behaviour. Figures were measured before the LLVM backend, so the speedup may have changed. Compiler codegen clarification (naive, debug-grade code by design) merged here only as a brief remark._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> A procedure compiled with LLVM through **CompilerOptions** may be a **Sub** or a **Function**. Returning a value is good practice but is not required. + +> [!NOTE] +> A report exists that `[CompilerOptions("+llvm,+optimize,+optimizesize")]` takes effect when the project runs in the IDE but not in the compiled .exe, in BETA 983. This is unconfirmed. When benchmarking LLVM-compiled code, test the built executable as well as the IDE run. + +_Source threads: 1134349410319470642-2025-04 · confidence: medium_ +_Date range: 2025-04-22 to 2026-08-24_ +_Reviewer note: Second callout comes from an unanswered report (BETA 983, low confidence); verify before publishing or drop. The first claim is a community answer (fafalone); the Getting-Started example already uses a Sub (FullyOptimizeMe) without stating it. Page index has no Core/CompilerOptions key; the Attributes.md CompilerOptions section was chosen._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> To keep the CLSID of an exposed class identical from build to build, set [ClassId](#classid), [InterfaceId](#interfaceid) and [EventInterfaceId](#eventinterfaceid) explicitly on the class. The attributes are written above the `Public Class` line in a `.twin` file. Classic `.cls` files (including those migrated from a VB6 project) cannot carry these attributes, because there is no class block in a `.twin` file for them to attach to; the GUIDs of such classes are generated anew by each build. +> +> For binary compatibility of an ActiveX DLL, the project UUID should also be set to the same value as the type library UUID. + +_Source threads: 1560558005039136818 · confidence: high_ +_Date range: 2026-10-16_ +_Reviewer note: Placed after the ClassId section. The thread also mentions EnumId not showing its uuid in OLE viewer and flipped enum member order (maintainer said he would fix both); omitted as cosmetic/known defects with no build named. Verify project UUID setting name against the project settings docs._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +The attribute accepts an optional string literal giving a friendly name to use for the variable when it is serialized. The **Serialize** and [**CustomDesigner**](#customdesigner) attributes propagate through [**Implements ... Via**](Implements). + +_Source threads: 927834661298798602-2022-02 · confidence: high_ +_Date range: 2022-02-04 to 2022-02-06_ +_Reviewer note: Source: v0.14.1 and v0.14.6. The exact argument syntax (position relative to the Bool) must be verified against the compiler. DefaultDesignerEvent is already documented on this page. Insert in the Serialize section._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +In a generic procedure, **CompileIf** can select the implementation by a constant expression that is evaluated at compile time, such as one that tests the type argument with the generic forms **VarType(Of** *T* **)** and **IsObject(Of** *T* **)**. **Variant** is not allowed as a generic type argument. + +_Source threads: 927834661298798602-2022-01 · confidence: high_ +_Date range: 2022-01-10_ +_Reviewer note: From v0.13.70 (CompileIf, VarType(Of x)), v0.13.71 (IsObject(Of x), Variant disallowed as generic specifier). Needs verification against the compiler and ideally a tested code example; these generic forms may not be documented elsewhere._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> Each procedure marked **DllExport** must have a unique name within the project. Two exported procedures with the same name make the build fail. + +_Source threads: 927834661298798602-2022-09 · confidence: high_ +_Date range: 2022-09-15_ +_Reviewer note: From BETA 121. Verify whether uniqueness applies to the procedure name or the export name; insert after the DllExport examples, before the TB5163 paragraph or after it._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +A parameter can carry several **TypeHint** attributes, which combines the members of more than one enum in the Intellisense list. + +_Source threads: 927834661298798602-2023-05 · confidence: high_ +_Date range: 2023-05-12_ +_Reviewer note: Source BETA 298. Original finding said 'multiple datatypes'; verify whether this means repeated attributes or a list argument against the .twin source, and adjust the wording and add an example._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +A space is expected after each comma that combines attributes in the same square braces, as in `[Attribute1, Attribute2(param)]`. [Auto Prettify](../../IDE/Code/Editor#auto-prettify) adds the space when it is missing. It also removes spaces inside an attribute, so `[ Description ("Foo Bar") ]` becomes `[Description("Foo Bar")]`. + +_Source threads: 927834661298798602-2024-02 · confidence: high_ +_Date range: 2024-02-06 to 2024-02-25_ +_Reviewer note: From BETA 438 (spaces removed inside attributes) and BETA 454 (space after comma added). Verify the relative link to IDE/Code/Editor resolves from the Attributes page. Other findings in this batch (EnableTrackingPrevention, TB0015, TB0017, DefaultMember on Slider/DTPicker/MonthView/UpDown, IDE options and commands, crash-loop SAFE mode) are already documented and were dropped. The DIAGNOSTICS panel rename, HINT/INFORMATION levels, hover TB code and compiler trace-mode were not checked in detail against IDE/Code/Diagnostics.md._ + +--- + +## docs/Reference/Attributes.md · after-remarks + +> [!NOTE] +> The predeclared instance of a class has a higher resolution priority than other global symbols. When a global symbol has the same name as the class, the name refers to the predeclared instance. + +_Source threads: 927834661298798602-2025-03 · confidence: high_ +_Date range: 2025-03-20_ +_Reviewer note: From BETA 716. Place in the PredeclaredID section. Verify against the .twin source._ + +--- + +## docs/Reference/CEF/index.md · after-remarks + +> [!NOTE] +> The CEF runtime files have no system-wide installer and do not register in a shared location. Both the 32-bit and the 64-bit runtime distributions can be present on the same machine at the same time, each in its own folder. A project targeting both architectures places the appropriate runtime files alongside each compiled binary. + +_Source threads: 1503736509713481799 · confidence: high_ +_Date range: 2026-05-12_ +_Reviewer note: Place this note in or near the 'Runtime files' / 'Installing runtime files' subsection rather than at the top of the page._ + +--- + +## docs/Reference/Compiler Constants.md · after-remarks + +## Target OS and compiler constants + +The project's **Target OS** setting controls the minimum OS version recorded in the compiled PE header and gates certain runtime features --- for example, control transparency requires targeting Windows 8 or later. As of BETA 844, the Target OS value is not exposed as a conditional compilation constant, so code cannot branch on the target OS at compile time using `#If`. + +> [!NOTE] +> x64 (AMD64) builds cannot target Windows 2000, which never shipped a consumer 64-bit edition. The practical minimum for x64 builds is Windows XP x64 Edition. Only 32-bit twinBASIC builds can target Windows 2000. This is an OS-level constraint, not a twinBASIC limitation. + +_Source threads: 1389961632846843988 · confidence: high_ +_Date range: 2025-07-04 to 2025-07-06_ +_Reviewer note: Verify whether a Target OS compiler constant has been added since BETA 844 (July 2025). The page uses a non-standard format (## headings without frontmatter sections) --- match the existing style when inserting._ + +--- + +## docs/Reference/Compiler Constants.md · after-remarks + +### `TWINBASIC_TARGETOS_MAJOR` and `TWINBASIC_TARGETOS_MINOR` + +**Purpose:** Allows conditional compilation based on the target operating system version of the root project.\ +**Value:** Two `Long` values holding the major and minor parts of the *Target OS* setting in Project Settings. + +_Source threads: 927834661298798602-2025-07 · confidence: high_ +_Date range: 2025-07-10_ +_Reviewer note: From BETA 855. Verify exact value semantics (e.g. 5/1 for Windows XP) against the compiler; the finding mapped to #If, but Compiler Constants.md is the page listing the predefined constants. Place after the TWINBASIC_BUILD_TYPE entry._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> +> A procedure used as an **AddressOf** callback target does not need to be **Public**. Declaring it **Private** restricts its visibility within the module while still allowing it to be invoked by the Windows API through the function pointer. + +_Source threads: 1091707854488096851 · confidence: low_ +_Date range: 2023-04-03_ +_Reviewer note: Source finding is low-confidence and mentions SetTimer/KillTimer specifically (VBA package, null symbol). Mapped to AddressOf as the closest reference page. Verify against the twinBASIC compiler behaviour --- in classic VBA, AddressOf targets must be in a standard Module and Public; confirm that Private callbacks work correctly in twinBASIC's implementation before publishing._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> When using Tab to autocomplete a function name after the **AddressOf** operator in the twinBASIC IDE, the editor may incorrectly append empty parentheses to the function name, producing `AddressOf FunctionName()` instead of the correct `AddressOf FunctionName`. The trailing parentheses are invalid in this position and must be deleted manually. + +_Source threads: 1175257978023182426 · confidence: medium_ +_Date range: 2023-11-18_ +_Reviewer note: IDE editor bug reported in beta 423. Verify whether this has been fixed in a later release before publishing._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1136660342739566635, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> Applying **AddressOf** to a procedure obtained through a **Declare** statement introduces compiler-generated indirection on top of the raw entry point. The address produced is not the same as the pointer returned by `GetProcAddress` for the same function. When this indirection causes a delegate invocation to behave differently from a direct call---for example, a caller-allocated buffer pattern that succeeds as a direct call but silently returns an empty result through the delegate---obtain the pointer explicitly with `GetProcAddress` and cast it to the target delegate type: +> +> ```tb +> Dim lpfn As LongPtr = GetProcAddress(hLib, "FunctionName") +> Dim d As YourDelegate = CType(Of YourDelegate)(lpfn) +> ``` +> +> This produces a clean function pointer with no extra indirection, and the delegate invocation then behaves identically to a direct call. + +_Source threads: 1499900664275275969 · confidence: high_ +_Date range: 2026-05-01 to 2026-05-02_ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1291033244753727490, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> When `AddressOf` targets a class member (e.g. `AddressOf myInstance.Func`), twinBASIC generates an on-the-fly stub that captures the specific object instance and dispatches through it. The returned pointer is therefore unique per instance---it is **not** the address of the function as it appears in the vtable. Call the returned pointer as a callback and the implicit `Me` argument is populated automatically for that object. To obtain the raw vtable entry address instead, use pointer arithmetic on `ObjPtr`; that is only necessary for low-level COM work and is not required for ordinary callback use. + +_Source threads: 1136660342739566635 · confidence: high_ +_Date range: 2023-08-03 to 2023-08-16_ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1253786438454214788, 1156840378213867620] + +> [!NOTE] +> **AddressOf** does not return the raw machine address of a procedure. twinBASIC creates a stub thunk behind the scenes so that calling conventions and the implicit object pointer are handled correctly when the callback is invoked. VB6 code that identifies a callback by ordinal position rather than by name --- for example, passing the integer `1` to mean "the last Sub in the file" --- cannot be ported mechanically: that pattern relied on VB6's internal vtable numbering, which twinBASIC does not reproduce. Replace the ordinal reference with an explicit **AddressOf** *ProcedureName* expression, where *ProcedureName* is the actual name of the target procedure. In VB6 projects using this pattern the target was typically the last Sub or Function declared in the module. + +_Source threads: 1291033244753727490 · confidence: high_ +_Date range: 2024-10-02_ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1156840378213867620] + +> [!NOTE] +> +> In VB6, **AddressOf** is restricted to procedures in standard modules (BAS files). Using it inside a class module or form requires self-subclassing workarounds such as a separate forwarding module. In twinBASIC that restriction does not apply --- **AddressOf** accepts any method regardless of its containing module type, making patterns like Win32 `SetWindowSubclass` straightforward to implement directly on a class. + +_Source threads: 1253786438454214788 · confidence: high_ +_Date range: 2024-06-25_ +_Reviewer note: The existing twinBASIC enhancements section already documents that AddressOf works on class, form, and UserControl members. This note adds the VB6 restriction context. Check whether it duplicates or enriches the existing text before inserting, and consider integrating into the existing bullet point instead._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks [DUPLICATE? -- see also thread 1091707854488096851, 1175257978023182426, 1499900664275275969, 1136660342739566635, 1291033244753727490, 1253786438454214788] + +> [!NOTE] +> **64-bit only:** In VBA 64-bit (and twinBASIC running in 64-bit mode against a VBA-hosted runtime), the addresses of procedures declared in a standard module are uninitialized when first queried via **AddressOf** --- until those procedures have been called at least once from within Basic code. If a Windows API (for example, one that invokes a dialog callback or a WndProc) calls through the address before any Basic code has invoked the procedure, the address is not valid and the program crashes. +> +> The workaround is to add a public no-op `Sub` to the same module as the callbacks or WndProcs, then call it from early initialization code (for example, `Class_Initialize`) before passing any **AddressOf** addresses to external APIs. This forces the runtime to initialize the procedure addresses. +> +> This issue affects any 64-bit project that passes module-level function addresses as callbacks or WndProcs to Windows APIs. + +_Source threads: 1156840378213867620 · confidence: high_ +_Date range: 2023-09-28_ +_Reviewer note: Verify whether this 64-bit uninitialized-address behavior still occurs in current twinBASIC builds, or whether it has been fixed. The finding was reported against VBA 64-bit hosted code; behavior in native twinBASIC 64-bit may differ._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks + +> [!NOTE] +> A callback installed with **AddressOf** can also serve as a vectored exception handler, registered through the `AddVectoredExceptionHandler` API. Such a handler can recover from a CPU exception, such as an access violation, by advancing the instruction pointer in the `CONTEXT` structure (`Eip` in a 32-bit process, `Rip` in a 64-bit process). One report says this protection works only in a compiled EXE and does not work while the project runs under the IDE debugger, so test any handler of this kind in a compiled build. + +_Source threads: 1507659583437668512 · confidence: medium_ +_Date range: 2026-05-23_ +_Reviewer note: Single-source claim from a Discord show-and-tell; the author corrected an earlier statement that it also worked in the IDE. Verify by probe (tbrun) that a VEH handler registered via AddressOf fails to catch access violations in the IDE debug run but works in a compiled EXE. Placed on AddressOf because the finding named it (package VBA, symbol AddressOf); the page index has AddressOf only under Core._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks + +> [!NOTE] +> **AddressOf** on a class, form, or user-control member accepts a **Function** as well as a **Sub**, provided the function returns an integral type. + +_Source threads: 927834661298798602-2022-09 · confidence: high_ +_Date range: 2022-09-16_ +_Reviewer note: From BETA 122. Place in the 'Class, form, and user-control members' bullet area; verify exact definition of 'integral type' against the compiler._ + +--- + +## docs/Reference/Core/AddressOf.md · after-remarks + +> [!NOTE] +> The value **AddressOf** produces has a [**Delegate**](Delegate) type rather than a plain **LongPtr**, which lets the compiler check it against the destination type. It converts to **LongPtr** implicitly, so existing code that stores it in a **LongPtr** or passes it to a **Declare** parameter is unaffected. A class-member **AddressOf** target may return **Single**, **Double** or **Date**, and **CDecl** targets in 32-bit builds receive a correct stub. + +_Source threads: 927834661298798602-2024-09 · confidence: high_ +_Date range: 2024-09-29 to 2024-09-30_ +_Reviewer note: From BETA 616/617. Existing page says only 'bit-compatible with LongPtr' and does not state the delegate result type; verify wording against the compiler. Delegate syntax and TB0026 are already documented._ + +--- + +## docs/Reference/Core/Alias.md · after-remarks [DUPLICATE? -- see also thread 1459159860875624595] + +> [!NOTE] +> +> **POINTAPI compatibility change (BETA 947 / v9.2.634.0):** `POINTAPI` was changed from a standalone UDT to a type alias (`Public Alias POINTAPI As POINT`). Because type aliases are interchangeable for simple assignments and scalar parameters, most code continues to work; however, passing a `POINTAPI()` array to an API parameter declared as `POINT()` raised Runtime Error 5 until BETA 953, where the underlying mismatch was corrected. If porting code that uses `POINTAPI` arrays, prefer declaring both the array variable and the API parameter with the same type name (`POINT` or `POINTAPI`) to avoid the issue on intermediate builds. + +_Source threads: 1455209665733464247 · confidence: high_ +_Date range: 2025-12-29 to 2026-01-17_ + +--- + +## docs/Reference/Core/Alias.md · after-remarks [DUPLICATE? -- see also thread 1455209665733464247] + +> [!NOTE] +> The twinBASIC IDE automatically resolves alias chains when the underlying type is itself an alias. Writing `Public Alias MyPtr As LongPtr`, for example, causes the editor to rewrite `LongPtr` to `Long` or `LongLong` depending on the target architecture, because `LongPtr` is itself an alias. This behavior applies to any alias-of-alias, not only `LongPtr`. The rewrite is silent and occurs as soon as the source file is edited. The issue was a regression in BETA 950 and was fixed in BETA 951. + +_Source threads: 1459159860875624595 · confidence: high_ +_Date range: 2026-01-09 to 2026-01-16_ + +--- + +## docs/Reference/Core/Alias.md · after-remarks + +> [!NOTE] +> In builds around BETA 983 and 984, a function whose return type is an alias of an alias of a user-defined **Type** larger than 8 bytes crashed the compiler at project start, even if nothing called the function. Returning the base type, or an alias one level deep, avoids the crash. One user reported this, and no fix was confirmed. + +_Source threads: 1548875667045621801 · confidence: medium_ +_Date range: 2026-09-14_ +_Reviewer note: Single unconfirmed report of a compiler defect. Consider moving it to BUGS-TO-REPORT.md instead, or dropping it once a later build is tested with the reproduction (Type over 8 bytes; Alias aliasA As myType; Alias aliasB As aliasA; Function test() As aliasB)._ + +--- + +## docs/Reference/Core/Alias.md · after-remarks + +> [!NOTE] +> Known defect: a program crashes at start-up when any code refers to a function whose return type is an alias of another alias (for example, `Public Alias D2D1_COLOR_F As D2D_COLOR_F` where `D2D_COLOR_F` is itself an alias of a **Type**), even if the function is never called. Until this is fixed, alias the underlying type directly so that each alias is a single level. + +_Source threads: 1125359845936205844 · confidence: medium_ +_Date range: 2026-01-13 to 2026-09-14_ +_Reviewer note: Bug reported September 2026 in the thread; build number not stated. Contradicts the statement in Alias-Types.md that aliases of aliases can be created; confirm whether still reproducible before publishing._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1157647794484543589, 1481282096877146244, 1458787436607045693, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> The compiler unconditionally generates implementations of the three core `IUnknown` members --- `AddRef`, `Release`, and `QueryInterface` --- for every **Class**. `AddRef` and `Release` manage the object's reference count and release all associated memory when the count reaches zero; the compiler enforces this by raising a "duplicate implementation of member" error if user code defines `AddRef` or `Release`. `IDispatch` members, by contrast, can be manually implemented. If custom `IUnknown` forwarding is required (for example, to expose a vtable-based interface that includes `IUnknown` members at known offsets), the only supported path is vtable patching at runtime. + +_Source threads: 1060905587577208863 · confidence: high_ +_Date range: 2023-01-06_ +_Reviewer note: Verify against the compiler source or a test project that AddRef/Release genuinely raise a compile error, and that QueryInterface does not (the source described that as a bug rather than a supported pattern)._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1481282096877146244, 1458787436607045693, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> twinBASIC dispatches class method calls through the native COM vtable function pointer, as the COM specification requires. VBA64 (Office 2010 with service packs and later) does not: when it detects that the target object is owned by its own runtime, it bypasses the vtable and jumps directly to the p-code stream. As a result, replacing a vtable slot with a pointer to custom executable memory works correctly in twinBASIC but has no effect in VBA64 --- and may crash it, because the VBA64 runtime navigates from the original vtable function pointer to the p-code stream and expects the pointer to be intact. This optimization was absent in Office 2010 RTM, where native vtable calls were used and vtable slot replacement worked. + +_Source threads: 1157647794484543589 · confidence: high_ +_Date range: 2023-09-30 to 2023-10-01_ +_Reviewer note: Placement on the Class page is reasonable but this could alternatively live on a dedicated low-level/interop features page if one is added. The p-code navigation detail (vtable pointer as a key to locate the p-code stream) is confirmed by WaynePhillipsEA (twinBASIC author) in the thread._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1458787436607045693, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> A **Public** array member of a class is automatically converted by the compiler into a **Property Get** / **Property Let** pair. The **Property Get** returns a *copy* of the array, not a reference to the storage inside the object. As a result, writing to an element through the returned copy --- or calling **ReDim** on it --- has no effect on the object's own data, and no compile-time error is raised. For example, `Set obj.Children(1) = node` silently discards the assignment, and `ReDim obj.arr(4)` raises a run-time error because `obj.arr` evaluates to a copy, not the original storage. + +To expose a mutable array from a class, declare the field **Private** (or **Protected**) and provide dedicated mutation methods: + +```tb +Private Children() As Node + +Public Sub SetChild(index As Long, n As Node) + Set Children(index) = n +End Sub +``` + +Arrays inherited from a base class are not affected --- they behave as direct field accessors. + +_Source threads: 1481282096877146244 · confidence: high_ +_Date range: 2026-03-11_ +_Reviewer note: Verify that the base-class exception (inherited arrays not subject to the copy behavior) is still accurate in the current compiler._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1459196491380818071, 1485008719719698604] + +> [!NOTE] +> Calling any method on a **Nothing** reference raises a run-time error at the call site --- the method body is never entered. All class methods are virtual (vtable) calls, and the runtime checks the instance before dispatching. A method body that reads `VarPtr(Me) = 0` to detect a **Nothing** caller will therefore never execute. +> +> Two workarounds are available: +> - Declare the method as `Protected` (which removes its vtable entry) and call it through [`UnprotectedAccess`](../../Modules/HiddenModule/UnprotectedAccess) at the call site. +> - Wait for proper static (`Shared`) class members, which are not yet implemented. +> +> Note also that `IUnknown::AddRef` and `Release` are called implicitly on the `Me` argument in member prologue and epilogue, so additional issues arise if a null instance reaches the method body by any other means. + +_Source threads: 1458787436607045693 · confidence: high_ +_Date range: 2026-01-08_ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1458787436607045693, 1485008719719698604] + +> [!NOTE] +> A `.cls` file can contain only one class definition, matching VB6 behaviour. A `.twin` file may contain multiple **Class** blocks --- along with [**Interface**](Interface), [**CoClass**](CoClass), [**Module**](Module), and [**Alias**](Alias) declarations --- in the same file. Classes defined in a `.twin` file are full, normal classes, accessible from anywhere in the project. + +_Source threads: 1459196491380818071 · confidence: high_ +_Date range: 2026-01-09 to 2026-01-10_ +_Reviewer note: The existing Class.md body already notes that .twin files may share a file with Interface/CoClass/Module, but does not explicitly say multiple Class blocks are allowed in one .twin file. Verify this and merge with the existing paragraph rather than duplicating it._ + +--- + +## docs/Reference/Core/Class.md · after-remarks [DUPLICATE? -- see also thread 1060905587577208863, 1157647794484543589, 1481282096877146244, 1458787436607045693, 1459196491380818071] + +> [!NOTE] +> Generic classes do not support inheritance. A class declared with a type parameter (e.g. `Class Derived(Of T)`) cannot use `Inherits`. Attempting to do so does not produce an immediate compile error, but accessing any inherited `Protected` member from within the generic derived class produces error TB5079 "Unrecognized symbol" when the generic class is instantiated. `Public` members of the base class may appear to work, which can mask the underlying lack of support. The limitation only surfaces at the point where the first instance of the generic class is created. Inheritance support for generic classes is planned for a future release. + +_Source threads: 1485008719719698604 · confidence: high_ +_Date range: 2026-03-21 to 2026-03-23_ +_Reviewer note: Verify against current compiler behaviour -- specifically whether TB5079 is the exact error number and whether Public member access truly works when Protected fails._ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> In early twinBASIC releases, a `Const` declaration that referenced an enum member defined in a separate standard module (for example, `Private Const MyConst As Long = MyEnum.MyEnumValue`) could fail to compile when the compiler processed the class module before the standard module declaring the enum. The failure was evaluation-order-dependent: adding, removing, or reordering modules in the project could change whether it compiled. This was fixed by the twinBASIC maintainer shortly after December 2022. + +_Source threads: 1053323243085385828 · confidence: high_ +_Date range: 2022-12-16 to 2023-01-07_ +_Reviewer note: Describes a now-fixed compiler bug. Verify against release notes around January 2023 to confirm the fix landed and the version number, if it should be cited._ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> twinBASIC warns when a hex literal is implicitly coerced from **Integer** to **Long** and sign-extended --- for example, `&H8000` becoming `&HFFFF8000`. This warning is suppressed when the hex value appears in a **Const** declaration, because the constant retains its **Integer** representation and no coercion occurs at that point. The warning fires only at the site of the actual coercion to **Long**, not at the declaration. + +_Source threads: 1071216635966402610 · confidence: high_ +_Date range: 2023-02-04 to 2023-02-07_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> A `Public Const` expression that references an enum member defined in another module may produce an "Error evaluating constant expression" compile error if the module containing the enum sorts alphabetically after the module containing the constant. The compiler evaluates constant expressions during an early pass; if the enum's defining module has not yet been processed, the value is not known at that point. To avoid this, either place the enum and the constants that depend on it in the same module, or name the module containing the enum so that it sorts alphabetically before the module that references it. + +_Source threads: 1082234628385034240 · confidence: medium_ +_Date range: 2023-03-06_ +_Reviewer note: Reported behavior included non-deterministic failure across IDE restarts, suggesting the module load order may not be strictly alphabetical in all cases. Verify whether this has been fixed in a later twinBASIC build before publishing._ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> Before twinBASIC BETA 298, a literal value that exceeded the range of its type-declaration character suffix (for example, `&H10012002%`, where `%` forces **Integer** type) was silently truncated rather than reported as a compiler error. The same code raises an error at edit time in VBA and VBx. The truncation affected both **Const** declarations and variable assignments. The bug was fixed in BETA 298; such out-of-range literals now produce a compiler error as expected. + +_Source threads: 1096942103180869632 · confidence: high_ +_Date range: 2023-04-15 to 2023-05-12_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> A self-referential `Const` declaration --- one where the constant name appears on the right-hand side of its own initializer, for example `TVN_BEGINRDRAG = TVN_BEGINRDRAG` instead of the intended `TVN_BEGINRDRAG = TVN_BEGINRDRAGW` --- causes the twinBASIC IDE syntax highlighter to enter an endless loop. The symptom is that all syntax coloring stops working and every token renders in a single color; the broken state persists after restarting the compiler. Correcting the self-referential assignment restores normal highlighting. This bug was fixed in BETA 312. + +_Source threads: 1110426304710459442 · confidence: high_ +_Date range: 2023-05-23_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1382265105621979166, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> Constants whose *expression* references a constant declared in a different module may intermittently fail to evaluate on compiler startup, producing a "cannot evaluate constant expression" error. The failure is non-deterministic: restarting the compiler without changing any source can alternate between reporting the error and not. A reliable workaround is to declare a private copy of the base constant in the same module that uses it, eliminating the cross-module dependency. + +_Source threads: 1201555639005106257 · confidence: high_ +_Date range: 2024-01-29 to 2026-02-05_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1487572362898444458, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> +> **Hexadecimal literals and type suffixes.** A hexadecimal literal is typed by its magnitude. The literal `&H8000` fits in a 16-bit **Integer** and is therefore interpreted as **Integer** `-32768`, not **Long** `32768`. Declaring `Const SOMECONSTANT As Long = &H8000` triggers warning TB0012 (*Possible misuse of hexadecimal literal notation*) because the literal is being widened --- a sign that the value is almost certainly wrong. The correct form is `&H8000&`, where the trailing ampersand forces the literal to **Long** type, giving the value `32768`. +> +> The practical consequence is that comparisons or flag checks using the unqualified form can evaluate to unexpected results --- for example, `&H8000 >= &H100` evaluates to `False` because `&H8000` is `-32768` as an **Integer**. + +_Source threads: 1382265105621979166 · confidence: high_ +_Date range: 2025-06-11 to 2025-06-25_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1069548868200583168, 1466129455276757117] + +> [!NOTE] +> Certain extreme numeric literals require special handling when used in **Const** declarations: +> +> **Currency.** The `@` suffix is required to tell the compiler that the literal is **Currency**. The accepted minimum value constant is `-922337203685477.5807@`; the literal `-922337203685477.5808@` is rejected by the compiler. This matches VB6 behavior. +> +> **LongLong.** The minimum **LongLong** value cannot be written as a single negative literal. Use the expression form instead: +> ```tb +Public Const MINLONGLONG As LongLong = (-9223372036854775807^ - 1) +``` +> or a string literal: `Public Const MINLONGLONG As LongLong = "-9223372036854775808"`. +> +> **Decimal.** There is no type-suffix character for **Decimal**, so extreme-range constants must be written using `CDec` with a string argument: +> ```tb +Public Const G_MAX_DECIMAL As Decimal = CDec("79228162514264337593543950335") +Public Const G_MIN_DECIMAL As Decimal = CDec("-79228162514264337593543950335") +``` + +_Source threads: 1487572362898444458 · confidence: high_ +_Date range: 2026-03-28 to 2026-03-30_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1466129455276757117] + +> [!NOTE] +> Enum types cannot be used as the declared type of a **Const**. A declaration such as `Const x As SomeEnum = SomeEnum.Value` raises a compile-time error (`Expected type name`). This restriction is inherited from VB6. Because all enum values are backed by **Long**, the workaround is to omit the **As** *type* clause and let the constant take on the type of its initializer expression, or to declare it explicitly **As Long**. + +_Source threads: 1069548868200583168 · confidence: high_ +_Date range: 2023-01-30_ + +--- + +## docs/Reference/Core/Const.md · after-remarks [DUPLICATE? -- see also thread 1053323243085385828, 1071216635966402610, 1082234628385034240, 1096942103180869632, 1110426304710459442, 1201555639005106257, 1382265105621979166, 1487572362898444458, 1069548868200583168] + +> [!NOTE] +> A hex literal without a type suffix is assigned the type **Integer** by the compiler (for compatibility with VBA and VB6). A value such as `&H8000` is therefore an **Integer** constant whose high bit is set. When the constant is used in a **Long** context, the runtime sign-extends the **Integer** value, so `&H8000` becomes `&HFFFF8000` rather than `&H00008000`. Declaring the constant `As Long` does not change this --- the type of the *literal* itself determines the intermediate representation. The correct fix is to append the **Long** type-declaration character to the literal: `&H8000&`. This affects any hex constant whose value is `>= &H8000`. + +_Source threads: 1466129455276757117 · confidence: high_ +_Date range: 2026-01-28_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!WARNING] +> +> **`RtlInitUnicodeString` does not allocate memory.** When porting VB6 code that uses Windows NT Native API `UNICODE_STRING` structs, verify that every call to `RtlFreeUnicodeString` corresponds to an API that explicitly heap-allocates the string buffer --- such as `RtlDuplicateUnicodeString`. `RtlInitUnicodeString` only stores the length and address of an existing buffer into the `UNICODE_STRING` struct; it performs no heap allocation. Calling `RtlFreeUnicodeString` on a `UNICODE_STRING` that was initialized with `RtlInitUnicodeString` is incorrect and causes a hard crash in twinBASIC. The same misuse may appear to succeed silently in VB6 due to runtime memory layout differences. + +_Source threads: 1053654998556348457 · confidence: high_ +_Date range: 2022-12-17 to 2023-01-07_ +_Reviewer note: This is a Win32 NT Native API porting gotcha (ntdll.dll: RtlInitUnicodeString / RtlFreeUnicodeString / RtlDuplicateUnicodeString). The finding was confirmed by Wayne Phillips as an API usage error, not a twinBASIC bug. Placement on the Declare page is reasonable but the site has no dedicated Win32 API porting guide; a reviewer may prefer to place this on the Features/Advanced/API-Declarations page instead._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When a declared function's output parameter is typed `ByRef pUnk As IUnknown` and the caller passes a variable of a more specific interface type (for example, `IPicture`), the compiler inserts an implicit **QueryInterface** call at the callsite after the function returns. If **QueryInterface** succeeds the variable is populated with the correct interface pointer. If it fails --- for example, the returned object does not implement the requested interface --- a runtime error such as "No such interface supported" or "Type mismatch" is raised. This behavior is identical in VB6 and twinBASIC. A practical consequence is that when calling an API such as `OleCreatePictureIndirect`, passing `IID_IUnknown` as the `riid` argument is sufficient: the implicit QI at the callsite performs the cast to the caller's declared type, so supplying `IID_IPicture` explicitly is redundant. + +_Source threads: 1053698170846183434 · confidence: medium_ +_Date range: 2022-12-17_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> Prior to BETA 269, a **Public Declare** and a **Public Enum** with the same base name (compared case-insensitively --- for example, `ShowWindow` and `SHOWWINDOW`) in separate modules within the same project caused an ambiguity error. The compiler's symbol resolver is context-free: it looks up a name in the symbol table without examining how the name is used at the call site (whether it is followed by a dot, appears as a type annotation, or is being called as a procedure), so it cannot distinguish between a callable declaration and a data type of the same name when both are in scope. This was fixed in BETA 269. In earlier versions the workaround was to place the **Declare** and the conflicting **Enum** in the same module, or to qualify the symbol with its module name. + +_Source threads: 1084723164296261672 · confidence: high_ +_Date range: 2023-03-13_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When a standard DLL function returns an array of UDTs directly as its return value, the caller may crash. The cause is a lifetime management issue at the DLL boundary: the array may be released before the caller has finished with it. A reliable workaround is to store the array in a `Private` module-level variable inside the DLL module, then return that variable. The module-level variable extends the array's lifetime beyond the function frame and prevents the premature deallocation. + +_Source threads: 1099208311313813534 · confidence: medium_ +_Date range: 2023-04-22_ +_Reviewer note: Needs verification against the twinBASIC runtime source to confirm the root cause (array released too soon at DLL boundary). The workaround (store in module-level variable) was reported as effective in practice, but the underlying cause has not been confirmed from first principles._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When writing a COM interface method that takes an optional output pointer (such as a D2D1\_TAG-style out-parameter), declaring the parameter as `ByRef tag As LongLong` and calling it with `ByVal 0` does not pass a null pointer on 32-bit (x86) builds. The `ByVal` modifier at the call site promotes the argument to a 64-bit value, so two 64-bit zero pushes land on the stack instead of two 32-bit null pointer pushes. The call returns with the stack misaligned, causing an access violation. Declare the parameter as `ByRef tag As Any` (the convention used by oleexp/olelib type libraries) so that `ByVal 0` correctly passes a 32-bit null pointer. + +_Source threads: 1101704869804511265 · confidence: high_ +_Date range: 2023-05-11 to 2023-05-12_ +_Reviewer note: This applies specifically to COM interface method declarations (Interface blocks), not only to Declare statements. If a dedicated page for COM interface method argument conventions exists or is added, consider moving this note there. Verify the claim against the twinBASIC compiler behavior on x86 targets._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When an ANSI Win32 API is declared and a UDT parameter contains a fixed-length string field (for example, `dmDeviceName As String * CCHDEVICENAME`), the compiler generates implicit ANSI-conversion procedures for the UDT. Before a compiler fix issued in a subsequent BETA release, those generated procedures did not explicitly return `S_OK` on success, leaving the return value of the procedure's final instruction (a pointer) in `EAX`/`RAX` to be interpreted as an `HRESULT`. On x86 this rarely caused a problem because pointer values seldom set the `0x80000000` failure bit. On x64, the larger address space means pointers frequently have that bit set, so the implicit conversion is treated as failed and the API call is aborted intermittently. +> +> Two workarounds are available: +> +> 1. Replace the fixed-length string field with a `Byte` array of the same length (for example, `dmDeviceName(CCHDEVICENAME - 1) As Byte`). +> 2. Switch to the Unicode variant of the API using `DeclareWide`, replacing `String` fields with `String` and `Integer` arrays in place of `Byte` arrays for wide-char fields. + +_Source threads: 1107676681449254952 · confidence: high_ +_Date range: 2023-05-18_ +_Reviewer note: Verify the exact BETA release version in which the compiler fix shipped. The finding says 'a subsequent BETA release' without a specific number. Also confirm that DeclareWide is the correct twinBASIC keyword for the Unicode workaround._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> In versions of twinBASIC before BETA 331, passing a `ByVal` expression as a named argument to a **Declare**-imported procedure caused a syntax error. For example, `lpOutput:=ByVal 0` was rejected. The workaround was to wrap the expression in parentheses: `lpOutput:=(ByVal 0)`. VB6 accepted the bare form without parentheses. This restriction was removed in BETA 331. + +_Source threads: 1117974216810188920 · confidence: high_ +_Date range: 2023-06-13_ +_Reviewer note: Verify the exact BETA 331 fix version against the twinBASIC changelog. The Discord thread attributed the fix to BETA 331 and confirmed it was a named-argument-only issue, but the release number should be confirmed before publishing._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When a `Declare` statement loads a DLL from the application's own folder, the DLL may fail to load on subsequent IDE sessions even though it loads correctly on the first run after import. The DLL search path used during the initial session does not persist once the IDE is closed and reopened. Add the `[SetDllDirectory(True)]` attribute to the `Declare` statement to include the application's directory in the DLL search path: +> +> ```tb +> [SetDllDirectory(True)] +> Public Declare PtrSafe Sub sqlite3_open Lib "sqlite.dll" (ByVal FileName As String, ByRef handle As LongPtr) +> ``` +> +> If the DLL is installed into a Windows system directory (`System32` or `SysWOW64`), the attribute is not needed. + +_Source threads: 1139937031775072307 · confidence: medium_ +_Date range: 2023-08-13 to 2023-08-14_ +_Reviewer note: Verify that [SetDllDirectory(True)] on a Declare statement is the canonical fix for this behavior and that the description in docs/Reference/Attributes.md (which says it 'allows searching the app path for the DLLs in the base app's declare statements') matches the behavior reported. The example uses PtrSafe for correctness on 64-bit; reviewer should confirm the attribute works on both 32-bit and 64-bit targets._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC supports the `_cdecl` calling convention natively in **Declare** statements. VB6 cannot call `_cdecl` DLL functions directly and requires a separate stdcall wrapper DLL instead. In twinBASIC, a direct **Declare** with the **CDecl** keyword replaces any such wrapper. See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#cdecl-support) for syntax and examples. + +_Source threads: 1140151535217680436 · confidence: high_ +_Date range: 2023-08-13_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Specifying a relative subfolder path in *libname* --- for example `Lib "subfolder\anyname.dll"` --- results in an "Error Loading DLL" at runtime. twinBASIC does not resolve the subfolder relative to the executable's location. The workaround is to call the Win32 `SetDllDirectory` API with the target directory before the first call that loads the library, or to use an absolute path in *libname*. The file extension does not have to be `.dll`; other extensions such as `.dat` are accepted provided the folder path is resolved correctly. + +_Source threads: 1148585708362149899 · confidence: medium_ +_Date range: 2023-09-05_ +_Reviewer note: Verify whether this limitation is still present in current builds, and whether there is a [SetDllDirectory] attribute documented on the Attributes page that already covers this workaround._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **MIDL type library type mapping** -- When a type library (TLB) compiled with MIDL is imported into twinBASIC, the MIDL pointer-width types do not map to twinBASIC's intrinsic **LongPtr** as they do conceptually in C: +> +> - `LONG_PTR` always maps to a 64-bit integer regardless of whether the TLB was compiled with `/env win32` or `/env win64`. +> - `__int3264` maps to **LongLong** in both 32-bit and 64-bit configurations. +> - `size_t` causes a parse error because it maps to an unsigned 64-bit value that twinBASIC cannot represent. +> +> As a result, no MIDL type currently maps to twinBASIC's intrinsic **LongPtr** when imported through a TLB. +> +> **Workaround 1 -- shared TLB:** Add `typedef [public] long LongPtr;` to the MIDL source. VB6 treats it as **Long** (4 bytes). twinBASIC gives its own intrinsic **LongPtr** priority over the TLB-defined alias when the name is used unqualified; if the type is referenced with its library qualifier (e.g. `mytypelib.LongPtr`), twinBASIC resolves it to the TLB's 4-byte **Long** definition instead. +> +> **Workaround 2 -- `void*` as `As Any`:** Arguments declared as `void*` in MIDL map to **As Any** in twinBASIC, independent of the TLB's compilation target. Passing a pointer `ByVal` with **As Any** works in both 32-bit and 64-bit builds. This requires callers to write `ByVal` explicitly on each call site, which is error-prone, and `void*` cannot be used inside UDTs --- it is unsupported in both VB6 and twinBASIC for that context. + +_Source threads: 1159270753468952576 · confidence: medium_ +_Date range: 2023-10-04 to 2023-10-05_ +_Reviewer note: Verify current behavior against twinBASIC source or a test TLB: in particular, confirm that LONG_PTR still maps to 64-bit rather than LongPtr, and that the void*/As Any behavior is still architecture-independent. The thread reports Beta 399 behavior; check whether any of these mappings have changed in later builds._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> `[PreserveSig(False)]` is supported on **Declare** statements in twinBASIC. When translating a type library whose COM methods return an HRESULT that should be hidden from tB callers, `[PreserveSig(False)]` is the correct annotation --- the compiler transforms the call so that the HRESULT is checked automatically and the last out-parameter is promoted to the function's return value. An earlier version of the twinBASIC typelib viewer incorrectly emitted a warning comment instead of the `[PreserveSig(False)]` attribute for such declarations; this was corrected in BETA 553. + +_Source threads: 1248884196575809578 · confidence: high_ +_Date range: 2024-06-08_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When a `Declare` statement passes a SafeArray pointer to a Windows API function such as `SafeArrayLock` or `SafeArrayUnlock`, declare the pointer parameter **ByVal**, not **ByRef**. Declaring it **ByRef** passes the address of the local variable that holds the pointer --- a double indirection --- which corrupts the lock and unlock operations. The symptom is that a class constructor that calls these functions appears to finish correctly (the instance looks valid inside the constructor body) but the caller receives **Nothing** or an instance in an incorrect state. + +_Source threads: 1270386953115930828 · confidence: medium_ +_Date range: 2024-08-06 to 2024-08-07_ +_Reviewer note: Verify against the SafeArrayLock / SafeArrayUnlock Win32 API signatures to confirm ByVal is correct for the SAFEARRAY* parameter. The finding is self-reported by the user after fixing their own bug; no maintainer confirmation in the thread._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC automatically redirects `Declare` statements that target `msvbvm60` by exported name to its own runtime equivalents, so VB6-compatible code runs without requiring `msvbvm60.dll` on disk. However, this redirect applies only when the `Alias` clause specifies an exported name. If the alias is an ordinal reference --- for example `Alias "#307"` --- twinBASIC does not recognise the declaration as a redirected call and will attempt to load the real `msvbvm60.dll` at runtime. VB6 code in the wild sometimes imports `msvbvm60` functions by ordinal (for example `PutMem4 Lib "msvbvm60" Alias "#307"`), so this limitation can affect VB6 migration projects. + +_Source threads: 1295648317820108831 · confidence: high_ +_Date range: 2024-10-15 to 2024-10-21_ +_Reviewer note: A twinBASIC maintainer (WaynePhillipsEA) described this as a bug that should be straightforward to fix. Verify whether this has been fixed in a later release before publishing; if fixed, the note may be removed or converted to a historical remark._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> On Windows Vista and Windows 7, including the `.dll` extension in the *libname* string (for example, `"cabinet.dll"`) causes a DLL search path error at runtime, preventing the DLL from loading. Omit the extension (use `"cabinet"` rather than `"cabinet.dll"`) when targeting those OS versions. This restriction does not apply to Windows XP, Windows 10, or Windows 11. The same issue can affect other DLLs listed in the import address table (IAT). Note that the failure occurs only in compiled executables; running from the twinBASIC IDE is not affected. + +_Source threads: 1310261549574656001 · confidence: high_ +_Date range: 2024-11-24 to 2024-12-14_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **ByVal with object variables differs from VB6.** In VB6, passing an object variable `ByVal` to a `Declare`-style API (for example a `CopyMemory` wrapper) treats the argument as the address of the reference slot --- equivalent to `ByVal VarPtr(o)`. In twinBASIC the same call behaves as `ByVal ObjPtr(o)`, dereferencing the object pointer first. Low-level object-pointer code that relies on `ByVal ` without an explicit pointer function will therefore produce different results between the two compilers. To be explicit: use `ByVal ObjPtr(o)` when the API needs the vtable/object address, and `ByVal VarPtr(o)` when it needs the address of the reference variable itself. + +_Source threads: 1356284960495108156 · confidence: medium_ +_Date range: 2025-03-31_ +_Reviewer note: Verify against twinBASIC compiler source or a reproducible test case before publishing. The distinction between ObjPtr and VarPtr semantics for ByVal object arguments is subtle and this finding is medium-confidence._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> From BETA 797 onward, `ByRef ParamArray args As Any()` is supported in **Declare** (without **CDecl**) for `__stdcall` functions that accept a `va_list`-style pointer. Before BETA 797, using this pattern without **CDecl** raised a compiler error requiring **Variant**; the workaround was to add **CDecl**, which caused a calling-convention mismatch for true stdcall functions. +> +> **CDecl** remains required for genuinely variadic functions that use the C `...` ellipsis calling convention. + +_Source threads: 1378220872405487636 · confidence: high_ +_Date range: 2025-05-31 to 2025-06-08_ +_Reviewer note: Verify that the fix landed in exactly BETA 797 and that the description of `ByRef ParamArray args As Any()` being valid for stdcall va_list-style declarations is accurate against the twinBASIC compiler source._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC does not silently coerce `Object` variables to COM interface types such as `IUnknown` when passing them to **Declare** functions. Passing an `Object` variable to a parameter declared `As IUnknown` (or any other specific COM interface) causes an access violation (&HC0000005) at run time. VB6 accepted this by performing a `QueryInterface` coercion automatically. In twinBASIC the types must match exactly: declare a local variable `As IUnknown`, obtain the reference into that variable, then pass it to the API call. If the calling code needs an `Object` afterwards, assign the result separately with `Set objVar = ppUnk`. + +_Source threads: 1444009884637986966 · confidence: high_ +_Date range: 2025-11-28_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When the `[ComExport]` attribute is applied to a `Declare` function whose return type is a type alias, parameter types were correctly exposed under the alias name in the exported type information, but the return type was resolved to its underlying primitive (for example, a `WinRT.LongPtr` return type appeared as `Long`). Interface methods with alias return types were not affected. Fixed in BETA 957. + +_Source threads: 1463135813574594730 · confidence: high_ +_Date range: 2026-01-20 to 2026-01-21_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> When a **Declare** parameter is typed **As Any** and represents a null pointer, pass `vbNullPtr` rather than the integer literal `0`. Passing `0` only guarantees that the lower 16 bits of the argument slot are zero; the current twinBASIC compiler happens to zero-extend correctly, but an optimizing backend (such as the planned LLVM backend) may leave upper bits non-zero if it determines only the low 16 bits need satisfying. `vbNullPtr` is a native-pointer-sized zero and is always safe. + +_Source threads: 1505726092479041556 · confidence: high_ +_Date range: 2026-05-18_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **olepro32.dll** has no 64-bit version. When compiling in 64-bit mode, twinBASIC (as of BETA 311) automatically redirects any **Declare** that targets **olepro32.dll** to **oleaut32.dll**, where MSDN has long documented the same functions (such as `OleCreatePictureIndirect`). Existing VB6-derived code that declares these functions against **olepro32.dll** compiles and runs correctly in 64-bit mode without manual changes. + +_Source threads: 1110121787745894400 · confidence: high_ +_Date range: 2023-05-22_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> As of October 2025, the **PtrSafe** keyword is optional in twinBASIC for both 32-bit and 64-bit targets. A **Declare** statement without **PtrSafe** compiles for both architectures. The keyword is still accepted and still signals VBA7 x64 intent, but its absence is no longer a compile error on any target. Note that omitting **PtrSafe** while using 32-bit-only **Long** for pointer parameters still produces incorrect behaviour on x64 at runtime --- the compiler does not validate pointer-size correctness when **PtrSafe** is absent. +> +> **LongPtr** may be used in a **Declare** statement that does not carry **PtrSafe**. The requirement that **LongPtr** implies **PtrSafe** is a misconception: in 32-bit VBA7 (and in 32-bit or 64-bit twinBASIC), **LongPtr** is valid in non-PtrSafe declares. **PtrSafe** was only ever required by 64-bit VBA7; twinBASIC now makes it optional for all targets. + +_Source threads: 1421815804172701708 · confidence: high_ +_Date range: 2025-09-28 to 2025-10-15_ +_Reviewer note: The existing Declare page documents PtrSafe as "required in 64-bits" in its parameter table. That description should be updated to reflect that PtrSafe is now optional in twinBASIC (confirmed by maintainer WaynePhillipsEA around October 2025). The two NOTE callouts near the top of the page also assert that PtrSafe is required for x64 builds and should be reviewed for accuracy against the current compiler behaviour._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> When calling a Win32 API that accepts a `POINT` structure by value, passing two separate **Long** parameters (one for `x`, one for `y`) does not work reliably on x64. On 64-bit targets the first four arguments are passed in registers rather than on the stack, so a two-**Long** layout does not match what the API expects. A cross-bitness workaround is to declare the parameter as **ByVal Currency**: a **Currency** value is 8 bytes --- the same size as a 64-bit **POINT** --- and is passed in a single slot, matching the x64 calling convention. This approach works on both 32-bit and 64-bit twinBASIC targets without conditional compilation. + +_Source threads: 1421815804172701708 · confidence: medium_ +_Date range: 2025-09-30_ +_Reviewer note: Community-sourced workaround (wqweto). Verify that passing ByVal Currency for a POINT-by-value parameter produces the correct register layout on x64 before publishing. Consider whether an illustrative code example would be helpful here._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Overloaded `Declare` statements --- for example, one overload taking `LongPtr` parameters and another taking `String` parameters for the same API --- cannot be disambiguated by the compiler when the declarations appear inside a **Class** or **Form** module. The compiler reports "Unable to disambiguate between overloaded methods" in this case. The same declarations work correctly when placed in a standard **Module**. As a workaround, move overloaded API declarations to a standard module and call them from the class or form. + +_Source threads: 1432054854016045096 · confidence: high_ +_Date range: 2025-12-06_ +_Reviewer note: Reported as GitHub issue #2102. Verify whether it has been fixed before publishing._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> Some Win32 ANSI APIs that accept a null or empty buffer to probe the required size may return a byte count rather than a character count. `GetUserNameA` (in `Advapi32.dll`) is a known case: calling it with a null buffer sets the output length to N\*2 instead of N. The practical remedy is to pre-allocate a buffer large enough for the maximum possible value (for example, UNLEN\+1 = 257 characters for user names) rather than relying on a probe call to size the buffer. The `GetUserNameW` (Unicode) variant and similar ANSI APIs such as `GetComputerNameA` do not exhibit this behavior. + +_Source threads: 1187218188728008785 · confidence: medium_ +_Date range: 2023-12-21 to 2023-12-26_ +_Reviewer note: This is a Windows API quirk, not a twinBASIC-specific behavior. Verify whether this note is within scope for Declare.md (which currently focuses on syntax) or whether it belongs in a different location such as a Win32 API usage guide._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> The `Null` keyword is only valid with **Variant** typed values. Assigning `Null` to a `LongPtr` or any non-Variant numeric type raises run-time error 'Invalid use of Null'. To pass a null pointer to a Win32 API declared with a numeric pointer parameter, use the value `0` (or `ByVal 0` when the parameter is `ByRef`). + +> [!NOTE] +> twinBASIC introduces **vbNullPtr** as a special value that can be passed in place of a typed UDT argument when the underlying Win32 API accepts NULL for that parameter. VB6 required such optional struct arguments to be declared `As Any` and called with `ByVal 0&`; twinBASIC lets the declaration use the real UDT type and the caller substitutes **vbNullPtr** to pass the equivalent of a C NULL pointer. + +```tb +' Win32 declaration using the real UDT type: +Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileW" ( _ + ByVal lpFileName As LongPtr, _ + ByVal dwDesiredAccess As Long, _ + ByVal dwShareMode As Long, _ + lpSecurityAttributes As SECURITY_ATTRIBUTES, _ + ByVal dwCreationDisposition As Long, _ + ByVal dwFlagsAndAttributes As Long, _ + ByVal hTemplateFile As LongPtr) As LongPtr + +' Pass vbNullPtr where the API accepts NULL: +Dim hFile As LongPtr +hFile = CreateFile(StrPtr("C:\test.txt"), GENERIC_READ, FILE_SHARE_READ, _ + vbNullPtr, OPEN_EXISTING, 0, 0) +``` + +### String arguments and DeclareWide + +Win32 API declarations in twinBASIC follow one of three patterns depending on how string arguments are handled: + +- **ANSI variant** (e.g. `MessageBoxA`) --- string parameters declared `As String`; string literals or **String** variables are accepted directly. +- **Wide variant** (e.g. `MessageBoxW`) --- string parameters declared `As LongPtr`; the caller must pass `StrPtr(s)` to obtain the address of the string's character buffer. +- **Alias variant** (e.g. `MessageBox`) --- declared using twinBASIC's `DeclareWide` feature; accepts bare **String** arguments and internally calls the W API. This is the simplest form for Unicode-correct calls. + +Passing a **String** variable where a `LongPtr` is expected results in run-time error 13 (Type Mismatch). + +_Source threads: 1195546112459821176 · confidence: high_ +_Date range: 2024-01-13_ +_Reviewer note: Verify the vbNullPtr feature is documented in the .twin sources. The CreateFile example is illustrative; reviewer should check it compiles correctly with WinDevLib-style declarations. DeclareWide description should be cross-checked against docs/Features/Advanced/API-Declarations._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC (like VB6) cannot consume .NET managed assemblies. Only traditional COM/ActiveX DLLs and standard Win32 DLLs are supported. Importing a .NET assembly through the **Project > References** dialog will add it to the Imported Type Libraries node but its classes will not be available for use. To call into a .NET library from twinBASIC, the library must expose a COM-callable wrapper. + +> [!NOTE] +> +> twinBASIC supports the `CDecl` calling convention natively in **Declare** statements. Append **CDecl** after the procedure name in the declaration to call C-exported functions that use the `cdecl` convention rather than `stdcall`. In VB6, `cdecl` support requires a third-party patch and does not work in the IDE. See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#cdecl-support) for syntax and examples. + +_Source threads: 1253786438454214788 · confidence: high_ +_Date range: 2024-06-21 to 2024-06-24_ +_Reviewer note: Two separate notes merged here. (1) The .NET-assembly restriction should be verified against any twinBASIC roadmap items --- a COM-callable-wrapper requirement is the current state as of the Discord thread. (2) The CDecl note references the Features/Advanced/API-Declarations page; confirm the anchor #cdecl-support resolves correctly._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Type libraries that follow the Windows SDK definition closely (such as WinDevLib) declare pointer-to-buffer and pointer-to-UDT parameters as `ByRef ` rather than `ByVal LongPtr`. At the call site, pass the variable or array element directly --- for example, `Data(0)` rather than `VarPtr(Data(0))`. For an optional pointer that should be null, pass `vbNullPtr` (without `ByVal`) rather than `ByVal 0`, which causes a type-coercion error. This differs from declarations that use `ByVal LongPtr` throughout, where `VarPtr()` is needed at every call site. Because twinBASIC allows substituting a `LongPtr` for a UDT argument, a library can retain the real SDK types for all pointer parameters while still accepting a `LongPtr` at the call site when needed. + _Source threads: 1376962828497326190 · confidence: high_ _Date range: 2025-05-27 to 2025-05-30_ --- -## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> Declaring an external function parameter as `ByVal Value As String` in a `DeclareWide` declaration does not copy the string at the call site. Passing a **String** `ByVal` is equivalent to passing `StrPtr(s)` --- the runtime passes the raw pointer to the BSTR data. For `DeclareWide` (`LPWSTR` / BSTR) parameters there is no conversion copy at all. This is why `ByVal ... As String` is used to declare parameters that accept `LPWSTR` even though the parameter is conceptually a pointer. +> +> To pass `NULL` to a `DeclareWide` **String** parameter, use `vbNullString` --- not `0&` or `vbNullPtr`. + +_Source threads: 1382372425118781500 · confidence: high_ +_Date range: 2025-06-11_ +_Reviewer note: Verify that this is specifically true of DeclareWide and not all Declare forms. The source (WaynePhillipsEA) is the twinBASIC maintainer, so confidence is high, but it would be worth noting whether ANSI Declare (non-Wide) behaves differently at the call site._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1488640456316420182, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> twinBASIC does not follow the VB6 convention of searching the application's own folder first when loading a DLL at runtime. Instead, it applies a search order that prioritizes system folders. If both the system and the application folder contain a file with the same name, the system copy is loaded rather than the local copy. This is a known difference from VB6 (tracked in GitHub issue [#455](https://github.com/twinbasic/twinbasic/issues/455)). Projects ported from VB6 that ship local copies of DLLs alongside the executable should account for this behavior. + +_Source threads: 1450300236130947143 · confidence: medium_ +_Date range: 2025-12-16_ +_Reviewer note: Verify the GitHub issue number and confirm the search order behavior against the twinBASIC source or issue tracker._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1255834190461669446, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> **Passing strings from a VB6 caller into a tB Standard DLL.** When a VB6 program passes a string to a tB Standard DLL function via `StrPtr` (as a `LongPtr`), the tB side cannot simply assign the pointer to a `String` variable. The correct conversion uses `SysReAllocStringW` from `oleaut32.dll`: +> +> ```tb +> Private Declare PtrSafe Function SysReAllocStringW Lib "oleaut32" _ +> Alias "SysReAllocStringW" (ByVal pbstr As LongPtr, ByVal psz As LongPtr) As Long +> +> Public Sub DoSomethingWithAString(ByVal lpString As LongPtr) +> Dim s As String +> SysReAllocStringW VarPtr(s), lpString +> ' s now holds the caller's string +> End Sub +> ``` +> +> The alternative is to declare the parameter `As Variant` on both sides --- VB6 passes a `Variant` holding the string, and tB receives it without needing `StrPtr` or pointer arithmetic at all. Using `SysAllocStringLen` combined with `CopyMemory` is incorrect and causes a crash on DLL unload. + +_Source threads: 1488640456316420182 · confidence: high_ +_Date range: 2026-03-31_ +_Reviewer note: Verify the exact declaration alias for SysReAllocStringW (oleaut32 export name) and confirm the VarPtr usage against the tB runtime. Also verify whether the Variant-parameter alternative works without any special marshalling on the VB6 Declare side._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1315314732017778808, 1359525652751192184] + +> [!NOTE] +> +> twinBASIC supports the **CDecl** calling convention modifier on **Function** and **Sub** declarations. Place **CDecl** between the procedure name and the argument list to mark a procedure as using the C calling convention rather than the default **StdCall**: +> +> ```tb +> Private Function WriteBlock CDecl(ByVal pThis As LongPtr, ByVal pData As LongPtr, ByVal size As Long) As Long +> ``` +> +> This is particularly useful when passing a function pointer to a DLL that expects a `CDecl`-convention callback (for example, a struct containing a function pointer such as `FPDF_FILEWRITE` in pdfium). In VB6, achieving this required a third-party addin; twinBASIC handles it natively. Omitting **CDecl** when the DLL requires it causes a stack imbalance and typically a crash under 32-bit. +> +> **CDecl** is also accepted on `Declare` statements themselves, for DLL exports that use the C calling convention: +> +> ```tb +> Declare PtrSafe Function SomeFunc CDecl Lib "some.dll" (ByVal x As Long) As Long +> ``` + +_Source threads: 1255834190461669446 · confidence: medium_ +_Date range: 2024-06-27 to 2024-07-02_ +_Reviewer note: Verify exact syntax position of CDecl in both the callback-function form and the Declare-statement form against the twinBASIC .twin sources or compiler grammar. The thread confirms the callback form `Function WriteBlock CDecl(...)` works, but the Declare-statement form shown in the second code block is inferred from context (fafalone mentions changing `Lib` to `CDecl Lib` for 32-bit pdfium declares) and may have a different syntax shape. Adjust or split accordingly._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1359525652751192184] + +> [!NOTE] +> +> When declaring undocumented or poorly documented Win32 APIs, C `SIZE_T` parameters must be declared as **LongPtr** rather than **Long**. `SIZE_T` is 4 bytes on 32-bit Windows and 8 bytes on 64-bit Windows; declaring it as **Long** causes incorrect behavior or crashes on 64-bit systems. Similarly, any C `PVOID`, `HANDLE`, or pointer-typed parameter that appears as `Long` in 32-bit code must become **LongPtr** before the declaration is marked **PtrSafe**. + +_Source threads: 1315314732017778808 · confidence: medium_ +_Date range: 2024-11-28 to 2024-12-08_ +_Reviewer note: Merges findings from threads 1311572762413764618 (PtrSafe required for 64-bit builds) and 1315314732017778808 (SIZE_T must be LongPtr). The PtrSafe/LongPtr requirement is already covered by the two existing NOTEs in the page; the SIZE_T detail is the genuinely new information. Verify the SIZE_T claim against the .twin source or Win32 type headers if possible._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks [DUPLICATE? -- see also thread 1053654998556348457, 1053698170846183434, 1084723164296261672, 1099208311313813534, 1101704869804511265, 1107676681449254952, 1117974216810188920, 1139937031775072307, 1140151535217680436, 1148585708362149899, 1159270753468952576, 1248884196575809578, 1270386953115930828, 1295648317820108831, 1310261549574656001, 1356284960495108156, 1378220872405487636, 1444009884637986966, 1463135813574594730, 1505726092479041556, 1110121787745894400, 1421815804172701708, 1421815804172701708, 1432054854016045096, 1187218188728008785, 1195546112459821176, 1253786438454214788, 1376962828497326190, 1382372425118781500, 1450300236130947143, 1488640456316420182, 1255834190461669446, 1315314732017778808] + +> [!NOTE] +> Pointer-type parameters in `Declare` statements default to **ByRef** if no passing convention is specified, which is correct for output or in-out pointer arguments. For memory-copy APIs such as `RtlMoveMemory` / `CopyMemory`, parameters that carry a raw address (rather than an actual reference to a variable) must be declared **ByVal**. Omitting `ByVal` on such parameters passes the address of the address rather than the address itself, causing silent data corruption --- including misdirected writes --- that may not produce an obvious error but can affect program behavior in ways that are hard to diagnose. + +_Source threads: 1359525652751192184 · confidence: medium_ +_Date range: 2025-04-09_ +_Reviewer note: The original finding came from a UserControl that called CopyMemory/RtlMoveMemory. The note is a general ByVal/ByRef correctness reminder rather than specific to CopyMemory. Consider whether this overlaps with existing Declare documentation on ByRef being the default._ + +--- + +## docs/Reference/Core/Declare.md · see-also + +- [Enhanced API Declarations](../../Features/Advanced/API-Declarations) -- CDecl calling convention support, DeclareWide, variadic arguments, and other Declare enhancements + +_Source threads: 1309934209338703882 · confidence: high_ +_Date range: 2024-11-23_ +_Reviewer note: The CDecl native support finding (twinBASIC eliminates the need for VBCDeclFix) is already fully documented on the Enhanced API Declarations feature page. This See Also entry surfaces that page from the Declare reference page, where users are most likely to look. Verify the relative URL resolves correctly from /tB/Core/Declare to /Features/Advanced/API-Declarations._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> A **Declare** statement converts each **String** argument to an ANSI string (the system code page) before the call. This suits a parameter typed `LPSTR` or `char*`. +> +> **DeclareWide** disables that conversion and passes the **String** as UTF-16. A C function that expects `char*` then sees only the first character, because the second byte of each UTF-16 character is zero. Use plain **Declare** for functions that take ANSI strings, and **DeclareWide** only for functions that take wide strings (`LPWSTR` or `wchar_t*`). See [Enhanced API Declarations](../../Features/Advanced/API-Declarations#declarewide). + +_Source threads: 1530026813537914880 · confidence: high_ +_Date range: 2026-07-24_ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> When a **Declare** takes a COM object pointer, such as `__vbaObjSetAddref`, declaring the parameter as a strong interface type adds a **QueryInterface**, an **AddRef** and a **Release** to each call. Declaring it **As Any** avoids that cost, but an **As Any** declaration cannot be overloaded with other typed declarations. The `vba*` helper functions are built in and need not be declared. + +_Source threads: 1125359845936205844 · confidence: low_ +_Date range: 2026-09-02_ +_Reviewer note: Low confidence, single remark in thread; verify the extra reference-counting behaviour._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> Only a **Declare** statement can use **As Any** for a parameter. Neither VB6 nor twinBASIC accepts **As Any** in a procedure defined in Basic code. A wrapper around an API such as `CopyMemory` therefore takes pointer-typed parameters (**LongPtr**) instead, and the caller passes addresses obtained with [**VarPtr**](../Default/VBA/Information/VarPtr), **StrPtr** or **ObjPtr**. + +_Source threads: 1507659583437668512 · confidence: medium_ +_Date range: 2026-05-23_ +_Reviewer note: Verify against the compiler that As Any is rejected in a non-Declare Sub/Function parameter list. The relative link to VarPtr is a guess: check the VarPtr page's real permalink (use its /tB/... permalink form) and that StrPtr/ObjPtr pages exist before linking them._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> A C or C++ API often accepts a null pointer for an optional interface argument. When the twinBASIC declaration of such an argument is **ByRef** to an interface type, passing `ByVal Nothing` at the call site supplies a true null pointer. Passing a variable that holds **Nothing** supplies a pointer to that variable instead, which is not the same thing, and some declarations do not accept it. +> +> ```tb +> ' pOptionalInterface is declared ByRef As IUnknown +> hr = SomeApi(hWnd, ByVal Nothing) +> ``` +> +> If the declaration marks the argument **Optional**, omitting it at the call site also passes a null pointer. `ByVal Nothing` is twinBASIC syntax, and VB6 may not accept it. + +_Source threads: 1539249403079172156 · confidence: medium_ +_Date range: 2026-08-23_ +_Reviewer note: Verify against the compiler that ByVal Nothing is legal for a ByRef interface parameter, and how it interacts with Optional. Discord discussion is informal and does not name the exact declaration. Declare.md may not be the ideal home (no dedicated Nothing or argument-passing page exists); Core package, symbol 'ByVal Nothing'. The illustrative sample is invented and has not been compiled; replace or drop it after checking._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> +> When a VB6 project that contains many Win32 **Declare** statements is imported for a 64-bit build, adding the community package WinDevLib to the project can replace the hand-converted declarations. The package declares common Windows APIs and COM interfaces with 64-bit-correct types. Add it through **Project → References → Available Packages**; with **Embedded** unticked, the package is linked and shared by all projects. Code that handles pointers still needs review, because pointer and handle sizes differ between 32-bit and 64-bit builds. See [64bit Compilation](../../Features/64bit) for the full list of considerations. + +_Source threads: 1546564453514874930 · confidence: low_ +_Date range: 2026-09-07_ +_Reviewer note: Advice comes from one community member. WinDevLib is a third-party package, not shipped with twinBASIC. 64bit.md and Windows-API.md already link to it, so this may be redundant on the Declare page; consider dropping it or moving it to the VB6 migration material instead._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> +> A DLL that exports its functions with the cdecl convention and undecorated names (no leading underscore and no `@N` suffix), as recent builds of pdfium.dll do, can be called directly by adding **CDecl** to the **Declare** statement. No intermediate stdcall wrapper DLL is needed. +> +> ```tb inert=skeleton +> Declare PtrSafe Sub FPDF_InitLibrary CDecl Lib "pdfium.dll" () +> ``` + +_Source threads: 1478865333245837444-2026-09 · confidence: medium_ +_Date range: 2026-09-29_ +_Reviewer note: Page already documents the CDecl callconv; this adds only the cdecl/undecorated-export use case. Verify the pdfium export name and signature in the example, and that the DLL is cdecl on both 32-bit and 64-bit builds._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> Two **Declare** statements for the same DLL procedure are not ambiguous when both name the same entry point and their parameter types match exactly; the compiler accepts them, as VBx does. Otherwise the compiler reports warning TB0010, *Duplicate DLL declaration detected*. + +_Source threads: 927834661298798602-2022-10 · confidence: high_ +_Date range: 2022-10-19 to 2022-10-31_ +_Reviewer note: From BETA 163/164. Wording of when TB0010 fires is inferred from the release notes; verify against the compiler source. Consider adding TB0010 to the diagnostics list._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> A DLL **Declare** can be written as a **Property Get**, **Property Let** or **Property Set**, as well as a **Sub** or **Function**. The [ConstantFoldable](Attributes#constantfoldable) attribute is accepted on a DLL **Declare** too. + +_Source threads: 927834661298798602-2023-11 · confidence: medium_ +_Date range: 2023-11-01_ +_Reviewer note: From BETA 416 (introduced for the VBA package). Verify Property Declare support against the compiler; Declare.md did not mention it._ + +--- + +## docs/Reference/Core/Declare.md · after-remarks + +> [!NOTE] +> Out-only parameters are zero-filled by the generated code, as in VBx, so a procedure that never assigns one returns zero, an empty string or **Nothing** rather than stale data. Code should still assign every out-only parameter explicitly rather than rely on this. + +_Source threads: 927834661298798602-2025-10 · confidence: high_ +_Date range: 2025-10-15_ +_Reviewer note: Finding has symbol null and is general to codegen, not specific to Declare; Declare.md is a tentative target. Verify against the compiler which parameter kinds (Declare, interface members, [Out]) are zero-filled. Source: BETA 893._ + +--- + +## docs/Reference/Core/Deftype.md · after-remarks [DUPLICATE? -- see also thread 1186715657568534628] + +> [!NOTE] +> twinBASIC applies **Def**_type_ inference to typeless **constants**, unlike VB6 and VBA. With `DefObj A-Z` active, a constant declared without an explicit type --- such as `Private Const MySize = 8` --- is inferred as **Object**. Using that constant where a numeric value is required, for example as an array bound, fails at compile time with "error occurred in evaluating constant expression". The fix is to declare the constant with an explicit type: `Private Const MySize As Long = 8`. +> +> The same inference applies to typeless **Optional** parameters. With `DefObj A-Z` active, `Optional ByVal TimeOutMs = 30000` causes a compile error because the numeric default cannot be converted to **Object** without silently discarding the value. twinBASIC treats this as an error rather than silently dropping the default (the VB6 behavior). The fix is to type the parameter explicitly: `Optional ByVal TimeOutMs As Long = 30000`. +> +> Both behaviors were confirmed as intended by BETA 207 (the constant case was previously a bug and was fixed in that release; the Optional-parameter error is by design). + +_Source threads: 1052611285310775406 · confidence: high_ +_Date range: 2022-12-14 to 2022-12-17_ + +--- + +## docs/Reference/Core/Deftype.md · after-remarks [DUPLICATE? -- see also thread 1052611285310775406] + +> [!NOTE] +> twinBASIC does not fully support type-declaration suffix characters (such as `&` for **Long**, `!` for **Single**, and so on) in all expression contexts. When a variable name with a `&` suffix appears inside a string-concatenation expression, the compiler misreads the `&` suffix as the string-concatenation operator and reports 'Two operators encountered, without an operand between them'. VB6 handles this case correctly. Avoid mixing type-declaration suffix characters with concatenation expressions; use explicit **As** *type* declarations instead. +> +> A related parser limitation: twinBASIC does not auto-correct the reversed comparison operator `=<` to `<=` as VB6 did --- it reports the same misleading error. + +_Source threads: 1186715657568534628 · confidence: medium_ +_Date range: 2023-12-19_ +_Reviewer note: This finding is about type-declaration suffix characters (e.g. example_long&) rather than the Def* statements the Deftype page covers. The Deftype page is the closest match in the page index for implicit typing. Verify against the twinBASIC compiler source or a current build to confirm whether this has been fixed before publishing._ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1462084095428526296, 1373697250865451099, 1306091764687573062, 1363971952691118140] + +> [!NOTE] +> In releases before BETA 786, using a **Delegate** type as a parameter type in an **Interface** defined in an external package caused compilation errors (TB5079 'Unrecognized datatype symbol', TB5004, and TB5000) when a consumer project attempted to implement that interface. This was a compiler bug --- not a language restriction --- and was reported as fixed in BETA 786. + +_Source threads: 1293249305355747409 · confidence: high_ +_Date range: 2024-10-08 to 2025-06-02_ +_Reviewer note: Verify that BETA 786 is the correct fix version and that no further regression has been introduced before the current release._ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1373697250865451099, 1306091764687573062, 1363971952691118140] + +> [!NOTE] +> When a delegate-typed variable is declared as a member of a class (or `[AppObject]` class), calling it with the normal invocation syntax --- `delegateVar(arg1, arg2)` --- raises *Unexpected call arguments* because the compiler treats the access as a property get rather than a callable invocation. The workaround is to use an extra set of parentheses: +> +> ```tb +> delegateVar()(arg1, arg2) +> ``` +> +> Delegate variables declared at module level (not inside a class) are not affected. The bug was a regression introduced around BETA 929 and was fixed in BETA 954. + +_Source threads: 1462084095428526296 · confidence: high_ +_Date range: 2026-01-17_ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1462084095428526296, 1306091764687573062, 1363971952691118140] + +> [!NOTE] +> `As Any` is not permitted as the return type of a **Delegate Function** declaration. Using it produces compiler error TB5079 (*Unrecognised datatype symbol Any*). `Any` is valid as a parameter type (`ByRef param As Any`) but not as the return type. The standard workaround is to change the delegate to return **Boolean** and add a `ByRef` output parameter typed `As Any` to carry the return value. +> +> Assigning an **AddressOf** expression to a delegate array may also produce warning TB0026 (*Implicit conversion to delegate type. Use CType(of) to explicitly convert the delegate to a different type*). This warning is expected and the code operates correctly. + +_Source threads: 1373697250865451099 · confidence: high_ +_Date range: 2025-05-18 to 2025-05-21_ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1462084095428526296, 1373697250865451099, 1363971952691118140] + +> [!NOTE] +> Delegate support was added in twinBASIC beta 617. Opening a project that uses **Delegate** syntax in an earlier version produces a large number of compile errors. Users on an older build must upgrade to beta 617 or later before working with such projects. + +_Source threads: 1306091764687573062 · confidence: high_ +_Date range: 2024-11-13_ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks [DUPLICATE? -- see also thread 1293249305355747409, 1462084095428526296, 1373697250865451099, 1306091764687573062] + +> [!NOTE] +> Delegate-based thread spawning is exclusive to twinBASIC --- VB6 and standard VBA have no equivalent. A concrete use case is offloading per-frame decode work in an animated-GIF player: rather than decoding each frame serially in a `Timer` handler on the UI thread, instantiate a delegate for the per-frame callback and let each frame complete on its own thread via `CreateThread` or a similar API, passing `AddressOf` *DecodeFrame* as the thread entry point. See [Multithreading](../../Features/Advanced/Multithreading) for a working `CreateThread` example. + +_Source threads: 1363971952691118140 · confidence: medium_ +_Date range: 2025-04-21 to 2025-07-22_ + +--- + +## docs/Reference/Core/Delegate.md · example + +An array of delegates provides a clean replacement for VB6's computed **GoSub** pattern. Declare a delegate type, fill an array with **AddressOf** references, then call a specific delegate by index: + +```tb +Private Delegate Function Operation (ByVal A As Long, ByVal B As Long) As Long + +Public Function Addition(ByVal A As Long, ByVal B As Long) As Long + Return A + B +End Function + +Public Function Subtraction(ByVal A As Long, ByVal B As Long) As Long + Return A - B +End Function + +Private Sub Command1_Click() + Dim ops(0 To 1) As Operation + ops(0) = AddressOf Addition + ops(1) = AddressOf Subtraction + + Dim index As Long + index = 0 ' select which operation to run + Dim result As Long + result = ops(index)(10, 3) ' calls ops(0), i.e. Addition + MsgBox result ' displays 13 +End Sub +``` + +> [!NOTE] +> When invoking a delegate stored in an array, the call-site parentheses are required: `ops(index)(arg1, arg2)`. Writing `ops(index)` without trailing parentheses reads the function pointer value but does not call it; the IDE may also report warning TB5086 (*Expression is neither used nor assigned*) at that point. +> +> As of January 2026, delegates with no parameters invoked from an array are reported as broken. Parameterized delegates work correctly. The [**On...GoSub**](On-GoSub) statement remains available as an alternative for index-based dispatch. + +_Source threads: 1463264948942672172 · confidence: high_ +_Date range: 2026-01-20 to 2026-01-22_ +_Reviewer note: Add as an additional example block after the existing three examples in Delegate.md. Verify whether the parameterless-delegate-from-array bug is still present in the current release; update or remove the note if it has been fixed._ + +--- + +## docs/Reference/Core/Delegate.md · after-remarks + +> [!NOTE] +> One report describes crashes when a **Delegate** points at code generated at run time, such as an API-hooking trampoline allocated with `VirtualAlloc`, in 64-bit builds. The behaviour varied between machines and between runs, and worked in BETA 979 but not in BETA 983. Test such delegate calls in both the IDE and a compiled build, in 32-bit and 64-bit. + +_Source threads: 1503093611246391338 · confidence: low_ +_Date range: 2026-05-10 to 2026-09-18_ +_Reviewer note: Low confidence: anecdotal report with no confirmed root cause and an undescribed fix by the author; likely a compiler bug that belongs in BUGS-TO-REPORT.md (with a narrowed reproduction) rather than in the reference page. Recommend not publishing without verification._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> In a multi-variable declaration, each variable requires its own **As** *type* clause. Writing `Dim a, b, c As Integer` declares only `c` as **Integer**; `a` and `b` receive no explicit type and default to **Variant**. This matches VB6 and VBA behavior. twinBASIC reports a warning for each variable whose type was not explicitly declared. To declare multiple variables of the same type, use a separate **As** clause for each: `Dim a As Integer, b As Integer, c As Integer`. + +_Source threads: 1058362981156671629 · confidence: high_ +_Date range: 2022-12-30 to 2022-12-31_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> An array variable used in an expression without a subscript index --- for example, writing `He` instead of `He(i)` when `He` is declared `As String` array --- is a type error. Prior to BETA 313, the compiler accepted such code silently and then crashed at the code-generation stage with an access violation before the program started. From BETA 313 onward, the compiler correctly reports this as a compile-time error. + +_Source threads: 1099362561482301523 · confidence: high_ +_Date range: 2023-05-24_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> Inline initialization (`= *expression*`) is not permitted when the variable name includes the array-suffix `()`. For example, `Dim myA() As Variant = Array(1,2,3)` produces a compiler error. Two alternatives exist: +> +> 1. Split the declaration and assignment: `Dim myA() As Variant` on one line, then `myA() = Array(1,2,3)` on the next. +> 2. Declare without the `()` suffix: `Dim myA As Variant = Array(1,2,3)`. +> +> These two forms are not interchangeable. `myA() As Variant` declares a typed SAFEARRAY of **Variant** elements. `myA As Variant` declares a scalar **Variant** that can hold a SAFEARRAY of any element type. Only the bare-**Variant** form supports inline initialization. + +_Source threads: 1172828722081058897 · confidence: medium_ +_Date range: 2023-11-11_ +_Reviewer note: Verify against current compiler behavior: does the restriction on inline initialization for array-suffix declarations still apply, and is the distinction between `myA() As Variant` (typed SAFEARRAY) and `myA As Variant` (scalar Variant wrapping a SAFEARRAY) accurate?_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> In VB6 and VBA, assigning a `Variant()` array variable directly to a typed array variable (for example, `myLongArray = myVariantArray`) is a compile-time error: *Can't assign to array*. In twinBASIC through at least BETA 495, this assignment compiles and runs without error, but produces incorrect results: each element of the typed array is set to the **VarType** integer of the source element type rather than the element value itself (for example, `3` instead of the actual **Long** value). The same problem affects **String** arrays. Code migrated from VB6 that relied on the compile-time error to catch this mistake will silently produce wrong output. Until the compiler is updated to match VB6 behaviour, copy elements individually instead of assigning the array variable directly. + +_Source threads: 1225747919072923678 · confidence: high_ +_Date range: 2024-04-05 to 2024-04-07_ +_Reviewer note: Confirm whether this bug has been fixed in a release after BETA 495. If fixed, update the note to name the fixing release and convert to past-tense historical context or remove if already documented elsewhere._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1326158767561248768, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> When an array bound is computed from unqualified integer literals, the compiler evaluates the expression using 16-bit **Integer** arithmetic -- the same behaviour as VB6. Expressions whose result exceeds 32767 silently overflow or raise a run-time error. To force **Long** arithmetic, append the `&` type-declaration character to at least one literal in the expression: +> +> ```tb +> Dim Pixels(640& * 480& - 1) As Long ' Long arithmetic; no overflow +> ' Dim Pixels(640 * 480 - 1) As Long ' Integer arithmetic; overflows +> ``` +> +> This applies wherever a constant expression is used for an array bound or index -- including [**ReDim**](ReDim) statements. + +_Source threads: 1434184886280786001 · confidence: high_ +_Date range: 2025-11-01_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1468334726770200800, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> twinBASIC extends **WithEvents** to support dynamic arrays: `Private WithEvents myObjs() As MyClass` is valid and the array can be resized with [**ReDim**](ReDim). Fixed-length array declarations such as `Private WithEvents myObjs(2) As MyClass` are not supported. When any element raises an event, the corresponding handler (for example `Private Sub myObjs_MyEvent()`) fires but receives no index parameter --- there is no built-in way to identify which element was the source. To work around this, include an explicit index in the event's parameter list on the raising side, or wrap each object in a separate class that carries identity information. + +_Source threads: 1326158767561248768 · confidence: high_ +_Date range: 2025-01-07 to 2025-01-08_ +_Reviewer note: Verify against the twinBASIC compiler source or a live test: confirm that (a) dynamic WithEvents arrays compile and function correctly, and (b) fixed-length WithEvents arrays crash the IDE as reported, before publishing._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1384557209232478240, 1402639656574648331] + +> [!NOTE] +> `Dim obj As New MyClass` uses lazy instantiation --- the object is not created until the variable is first accessed. The inline-initialization form `Dim obj As MyClass = New MyClass(args)` creates the object immediately at the point of declaration. These two forms are not interchangeable when the timing of construction matters. + +When using parameterized constructors (classes marked `[COMCreatable(False)]` with a constructor that takes arguments), `Dim obj As MyClass = New MyClass(args)` requires the class name twice. The short form `Dim obj As Any = New MyClass(args)` avoids the repetition: `As Any` causes the compiler to infer the actual type from the right-hand side and triggers immediate construction, not lazy instantiation. + +_Source threads: 1468334726770200800 · confidence: high_ +_Date range: 2026-02-03 to 2026-02-08_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1402639656574648331] + +> [!NOTE] +> In twinBASIC, a **Dim** statement with an inline initializer (e.g. `Dim id As Long = sqlite3_column_int(stmt, 0)`) is valid inside a loop body. On each iteration twinBASIC treats the declaration as a fresh assignment rather than a re-declaration error. Classic VBA and VB6 require **Dim** to appear at the top of the procedure scope and reject any re-declaration within the same scope. The semantics are equivalent to declaring the variable once at procedure scope and assigning it on each pass through the loop, but the inline form is accepted twinBASIC syntax. + +_Source threads: 1384557209232478240 · confidence: medium_ +_Date range: 2025-06-20_ +_Reviewer note: The thread_path is from the debugging thread but the Dim finding came from a code snippet in that thread; the actual thread path for this finding is wisdom/data/threads/questions/1384557209232478240. Verify against the twinBASIC compiler spec or .twin source that duplicate Dim in a loop truly does not error --- the Dim.md page already documents the initializer form but does not address loop-body re-declaration. If unverified, consider lowering confidence or omitting._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks [DUPLICATE? -- see also thread 1058362981156671629, 1099362561482301523, 1172828722081058897, 1225747919072923678, 1434184886280786001, 1326158767561248768, 1468334726770200800, 1384557209232478240] + +> [!NOTE] +> When multiple variable names precede a single **As** *type* clause --- `Dim x, y, z As Integer` --- only the last name (`z`) receives the declared type. The earlier names (`x` and `y`) are implicitly typed as **Variant**. This matches VB6 behavior and is preserved for compatibility. twinBASIC issues a compiler warning on each implicitly typed name in such a list; the warning does not prevent compilation. To give all three variables the same type, use a separate **As** clause for each: `Dim x As Integer, y As Integer, z As Integer`. + +> [!NOTE] +> `Dim x As Any = 1&` infers the variable's concrete type from the initializer --- in this case **Long** --- rather than declaring it as **Variant**. After that declaration, `x = "test"` is a type error. `As Any` without an initializer behaves differently; it is not a synonym for `As Variant`. See [Type Inference](../../Features/Language/Type-Inference). + +_Source threads: 1402639656574648331 · confidence: high_ +_Date range: 2025-08-06 to 2025-09-01_ +_Reviewer note: Two separate behavioral clarifications about Dim are merged here. Verify the As Any without initializer behavior against the .twin source or compiler tests: the finding says it "behaves differently" but does not fully specify what it does in that case._ + +--- + +## docs/Reference/Core/Dim.md · example + +This example shows the short form for constructing objects with parameterized constructors, avoiding repetition of the class name. + +```tb +' Full form — class name appears twice. +Dim conn As MyConnection = New MyConnection("localhost", 5432) + +' Short form using As Any — type is inferred from the right-hand side. +' Constructs immediately (not lazily). +Dim conn As Any = New MyConnection("localhost", 5432) +``` + +_Source threads: 1468334726770200800 · confidence: high_ +_Date range: 2026-02-05 to 2026-02-08_ + +--- + +## docs/Reference/Core/Dim.md · after-remarks + +> [!NOTE] +> The bounds of a fixed-length array must be constant expressions. A non-constant value in a bound is a compile-time error. **Len** and **LenB** calls are allowed in bound expressions. + +_Source threads: 927834661298798602-2023-09 · confidence: high_ +_Date range: 2023-09-06 to 2023-09-20_ +_Reviewer note: Source builds: BETA 393 (error added), following build (Len/LenB allowed). Verify against the compiler._ + +--- + +## docs/Reference/Core/Dim.md · after-remarks + +> [!NOTE] +> When no **As** clause is given but an initializer is, the variable takes the type of the initializer rather than **Variant**. `Dim A = 5&` is the same as `Dim A As Long = 5`. + +_Source threads: 927834661298798602-2026-03 · confidence: high_ +_Date range: 2026-03-26_ +_Reviewer note: From BETA 978. Conflicts with the existing sentence that a variable with no type is Variant by default; that sentence may need a qualifier. Verify against the compiler._ + +--- + +## docs/Reference/Core/Divide.md · after-remarks + +> [!NOTE] +> When both operands are compile-time constants (numeric literals), the compiler detects division by zero during constant folding and emits a runtime error call instead of performing the FPU operation. As a result, `On Error Resume Next` catches the error before the result is written to the variable, leaving the variable at its default value (0 for **Double**) rather than the IEEE 754 special value. To obtain the correct IEEE 754 result, store the divisor in a variable so constant folding does not apply: +> +> ```tb +> Dim zero As Double = 0 +> Dim posInf As Double +> On Error Resume Next +> posInf = 1 / zero ' posInf receives +Infinity +> On Error GoTo 0 +> ``` +> +> This was confirmed as a bug and fixed in twinBASIC BETA 375. + +_Source threads: 1135331052928389260 · confidence: high_ +_Date range: 2023-07-30 to 2023-08-14_ + +--- + +## docs/Reference/Core/End.md · after-remarks + +> [!NOTE] +> In some twinBASIC builds, the bare **End** statement does not terminate execution as expected --- the program may continue running or re-show closed forms rather than stopping. Use `Unload ` on each open form and let the program exit naturally when no forms remain, or close the last form from its own `QueryUnload` or `Unload` handler. This behaviour has been reported as a known issue. + +_Source threads: 1109123342537085009 · confidence: high_ +_Date range: 2023-05-19_ +_Reviewer note: Verify whether this issue is still present in current twinBASIC builds and whether a specific bug number or fix version can be cited. The Discord report was from BETA 308 (May 2023). If fixed, remove or qualify the note with a fixed-in version._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> The twinBASIC IDE automatically reformats **Enum** blocks when the cursor enters or scrolls past them. Extra spaces added before `=` to align member values across lines are removed, and leading zeros added after `&H` in hexadecimal literals (for example, `&H0001` written to match the width of `&H0100`) are also removed. Manual visual alignment of enum members is not preserved. + +_Source threads: 1085419449387073627 · confidence: medium_ +_Date range: 2023-03-15_ +_Reviewer note: Verify this auto-format behavior is still present in current IDE builds and that it applies on scroll as well as on direct edit. The finding is from March 2023 and IDE behavior may have changed._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> Member names that require bracket escaping --- names containing spaces, hyphens, or other characters that are not valid in an unquoted identifier (for example, `[8 Bytes]`, `[bytes-8]`, `[24_Bytes]`) --- are accepted by the compiler. Prior to BETA 356, IntelliSense completions inserted such names without the enclosing square brackets, producing code that did not compile. The fix ships in BETA 356; on earlier builds, type the brackets manually when using a bracket-escaped member name. + +_Source threads: 1127957663662219364 · confidence: high_ +_Date range: 2023-07-10 to 2023-07-12_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> twinBASIC reports a compile-time ambiguity error when two or more UserControls in the same project declare an **Enum** with overlapping member names. VB6 permitted this pattern without error. Code ported from VB6 projects that relies on same-named enum members in separate UserControl files must rename the conflicting members (or the enumerations themselves) to be unique across the project. + +_Source threads: 1128708250875998358 · confidence: high_ +_Date range: 2023-07-12_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> If an **Enum** type and a **Sub** or **Function** share the same name within the same project, twinBASIC may compile the project without a clear error but the resulting program can crash or behave erratically at run time. Renaming one of the conflicting identifiers resolves the problem. + +_Source threads: 1141408151762112583 · confidence: medium_ +_Date range: 2023-08-16_ +_Reviewer note: Verify whether this ambiguity is compiler-detected in current twinBASIC builds (the report is from August 2023 and the compiler may have added a diagnostic since then). If a clear compile-time error is now raised, this note is not needed or should be reworded to reflect the current behavior._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1455873650095030384, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> Declaring a `String` variable and an `Enum` with the same identifier in the same module does not produce a compile-time diagnostic. The name clash is only detected at run time when the conflicting name is accessed. Projects with this pattern can appear to build cleanly but fail unexpectedly at run time. + +_Source threads: 1303048284633432115 · confidence: medium_ +_Date range: 2024-11-04 to 2024-11-06_ +_Reviewer note: Verify whether this missing diagnostic has been addressed in a later compiler release. If fixed, the note should state the fix version or be removed._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1141006440270676070, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> +> Assigning `2 ^ 31` to an **Enum** member raises an overflow error. The `^` operator returns a **Double**, and the resulting value 2 147 483 648 lies outside the range of a signed **Long** (the underlying type of every **Enum** member). Use the hex literal `&H80000000&` instead to represent the most-significant bit of a **Long** flag. This matches VBA and VB6 behavior. + +_Source threads: 1455873650095030384 · confidence: high_ +_Date range: 2025-12-31_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1381902597056757821, 1385161863519670332] + +> [!NOTE] +> **Enum values are 32-bit integers.** This is a requirement of the COM specification, which mandates that enum types are 32-bit for interoperability with non-twinBASIC COM consumers. Values outside the signed 32-bit range are not supported in an **Enum** declaration. + +> [!NOTE] +> **Hex literal type-suffix workaround.** The `[IgnoreWarnings]` attribute is not valid on individual enum member lines. When a hex literal assigned to an enum member triggers the "hex misuse" compiler warning (for example, `&HFFFF` used where the bit pattern represents a negative **Long**), append the **Integer** type suffix to suppress it: `&HFFFF%`. twinBASIC treats the type-hinted literal as an **Integer** and stores the correct bit pattern without raising the warning. + +_Source threads: 1141006440270676070 · confidence: high_ +_Date range: 2023-08-15 to 2023-08-31_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1385161863519670332] + +> [!NOTE] +> **Member initializers and built-in functions.** Enum member values may call built-in functions such as `Asc`, but not user-defined functions. Calling a custom function inside an Enum member expression is a compiler error. + +> [!NOTE] +> **`[BitFlags]` attribute.** Decorating an **Enum** with `[BitFlags]` (see [Attributes](../Attributes.md#bitflags)) causes members with no explicit value to be assigned successive powers of two (1, 2, 4, 8, ...) automatically. Once any member is given an explicit value, every subsequent member must also carry an explicit value; mixing explicit and auto-assigned values after the first explicit entry is a compiler error. + +> [!NOTE] +> **Compiler warning TB0019.** twinBASIC raises warning TB0019 when a value of one enum type is assigned to a variable of a different enum type. The warning is disabled (IGNORE) by default because enabling it on imported VB6 or VBA code would produce a large number of warnings. Enable TB0019 in the project settings when stricter enum-type checking is wanted. + +_Source threads: 1381902597056757821 · confidence: high_ +_Date range: 2025-06-10 to 2025-06-11_ +_Reviewer note: Verify the final attribute name ([BitFlags] vs [Flags]) against the Attributes page and the .twin source for Core/Enum. The thread indicated the name was still under discussion as of beta 803. The Enum page already links to Attributes#flags --- confirm whether the canonical name is [Flags] or [BitFlags] and update the link anchor accordingly._ + +--- + +## docs/Reference/Core/Enum.md · after-remarks [DUPLICATE? -- see also thread 1085419449387073627, 1127957663662219364, 1128708250875998358, 1141408151762112583, 1303048284633432115, 1455873650095030384, 1141006440270676070, 1381902597056757821] + +> [!NOTE] +> All enum members occupy exactly 4 bytes (**Long**) in both 32-bit and 64-bit builds. twinBASIC is COM-based, and the typelib format does not support enum members of other sizes for exported types such as ActiveX DLLs and controls. Future versions may allow enums with other underlying integer types for internal use, but such types cannot be placed in ActiveX DLLs or controls. + +_Source threads: 1385161863519670332 · confidence: high_ +_Date range: 2025-06-19_ + +--- + +## docs/Reference/Core/Enum.md · after-remarks + +> [!NOTE] +> The underlying type of an **Enum** is always **Long**. An **Enum** cannot be declared with a 16-bit (**Integer**) base type. When porting a definition from another language that uses a 16-bit enumeration (for example a C enum declared as `ushort`), declare it as an ordinary **Long**-based **Enum** instead. Tools that convert headers or metadata automatically need to map small-integer enumerations to **Long** in the same way. + +_Source threads: 1397669015777968289-2025-12 · confidence: medium_ +_Date range: 2025-12-24_ + +--- + +## docs/Reference/Core/Event.md · after-remarks + +> [!NOTE] +> In an ActiveX DLL, an event parameter declared **As Object** is written to the type library as **IDispatch**, and one declared **As IUnknown** as **IUnknown**. Both match what Visual Basic 6 produces, so a VB6 client can handle the event with the same parameter type. + +_Source threads: 1352268312117252126 · confidence: high_ +_Date range: 2025-03-20 to 2025-03-21_ +_Reviewer note: Source: fixed in BETA 717 (Object -> IDispatch) and BETA 718 (IUnknown regression). Build numbers deliberately omitted per rules. Verify against a built ActiveX DLL typelib._ + +--- + +## docs/Reference/Core/Exit.md · after-remarks + +> [!NOTE] +> The form `Exit For` always exits the immediately enclosing `For` loop. Naming a specific loop variable in the exit statement (e.g. `Exit For outerVar`) is not valid syntax in current twinBASIC. The standard workaround for breaking out of an outer loop from an inner one is to place a [**GoTo**](GoTo) label immediately after the outer loop, or to extract the inner loop body into a separate **Sub** and use **Exit Sub**. + +_Source threads: 1058360505904934923 · confidence: high_ +_Date range: 2022-12-30 to 2023-01-03_ +_Reviewer note: The Discord thread dates from January 2023. Verify whether named-loop-variable Exit For has been added in a later twinBASIC release before publishing._ + +--- + +## docs/Reference/Core/Exponent.md · after-remarks + +> [!NOTE] +> +> twinBASIC IDE BETA 423 contained a bug where the **^** operator reversed its operands when the exponent was a variable rather than a literal constant. For example, `15 ^ (TreeLevel - 1)` with `TreeLevel = 3` returned 32768 (2^15) instead of 225 (15^2). This bug was fixed in a subsequent release. If unexpected exponentiation results occur, verify that the twinBASIC IDE is up to date. + +_Source threads: 1189197083534708818 · confidence: medium_ +_Date range: 2023-12-26_ +_Reviewer note: The bug was reported in BETA 423 and expected to be fixed soon after. Confirm the fix version before publishing, or consider omitting this note if it is no longer relevant to any supported release._ + +--- + +## docs/Reference/Core/Exponent.md · after-remarks + +> [!NOTE] +> In source code, the **^** operator should be surrounded by spaces. Writing `2^2` without spaces is rejected with *Expected operator between the operands*; write `2 ^ 2` instead. The likely cause is that `^` is also the **LongLong** type-declaration character, in the same way that `&` is for **Long**, so `1^1` is read as a typed literal rather than an exponentiation. + +_Source threads: 1262807077609406676-2025-07 · confidence: medium_ +_Date range: 2025-07-18_ +_Reviewer note: Explanation is a community theory (VBA7 LongLong type-declaration character); verify against the parser before publishing, and check whether the error text is accurate for current builds._ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1500134322588618753, 1260650408179667046, 1478776593890410558] + +> [!NOTE] +> +> Iterating over a **Byte** array with **For Each** acquires a runtime lock on the array. If the enclosing function exits before the loop completes --- for example via a **Return** statement --- the lock may not be cleared. A subsequent call that passes the same array to another procedure can then raise runtime error "Memory is locked". The workaround is to replace the **For Each** loop with an index-based **For** loop: +> +> ```tb +> For i = 0 To UBound(arr) +> ' process arr(i) +> Next +> ``` +> +> This avoids the array-locking mechanism entirely. + +_Source threads: 1456208651550331067 · confidence: medium_ +_Date range: 2026-01-01_ +_Reviewer note: Whether the failure to unlock on early exit is a known bug or VB6-compatible behavior was unresolved in the source thread. Verify against current runtime behavior before publishing._ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1456208651550331067, 1260650408179667046, 1478776593890410558] + +> [!NOTE] +> Iterating a typed array (for example, `Dim pts() As PointXY`) with **For Each** after passing the array through one or more method calls may raise an 'Object required' error when methods on the loop control variable are accessed. The same array enumerated with a numeric index loop works correctly. Workarounds: (1) declare the array as `Variant` instead of a specific class type; (2) use an index-based **For...Next** loop; (3) cast the loop variable to the interface that exposes the required method. + +_Source threads: 1500134322588618753 · confidence: medium_ +_Date range: 2026-05-02_ +_Reviewer note: Observed in Beta 979 on Windows 11; a minimal reproduction was not produced. Verify whether this is still present in later builds before publishing._ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1456208651550331067, 1500134322588618753, 1478776593890410558] + +> [!NOTE] +> When a breakpoint is set inside a custom `IEnumVARIANT.Next` implementation, the IDE Variables pane automatically enumerates the collection to populate its display. This triggers another call to `Next` before the previous call returns, causing the debugger to appear to enter an endless loop and the IDE to become unresponsive. The workaround is to collapse the Variables pane node for the collection before reaching the breakpoint in `Next`. With that node collapsed, stepping through `Next` with **F11** works normally. + +_Source threads: 1260650408179667046 · confidence: high_ +_Date range: 2024-07-10_ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks [DUPLICATE? -- see also thread 1456208651550331067, 1500134322588618753, 1260650408179667046] + +> [!NOTE] +> To make a custom class support **For Each...Next** enumeration, implement the `IEnumVARIANT` COM interface directly in the class. Unlike VB6, no assembly thunks or external helpers are required --- twinBASIC allows the interface to be declared and implemented in plain source code: +> +> ```tb +> [InterfaceId("00020404-0000-0000-C000-000000000046")] +> Interface IEnumVARIANT +> Extends stdole.IUnknown +> Sub Next(ByVal ipRequestedCount As Long, ByRef iopReturnedItems As Variant, ByRef opReturnedCount As Long) +> Sub Skip(ByVal ipRequestedCount As Long) +> Sub Reset() +> Sub Clone(ppenum As IEnumVARIANT) +> End Interface +> ``` +> +> A simpler alternative when the data is already stored in a `Collection`: expose a `[_NewEnum]` property that returns the inner Collection's enumerator, and **For Each** works without implementing `IEnumVARIANT` directly. + +_Source threads: 1478776593890410558 · confidence: high_ +_Date range: 2026-03-04_ + +--- + +## docs/Reference/Core/For-Each-Next.md · after-remarks + +> [!NOTE] +> Using the literal **Nothing**, **Null** or **Empty** as *group* is a compile error. A variable that holds one of these values at run time is not caught by the compiler. + +_Source threads: 927834661298798602-2025-12 · confidence: high_ +_Date range: 2025-12-05_ +_Reviewer note: From BETA 909. The second sentence (run-time variables) is inferred from the word 'literals' in the release note; verify. The same note says generic procedures inside a UDT now give a compile error instead of crashing the compiler; not drafted._ + +--- + +## docs/Reference/Core/For-Next.md · after-remarks [DUPLICATE? -- see also thread 1382180932131229716, 1468853059148054752] + +> [!NOTE] +> Inline loop-variable declaration (`For myIndex As Long = 1 To 25`) is a twinBASIC extension. VBA and VB6 require the variable to be declared separately with **Dim** before the **For** statement. + +_Source threads: 1346588557325762621 · confidence: high_ +_Date range: 2025-03-04 to 2025-03-13_ + +--- + +## docs/Reference/Core/For-Next.md · after-remarks [DUPLICATE? -- see also thread 1346588557325762621, 1468853059148054752] + +> [!NOTE] +> +> **Driver projects and integer overflow checking.** In twinBASIC driver projects compiled without the standard runtime, any loop construct --- including **For...Next**, **Do...Loop**, **While...Wend**, and **Do...Loop Until** --- causes the compiler to emit imports for `KERNEL32.dll!HeapAlloc`, several `OLEAUT32.dll` BSTR marshalling and variant conversion functions (`BSTR_UserMarshal`, `BSTR_UserUnmarshal`, `BSTR_UserSize`, `SysAddRefString`, `VarBstrCmp`, `VarDateFromI2`, `VarDateFromI4`), `USER32.dll!PostMessageW`, `USER32.dll!MessageBoxW`, and additional `KERNEL32.dll` helpers (`FormatMessageW`, `lstrlenW`, `VirtualQuery`, `Sleep`). These come from the integer overflow checking machinery that the compiler emits around loop counter arithmetic. +> +> Apply `[IntegerOverflowChecks(False)]` to the procedure containing the loop to suppress these imports. For driver projects it is advisable to set this attribute project-wide. + +_Source threads: 1382180932131229716 · confidence: high_ +_Date range: 2025-06-11 to 2025-06-25_ + +--- + +## docs/Reference/Core/For-Next.md · after-remarks [DUPLICATE? -- see also thread 1346588557325762621, 1382180932131229716] + +> [!IMPORTANT] +> +> When compiling a kernel-mode driver, any routine that contains a loop generates calls to user-mode DLLs (`KERNEL32.dll`, `OLEAUT32.dll`, `USER32.dll`) unless the `[IntegerOverflowChecks(False)]` attribute is applied to that routine. The driver verifier flags these calls as invalid for kernel code, which prevents the driver from loading. Apply `[IntegerOverflowChecks(False)]` to every routine in the driver module, or set integer overflow checking off at the project level. This requirement applies even to routines with empty loop bodies. + +_Source threads: 1468853059148054752 · confidence: high_ +_Date range: 2026-02-05_ +_Reviewer note: Verify that [IntegerOverflowChecks(False)] is the correct attribute syntax and that it can be applied project-wide via a project setting in twinBASIC._ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1371107177791623208, 1406533475548401664, 1325528980006113280, 1363566343353532446] + +> [!NOTE] +> Indirect recursion --- where function A calls function B, which calls function A again --- causes the same stack exhaustion as direct self-calls, but twinBASIC may surface it as a blue **Native Exception** dialog rather than a recognizable stack-overflow error. When an unexpected native exception appears shortly after entering a method, check whether any function in the call chain eventually calls back into a function that is already on the call stack. + +_Source threads: 1207082349364252743 · confidence: medium_ +_Date range: 2024-02-13 to 2024-02-15_ +_Reviewer note: Finding targets VB package (symbol null); placed on Core/Function because that page already discusses recursion and stack overflow. Verify that the native-exception behavior described is reproducible and consistent across twinBASIC builds before publishing._ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1406533475548401664, 1325528980006113280, 1363566343353532446] + +> [!NOTE] +> When a **Function** returns an array type, writing `FunctionName(i) = value` inside the function body does **not** assign to element *i* of the return value. twinBASIC parses `FunctionName(i)` as a recursive call to the function with `i` as an argument, causing infinite recursion that exhausts the call stack. Unlike VB6, twinBASIC does not reject this syntax at compile time. +> +> The correct pattern is to declare a local variable, build the array into it, and return the variable: +> +> ```tb +> Function GetKeys() As String() +> Dim retVal() As String +> ReDim retVal(0 To 2) +> retVal(0) = "alpha" +> retVal(1) = "beta" +> retVal(2) = "gamma" +> Return retVal +> End Function +> ``` +> +> Using `Return retVal` is the recommended form because it removes all ambiguity. Assigning `GetKeys = retVal` is also correct. + +_Source threads: 1371107177791623208 · confidence: high_ +_Date range: 2025-05-11 to 2025-05-14_ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1371107177791623208, 1325528980006113280, 1363566343353532446] + +> [!NOTE] +> When the result assignment (`FunctionName = expression`) is the last line of code in a **Function** procedure, twinBASIC performs a move-pointer (MovePtr) optimization: instead of copying the heap-allocated value, the compiler transfers ownership of the pointer, avoiding the copy. This optimization applies most visibly to arrays and other heap-allocated types. It does not apply to **String** return values. + +_Source threads: 1406533475548401664 · confidence: high_ +_Date range: 2025-08-17 to 2025-08-23_ +_Reviewer note: Verify against compiler source or release notes that MovePtr optimization applies in twinBASIC as described. The finding was confirmed by WaynePhillipsEA but the string exclusion note comes from a secondary observation. Place before the ### Example section._ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1371107177791623208, 1406533475548401664, 1363566343353532446] + +> [!NOTE] +> twinBASIC emits diagnostic TB0003 ('Datatype has not been explicitly declared so will default to Variant') when a **Function** declaration omits the **As** *type* return-type clause. This is a warning, not a compilation error --- the function compiles and runs, returning a **Variant**. To suppress the warning, either add the return type, convert the procedure to a **Sub** if no value is returned, or adjust which warnings are reported in the project settings panel in the IDE. + +_Source threads: 1325528980006113280 · confidence: high_ +_Date range: 2025-01-05_ + +--- + +## docs/Reference/Core/Function.md · after-remarks [DUPLICATE? -- see also thread 1207082349364252743, 1371107177791623208, 1406533475548401664, 1325528980006113280] + +> [!NOTE] +> VB6 and VBA cap the number of parameters per procedure at 60. twinBASIC does not enforce this limit. Procedures with 150 or more parameters compile and run without error. The error message "too many arguments" indicates a mismatch between the call site and the declaration --- not a hard compiler limit. + +_Source threads: 1363566343353532446 · confidence: high_ +_Date range: 2025-04-20_ + +--- + +## docs/Reference/Core/Function.md · after-remarks + +> [!NOTE] +> The compiler does not inline procedures, and there is no keyword to hint that a procedure should be inlined. Where call overhead matters, small helper procedures have to be inlined by hand. + +_Source threads: 1555243415174774865 · confidence: high_ +_Date range: 2026-10-01_ +_Reviewer note: Stated by Wayne Phillips in Oct 2026 build; inlining is planned. Applies to Sub and Function generally; consider a better home than Function.md._ + +--- + +## docs/Reference/Core/Get.md · after-remarks + +> [!NOTE] +> In twinBASIC BETA 317, reading a UDT from a binary file with **Get** produced incorrect data when the UDT contained a dynamic array member. The array elements were read with values far outside the expected range. This was a bug in the **Get** statement's handling of dynamic arrays embedded in UDTs; it was fixed in BETA 320. + +_Source threads: 1112631246338662420 · confidence: high_ +_Date range: 2023-05-30_ +_Reviewer note: Confirm the fix release number (BETA 320) against the changelog before publishing. The note is placed after the existing UDT/dynamic-array bullet in the Random-mode rules section._ + +--- + +## docs/Reference/Core/GoSub-Return.md · after-remarks [DUPLICATE? -- see also thread 1457034604769706168] + +> [!NOTE] +> In twinBASIC, a **Return** statement that can be reached without a prior **GoSub** in the execution path is a compilation error. In VB6 this was a runtime error. Code imported from VB6 projects may fail to compile if a **Return** is reachable without a corresponding **GoSub**---for example, when the **GoSub** is commented out or inside a conditional branch that never executes before **Return**. + +_Source threads: 1324800932869046312 · confidence: high_ +_Date range: 2025-01-03 to 2025-01-06_ + +--- + +## docs/Reference/Core/GoSub-Return.md · after-remarks [DUPLICATE? -- see also thread 1324800932869046312] + +> [!NOTE] +> +> **GoSub** has lower call overhead than a separate **Sub** or **Function** call, because it does not create a new stack frame. The tradeoff is readability: code structured around **GoSub** labels is harder to follow than code split into named helper procedures. For performance-critical inner loops where a helper call is measurably slow, **GoSub** is worth considering; in all other cases a separate procedure is preferred for clarity. + +_Source threads: 1457034604769706168 · confidence: medium_ +_Date range: 2026-01-04_ +_Reviewer note: The existing page carries a [!TIP] recommending separate callable procedures. This note is compatible but subtly contradicts the tip's implied framing. Verify the performance claim against twinBASIC compiler output or a maintainer statement before publishing._ + +--- + +## docs/Reference/Core/GoSub-Return.md · after-remarks + +> [!NOTE] +> A **Return** with no preceding **GoSub** is reported as warning [TB0027](../../Features/Compiler-IDE/Compiler-Warnings#tb0027-return-without-gosub), not as a compile error, so a project that contains one still compiles. The statement raises a run-time error only if it executes without an active **GoSub**. + +_Source threads: 927834661298798602-2025-01 · confidence: high_ +_Date range: 2025-01-06 to 2025-01-10_ +_Reviewer note: Sources BETA 637 and 643. Verify the relative link path and the run-time behaviour (error raised on execution) against the compiler source before publishing._ + +--- + +## docs/Reference/Core/GoTo.md · after-remarks + +> [!NOTE] +> In the twinBASIC IDE, pressing **F12** with the cursor on a label name invokes **Go to Definition** and navigates to the label's declaration. Right-clicking the label and choosing **Go to Definition** from the context menu has the same effect. + +_Source threads: 1466015542014902294 · confidence: high_ +_Date range: 2026-01-28_ + +--- + +## docs/Reference/Core/Handles.md · after-remarks + +> [!NOTE] +> When a `Handles` clause references an event declared on an interface that is introduced via `Implements`, IntelliSense may show incorrect or irrelevant completions for the event target. The issue is specific to the `Implements` + `Handles` combination; `Handles` clauses on non-interface event sources are unaffected. + +_Source threads: 1086882000025178112 · confidence: medium_ +_Date range: 2023-03-19 to 2023-03-28_ +_Reviewer note: Reported for IExplorerBrowserEvents used via Implements. Verify whether this is still reproducible in a current twinBASIC build before publishing._ + +--- + +## docs/Reference/Core/If-Then-Else.md · after-remarks + +> [!NOTE] +> A colon placed immediately after `Then` --- as in `If Then:` --- causes the compiler to treat the statement as a completed single-line **If**, not as a block **If** with a colon separator. Any `ElseIf`, `Else`, or `End If` on a following line then produces a compile error. Remove the trailing colon when the block form is intended: write `If Then` without it. twinBASIC catches this at compile time; VB6 only fails at run time. + +_Source threads: 1389259463428542564 · confidence: medium_ +_Date range: 2025-06-30_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> twinBASIC does not support `PreserveSig` when implementing COM interfaces. Interface members declared as functions (returning a value directly rather than via HRESULT) are handled the VB6 way: the compiler generates a `Sub` stub and drops the return value. This matches classic VBA behavior. The auto-generated stub omitted parameter names in versions before BETA 240 (producing invalid syntax such as `Private Sub IOleWindow_GetWindow(As LongPtr) Implements ...`); that bug was fixed in BETA 240. + +_Source threads: 1066331884382072912 · confidence: high_ +_Date range: 2023-01-21 to 2023-02-03_ +_Reviewer note: Verify the exact BETA 240 fix details against the twinBASIC changelog or .twin source if possible. The PreserveSig behavior is consistent with VB6 and is confirmed, but the precise build number of the fix should be double-checked._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> IntelliSense for variables declared as an interface type (e.g. `Private pBrowser As IExplorerBrowser`) stops providing member completions when the module contains parse-level errors---for example, an unclosed `If...Then` block with a missing `End If`. The IDE cannot resolve interface member completions in the presence of such errors. Resolve all syntax errors in the module first; once the module parses cleanly, interface IntelliSense resumes correctly. + +_Source threads: 1086882000025178112 · confidence: high_ +_Date range: 2023-03-19 to 2023-03-28_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> When using `Implements X Via Y` delegation, assigning an object instance to the delegation variable works when `Y` is a local variable. Assigning to `Y` when it is a class-level **Public** field or a field inside a UDT declared at class level may produce a native access violation at runtime. Limit delegation variable assignments to local scope until this restriction is lifted. + +_Source threads: 1305613804180209785 · confidence: low_ +_Date range: 2024-11-11_ +_Reviewer note: Low-confidence finding --- the root cause (VBACopyByte copy becoming invalid after method return) is suspected but not confirmed. Verify against current twinBASIC behavior before publishing. Also confirm whether the 'Implements Via' syntax is documented on this page or elsewhere (the Implements page currently covers only the standard form)._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> Callback interfaces that have no associated GUID and are declared with `Extends Nothing` --- such as those required by the XAudio2 API --- may not be implementable with the **Implements** keyword in twinBASIC. If a callback interface cannot be implemented this way, consider using CoreAudio as an alternative audio API, which is generally supported in twinBASIC. + +_Source threads: 1477515216680911000 · confidence: medium_ +_Date range: 2026-03-02_ +_Reviewer note: This limitation was flagged as a possibility rather than confirmed. Reviewer should test whether twinBASIC actually rejects Implements on a GUID-less Extends-Nothing interface, and whether the issue has been resolved in a subsequent compiler release._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +### Implements WithEvents ... Via + +A class can both implement an interface and forward that interface's events to outside consumers by combining the `WithEvents` and `Via` modifiers: + +```tb +Implements WithEvents InterfaceName Via memberVariable +``` + +The member variable named after `Via` must itself be declared `WithEvents` inside the class. This lets a `WithEvents` variable typed as the interface receive events raised by whichever concrete object is assigned to `memberVariable` at runtime --- making it possible to swap implementations (for example, `StdIO` versus `NamedPipeIO`) while event-handler code written against the interface remains unchanged. + +An example from the twinBASIC runtime source (`ActiveXExtender.twin`): + +```tb +Implements WithEvents T Via ClientObject +``` + +where `T` is the interface type parameter and `ClientObject` is the `WithEvents`-declared member. + +> [!NOTE] +> A known compiler limitation requires at least one `Event` declaration to be present in the class body for the `WithEvents Implements Via` to be included in the compiled output. Without any `Event` on the class, the forwarded events from the interface are silently dropped. A dummy event declaration works around this until the limitation is resolved: +> +> ```tb +> Event xIgnored() ' Required by compiler until limitation is resolved. +> ``` + +_Source threads: 1391490480432349346 · confidence: high_ +_Date range: 2025-07-08_ +_Reviewer note: Verify the exact syntax (Implements WithEvents T Via ClientObject) against ActiveXExtender.twin line 2379 and confirm the FIXME comment about the dummy Event still being present. Place this new subsection inside the existing 'twinBASIC enhancements' section, before the See Also._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> A method does not automatically satisfy an interface contract just because its signature matches the interface method. The implementing method must either follow the traditional underscore naming convention (`IFoo_MethodName`) or append an explicit `Implements IFoo.MethodName` clause after the method signature. Declaring a public method whose name and parameter list match an interface member --- without either convention --- produces a compile error rather than silently binding the two together. + +_Source threads: 1243608396150603838 · confidence: high_ +_Date range: 2024-05-24_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> When a class uses `Implements ClassName`, twinBASIC generates an implicit field inside the implementing class with the same name as the implemented class. This field starts as **Nothing** and must be set explicitly if the class intends to delegate to another instance. Accessing this auto-generated field is unusual; the compiler emits a warning --- *"Unusual use of implicit field generated from an Implements statement"* --- when it is referenced. The warning does not prevent compilation. This behavior matches VB6. + +_Source threads: 1397159152203206656 · confidence: high_ +_Date range: 2025-07-22_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1491034044325036173, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> When a derived class inherits a base class that already implements an interface, the derived class does not need to re-declare the implementation --- inherited interface members are available automatically unless the derived class overrides them. However, if the derived class needs to implement a second interface that extends the first, there is no syntax to instruct the compiler to use the base class's existing implementation for the shared portion. The only workaround is to add forwarding methods in the derived class that delegate to the inherited implementations. This delegation shorthand is planned for a future release. + +_Source threads: 1484135023694184560 · confidence: high_ +_Date range: 2026-03-19_ +_Reviewer note: Confirm the exact current behaviour against the Inheritance feature page and any related compiler notes -- in particular verify that inherited interface members are truly automatic without re-declaration in the derived class._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1208962924820045896, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> **Resolving interface name collisions with twinpack libraries.** If an interface name imported from a twinpack library collides with a name in another referenced type library (for example, `IEnumVariant` exists in both stdole and a custom twinpack), the compiler may resolve `Implements IEnumVariant` to the wrong type. Qualify the interface name with the package name to disambiguate: +> +> ```tb +> Implements PackageName.IEnumVariant +> ``` +> +> If the qualified form still does not resolve correctly, closing and reopening the twinBASIC IDE (restarting the compiler) has been reported to resolve the issue. + +_Source threads: 1491034044325036173 · confidence: medium_ +_Date range: 2026-04-07 to 2026-04-29_ +_Reviewer note: Verify that package-qualified Implements syntax (PackageName.IEnumVariant) is officially supported. The restarting-the-IDE workaround is anecdotal --- reviewer should confirm whether there is a known compiler bug or whether the restart is coincidental._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1386545607916912720, 1478113080419418213] + +> [!NOTE] +> Some COM interfaces declare methods with a true `void` return type rather than `HRESULT`. twinBASIC does not require `[PreserveSig]` to implement these; redeclaring such methods as returning `Long` (HRESULT) works in practice for both 32-bit and 64-bit compiled output --- the same approach used in VB6. Occasional unexpected error values may surface until the interface definition is refined. See [PreserveSig](../../Core/Attributes#preservesig) for the attribute that controls this behavior formally. + +_Source threads: 1208962924820045896 · confidence: medium_ +_Date range: 2024-02-23_ +_Reviewer note: Verify against twinBASIC source whether PreserveSig(True) is the correct formal solution for void-returning COM interface methods, or whether there is another mechanism. The workaround (return Long) was confirmed to work in practice for Direct2D font interfaces._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1478113080419418213] + +### COM implementation gotchas + +Two pitfalls arise specifically when implementing COM interfaces that the Windows OS calls back into: + +**Uninitialized `[out]` parameters.** When the OS calls an interface method and passes a `ByRef ppObject As ISomeInterface` that is semantically write-only (an `[out]` parameter), the incoming value is not guaranteed to be `Nothing` --- it may contain a junk (uninitialized) pointer. A plain `Set ppObject = New SomeClass` will first attempt to release the original value of `ppObject`, which dereferences the junk pointer and crashes. Zero the pointer before the assignment: + +```tb +PutMemPtr VarPtr(ppObject), vbNullPtr +Set ppObject = New SomeClass +``` + +This is different from a twinBASIC-to-twinBASIC call, where the compiler can sanitize callers automatically; when the caller is the OS, no such guarantee exists. + +**`QueryInterface` with the same source and destination variable.** Calling `QueryInterface` and passing the same variable for both the input and output produces a reference leak: + +```tb +' Wrong -- leaks the original reference: +ppObject.QueryInterface(IID_IFoo, ppObject) +``` + +The `QueryInterface` implementation overwrites the output variable without releasing the old reference stored there. Use a separate, initially-Nothing destination variable: + +```tb +Dim pFoo As IFoo +ppObject.QueryInterface(IID_IFoo, pFoo) +Set ppObject = pFoo +``` + +_Source threads: 1386545607916912720 · confidence: high_ +_Date range: 2025-06-25 to 2025-06-26_ +_Reviewer note: These gotchas apply to implementing any Windows COM interface (IShellFolder, IPropertyStore, etc.), not just twinBASIC-defined ones. Verify that PutMemPtr is the canonical pattern used in the community (vs. ZeroMemory or CopyMemory). The IID_IFoo placeholder in the QueryInterface example should be replaced with whatever the docs convention is for GUID arguments._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks [DUPLICATE? -- see also thread 1066331884382072912, 1086882000025178112, 1305613804180209785, 1477515216680911000, 1391490480432349346, 1243608396150603838, 1397159152203206656, 1484135023694184560, 1491034044325036173, 1208962924820045896, 1386545607916912720] + +> [!NOTE] +> Interfaces declared with `Extends Nothing` (a lightweight interface that does not derive from `IUnknown`) cannot be implemented with **Implements**. Attempting to do so causes a crash at runtime. To satisfy such a callback interface, pass `ObjPtr` of a class instance with a matching vtable layout directly to the API --- the API expects a raw pointer to the vtable, not a COM object reference. This pattern is required for callback interfaces such as `IXAudio2VoiceCallback` that are declared as non-`IUnknown` interfaces. + +_Source threads: 1478113080419418213 · confidence: high_ +_Date range: 2026-03-03_ + +--- + +## docs/Reference/Core/Implements.md · after-remarks + +> [!NOTE] +> When a class inherits members through **Implements-Via**, an API **Declare** statement of the same name in the class shadows the inherited member. **Implements-Via** can be combined with members that have default argument values, and with dispinterface-based controls. + +_Source threads: 927834661298798602-2022-12 · confidence: high_ +_Date range: 2022-12-05 to 2022-12-14_ +_Reviewer note: Based on BETA 200, 203 and 205. Verify wording of the shadowing rule against the compiler; the Implements page does not otherwise cover Implements-Via._ + +--- + +## docs/Reference/Core/Implements.md · after-remarks + +> [!NOTE] +> A method marked with the [`Restricted`](../../Features/Language/Interfaces-CoClasses) attribute in an interface need not be implemented by the implementing class. The slot for that method in the class's vtable is left NULL, as in classic VB, so calling the method through the interface on such an implementer fails. + +_Source threads: 927834661298798602-2025-07 · confidence: high_ +_Date range: 2025-07-10_ +_Reviewer note: From BETA 854. Existing paragraph says all Public procedures must be implemented; this adds an exception. Verify the link target for the Restricted attribute and the exact failure mode of calling the NULL slot._ + +--- + +## docs/Reference/Core/Input.md · after-remarks + +> [!NOTE] +> The name `INPUT` conflicts with the built-in **Input #** statement. A project that tries to declare a `Sub` or `Function` named `INPUT` will encounter this name conflict. As a workaround, choose a different name for the custom routine --- for example, `XINPUT` is a conventional substitute used in BASIC-games ports. + +_Source threads: 1477405818444845328 · confidence: medium_ +_Date range: 2026-02-28_ +_Reviewer note: Verify that this name conflict applies at the compiler level (i.e., twinBASIC rejects a Sub named INPUT), not just at parse time. Confirmed by a user working on a VB6-era BASIC games port._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> **`[ComImport(Boolean)]` and COM registration.** When a compiled twinBASIC binary (OCX or DLL) is registered, twinBASIC calls a custom `RegisterTypeLib` implementation that walks each `Interface` declaration and decides whether to write its IID into the registry. The `[ComImport(Boolean)]` attribute controls this decision. It accepts a **Boolean** argument and defaults to `True` for every `Interface` block. +> +> The `True` default is deliberate. On pre-UAC systems (Windows XP and earlier) an unregistered DLL attempts to delete the corresponding registry keys on unregistration. If a project redefines a system interface such as `IShellView`, a `False` default would cause unregistration to delete the live system key, potentially corrupting the OS. With `[ComImport(True)]`, registration is attempted but skipped gracefully when the key is protected; unregistration is a no-op for that entry. +> +> Without this behaviour (present before BETA 243), `RegisterTypeLib` raised a `TYPE_E_REGISTRYACCESS` error when any interface GUID matched a protected system registry key --- for example, when a `UserControl` implemented `IShellFolderViewDual`, `IShellView2`, or `IFolderView2`. VB6 avoided this silently because its `oleaut32` import was shimmed by `aclayers.dll`, which ignored write failures on protected keys and returned `S_OK` regardless. +> +> Set `[ComImport(False)]` explicitly on a brand-new interface that the project defines entirely itself and that should never be written to (or removed from) the system registry. + +> [!NOTE] +> **`[OleAutomation(False)]` does not suppress registration for dual interfaces.** Applying `[OleAutomation(False)]` to an interface that `Extends IDispatch` (a dual interface) does not prevent `RegisterTypeLib` from attempting to register it. The `OleAutomation` flag is honoured only for pure dispatch-only interfaces (`dispinterface`). For dual interfaces the standard `RegisterTypeLib` ignores the flag and proceeds with registration regardless. Use `[ComImport(True)]` (the default) to handle protected-key failures gracefully instead. + +_Source threads: 1071135379975057469 · confidence: high_ +_Date range: 2023-02-03 to 2023-02-06_ +_Reviewer note: The current page documents `[ComImport]` as a flag only (no boolean argument, line 45). The findings indicate it takes `[ComImport(Boolean)]` with `True` as the default. Verify the exact attribute syntax against the twinBASIC compiler source or BETA 243 release notes before publishing. Also check whether `[ComImport]` (no argument) is still accepted as shorthand for `[ComImport(True)]`._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> **`VARIANT_BOOL` mapping and `VARIANT_TRUE`.** The Windows SDK defines `VARIANT_BOOL` as `typedef short VARIANT_BOOL` --- a 2-byte signed integer. Mapping it to `Integer` in an interface member signature is therefore type-size-correct. However, `Boolean` is the semantically preferred type and the one COM infrastructure expects. More importantly, `VARIANT_TRUE` is `-1` (all bits set), not `1`. Interface implementations that return `1` for a true result are incorrect according to the COM specification and may cause misbehavior in COM consumers that check for the canonical `VARIANT_TRUE` value. Always return **True** (which twinBASIC emits as `-1`) rather than a literal `1`. + +_Source threads: 1071135379975057469 · confidence: high_ +_Date range: 2023-02-06_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> **Spurious first member in extended dispatchable interfaces (fixed in BETA 243).** Before BETA 243, when an interface that extended another dispatchable interface was emitted into the generated type library, the compiler inserted a bogus extra first member with a default DispID that conflicted with the real members. The practical symptom was that VB6 could not add the resulting `UserControl` to its component list and reported an automation error. This off-by-one error in the DispID assignment logic was corrected in BETA 243 alongside the custom `RegisterTypeLib` implementation. + +_Source threads: 1071135379975057469 · confidence: high_ +_Date range: 2023-02-06_ +_Reviewer note: This documents a now-fixed compiler bug. Consider whether the docs site policy is to include version-specific bug history. If the site targets only the current release, this note may be omitted. If it is included, verify BETA 243 is the correct release label._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1129802433158189086, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> The typelib generator does not support forward references between COM interfaces. If interface A declares a method whose return type is interface B, and B is defined later in the source, the build fails with `[TYPELIB] failed to finalize typelibrary interface` and `[TYPELIB] failed to create typelibrary interface procedure ... (bad param datatype)`. The fix is to reorder declarations so that every interface used as a return type or parameter type appears before the interface that references it. This mirrors the requirement for forward declarations in IDL. + +_Source threads: 1071686119554568255 · confidence: high_ +_Date range: 2023-02-05 to 2023-02-07_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1071686119554568255, 1281475744492687465, 1392099515309359114] + +> [!NOTE] +> Declaring an interface method with a parameter typed `As Any` (e.g. `Sub Read(pv As Any)`) caused the compiler to crash with `NATIVE EXCEPTION: ACCESS_VIOLATION` on win64 builds when the containing file was subsequently edited. The workaround was to replace `As Any` with `ByVal LongPtr` in the interface declaration. This was fixed in BETA 362. + +_Source threads: 1129802433158189086 · confidence: high_ +_Date range: 2023-07-15 to 2023-07-17_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1392099515309359114] + +> [!NOTE] +> An interface defined with the **Interface** block can be used directly with [**Implements**](Implements) --- there is no separate concept of a "class interface". However, if any member of the interface carries the `[PreserveSig]` attribute, that interface cannot currently be used with **Implements** (support is planned). An interface does not need to occupy its own file; multiple **Interface** blocks can coexist in a single `.twin` file alongside other declarations. + +_Source threads: 1281475744492687465 · confidence: medium_ +_Date range: 2024-09-06_ +_Reviewer note: Verify the [PreserveSig] + Implements limitation against current twinBASIC compiler behaviour; this may have been resolved since September 2024._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks [DUPLICATE? -- see also thread 1071135379975057469, 1071135379975057469, 1071135379975057469, 1071686119554568255, 1129802433158189086, 1281475744492687465] + +> [!NOTE] +> The Project Explorer does not have a dedicated "Add Interface" option. To define a public interface, add a Class or Module `.twin` file to the project and write the `Interface ... End Interface` declaration at file scope, before any `Class...End Class` or `Module...End Module` block. When adding an `[InterfaceId(...)]` attribute, the IDE prompts to generate a GUID using a special algorithm that ensures global uniqueness --- use that rather than supplying a self-generated GUID. + +_Source threads: 1392099515309359114 · confidence: high_ +_Date range: 2025-07-08_ + +--- + +## docs/Reference/Core/Interface.md · after-remarks + +An interface declared as `Interface XYZ Extends Nothing` inherits from no base interface, not even the built-in **IUnknown**. This form allows a project to supply its own unrestricted definition of **IUnknown**. + +```tb +Interface IUnknown Extends Nothing + ' own QueryInterface / AddRef / Release prototypes +End Interface +``` + +_Source threads: 927834661298798602-2022-09 · confidence: high_ +_Date range: 2022-09-30_ +_Reviewer note: From BETA 132. Example is illustrative only; verify it compiles, and consider adding Nothing to the *baseinterface* syntax description. The same BETA also added the IDE setting 'IDE: Use new Handles/Implements syntax' (not covered here; no matching page found)._ + +--- + +## docs/Reference/Core/Interface.md · after-remarks + +> [!NOTE] +> The **Any** data type is allowed for the parameters of interface members, as it is for a **Declare** statement. + +_Source threads: 927834661298798602-2022-08 · confidence: high_ +_Date range: 2022-08-23_ +_Reviewer note: From BETA 97. Verify against the .twin source that Any is accepted for parameters only and not for return values. Place before the Example section (around line 54)._ + +--- + +## docs/Reference/Core/LSet.md · after-remarks [DUPLICATE? -- see also thread 1111237398291021934] + +> [!NOTE] +> The raw-memory-copy behavior of **LSet** between differently-typed UDTs applies only to *simple* UDTs --- those whose fields are all fixed-size numeric types. When a UDT contains `String`, `Object`, `Variant`, or dynamic-array fields, **LSet** copies each field individually using the same semantics as a normal assignment, not a raw byte copy. As a result, assigning an `Object` field from one UDT into a `Long` field of another via **LSet** does not extract the raw pointer value; the runtime applies field-level type rules instead. + +_Source threads: 1053405466409058335 · confidence: high_ +_Date range: 2022-12-17_ + +--- + +## docs/Reference/Core/LSet.md · after-remarks [DUPLICATE? -- see also thread 1053405466409058335] + +> [!NOTE] +> The UDT-copy form of **LSet** requires both *varname1* and *varname2* to be variables of user-defined types. Assigning a scalar value---such as a `LongPtr` or any numeric type---directly into a UDT variable using **LSet** is not valid and produces a compilation (codegen) error. To overlay raw bytes from a scalar into a UDT, use `CopyMemory` or the `PutMem` / `GetMem` helpers in the VBA `(Default)` module. + +_Source threads: 1111237398291021934 · confidence: medium_ +_Date range: 2023-05-25_ +_Reviewer note: Verify the exact error message text and confirm that CopyMemory / PutMem is the intended workaround for this pattern in twinBASIC. The Discord thread confirmed this is a language rule (not a twinBASIC-specific bug), but the recommended alternative should be checked against current twinBASIC practice._ + +--- + +## docs/Reference/Core/LeftShift.md · after-remarks + +> [!NOTE] +> VB6 has no explicit bit-shift operators, so code written for VB6 sometimes multiplies or divides by powers of two (`i * 2`, `i * &H4`) to perform a logical shift, relying on the compiler's optimizer to emit a shift instruction. In twinBASIC, using **\<\<** and **\>\>** directly is preferred---it is clearer in intent and does not depend on optimizer behaviour. + +_Source threads: 1112496566176075897 · confidence: medium_ +_Date range: 2023-05-28 to 2023-05-29_ +_Reviewer note: The same note could appear on the RightShift page (docs/Reference/Core/RightShift.md) with minor wording adjustment; add a cross-reference there if this addition is accepted._ + +--- + +## docs/Reference/Core/Module.md · after-remarks + +> [!NOTE] +> `[MustBeQualified]` has no effect when placed on a **Module** statement that is wrapped inside a `#If` ... `#End If` conditional compilation block, even when the condition evaluates to **True** and other attributes in the same block work correctly. Module members remain accessible in the global namespace as if the attribute were absent. Using `[MustBeQualified(True)]` does not resolve the issue. The only known workaround is to place the attribute unconditionally outside any `#If` guard. + +_Source threads: 1431883663796207707 · confidence: medium_ +_Date range: 2025-10-26 to 2026-02-05_ +_Reviewer note: Verify against the current twinBASIC build whether this bug has been fixed. The thread spans late 2025 to early 2026._ + +--- + +## docs/Reference/Core/Module.md · after-remarks + +> [!NOTE] +> A user-defined type with the same name as a **Module** produces a compiler warning rather than an error, as in VB6. + +_Source threads: 927834661298798602-2026-05 · confidence: high_ +_Date range: 2026-05-13_ +_Reviewer note: From BETA 981. Page not read in detail; verify placement and whether a specific TB warning number applies._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> twinBASIC supports parameterized constructors. A class can define `Public Sub New(Param1 As Type, ...)`, and instances are then created with `Set obj = New ClassName(arg1, arg2)`. VBA has no equivalent --- VBA code must use a factory method pattern instead. +> +> A class that is COM-creatable (the default) must also expose a parameterless `Public Sub New()` alongside any parameterized constructor, because COM requires that COM-creatable classes be instantiable without parameters. Defining only a parameterized constructor on a COM-creatable class causes a compilation error. To remove this constraint, mark the class `[COMCreatable(False)]`; the class can then expose only the parameterized constructor, but it cannot be created by COM clients outside twinBASIC. + +_Source threads: 1086699374073085992 · confidence: medium_ +_Date range: 2023-03-18 to 2023-03-19_ +_Reviewer note: Verify against the twinBASIC compiler source or a current .twin sample that (a) `Set obj = New ClassName(arg1)` is the correct instantiation syntax for parameterized constructors, and (b) the compilation error is actually triggered when a COM-creatable class has only a parameterized Sub New. Confidence reduced to medium because the gotcha detail came from a single community message._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> When a COM type library marks a class with `TYPEFLAG_FCONTROL` (the ActiveX control flag), twinBASIC treats it as an ActiveX control. Declaring such a class by its coclass name (e.g. `Dim sc As MSScriptControl.ScriptControl`) and instantiating it with **New** causes the compiler to substitute the generated ActiveX wrapper interface for the declared type. Because the object is created directly with **New** rather than placed on a form, it is not wrapped in twinBASIC's ActiveX extender and does not support that wrapper interface --- producing a runtime `E_NOINTERFACE` / "No such interface supported" error at assignment. `MSScriptControl.ScriptControl` exhibits this behavior; the same code works in VB6 only when added via the References dialog, not the Components (ActiveX controls) dialog. +> +> Two fixes are available: +> +> - **Project Settings fix (permanent):** in Project Settings, turn off the "Use ActiveX Controls" flag for the reference, save, and restart the compiler. twinBASIC then treats the class as a plain COM reference and the coclass declaration works normally. +> - **Declaration workaround:** declare the variable as the class's default interface rather than the coclass name (`Dim sc As IScriptControl`). The **New** expression on the right-hand side still creates the object correctly; only the variable's declared type needs to be the interface: +> +> ```tb +> Dim sc As IScriptControl +> Set sc = New MSScriptControl.ScriptControl +> ``` + +_Source threads: 1110498079972540436 · confidence: high_ +_Date range: 2023-05-23_ +_Reviewer note: Verify that the 'Use ActiveX Controls' flag name and location in Project Settings matches the current IDE UI. The workaround using IScriptControl as the declared type was confirmed by multiple Discord users in May 2023._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1145039659911622656, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!WARNING] +> Specifying an interface type (rather than a concrete CoClass name) with **New** causes an ACCESS_VIOLATION crash at startup. For example, `Dim x As New IFoo` --- where `IFoo` is an **Interface**, not a **CoClass** or **Class** --- produces a native exception rather than a compile-time or graceful run-time error. The crash occurs even when the declaration is inside a procedure that is never called during startup. Always use the concrete CoClass or Class name with **New**. + +_Source threads: 1291608868673294336 · confidence: high_ +_Date range: 2024-10-04 to 2024-10-06_ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1357331478786080972, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> When a class has both a `New` constructor (a twinBASIC addition) and a `Class_Initialize` or `Form_Initialize` event handler, the `New` constructor executes first, then `Initialize` fires. Both still fire --- neither suppresses the other. This ordering matters when a class has its predeclared ID set to `True`, because the default instance is created at application startup: code that relies on field values set in `Initialize` may run before the constructor has had a chance to supply them. + +_Source threads: 1145039659911622656 · confidence: medium_ +_Date range: 2023-08-26 to 2023-08-30_ +_Reviewer note: Verify constructor-vs-Initialize ordering against .twin sources or twinBASIC release notes; the medium-confidence source is a Discord discussion about VB6 MVVM porting patterns._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357675256252469454, 1468031072448020706] + +> [!NOTE] +> twinBASIC supports overloaded `Sub New(...)` constructors, but two limitations apply: +> +> **Parameter types:** Overload resolution fails when any overloaded `New` signature uses `Object` or `Variant` as a parameter type. These types prevent the overload discriminator from distinguishing candidates. Wrapping the ambiguous value in a `Type` (UDT) is the recommended workaround. +> +> **No constructor chaining:** There is no syntax to call one overloaded constructor from another (as C# allows via `this(...)`). Attempting it produces compile errors. Extract shared initialisation logic into a private method and call it from each overloaded constructor instead. + +_Source threads: 1357331478786080972 · confidence: medium_ +_Date range: 2025-04-03_ +_Reviewer note: Verify against twinBASIC compiler source or release notes: confirm the Object/Variant overload resolution limitation and that constructor chaining is explicitly unsupported._ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1468031072448020706] + +> [!NOTE] +> The syntax `New ClassName(args).Method` --- calling a method directly on a freshly constructed object --- is not yet supported. `With New ClassName(args)` compiles and works correctly at runtime. Wrapping in parentheses, `(New ClassName(args)).Method`, parses but fails at runtime with error 438 (Object doesn't support this property or method). A common workaround is to define a module-level factory function with the same name as the class: +> +> ```tb +> Function MyClass(ByVal Name As String) As MyClass +> Set MyClass = New MyClass(Name) +> End Function +> ``` +> +> This enables the `MyClass("foo").Method` fluent pattern without assigning the object to a variable first. + +_Source threads: 1357675256252469454 · confidence: high_ +_Date range: 2025-04-04 to 2026-01-28_ + +--- + +## docs/Reference/Core/New.md · after-remarks [DUPLICATE? -- see also thread 1086699374073085992, 1110498079972540436, 1291608868673294336, 1145039659911622656, 1357331478786080972, 1357675256252469454] + +## Parameterized constructors + +A class can declare a **Sub** named **New** with one or more parameters to act as a parameterized constructor. The twinBASIC runtime invokes this **Sub** automatically when the class is created with **New**, passing the arguments from the **New** expression: + +```tb +' Declaration (in the class body): +Private Sub New(ByVal title As String, ByVal width As Long) + Caption = title + Me.Width = width +End Sub + +' Creation (at the call site): +Dim w As New MyWindow("Settings", 4000) +' or: +Set w = New MyWindow("Settings", 4000) +``` + +> [!NOTE] +> The access modifier on `Sub New` can be **Private** (or any other modifier). Declaring it **Private** prevents external code from calling it as a regular method while still allowing the **New** expression to invoke it normally. The `Sub New` declaration can appear anywhere in the class body. + +> [!IMPORTANT] +> When a class defines a parameterized `Sub New`, the **Class_Initialize** event is never raised for that class --- the two mechanisms are mutually exclusive. Removing the `Sub New` declaration restores **Class_Initialize** behaviour. If initialisation logic must be shared, call the relevant code from inside `Sub New` explicitly; **Class_Initialize** will not fire automatically. + +_Source threads: 1468031072448020706 · confidence: high_ +_Date range: 2026-02-02 to 2026-02-03_ +_Reviewer note: New.md currently documents only the `New` expression keyword. This draft adds a new subsection about `Sub New` parameterized constructors. Verify against the twinBASIC .twin sources / Inheritance feature docs that the Class_Initialize suppression is correct and intentional._ + +--- + +## docs/Reference/Core/New.md · after-remarks + +> [!NOTE] +> The **As New** form does not respect a class's parameterized constructor. For a `[COMCreatable(False)]` class whose only constructor is `Private Sub New(ByVal lTest As Long)`, `Set obj = New MyClass` is rejected, but `Dim obj As New MyClass` is accepted in a build without LLVM. The object is then created without calling the constructor. In an LLVM build the same declaration fails with *LLVM compilation error in MyClass.{default_constructor}*. This is a reported compiler bug: the inline form is expected to require `New MyClass(1)`, as the **Set** form does. Until it is fixed, create such a class with **Set** or with an initializer (`Dim obj As MyClass = New MyClass(1)`), and do not use **As New** on it. + +_Source threads: 1554826871047458878 · confidence: medium_ +_Date range: 2026-09-30_ +_Reviewer note: Unfixed bug report from a single thread; verify against the current BETA before publishing, and remove or reword once fixed. The initializer workaround is inferred from the page's own documented forms, not stated in the thread. Also a candidate for BUGS-TO-REPORT.md._ + +--- + +## docs/Reference/Core/On-Error.md · after-remarks [DUPLICATE? -- see also thread 1113589601232240741, 1351545697060524033, 1478843597578174514] + +> [!NOTE] +> When a runtime error occurs and no error handler is active, the procedure exits at that point. The exit is silent---no dialog appears and no further statements in the procedure run. Output produced before the error is retained, but any output that would have followed is silently omitted. This can make a logic error appear as unexpected early termination or missing results rather than an obvious error. Adding an `On Error GoTo` handler that logs `Err.Number` and `Err.Description` is the most reliable way to diagnose such failures. + +_Source threads: 1201183841113612419 · confidence: high_ +_Date range: 2024-01-28_ +_Reviewer note: The original thread (BETA 424+) described this as a bug fixed in BETA 432. Verify whether the current runtime still exits silently on unhandled errors or now shows a dialog, and adjust the note accordingly._ + +--- + +## docs/Reference/Core/On-Error.md · after-remarks [DUPLICATE? -- see also thread 1201183841113612419, 1351545697060524033, 1478843597578174514] + +> [!NOTE] +> Structured exception handling (`Try`/`Catch`/`Finally`) is not available in the current release. Support is planned via vbWatchDog integration; see [lang-design issue #61](https://github.com/twinbasic/lang-design/issues/61) for status. Until that ships, `On Error GoTo` combined with a cleanup label is the only built-in mechanism. + +_Source threads: 1113589601232240741 · confidence: medium_ +_Date range: 2023-05-31 to 2023-06-01_ +_Reviewer note: Verify whether the vbWatchDog Try/Catch integration has shipped or the issue has been closed since mid-2023._ + +--- + +## docs/Reference/Core/On-Error.md · after-remarks [DUPLICATE? -- see also thread 1201183841113612419, 1113589601232240741, 1478843597578174514] + +> [!NOTE] +> When execution has entered an error-handling block --- that is, after an `On Error GoTo