diff --git a/BUGS-TO-REPORT.md b/BUGS-TO-REPORT.md index ca6a3bf79..f31576111 100644 --- a/BUGS-TO-REPORT.md +++ b/BUGS-TO-REPORT.md @@ -2579,3 +2579,934 @@ What does not reproduce it: neither procedure compiled with LLVM (cases 1 and 2) Severity: high for a program compiled with LLVM. An error that the caller handles ends the program, and with a callee compiled with LLVM it ends with an access violation. In the IDE the run ends without a message, so the cause is hard to find. No wrong result is produced. Compiling both procedures without LLVM avoids it. + +--- + +## The shipped `Dark.theme` and `Light.theme` lack a `;` after `DiagnosticsGroupFontSize`, so the IDE loads neither it nor `DiagnosticsGroupFontWeight` + +**Describe the bug** +Line 63 of both `themes\Dark.theme` and `themes\Light.theme`, the two theme files that the IDE ships and that every other theme inherits from, reads `DiagnosticsGroupFontSize: 90%` with no `;` at its end. The IDE's theme loader cuts a theme file into properties at each `;`, so the property runs into the next line, and the piece it cuts is not a property. The loader drops such a piece without a message, so both `DiagnosticsGroupFontSize` and `DiagnosticsGroupFontWeight` are lost. Each of the two files has 495 properties that load, and these 2 are not among them. No other piece of the three shipped theme files (`Classic.theme` is the third) is dropped. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open `themes\Dark.theme`, in the folder of the twinBASIC installation, in a text editor. There is no project to attach: the defect is in the installation's own files. +2. Lines 63 and 64 read: + ``` + DiagnosticsGroupFontSize: 90% + DiagnosticsGroupFontWeight: 500; + ``` + Line 63 ends without the `;` that ends line 64 and every other property of the file. (In the file, the spaces after each colon are tabs.) `themes\Light.theme` has the same two lines at the same place. +3. Open `ide\main.js` and find `applyThemeData`, the function that loads a theme file. It removes the `/* */` comments, cuts the text at each `;` followed by a space, a tab or a line end, removes the line breaks from each piece, cuts each piece at each `: ` and `:` followed by a tab, and keeps a piece only if that gives exactly two parts (the name and the value). Lines 63 and 64 are one piece, `DiagnosticsGroupFontSize:` and its value, then `DiagnosticsGroupFontWeight:` and its value, with nothing between them but the line break the loader removes. It gives three parts, so the piece is dropped. The loader's code has no other branch for a piece, and it writes no message. +4. Instead of reading the code, run `node scripts/probe_theme_semicolon.mjs` from the repository. It applies the same rule to every `.theme` file in the install's `themes` folder, and prints one line for each piece it drops. It starts no IDE. + +**Expected behavior** +Both properties load, with the values the files give: a font size of `90%` and a font weight of `500` for the Diagnostics group. That needs a `;` at the end of line 63 in both files. Any other property run together with the next one is lost in the same way, so the loader could also say which piece it dropped, in the DEBUG CONSOLE or the IDE's trace output. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +BETA 997 and 957 have the same two lines at the same place. `Classic.theme` has no such piece: it contains `inherits: light;` and its own properties, all of which end with `;`. The loader's rule was checked by applying the code of `applyThemeData` unchanged to each file: `Dark.theme` and `Light.theme` give 495 properties each, without the two, and `Classic.theme` gives 188. + +The effect on screen was not observed. In the IDE's `styles.css`, the rule of `.problemNodeGroup` reads `font-weight: var(--themeDiagnosticsGroupFontWeight); font-size: var(--themeDiagnosticsGroupFontSize);`, and the loader sets one CSS variable for each property it loads, so with these two missing, the variables have no value. By the CSS rules, a `var()` of a variable with no value makes the declaration invalid when the style is computed, and `font-size` and `font-weight` then take the value they inherit. So the elements of that class (by its name, the group headings of the Problems panel) would use the size and weight they inherit instead of 90% and 500. + +Severity: low. No data is lost, and the look of one kind of heading is the only thing that changes, but the IDE says nothing about it. A theme file that sets the two properties again, with its `;`, avoids it: a custom theme that inherits from Dark or Light, with `DiagnosticsGroupFontSize: 90%;` and `DiagnosticsGroupFontWeight: 500;` among its own lines, loads them. + + + +--- + +## A `Dark.theme` or `Light.theme` in `%APPDATA%\twinBASIC\themes` stops every custom theme from inheriting, and Reload from disk from applying the theme + +**Describe the bug** +When the common themes folder, `%APPDATA%\twinBASIC\themes`, holds a file named `Dark.theme` or `Light.theme`, no custom theme is linked to the theme it inherits from. Window > Theme lists every custom theme, the shipped Classic included, as ` (undefined)` instead of ` (Light)` or ` (Dark)`. Choosing one gives the dark base whatever its `inherits:` line says, with only the theme's own properties changed and every other colour left as the previous theme had it. Window > Theme > Reload from disk then reads the files again but never applies the active theme, so an edit to it does not show until the IDE is restarted. No message is shown. It was observed in an IDE started with an `APPDATA` of its own, by reading the theme list and the CSS variables of the IDE's page. + +**To Reproduce** +Steps to reproduce the behavior: +1. Close the IDE. There is no project to open: the defect is in how the IDE reads its theme folders. In `%APPDATA%\twinBASIC\themes` (the IDE creates the folder), write `AtStart.theme`: + ``` + inherits: light; + MenuBackColor: rgb(255, 0, 0); + ``` + and `Dark.theme`, with any text: its contents are not read. +2. Start the IDE and cancel the New / Open dialog. Open Window > Theme: it lists `Classic (undefined)` and `AtStart (undefined)`. +3. Choose `AtStart`: the IDE takes the dark base (dark panels, the dark code editor) with a red menu bar. +4. Change the colour in `AtStart.theme` to `rgb(0, 128, 0)` and choose Window > Theme > Reload from disk: the menu bar stays red. +5. Delete `Dark.theme` and restart the IDE: the list shows `Classic (Light)` and `AtStart (Light)`, `AtStart` gives the light base with a red menu bar, and Reload from disk after the same edit turns the menu bar green at once. + +**Expected behavior** +The themes keep their parents whatever else the common folder holds, as in step 5: `Classic (Light)` and `AtStart (Light)` in the list, the light base for `AtStart`, and Reload from disk applying the edit. A `Dark.theme` or `Light.theme` there is either ignored, as the code means it to be, or replaces the shipped theme of that name. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: `reloadThemesFromDisk`, which runs at start-up and for Reload from disk, counts every `.theme` file of both folders into `customThemesCount`, but in the common folder it skips `Dark.theme` and `Light.theme` without calling `addTheme`. `addTheme` links the themes to their parents (`inheritsFromLink` and `group`), and runs `afterRefreshedThemes`, only once `customThemes.length` reaches `customThemesCount`, which then never happens. Reload from disk applies the theme in `afterRefreshedThemes`, so that never runs either. Counting only the files that are added would cure both. + +Only `Dark.theme` was tried; `Light.theme` takes the same branch of the code. The file was in place when the IDE started; adding it and choosing Reload from disk goes through the same function, and was not tried. + +Severity: moderate. Nothing is lost, but copying the shipped `Dark.theme` into the common folder to change it, the obvious way to adjust a shipped theme, makes every custom theme wrong, and nothing says why. + + + +--- + +## Two themes that inherit from each other freeze the IDE, and choosing one fails with a stack overflow + +**Describe the bug** +When two theme files name each other in their `inherits:` lines, loading the themes stops the IDE's page for several seconds and leaves the themes half linked: one of the two is listed as ` (undefined)`. Choosing either of them afterwards changes nothing on screen: the page raises an uncaught `RangeError: Maximum call stack size exceeded` while it applies the theme, and the previous theme's colours stay. Nothing on screen reports the error. It was observed in an IDE started with an `APPDATA` of its own, with a timer in the IDE's page recording the longest time the page did not run, and a listener for the page's `error` event catching the exception. + +**To Reproduce** +Steps to reproduce the behavior: +1. Start the IDE. There is no project to open. In `%APPDATA%\twinBASIC\themes`, write `LoopA.theme`: + ``` + inherits: LoopB; + MenuBackColor: rgb(1, 1, 1); + ``` + and `LoopB.theme`: + ``` + inherits: LoopA; + MenuBackColor: rgb(2, 2, 2); + ``` +2. Choose Window > Theme > Reload from disk. The IDE does not respond for about seven seconds (7.5 s on the machine it was found on). +3. Open Window > Theme: it lists `LoopB (undefined)`. +4. Choose `LoopA`: the menu bar does not take `rgb(1, 1, 1)`, and nothing else changes. +5. To recover, remove the loop from the files and restart the IDE. + +**Expected behavior** +The IDE stays responsive, and the themes in the loop fall back to the Dark theme, which the code in `addTheme` means them to do: it checks for a theme met twice, and sets the group to Dark when it finds one. Choosing `LoopA` then applies at least its own `MenuBackColor`. A message naming the loop would help the person who wrote it. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: in `addTheme`, the pass that gives each theme its group follows `inheritsFromLink` in a `while` loop and records each theme it passes. When it meets a theme a second time it sets the group to Dark, but does not leave the loop, so it keeps going round the two themes until it stops after several seconds without finishing: `LoopB` is never given a group, and `afterRefreshedThemes` is still pending afterwards. `applyThemeData` then follows the same links recursively when a theme of the loop is applied, and overflows the stack. A `break` after the fall-back, and the same check in `applyThemeData`, would cure both. + +Start-up loads the themes through the same `reloadThemesFromDisk`, so an IDE started with the two files in place should freeze the same way; only Reload from disk was tried. A longer chain that returns to its first theme takes the same path in the code, and was not tried. + +Severity: moderate. Nothing is lost and the IDE recovers, but the freeze looks like a hang, and nothing says which files cause it. + + + +--- + +## A keyboard-shortcut group whose name contains `|` switches other groups on and off + +**Describe the bug** +The IDE keeps the list of keyboard-shortcut groups that are on as one string, each name between two bars, such as `|Mine||Other|`, and looks a name up in it as text. Manage Keyboard Shortcuts accepts a group name that contains `|`, and that name's entry can then contain another group's: with groups `A|B` and `B`, the text `|A|B|` is the entry of `A|B` and also contains `|B|`. Clicking `B` in Window > Keyboard Shortcuts then removes the wrong text: the first click leaves `B` ticked, and the second switches `A|B` off as well and saves a list that ends in a broken `|A`. Nothing warns about the name. It was observed by operating Manage Keyboard Shortcuts and the commands of the Window > Keyboard Shortcuts items in an IDE whose saves were blocked, and reading the list and the menu's ticks after each step. + +**To Reproduce** +Steps to reproduce the behavior: +1. There is no project to open. The steps change the groups saved in `HKCU\Software\VB and VBA Program Settings\twinBASIC_IDE\IDESettings` (`CUSTOM_KEYBOARD_SHORTCUT_GROUPS` and `ACTIVE_KEYBOARD_SHORTCUT_GROUPS`): export that key first if they matter. Choose Window > Keyboard Shortcuts > Manage Keyboard Shortcuts..., Add New..., type `A|B`, press Enter, and Save Changes. The new group is switched on. +2. Do the same with the name `B`. Window > Keyboard Shortcuts shows `Custom 1. A|B` and `Custom 2. B`, both ticked. The list of groups that are on is `|A|B||B|`. +3. Click `Custom 2. B`. It stays ticked: the IDE removed the first `|B|` it found, which is the one inside `|A|B|`, and the list is now `|A|B|`. +4. Click `Custom 2. B` again. `A|B` and `B` are both unticked, though only `B` was clicked, and the list saved is `|A`, which names no group. + +**Expected behavior** +Step 3 switches `B` off and leaves `A|B` on. Either Manage Keyboard Shortcuts refuses a name that contains `|`, as it refuses one that starts with `<`, or the list is kept in a form that a name cannot break, such as a JSON array of names. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The code, read from `ide\main.js`: `toggleActiveKeyboardShortcutName` tests the list with `indexOf("|" + name + "|")` and removes the entry with `replace`, which replaces the first match; `getCustomKeyboardShortcutMenuOptions` ticks a group by the same `indexOf`. The merge of the groups that are on matches names the same way: with the list `|A|B|`, `A|B` on and `B` defined but never switched on, a shortcut that only `B` defines works, and `B` is ticked in the menu. That was tried with the groups set in the IDE's page as Save Changes sets them, and a real key press. + +Severity: low. A name with `|` is unusual, and the groups themselves are kept, but the saved list is corrupted without a message, and the menu shows a group as on after it was switched off. + + + +--- + +## Format Selection from a continuation line fails with *IDE startup failure*, and closes an IDE that was opened with its project + +**Describe the bug** +Edit > Format Selection on a selection whose first line continues the line above it (the line above ends in ` _`) formats nothing, and the IDE's page raises `TypeError: Assignment to constant variable.` When the IDE was started with the project on its command line, as it is when a `.twinproj` is opened from Explorer, the error is shown as an alert headed *IDE startup failure*, and after the alert the IDE closes itself: at once when nothing is unsaved, and after asking whether to save when something is. Started from its icon, with the project opened from the New / Open dialog, the IDE prints the same error in the DEBUG CONSOLE and stays open. It was observed in an IDE started with an `APPDATA` of its own, by recording the page's alerts and watching the IDE's process. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project by double-clicking its `.twinproj`, so that the IDE starts with it. Add this procedure to a module, and save: + ``` + Public Sub Continued() + Dim a As Long, _ + b As Long + Select Case a + Case 1 + a = 2 + End Select + End Sub + ``` +2. Select from the start of the line `b As Long` to the end of `Select Case a`, and choose Edit > Format Selection. +3. An alert reads `IDE startup failure (BETA twinBASIC IDE BETA 1005):`, then `JS Promise Rejection(1):`, `Assignment to constant variable.` and a stack trace through `provideDocumentRangeFormattingEdits`. Nothing is formatted. Click OK: the IDE closes. +4. Open the project again, change anything, and repeat step 2. After the alert the IDE asks *You've made changes to this project. Save now?*, with Save, Discard Changes and Cancel. Cancel keeps the IDE open; the other two close it. + +**Expected behavior** +The selection is re-indented, as it is when it starts on any other line: the formatter moves the start up to the first line of the continued statement and formats from there. A failure inside a command does not close the IDE. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js` and `ide\main2.js`: `provideDocumentRangeFormattingEdits` declares the start line with `const` and then decrements it (`o--`) in the loop that moves the start up over the lines ending in `_`, which throws as soon as the selection starts on a continuation line. The rejection reaches `window.onunhandledrejection` in `main2.js`, which alerts *IDE startup failure* and calls `hostAppObject.CloseWindow` unless `reportIDEerrorsToDebugConsole` is set, and only `startNow`, the start without a project, sets it. So in an IDE opened with its project, any unhandled rejection in the page closes the IDE. + +Format Selection on a selection that starts on any other line, the `Select Case` line included, works. Format Document, on the same text, works. + +Severity: high. The IDE closes during ordinary editing. Nothing is lost if the question in step 4 is answered Save or Cancel, but Discard Changes, a likely answer to a question nobody expected, loses every unsaved change. + + + +--- + +## Find with Whole Word Only never finds a match at the very start of the file + +**Describe the bug** +With Whole Word Only ticked, the Find / Replace panel never finds a match that begins at line 1, column 1 of the file. Find Next passes over it, and the search ends with *The specified region has been searched.* No message says that a match was skipped. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own, and reading the selection after each Find Next. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module whose first line is its `Module` line, with nothing above it, such as: + ``` + Module Finder + Public Sub Main() + Debug.Print 1 + End Sub + End Module + ``` +2. Put the caret on line 2 and press Ctrl+F. Type `Module` in Find What and tick Whole Word Only; Current File and Direction All are as Ctrl+F sets them. +3. Click Find Next: the `Module` of `End Module` is selected. Click Find Next again: *The specified region has been searched.* So it goes on every later press: the `Module` at line 1, column 1 is never selected. +4. Clear Whole Word Only and repeat from step 2: the second press selects line 1, column 1. + +**Expected behavior** +The second press selects the `Module` at line 1, column 1. The start of the file is a word boundary, as the start of any other line is. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: `doFindMatch` takes the character before and the character after a match with `charAt`, and treats each as a boundary when its `charCodeAt(0)` is below 128 and it is not a letter or a digit. Before the first character of the text searched, `charAt` returns an empty string, whose `charCodeAt(0)` is `NaN`, so the match is taken to be inside a word and is skipped. The same code skips a match that ends at the last character of the text searched; that, and a match that starts exactly at the caret (the text searched starts there), were not tried. + +Severity: moderate. The search reports the region searched while it has skipped a match, and nothing shows it. + + + +--- + +## Find with Regular Expressions finds nothing when the pattern holds a capital letter + +**Describe the bug** +With Regular Expressions ticked, a pattern that holds a capital letter finds nothing unless it carries the `i` flag: `/End Sub/` ends with *The specified region has been searched.* in a file full of `End Sub`, while `/end sub/` and `/End Sub/i` find it. With Match Case clear, the panel searches the file's text in lower case but leaves the regular expression as it was typed; and ticking Regular Expressions clears Match Case (ticking Match Case clears Regular Expressions), so a regular expression always runs against the text in lower case. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module that holds `End Sub`. Press Ctrl+F and tick Regular Expressions; Match Case is cleared. +2. Type `/End Sub/` in Find What, put the caret at line 1, column 1, and click Find Next: *The specified region has been searched.* +3. Type `/end sub/`, or `/End Sub/i`, instead: Find Next selects `End Sub`. + +**Expected behavior** +`/End Sub/` finds `End Sub`. Either the search runs on the text as it is, with the `i` flag added when Match Case is clear, or Match Case can be ticked together with Regular Expressions to make the search case-sensitive. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: the Find Next code lower-cases the text searched (`toLowerCase`) when Match Case is clear, and `createRegexFromUserString` builds the expression from the pattern as typed, adding only the `m` flag. A class such as `\D` (not a digit) matches lower-case text as before, so `/\Dim/` still finds `Dim`: only a literal capital letter fails. + +Severity: moderate. The search reports nothing found for text that is there. + + + +--- + +## A regular-expression match that spans a line break is shown as an empty selection, and Find Next does not move past it + +**Describe the bug** +When a regular expression in the Find / Replace panel matches across a line break, Find Next selects nothing: it puts the caret at the end of the line where the match starts, with an empty selection. The next Find Next finds the same match and stays at the same place, so the search can go no further. It was observed by operating the panel with mouse and key events in an IDE started with an `APPDATA` of its own, and reading the selection after each Find Next. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module in which an indented line comes before `End Sub`, such as: + ``` + Module Finder + Public Sub Main() + Debug.Print 1 + End Sub + End Module + ``` +2. Press Ctrl+F, tick Regular Expressions and type `/^\s+end sub$/` in Find What (in lower case: see the entry about capital letters). Put the caret at line 1, column 1 and click Find Next. +3. The selection is empty, at the end of the `Debug.Print` line. Click Find Next again: the selection is at the same place, however often it is clicked. + +**Expected behavior** +The match is selected, from the line break to the end of `End Sub`, and the next Find Next goes on from the end of the match. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: `doFindMatch` turns the start of the match into a line and a column, but puts its end on the same line, at the start column plus the match's length. For a match that holds a line break that column lies past the end of the line, and the editor shortens the selection to the line's end. The next search starts from that place, which is before the match, and finds it again. The match starts at the line break because the IDE adds the `m` flag, so `^` also matches inside the line break. A match within one line, such as `/sub$/i`, is selected properly. + +Severity: low. Patterns that span lines are rare, but Find Next cannot get past one. + + + +--- + +## The Find / Replace panel's *Invalid RegExp:* alert leaves out the reason + +**Describe the bug** +When the pattern in Find What is not a valid regular expression, Find Next shows an alert that reads only *Invalid RegExp:*, without saying what is wrong with the pattern. It was observed by recording the alerts of the IDE's page while operating the panel. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module. Press Ctrl+F, tick Regular Expressions and type `/(/` in Find What. +2. Click Find Next. The alert reads `Invalid RegExp:` and nothing after the colon. + +**Expected behavior** +The alert gives the reason, such as `Invalid RegExp: Invalid regular expression: /(/m: Unterminated group`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: `createRegexFromUserString` calls `alert("Invalid RegExp:", e.message)`. `alert` shows its first argument only, so the message is dropped; `alert("Invalid RegExp: " + e.message)` would show it. + +Severity: low. + + + +--- + +## After a search in Selected Text, the scope stays on a greyed Selected Text, and Find Next stops at once + +**Describe the bug** +The Find / Replace panel ticks Selected Text when Ctrl+F is pressed with a selection of several lines. Once that selection is gone, after a click elsewhere in the code, the panel greys Selected Text but leaves it ticked, and ticks no other scope. Find Next then searches nothing: it asks at once whether to continue from the beginning, and finds nothing in the file. It was observed by operating the panel and the code editor with mouse and key events in an IDE started with an `APPDATA` of its own. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module. Select three lines that hold the word `total` and press Ctrl+F: Selected Text is ticked. Type `total` in Find What and click Find Next: the first `total` in the selection is selected. +2. Click in the code on a line outside the selection. Selected Text is still ticked, and greyed; Current File is not ticked. +3. Click Find Next. At once: *End of search scope has been reached; do you want to continue from the beginning?* + +**Expected behavior** +When Selected Text can no longer be used, the panel ticks Current File, as Ctrl+F does when there is no selection, and Find Next searches the file. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +Ticking another scope, such as Current Procedure, recovers. Severity: low. The message suggests that the file holds no further match, while the search did not look. + + + +--- + +## The Diagnostics panel's Copy and Copy All put HTML in the text, and label an info or a hint {WARNING} + +**Describe the bug** +The text that the DIAGNOSTICS panel's right-click menu copies holds the HTML the panel draws the diagnostic's code with: each message starts `TB0018`. Copy All also labels every row that is not an error `{WARNING}`, infos and hints included. It was observed in an IDE started with an `APPDATA` of its own, with the page's clipboard calls replaced by recorders, so that the copied text could be read. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project with a warning and an info, such as one whose settings make TB0012 an info and TB0018 a warning, with this module: + ``` + Module Helpers + Public Const BigValue As Long = &H8000 + + Public Function Twice(ByVal n As Long) As Long + Return n * 2 + End Function + + Public Sub Narrowing() + Dim narrow As Integer + narrow = Twice(2) + End Sub + End Module + ``` +2. In the DIAGNOSTICS panel, right-click the TB0018 row and choose Copy. The text copied is `{WARNING} //Sources/Helpers.twin [,]: TB0018implicit narrowing conversion from Long to Integer. Use CInt(expression) to avoid this message.` +3. Click the Info counter on the status bar, so that the panel shows infos, right-click a row and choose Copy All. Every row's code is between `` and ``, and the info TB0012 is labelled `{WARNING}`. + +**Expected behavior** +Plain text, with the code and the message apart: `{WARNING} //Sources/Helpers.twin [,]: TB0018 implicit narrowing conversion...`, and infos and hints labelled as what they are, such as `{INFO}` and `{HINT}`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The cause, read from `ide\main.js`: the panel adds each diagnostic with the caption `"" + code + "" + message`, the `` styling the code, and `generateCopyPasteTextForProblem` copies the caption as it is, labelling severity 1 `{ERROR}` and every other severity `{WARNING}`. Copy All Errors copies the errors with the same ``. + +Severity: low. The copied report has to be cleaned by hand before it is pasted into an issue or a message. + + + +--- + +## The Outline panel lists a file's modules and classes but none of their members + +**Describe the bug** +The OUTLINE panel shows only the top-level types of the active file, one row each, with no arrow to expand them: `Editing` for a module, `Ev` and `Ev2` for a file holding two classes. None of the procedures, properties, variables or other members is listed, so the filters in the panel's `...` menu (Show Procedures, Show Variables and the rest) change nothing. The symbols the panel receives for the file have an empty `children` list. It was observed in an IDE started with an `APPDATA` of its own, by reading the panel and the symbols it holds (`outlinePanel.cachedSymbols`). + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module that holds procedures, such as: + ``` + Module Editing + Public Sub Main() + End Sub + + Public Sub Plain() + End Sub + End Module + ``` +2. Show the OUTLINE panel. It shows one row, `Editing`, with no arrow, and neither `Main` nor `Plain`. + +**Expected behavior** +The module's row expands to its members, `Main` and `Plain`, as the panel's filters and its Sort Alphabetically option assume. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The symbols reach the panel's `syncSymbols` with `children: []` for every type, so either the compiler sends no members or the IDE drops them before the panel; which of the two was not settled. + +Severity: moderate. The panel cannot be used to move around a file. + + + +--- + +## Go To Definition, Ctrl+Up and Ctrl+Down put the caret on the line after a declaration + +**Describe the bug** +Go To Definition (F12 or Shift+F2) on the name of a procedure puts the caret on the line after the procedure's declaration, the first line of its body, instead of on the declaration. In the trials the same happened for a class member reached through an object variable, and for a class name (line 2 of a class declared on line 1). Ctrl+Down and Ctrl+Up, which move to the next and the previous procedure, also put the caret on the first line of the body, at column 1. Go To Definition on a local variable or an enum member puts the caret on its declaration. It was observed by pressing the keys in an IDE started with an `APPDATA` of its own and reading the caret's place. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project with a module: + ``` + Module Helpers + Public Function Twice(ByVal n As Long) As Long + Return n * 2 + End Function + End Module + ``` + and, in a procedure of another module, the line `total = Helpers.Twice(3)`. +2. Put the caret on `Twice` in that line and press F12. `Helpers.twin` opens with the caret on `Return n * 2`, the line after `Public Function Twice`. +3. In a module with several procedures, put the caret in the first one and press Ctrl+Down: the caret goes to the first line of the next procedure's body, column 1. Ctrl+Up from inside a procedure goes to the first line of its own body. + +**Expected behavior** +The caret on the declaration line, at the name, as Go To Definition does for a local variable, so that the declaration is what the reader sees first. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +Severity: low. The declaration is one line above the caret, and in view. + + + +--- + +## Escape does not remove the extra cursors + +**Describe the bug** +After cursors are added with Ctrl+Alt+Down or Alt+click, Escape leaves them all: pressed once or twice, the code editor still has every cursor, and what is typed next goes in at each of them. A click in the code goes back to one cursor. It was observed by pressing the keys in an IDE started with an `APPDATA` of its own and reading the editor's selections. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module. Put the caret on a line that has two more lines below it and press Ctrl+Alt+Down twice: there are three cursors. +2. Press Escape, then Escape again. The three cursors remain. + +**Expected behavior** +Escape leaves only the first cursor, as it does in the Monaco editor the code editor is built on. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +Escape is not among the keys the IDE stops before they reach Monaco (`monacoApplyKeyDownFilter`), and the IDE does not refer to Monaco's command for it, `removeSecondaryCursors`; where the key goes was not settled. + +Severity: low. A click removes the extra cursors. + + + +--- + +## In the Memory pane, the name of a Double, Boolean or user-defined type variable throws an uncaught BigInt error, and the box is not marked invalid + +**Describe the bug** +The Memory pane's box sends the expression to the debugger and turns the result into an address: a String gives `StrPtr`, an object `ObjPtr`, an array `VarPtr`, and any other result is read as a whole number with `BigInt`. A result that is not a whole number --- a **Double** such as `1.5`, a **Boolean** (`True`), a variable of a user-defined type (`{user defined type, 8 bytes}`) --- makes `BigInt` throw. The exception is not caught: the DEBUG CONSOLE prints `IDE internal error: Uncaught SyntaxError: Cannot convert 1.5 to a BigInt`, the box is not turned red, and the rows stay as they were. It was observed by typing the names into the box in an IDE started with an `APPDATA` of its own, at a `Stop`. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run a project with: + ``` + Module Startup + Private Type Rec + A As Long + B As Integer + C As Integer + End Type + + Public Sub Main() + Dim d As Double, flag As Boolean, udt As Rec + d = 1.5: flag = True: udt.A = 5 + Stop + End Sub + End Module + ``` +2. At the `Stop`, choose View > MEMORY 1, type `d` in the box and press Enter. The DEBUG CONSOLE prints `IDE internal error: Uncaught SyntaxError: Cannot convert 1.5 to a BigInt`; the box keeps `d`, is not red, and the rows do not move. +3. The same with `flag` (`Cannot convert True to a BigInt`) and `udt` (`Cannot convert {user defined type, 8 bytes} to a BigInt`). + +**Expected behavior** +The box turned red, as it is for an expression the debugger cannot evaluate, since the value is not an address; or, for a variable, the pane showing the variable itself (its `VarPtr`), as it does for an array. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The throw is in the box's `evaluate` callback (`BigInt(a)` in `createHexEditor`, `ide/main.js`). + +Severity: low. `VarPtr(d)`, `VarPtr(flag)` and `VarPtr(udt)` show the variables. + + + +--- + +## In a 64-bit program, the Memory pane's Address column drops the high 32 bits + +**Describe the bug** +In a program built for win64, the Memory pane reads and shows the right memory, but its **Address** column shows only the low 32 bits of each address. A stack variable's address above 4 GB, such as `&H4232CFF438`, is shown as `&H32CFF438`, which is another address, with nothing on screen to say so. It was observed in an IDE started with an `APPDATA` of its own, by comparing the column with the address the program printed. + +**To Reproduce** +Steps to reproduce the behavior: +1. Switch a project to win64 with the toolbar's architecture box. Its `Sub Main`: + ``` + Dim n As Long + n = 1234 + Debug.Print Hex(VarPtr(n)) + Stop + ``` +2. Run it. At the `Stop` the DEBUG CONSOLE shows an address above 4 GB, such as `4232CFF438`. +3. Choose View > MEMORY 1, type `VarPtr(n)` in the box and press Enter. The first row holds `n`'s bytes, `D2 04 00 00`, and its address reads `&H32CFF438`. + +**Expected behavior** +The Address column showing the whole address, `&H0000004232CFF438` or `&H4232CFF438`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The pane is made with `createHexEditor(r, {stream: n, addressBits: 32, columns: 16})` whatever the build target (`_hexEditorPanel`, `ide/main.js`), and the label of each row is masked to `addressBits`. The cell that takes the focus has the whole address in its `data-addr`, and the reads use it. + +Severity: medium. An address copied from the column, or compared with one the program prints, is wrong, and nothing shows it. + + + +--- + +## The Memory pane shows memory it cannot read as still loading, with ?? in every cell + +**Describe the bug** +At an address the program cannot read, such as `0`, `16` or the value of a **Long** variable, every value cell of the Memory pane shows `??` and every text cell `·`, with the class `loading`: the placeholders of a cell whose bytes are still being read. They never change. The pane has a style for unreadable memory, blank grey cells (`invalidMem`), but the panel never applies it. It was observed in an IDE started with an `APPDATA` of its own, five seconds after the expression was entered. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run any project to a `Stop`. +2. Choose View > MEMORY 1, type `0` in the box and press Enter. +3. Every row, from `&H00000000`, shows `??` in all its value cells and `·` in its text cells, and still does after any wait. + +**Expected behavior** +The cells of unreadable memory drawn as unreadable (blank and grey, the `invalidMem` style), so that they differ from memory still being read. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +`invalidMem` is set only through the stream's `onInvalidate`, which only a demonstration stream in `ide/main.js` calls. + +Severity: low. Memory that stays `??` is in practice unreadable, but a slow read looks the same. + + + +--- + +## A negative number in the Memory pane's box gives rows whose addresses jump + +**Describe the bug** +When the expression in the Memory pane's box gives a negative number, the pane starts at that negative address. The first row is labelled with it masked to 32 bits, and the rows after it count up from the negative number: with `-8`, the rows read `&HFFFFFFF8`, `&H00000008`, `&H00000018`. It was observed in an IDE started with an `APPDATA` of its own. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run a project whose `Sub Main` has: + ``` + Dim neg As Long + neg = -8 + Stop + ``` +2. At the `Stop`, choose View > MEMORY 1, type `neg` in the box and press Enter. +3. The first row's address reads `&HFFFFFFF8`, the second `&H00000008` and the third `&H00000018`. Every cell shows `??`. + +**Expected behavior** +The box turned red, since a negative number is not an address; or the number read as the unsigned address `&HFFFFFFF8`, with each row's label and reads following from it. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The base address is kept as a signed `BigInt`; only each row's label is masked. + +Severity: low. Nothing at a negative address can be read. + + + +--- + +## Shift+F10 opens the code editor's context menu at the top-left corner of the IDE window, not at the caret + +**Describe the bug** +Shift+F10 (`tbEditor_ShowContextMenu`) opens the code editor's context menu at the top-left corner of the IDE window, over the menu bar, wherever the caret is. A right-click opens the same menu at the pointer. It was observed by pressing the key in an IDE started with an `APPDATA` of its own and reading where the menu opened. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open any project and a module, and put the caret on a line in the middle of the code editor. +2. Press Shift+F10. The menu that starts with **Go To Definition** opens with its top-left corner at the top-left corner of the window. + +**Expected behavior** +The menu at the caret, as Shift+F10 opens a context menu elsewhere in Windows and in the Monaco editor the code editor is built on. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The command calls `showContextMenuMonaco({clientX: 0, clientY: 0})` (`ide/main.js`). + +Severity: low. The menu works; it opens far from where the reader is looking. + + + +--- + +## An editor tab opened after the compiler has reported its file is not tinted for the file's warnings or errors + +**Describe the bug** +A file that has errors or warnings has its row in the Project Explorer tinted, and its editor tab too, but the tab is tinted only when the compiler reports the file while the tab is open. A tab opened on a file the compiler has already reported --- the usual case, since the whole project is compiled when it opens --- stays untinted until the compiler checks the file again, after an edit to it. The Project Explorer row is tinted either way. It was observed in an IDE started with an `APPDATA` of its own, by reading the classes of the row and the tab. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project with a module that has a warning, such as: + ``` + Module Helpers + Public Sub Narrowing() + Dim narrow As Integer + Dim wide As Long + wide = 5 + narrow = wide + End Sub + End Module + ``` + with TB0018 set to a warning in the project's settings (`"project.warnings": {"warnings": ["TB0018"]}`), and do not open the module. Its row in the Project Explorer is tinted. +2. Double-click the row. The module's tab opens untinted, and stays so. +3. Type a space at the end of a line, or type it and undo it. The compiler reports the file again, and the tab is tinted. + +**Expected behavior** +The tab tinted as soon as it opens, as the Project Explorer row is. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +`syncFileDiagnosticsToExplorer` (`ide/main.js`) sets the tab's class only when the file has an open tab at the time; opening a tab does not read the file's cached counts. + +Severity: low. The Project Explorer and the Diagnostics panel show the problems. + + + +--- + +## The Variables panel's edit box cuts a String at an apostrophe, and leaving it sets the variable to the empty string + +**Describe the bug** +Double-clicking a String variable in the VARIABLES panel opens an edit box holding its value, but only up to the first apostrophe: for `"it's"` the box holds `"it`. Leaving the box --- Enter, or a click on another row --- commits its text even when nothing was typed, and the variable becomes the empty string. Nothing shows that the value was changed. It was observed in an IDE started with an `APPDATA` of its own, reading the row and `? s` in the DEBUG CONSOLE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Stop at a breakpoint in a procedure with `Dim s As String`. +2. In the DEBUG CONSOLE, run `s = "it's"`. The VARIABLES panel shows `"it's"`. +3. Double-click the `s` row. The edit box holds `"it`. +4. Press Enter without typing. The row shows `""`, and `? s` prints an empty line. +5. Set `s = "it's"` again, double-click the row and click another row. The same. + +**Expected behavior** +The box holding the whole value, `"it's"`, and leaving it with nothing typed changing nothing. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +`onVariableDblClick` (`ide/main.js`) builds the box as HTML, `""`, so the apostrophe ends the attribute; the box's `onblur` sends `setVariable` with whatever the box holds. A value with double quotes (`"say ""hi"""`) is shown and kept correctly. Escape leaves the variable as it was. + +Severity: medium. A variable's value is changed with nothing typed and nothing shown, and the program then runs on with it. + + + +--- + +## Hovering over a Sub call while paused runs the Sub + +**Describe the bug** +While a program is paused, holding the pointer over a word of the code evaluates the expression under it and shows the result. Over the name of a **Sub** in a call, such as `Bump` in `t.Bump`, the evaluation calls the Sub: its code runs, with all its effects, and the hover shows `t.Bump: 0`. Each hover runs it again. It was observed in an IDE started with an `APPDATA` of its own, with `? t.Value` in the DEBUG CONSOLE before and after the hover. + +**To Reproduce** +Steps to reproduce the behavior: +1. A project with a class: + ``` + Class Thing + Public Value As Long + Public Sub Bump() + Value = Value + 1 + End Sub + End Class + ``` + and a procedure that holds `Dim t As New Thing`, `t.Value = 5`, `t.Bump` and a line after it. +2. Stop at a breakpoint on the line after `t.Bump`. In the DEBUG CONSOLE, `? t.Value` prints `6`. +3. Hold the pointer over `Bump` in `t.Bump` until the hover shows. It reads `t.Bump: 0`. +4. `? t.Value` prints `7`. + +**Expected behavior** +A hover that does not run a Sub: it has no value to show, so no hover, or the compiler's description of the Sub. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The hover sends `evaluate` with `context: "hover"` and the expression `t.Bump`. A hover also runs a Function or a Property Get to show its value (`t.Doubled`, `Level1(n)`), which is how the hover is meant to work; for a Sub nothing is shown that could justify running it. Hovering over a variable, such as `Value` in `t.Value`, shows `t.Value: 6` and changes nothing. + +Severity: medium. The program's state changes with nothing shown, from moving the mouse. + + + +--- + +## An edit at the run-time error panel prints Aborting, and then the rest of the program runs + +**Describe the bug** +An edit to the code while a program is paused ends the run, since Edit and Continue is not supported: at a breakpoint the DEBUG CONSOLE prints `[EDIT & CONTINUE] not yet supported. Aborting.` and `aborted`, and nothing more runs. At the run-time error panel the same edit prints the same `Aborting.` line, but then only the failing procedure ends: its callers go on, and the rest of the program runs. It was observed in an IDE started with an `APPDATA` of its own, reading the DEBUG CONSOLE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run a project with: + ``` + Module Startup + Public Sub Main() + Fails + Debug.Print "main end" + End Sub + + Public Sub Fails() + Err.Raise 5, , "Fails with A & B" + Debug.Print "after raise" + End Sub + End Module + ``` +2. The run stops at the error panel on the `Err.Raise` line. +3. Type a letter anywhere in the code editor. The DEBUG CONSOLE prints `[EDIT & CONTINUE] not yet supported. Aborting.`, then `main end`, then `(time taken: ...)`. + +**Expected behavior** +The run aborted, as the message says and as the same edit at a breakpoint does: `aborted`, and no `main end`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +What runs is what the panel's **Stop** button runs: it ends only the failing procedure, which #2441 reports. Main runs on with code that the edit has since changed in the editor but not in the program. + +Severity: medium. Code runs after the reader was told the run is aborted --- files written, a database changed --- with nothing to warn of it. + + + +--- + +## After an edit in the Variables panel, a Double or Date row shows a wrong value until the next refresh + +**Describe the bug** +A **Double** or **Date** variable edited in the VARIABLES panel gets the value typed, but its row shows another value: `0` for a Double, `#12:00:00 AM#` for a Date. The row stays wrong until the panel is next refreshed, by a line in the DEBUG CONSOLE or the next stop. A **Long** edited the same way shows its new value at once. It was observed in an IDE started with an `APPDATA` of its own, reading the row two seconds after the edit and `? d` in the DEBUG CONSOLE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Stop at a breakpoint in a procedure with `Dim d As Double = 2.5` and `Dim dt As Date`. +2. In the VARIABLES panel, double-click `d`, type `7.25` and press Enter. The row shows `0`. +3. In the DEBUG CONSOLE, run `? d`. It prints `7.25`, and the row now shows `7.25`. +4. Double-click `dt`, type `#1/2/2020#` and press Enter. The row shows `#12:00:00 AM#` until `? dt`, which prints `1/2/2020`. + +**Expected behavior** +The row showing the value the variable now holds, as it does for a Long. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The row is redrawn from the answer to the `setVariable` request (`body.value`), so that answer holds `0` and `#12:00:00 AM#`. With `-1.5E+20` the row showed `2.19923962666636E-315`, which suggests the answer is read from the wrong type. Currency, Boolean, Variant and enum rows show the edit at once. + +Severity: medium. The variable is right; the panel shows a wrong value with nothing to say so, and a reader who checks the edit there concludes it failed. + + + +--- + +## After a stop at a breakpoint, Step Out does not stop in the procedure's caller + +**Describe the bug** +When a run has stopped at a breakpoint inside a procedure, no step stops in a caller of that procedure. Step Out runs on to the next breakpoint, error or the end of the program; F8 off the procedure's `End Sub` stops only at the next procedure the caller calls. Steps inside the procedure, and into procedures it calls and back, work. When the procedure was entered by F8 from its caller, Step Out stops in the caller as it should. It was observed in an IDE started with an `APPDATA` of its own, reading the line the debugger stopped on and the DEBUG CONSOLE. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run a project with: + ``` + Module Startup + Public Sub Main() + Direct + Debug.Print "m2" + Other + End Sub + + Public Sub Direct() + Debug.Print "in direct" + End Sub + + Public Sub Other() + Debug.Print "other" + End Sub + End Module + ``` + with a breakpoint on `Debug.Print "in direct"`. +2. The run stops there. Press Ctrl+Shift+F8 (Step Out). The run does not stop in `Main` on `Debug.Print "m2"`: `m2` is printed and the run goes on to its end. +3. Run again to the breakpoint, and press F8 twice, to `End Sub` and off it. The next stop is `Other`'s `Sub` line, after `m2` is printed. + +**Expected behavior** +A stop in `Main` on `Debug.Print "m2"`, the line after the call, as Step Out gives when `Direct` was entered by F8 from a breakpoint in `Main`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The same for every caller tried: `Main` and another Sub, with the call made directly, inside `Select Case`, `If` or `For`, and a Function call in an assignment. + +Severity: low. The program runs its own code; the pause the reader asked for is lost. + + + +--- + +## A compile error at F5 is reported as a codegen error to report upstream, at the caller's line + +**Describe the bug** +With a compile error in a procedure that `Main` calls, F5 does not say that the project has a compile error, or where. It stops on `Main`'s `Sub` line with a panel headed **ERROR OCCURRED** that reads `COMPILATION (CODEGEN) ERROR AT LINE #7. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S`, where line 7 is `Main`'s call to the procedure, and the DEBUG CONSOLE prints `[LINKER] compilation (codegen) error detected in 'Startup.Main' at line #7`. It was observed in an IDE started with an `APPDATA` of its own. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a project whose `Sub Main` calls `Direct` on line 7. +2. Add the line `x = = 1` to `Direct`. The editor marks it, and the DIAGNOSTICS panel lists it. +3. Press F5. The DEBUG CONSOLE prints `Executing 'Main'...` and the `[LINKER]` line above; the editor stops on `Main`'s `Sub` line with the panel above. Nothing of `Main` runs. + +**Expected behavior** +A message that the project has compile errors, naming the line with the error, or the DIAGNOSTICS panel shown; not a request to report a compiler defect, and not a line that holds no error. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +With the error in `Main` itself there is no panel; the DEBUG CONSOLE prints `[LINKER] compilation (codegen) error detected in '{temp_component}.{temp_procedure}' at line #1` and `(compile error: codegen error; check for compilation errors)`. + +Severity: low. Nothing runs, and the DIAGNOSTICS panel lists the real error. + + + +--- + +## F6 does not find the procedure when the caret is right of the column where the procedure's End line ends, or on that End line + +**Describe the bug** +F6 (Run Or Preview) runs the procedure the caret is in. On a line inside the procedure, it finds the procedure only when the caret is left of the column where the procedure's `End Sub` or `End Function` line ends; from that column on, and anywhere on the `End` line itself, the DEBUG CONSOLE prints `RunCodeUnderCursor: failed to identify the procedure at the cursor position.` and nothing runs. With the usual indentation, the caret in the word `Debug` of a `Debug.Print` line is already too far right in a Sub. It was observed in an IDE started with an `APPDATA` of its own, pressing F6 at several columns of each line. + +**To Reproduce** +Steps to reproduce the behavior: +1. Open a module with: + ``` + Module Startup + Public Sub Direct() + Debug.Print "in direct" + End Sub + + Public Function Answer() As Long + Answer = 42 + End Function + End Module + ``` +2. Put the caret on the `Debug.Print` line at column 11 and press F6: `Direct` runs. At column 12, where the `End Sub` line ends, F6 prints the message. +3. On the `Answer = 42` line, column 16 runs `Answer`, and column 17, where `End Function` ends, prints the message. +4. On the `End Sub` line, F6 at any column prints the message. + +**Expected behavior** +F6 running the procedure from any column of any of its lines, its `End` line included. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The IDE sends `runCodeUnderCursor` with the line and column, and the message is the compiler's answer. The procedure's own `Sub` line works at every column tried, right of that limit included. A trailing comment on the `End Sub` line moves the limit to where the comment starts. The check looks like a range test that compares the column with the end of the range on every line. + +Severity: low. Nothing runs, and the message says so. + + + +--- + +## Search Online on the run-time error panel does not encode the error text, so an & in it cuts the search short + +**Describe the bug** +The run-time error panel's **Search Online** button opens a Google search for the error's header and description, but puts the text into the URL as it is. An `&` in the description ends the `q` parameter, so the search loses the rest of the text, and a `"` would end the quoted URL in the command line. It was observed in an IDE started with an `APPDATA` of its own, by recording the command line the button passes to the IDE host in place of running it; no browser was opened. + +**To Reproduce** +Steps to reproduce the behavior: +1. Run a project whose `Sub Main` executes `Err.Raise 5, , "Fails with A & B"`. +2. At the error panel, click **Search Online**. +3. The IDE runs `cmd.exe /c start "link" "https://www.google.com/search?q=twinBASIC VB6 Run-time error 5 (00000005) Fails with A & B"`, so the query the search receives ends at the `&`: `twinBASIC VB6 Run-time error 5 (00000005) Fails with A`. + +**Expected behavior** +The query URL-encoded (`encodeURIComponent`), so that the search is for the whole text: `...q=twinBASIC%20VB6%20Run-time%20error%205%20(00000005)%20Fails%20with%20A%20%26%20B`. + +**Desktop:** + - OS: Windows 10 Pro 22H2 (build 19045) + - twinBASIC compiler version: BETA 1005 + +**Additional context** +The button's handler (`showErrorWidget`, `ide/main.js`) is `root.Shell('cmd.exe /c start "link" "https://www.google.com/search?q=' + e + '"')`, with `e` the header and description joined by spaces. + +Severity: low. The search still opens, on part of the text. + + diff --git a/WIP.IDE.md b/WIP.IDE.md new file mode 100644 index 000000000..1d46753e5 --- /dev/null +++ b/WIP.IDE.md @@ -0,0 +1,211 @@ +# 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 +covers the structure, the conventions, where each fact comes from, and how a chapter is +written. Owner decisions, 2026-10-11: + +- task chapters plus a reference part; +- permalinks flattened, with redirects; +- the how-to material in `Features/Compiler-IDE` moved here; +- `docs/IDE/_Images/*.af` deleted. + +The section is not bound to the structure it had. The existing pages are raw material. + +## What "book-ready" means for a page + +- No heading without text under it, no TODO, and no list of labels copied off a screenshot. + Every menu item, button and option has at least a sentence saying what it does. +- Every claim about behaviour has been checked against BETA 1005 or newer: read from `main.js`, + or tried in an IDE on a private desktop. A screenshot or an older page is not a source. +- The prose carries the information. Alt text describes the picture and holds no fact the + prose lacks; a long alt text that lists labels is a sign the prose is missing. +- The conventions below are followed, and the page is in the IDE part of `_book.yml`. + +## Structure + +**Part A, Working in the IDE.** Each chapter is one page, or a short landing page with a few +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 | +| 3 | Projects and files | `/tB/IDE/Projects` | Project Explorer (file and object views, Add, context menu, resource editors), 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` | +| 5 | Designing forms and reports | `/tB/IDE/Forms`, `/tB/IDE/Reports` | tbForm, Toolbox, Properties, the Format menu's operations, tbReport | +| 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 | + +**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 now. 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. + +**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 +`Features/`, `Reference/`, `Tutorials/`, `LLVM/`, `Miscellaneous/FAQs.md`, `Documentation/` +and `docs/index.md`. An anchor another page links to keeps its heading text. +`redirect_from` cannot rescue an anchor, so check these: + +- `Project-Settings#…`; +- `Window#language`, from `FAQs.md`. + +Where a merged page's pictures live does not change: `shoot_docs.mjs` writes them by path +(`out`), so leave the files in `docs/IDE/Images/` and `Menu/Images/` and change only the links. + +**Features/Compiler-IDE** keeps a short "what's new compared to VB6" page that links into +the chapters. The how-to sections of `IDE-Features.md`, `Debugging.md` and `CodeLens.md` move +here, and those pages get `redirect_from` only if they are removed outright. + +**A landing page with children has a trailing slash** (`/tB/IDE/Customising/`, as +`/tB/IDE/AddIns/`). Without the slash, the link check resolves `Customising#anchor` to the +child folder. + +**Language names in a script the font subsets lack** (Cyrillic, Devanagari, Malayalam, CJK) are +written in English only. The subsets cover Latin and Greek; see `scripts/build_fonts.py`. + +**Nav order.** The chapters take `nav_order` 1--10 in the order of the table above. +`IDE Reference` has 99. The nav sorts numbered pages before unnumbered ones, so until a pane +page is absorbed it sorts after the Reference. + +**The book.** `_book.yml`'s IDE part becomes Part A's chapters followed by Part B. A page leaves +`left_out:` when it is finished, not before. + +## Conventions + +These apply across `docs/IDE/`. The first three also apply to any page that names the IDE's +controls or keys, and `docs/Documentation/Authoring.md` (*Formatting conventions*) states them +for contributors. + +- **UI names** in bold, as the IDE spells them: **Project Explorer**, **Break On All Errors**. +- **Menu paths** in bold with an arrow: **File → Export Project**. +- **Keys**: `Ctrl+Shift+F`, with key names in mixed case and no + spaces around the `+`. Every key is written by name: `Enter`, `F5`, + `Break`, and arrows as `Up`, `Down`, `Left` and + `Right`. Glyphs are avoided, because the font subsets need not hold them. The 59 existing `CTRL` + are converted in one mechanical pass. +- **Never re-list a picture's labels** (owner). The screenshots are SVGs whose text is + accessible, so a bare list of a menu's or dialog's items (label and keys, nothing else) only + repeats the picture. A reference entry says what the item does. An item with nothing to say + beyond its label is left to the picture. +- **Headings** in sentence case, with no icon and no shortcut inside them. +- **The section names no build**, except in a defect callout (owner's standing rule). Lines such + as "Components list coming soon" go, unless the IDE itself shows that text and the page says + so. +- **Defects the twinBASIC developers already know of** (owner) are not queued, but a page that + describes the feature still carries the callout rule 6 calls for: + - Rename Symbol (F2) renames nothing; + - accepting a function from the completion list by typing `(` doubles the parenthesis + (`Area(()`). +- Callouts follow WIP.md rule 6. A twinBASIC defect found while writing goes into + `BUGS-TO-REPORT.md` with a reproducer, never into a page alone. + +## Where the facts come from + +- **`\ide\main.js`** (one line, about 1.3 MB). Search it with a Node script and + bounded regexes, never with `grep`, which takes minutes on one line. Useful landmarks, by + name, since offsets move between builds: + - `ideCommands`: every command id, such as `tbEditor_ClipboardPasteAsComment`, and its default + keys; + - `ideOptionsSchema`: the IDE Options, one flat list of about 30 options; + - `rootMenu…offsetHeight` blocks: the menus; + - `runningProcessesContainer`: the running programs, shown as **PROCESS** boxes in the + status bar (not in the Run menu); + - `createDropDowns`: the editor's three combo boxes; + - `showDesignerContextMenu`: the form designer's context menu; + - `tbFormDesigner_MenuEditor*`: the menu editor; + - `tbProblems_*`: Diagnostics; + - `reloadThemesFromDisk`: `.theme` files; + - `statusErrorsDiv` and `statusMemUsageDiv`: the status bar. +- **The running IDE**, for behaviour the code does not show plainly. Use `Lane` from + `scripts/lib/tb-lane.mjs` on a private desktop, as `shoot_docs.mjs` setups do. Never use the + owner's IDE or the real `APPDATA`. +- **`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/beta995-probes/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, + 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. +3. **Pictures.** New `shoot_docs` shots for what the chapter shows and no picture shows yet. Use + an Opus agent, then `--svg`. +4. **Writing.** A Sonnet agent writes the pages from the fact sheet only, nothing from memory. +5. **Review.** Read the diff against the fact sheet, then run `build.bat && check.bat`. Then move + the pages into the book part, run `book.bat`, and check the pages there. + +**Order**, by what readers need most: + +1. Customising (IDE Options) +2. Writing code +3. Running and debugging +4. Forms and reports +5. Projects and files +6. Building +7. Starting a project +8. Troubleshooting +9. Reference +10. The tour + +The restructure comes first, in its own commit, before any chapter is written. It moves only +the pages whose place in the new structure is settled: + +- Project Settings; +- the menus, the Toolbar and the Status Bar, into Part B. + +It also sets up the nav and the book parts. + +A page that a chapter will absorb keeps its old URL until that chapter is written. The chapter +then takes the old URL into its `redirect_from:`. That way nothing moves twice. + +## What the IDE has that no page covers yet + +From the survey of BETA 1005. Each chapter's fact sheet confirms the items in its own chapter. + +- **IDE Options:** all of them except Tab Size and the LLVM options. Examples are Intellisense + mode, the Handles/Implements syntax, Show Hidden Members, advanced hover, sticky scrolling, + Restore Editors State, the fonts, and the real OS title bar. +- **Code editor:** + - the component, scope and procedure combo boxes, which also insert event handlers; + - Rename Symbol, Go To Definition and Last Position; + - bookmarks; + - the right-click menu and the tab context menu; + - Paste As Comment; + - Monaco's command palette and multi-cursor commands; + - automatic backups. +- **Finding:** Find In Project, the Quick Find widget, Go To Line/Column. +- **Form designer:** the menu editor, control arrays, locking, tab-index overlay, grid and zoom, + "Launch the form in isolation", Select Parent, the in-place toolbar, the context menu, and the + Toolbox's search, names toggle and "more components". +- **Properties:** search, the system-colour picker, ActiveX property pages. +- **Report designer:** everything beyond the list of bands and tools. +- **Running:** + - the status bar's **PROCESS** boxes for running programs (Terminate, Bring To Front); + - **Run Or Preview** (F6); + - setting a breakpoint in the gutter, and whether breakpoints persist; + - Step Out (on the toolbar, not on the Debug menu). +- **Diagnostics:** the four filter toggles and the context menu. +- **Debug Console:** the copy commands in its context menu. +- **Project Explorer:** the file context menu (Copy Path, View As JSON, View As Markdown Preview, + Export…), the object view, search, and the empty headings for each Add item. +- **Packages:** the Available Packages tab, Import from file, the link and overwrite prompts, and + the "package needed" prompts for WinNativeForms and CustomControls. +- **Status bar:** the error, warning, hint and info counters, and the memory figure. +- **Themes:** custom `.theme` files in a `themes` folder. +- **Webpage:** mobile mode, and back, forward and reload. +- **Troubleshooting:** the DevTools command (`tbIdeDevTools_Show`, which has no menu item). +- The **View → Object Browser** item does nothing; say so. diff --git a/WIP.Screenshots.md b/WIP.Screenshots.md index 6cf91ff99..bfe74897a 100644 --- a/WIP.Screenshots.md +++ b/WIP.Screenshots.md @@ -181,9 +181,9 @@ Exit codes: 0 every picture written or unchanged, 1 a picture failed, 2 the tool page's `currentDPI` to the 2x ratio, or the IDE's 5 s DPI check covers every designer with RESYNC), and `forms` (the same without the add-in), `community` (the demo, its compiler restarted on an empty licence key in the page) and `splash` (the demo, for the splash - alone), `programs`, `codelens`, `package` and `fusion` (pictures of a running program), + 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`, `customcontrols`, `code` and `sample9` (all described + Chrome, not an IDE), `featuremap`, `code-chapter`, `debugging`, `customcontrols`, `code` and `sample9` (all described below). A setup is `{name, start, prepare}`. Every add-in a setup loads has its `SaveSetting` key snapshotted, emptied and restored (`tbDocsHelp`, `GlobalSearchAddIn`). @@ -249,6 +249,51 @@ 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 "Writing code" chapter + +Setup `code-chapter` (three parts): FeatureTour, the feature map's Inventory project, with +`test/shots/codechapter/` staged onto it (`Helpers`, `Ev`/`Ev2`, `Watcher`) and the project's +warnings set so that TB0016 is a hint and TB0012 information. Its 20 pictures are under +`docs/IDE/Code/Images/`, plus `Menu_Edit` and `Menu_View`, now taken with the code focused. + +- **Each picture starts from the default layout and its own tabs** (`chapterTabs`), and closes + Monaco's find widget first: the editor is one for every file, so a widget left open shows over + the next file's code. +- **The minimap is left out of every crop but Editor_Panel**: it is drawn a little differently + according to what the editor showed before, so a crop with it changes with the shots' order. +- **A tab is tinted only when the compiler sends the file's diagnostics while it is open**, not + when it opens on a file already reported: Tabs_States edits Inventory and undoes it. +- Edits are made in the page and undone (`editedThenUndone`) or discarded with their tab; the + clipboard is the tool's own text (`FAKE_CLIPBOARD`), never the host's. + +## The "Running and debugging" chapter + +Setup `debugging` (three parts): the code-chapter project with `test/shots/debugging/` +staged onto it as well, a module `Checks` with the chapter's `FillTable` (a Subscript error on +its fourth pass), `PingLater` (`Shell "ping -n 20 127.0.0.1", vbHide`) and `WaitAWhile` (a +`DoEvents` loop of a minute at most). It is a setup of its own because the code chapter's +combo boxes list the project's modules. Its 16 pictures are under `docs/IDE/Debugging/Images/`, +plus `Menu_Debug` (paused, the code focused) and `Menu_Run` (WaitAWhile running). + +- **Each picture starts from nothing** (`debugReset`: the program stopped, no breakpoint, no + watch, no tab, the default layout, an empty DEBUG CONSOLE) and leaves the IDE so. Most stop + `Main` in `Inventory.Total` at the loop line (`pausedInTotal`, first or second pass); + `pausedInMain` stops it on Main's last line. +- **What changes from run to run is set in the page before the capture**: an object's + `{object: N}` (`OBJECT_ADDRESS`), the Variables tooltip's `Pointer` (`POINTER_TEXT`), the + PROCESS box's id (`PROCESS_ID`), and the console's times and `time taken` (`fixConsoleTimes`). +- **The ping ends with the run**: the box's own Terminate, else `killTree` on the id the console + printed (`endPing`). +- A tree row is expanded with a click on its plus box, made again until the flip holds + (`toggleRow`); the Variables edit box opens with a double-click made again the same way. +- The Variables tooltip is taken with the panel docked (a floating panel is drawn over it), once + it has the class `visibleTooltip` and its fade has ended: before that it is laid out, hidden, + at another place. +- The DEBUG CONSOLE is never cut out: a cut-out shows the line it measures text with. Its menu + is opened inside the panel instead. +- A line for the console is put in its input row whole and entered (`consoleRun`): a space typed + while the row's completion list is open accepts the list's first entry. + ## Special IDE states - **`Services_Unavailable`**: the badge in the `no-project` setup. With no project the badge's @@ -353,6 +398,13 @@ the page once real ones are shown, as History's times are. line times (the page's clock for some lines, the compiler's for `Debug.Print`) and the time taken are set in the page once shown. `Scale factor` prints what the lane's compiler reads, 1.50 on this machine. +- **`Memory_String`** (setup `memory`, `test/shots/memory`, MemoryDemo): Sub Main run with + Start to its `Stop`, the MEMORY 1 pane floated and sized to two rows, `s` typed into its box, + which the IDE turns into `StrPtr(s)`; the fifteen characters and their terminating null fill + the two rows, so no other memory shows. The string's address changes from run to run and is + set in the page once the rows are read (`MEMORY_ADDRESSES`). The box can already read + `StrPtr(s)` from the theme before, so the shot waits for the cell at the address to take the + focus, which happens only once the rows are drawn for the new answer, and then blurs it. - **`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 diff --git a/WIP.md b/WIP.md index b33505f9c..13a44040d 100644 --- a/WIP.md +++ b/WIP.md @@ -12,6 +12,7 @@ one covering what you are about to change. | About to | Read first | |---|---| | write or edit any page under `docs/` | [WIP.Authoring.md](WIP.Authoring.md) --- page template, frontmatter, cross-section linking tables, per-symbol workflow | +| write or restructure a page under `docs/IDE/` | [WIP.IDE.md](WIP.IDE.md) --- the plan for finishing the IDE section: its structure, conventions, fact sources and chapter workflow | | document a specific package | that package's own file, listed under [Package API notes](#package-api-notes) | | run or change the twinBASIC compiler harness | [WIP.Harness.md](WIP.Harness.md) --- `export`, the attribute census, `tbbuild`, `tbrun`, the add-in test runner | | change `builder/`, `scripts/`, or any gate | [WIP.Build.md](WIP.Build.md) --- the pipeline and why each gate exists | diff --git a/bugs/compile-error-reported-as-codegen/repro.json b/bugs/compile-error-reported-as-codegen/repro.json new file mode 100644 index 000000000..bfe37872b --- /dev/null +++ b/bugs/compile-error-reported-as-codegen/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "a compile error in a procedure Main calls is reported at F5 as a codegen error to report, at Main's call" + ], + "steps": "1. A Sub Main whose line 7 calls Direct. Add the line x = = 1 to Direct; the editor marks it as an error.\n2. Press F5. The DEBUG CONSOLE prints [LINKER] compilation (codegen) error detected in 'Startup.Main' at line #7, and the run stops on Main's Sub line with a panel headed ERROR OCCURRED: COMPILATION (CODEGEN) ERROR AT LINE #7. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S. Nothing of Main runs." +} diff --git a/bugs/definition-line-after/repro.json b/bugs/definition-line-after/repro.json new file mode 100644 index 000000000..4ba307207 --- /dev/null +++ b/bugs/definition-line-after/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "Go To Definition on a function in another module puts the caret on the line after its declaration", + "Ctrl+Down puts the caret on the line after the next procedure's declaration, and Ctrl+Up on the line after its own procedure's" + ], + "steps": "1. In one module, call a function declared in another: total = Helpers.Twice(3), where Helpers.twin declares Public Function Twice on line 5.\n2. Put the caret on Twice and press F12 (or Shift+F2). Helpers.twin opens with the caret on line 6, column 5, the first line of the body. Go To Definition on a local variable puts the caret on its Dim line.\n3. In a procedure, press Ctrl+Down: the caret goes to the first line of the next procedure's body, column 1. Ctrl+Up from inside a procedure puts it on the first line of that procedure's body." +} diff --git a/bugs/diagnostics-copy-html/repro.json b/bugs/diagnostics-copy-html/repro.json new file mode 100644 index 000000000..a7f4f3479 --- /dev/null +++ b/bugs/diagnostics-copy-html/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "the Diagnostics panel's Copy puts the code in a , and Copy All labels the info {WARNING}" + ], + "steps": "1. Open a project with a warning and an info, such as test/ide/probes/code-editor, whose Helpers.twin holds Public Const BigValue As Long = &H8000 (TB0012, an info in its settings) and a narrowing assignment (TB0018, a warning).\n2. In the DIAGNOSTICS panel, right-click the TB0018 row and choose Copy. The text copied is {WARNING} /CodeEditorProbe/Sources/Helpers.twin [11,9]: TB0018implicit narrowing conversion from Long to Integer. ...\n3. Show the infos (the Info counter on the status bar) and choose Copy All. Each row's code is in and , and the info TB0012 is labelled {WARNING}." +} diff --git a/bugs/edit-at-error-panel-runs-on/repro.json b/bugs/edit-at-error-panel-runs-on/repro.json new file mode 100644 index 000000000..20394d4c4 --- /dev/null +++ b/bugs/edit-at-error-panel-runs-on/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "an edit at the error panel prints Aborting, and then the rest of Main runs" + ], + "steps": "1. A Sub Main that calls Fails, then prints main end; Fails raises an error with Err.Raise and then prints after raise.\n2. Press F5. The run stops at the error panel on the Err.Raise line.\n3. Type a letter anywhere in the code editor. The DEBUG CONSOLE prints [EDIT & CONTINUE] not yet supported. Aborting., then main end: the rest of Fails is skipped, and the rest of Main runs.\nControl: the same edit at a breakpoint prints Aborting. and aborted, and nothing more of the program runs." +} diff --git a/bugs/escape-keeps-cursors/repro.json b/bugs/escape-keeps-cursors/repro.json new file mode 100644 index 000000000..537f754c2 --- /dev/null +++ b/bugs/escape-keeps-cursors/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "Escape leaves the two cursors that Ctrl+Alt+Down added" + ], + "steps": "1. Put the caret on a line of code and press Ctrl+Alt+Down twice: there are three cursors.\n2. Press Escape, once or twice. The three cursors remain. A click in the code goes back to one cursor." +} diff --git a/bugs/f6-procedure-not-found/repro.json b/bugs/f6-procedure-not-found/repro.json new file mode 100644 index 000000000..a1de159a4 --- /dev/null +++ b/bugs/f6-procedure-not-found/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "F6 on a line of a procedure, at or right of the column where its End line ends, does not find the procedure", + "F6 on an End Sub line does not find the procedure, at any column" + ], + "steps": "1. Open a module with:\n Public Sub Direct()\n Debug.Print \"in direct\"\n End Sub\n2. Put the caret on the Debug.Print line at column 12 (in the word Debug), and press F6. The DEBUG CONSOLE prints RunCodeUnderCursor: failed to identify the procedure at the cursor position. and nothing runs.\n3. The same at column 11 runs Direct. Column 12 is where the End Sub line ends; in a Function whose End Function ends at column 17, columns up to 16 run it and 17 does not.\n4. On the End Sub line, F6 at any column prints the same message." +} diff --git a/bugs/find-regex-alert-reason/repro.json b/bugs/find-regex-alert-reason/repro.json new file mode 100644 index 000000000..525b4297f --- /dev/null +++ b/bugs/find-regex-alert-reason/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-find", + "tests": [ + "the alert for an invalid regular expression, /(/, reads only Invalid RegExp:" + ], + "steps": "1. Press Ctrl+F, tick Regular Expressions and type /(/ in Find What.\n2. Click Find Next. The alert reads Invalid RegExp: and nothing after the colon." +} diff --git a/bugs/find-regex-capital-letter/repro.json b/bugs/find-regex-capital-letter/repro.json new file mode 100644 index 000000000..c0e3793cc --- /dev/null +++ b/bugs/find-regex-capital-letter/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-find", + "tests": [ + "a regular expression with a capital letter, /End Sub/, finds nothing" + ], + "steps": "1. Open a module holding End Sub, press Ctrl+F and tick Regular Expressions, which clears Match Case.\n2. Type /End Sub/ in Find What and click Find Next: The specified region has been searched.\n3. /end sub/ and /End Sub/i find End Sub. With Match Case clear the IDE searches the text in lower case, and leaves the expression as it was typed." +} diff --git a/bugs/find-regex-line-break/repro.json b/bugs/find-regex-line-break/repro.json new file mode 100644 index 000000000..e8509e444 --- /dev/null +++ b/bugs/find-regex-line-break/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-find", + "tests": [ + "a match of /^\\s+end sub$/, which spans a line break, is an empty selection, and Find Next stays on it" + ], + "steps": "1. Open a module in which an indented line comes before an End Sub. Press Ctrl+F, tick Regular Expressions and type /^\\s+end sub$/ in Find What.\n2. Put the caret at line 1, column 1 and click Find Next: the selection is empty, at the end of the line before End Sub.\n3. Click Find Next again: the selection is at the same place, and stays there however often it is clicked." +} diff --git a/bugs/find-scope-stuck-selected-text/repro.json b/bugs/find-scope-stuck-selected-text/repro.json new file mode 100644 index 000000000..71295edf0 --- /dev/null +++ b/bugs/find-scope-stuck-selected-text/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-find", + "tests": [ + "after a search in Selected Text and a click in the code, Selected Text stays ticked but greyed, and Find Next stops at once" + ], + "steps": "1. Open a module, select three lines that hold the word total and press Ctrl+F: Selected Text is ticked. Type total in Find What and click Find Next: total is found in the selection.\n2. Click in the code on a line outside the selection. Selected Text stays ticked but is greyed, and Current File is not ticked.\n3. Click Find Next. The IDE asks at once: End of search scope has been reached; do you want to continue from the beginning?" +} diff --git a/bugs/find-whole-word-file-start/repro.json b/bugs/find-whole-word-file-start/repro.json new file mode 100644 index 000000000..10dd55134 --- /dev/null +++ b/bugs/find-whole-word-file-start/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-find", + "tests": [ + "with Whole Word Only, Find Next never finds Module at line 1, column 1" + ], + "steps": "1. Open a project with a module whose first line is Module Finder, so that the word Module stands at line 1, column 1 (test/ide/probes/code-find/Sources/Finder.twin).\n2. Put the caret on line 2 and press Ctrl+F. Type Module in Find What and tick Whole Word Only; Current File and Direction All are as Ctrl+F sets them.\n3. Click Find Next: the Module of End Module is found. Click Find Next again: The specified region has been searched. Line 1, column 1 is never found.\n4. With Whole Word Only cleared, the second press finds line 1, column 1." +} diff --git a/bugs/format-selection-continuation/repro.json b/bugs/format-selection-continuation/repro.json new file mode 100644 index 000000000..27ade8e4c --- /dev/null +++ b/bugs/format-selection-continuation/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "Format Selection on a selection that starts on a continuation line opens an IDE startup failure alert, and the IDE then closes", + "with unsaved changes, the same Format Selection changes nothing, and the IDE asks to save them before it closes" + ], + "steps": "1. Open the project by double-clicking its .twinproj, so that the IDE starts with the project on its command line. In a procedure, write these lines without indents: Dim a As Long, _ / b As Long / Select Case a / Case 1 / a = 2 / End Select. Save.\n2. Select from the start of b As Long to the end of the next line, and choose Edit > Format Selection.\n3. An alert reads IDE startup failure (BETA twinBASIC IDE BETA 1005): JS Promise Rejection(1): Assignment to constant variable. Nothing is formatted. After OK the IDE closes.\n4. With unsaved changes, the IDE asks You've made changes to this project. Save now? (Save, Discard Changes, Cancel) before closing; Cancel keeps it open.\n5. Started from its icon, with the project opened from the New / Open dialog, the IDE prints the same error in the DEBUG CONSOLE and stays open; nothing is formatted either." +} diff --git a/bugs/hover-runs-sub/repro.json b/bugs/hover-runs-sub/repro.json new file mode 100644 index 000000000..07f31fb72 --- /dev/null +++ b/bugs/hover-runs-sub/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "hovering over a Sub call while paused runs the Sub" + ], + "steps": "1. A class Thing with Public Value As Long and Public Sub Bump(): Value = Value + 1.\n2. Stop at a breakpoint in a procedure that holds Dim t As New Thing and a line t.Bump.\n3. In the DEBUG CONSOLE, ? t.Value prints a number, such as 6.\n4. Hold the pointer over Bump in t.Bump. The hover reads t.Bump: 0.\n5. ? t.Value prints 7: the hover ran Bump.\nControl: hovering over Value in t.Value shows t.Value: 6 and changes nothing." +} diff --git a/bugs/keyboard-group-bar-name/repro.json b/bugs/keyboard-group-bar-name/repro.json new file mode 100644 index 000000000..1bb135af4 --- /dev/null +++ b/bugs/keyboard-group-bar-name/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:customising", + "tests": [ + "a group named A|B switches B on with it, and toggling B twice switches both off" + ], + "steps": "It changes the user's own keyboard-shortcut groups, saved in HKCU\\Software\\VB and VBA Program Settings\\twinBASIC_IDE\\IDESettings (CUSTOM_KEYBOARD_SHORTCUT_GROUPS and ACTIVE_KEYBOARD_SHORTCUT_GROUPS). Export that key first with reg export, and import it back afterwards.\n1. Window > Keyboard Shortcuts > Manage Keyboard Shortcuts... Add New..., type A|B, Enter, Save Changes.\n2. The same again with the name B. Window > Keyboard Shortcuts now shows Custom 1. A|B and Custom 2. B, both ticked; the active list is |A|B||B|.\n3. Click Custom 2. B. Expected: B is unticked and A|B stays ticked. Actual: B stays ticked; the IDE removed the first |B| it found, the one inside |A|B|, and the list is |A|B|.\n4. Click Custom 2. B again: A|B and B are both unticked, though only B was clicked, and the saved list is |A, which names no group." +} diff --git a/bugs/memory-address-64bit/repro.json b/bugs/memory-address-64bit/repro.json new file mode 100644 index 000000000..56822375a --- /dev/null +++ b/bugs/memory-address-64bit/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:memory", + "tests": [ + "in a 64-bit run, VarPtr(n) shows n's bytes, and the Address column only the low 32 bits of its address" + ], + "steps": "1. Switch a project to win64 with the toolbar's architecture box. Its Sub Main has Dim n As Long: n = 1234: Debug.Print Hex(VarPtr(n)): Stop.\n2. Run it to the Stop. The DEBUG CONSOLE shows an address above 4 GB, such as 4232CFF438.\n3. View > MEMORY 1, type VarPtr(n) in the box and press Enter. The first row shows n's bytes, D2 04 00 00, but its address reads &H32CFF438: the high 32 bits are gone." +} diff --git a/bugs/memory-bigint-throw/repro.json b/bugs/memory-bigint-throw/repro.json new file mode 100644 index 000000000..57a74ac89 --- /dev/null +++ b/bugs/memory-bigint-throw/repro.json @@ -0,0 +1,10 @@ +{ + "mode": "lane", + "lane": "ide:memory", + "tests": [ + "the name d throws \"Cannot convert 1.5 to a BigInt\", and leaves the box unmarked and the rows as they were", + "the name flag throws \"Cannot convert True to a BigInt\", and leaves the box unmarked and the rows as they were", + "the name udt throws \"Cannot convert {user defined type, 8 bytes} to a BigInt\", and leaves the box unmarked and the rows as they were" + ], + "steps": "1. Run a project whose Sub Main has Dim d As Double: d = 1.5: Stop, and wait for the Stop.\n2. View > MEMORY 1, type d in the box and press Enter.\n3. The DEBUG CONSOLE prints IDE internal error: Uncaught SyntaxError: Cannot convert 1.5 to a BigInt. The box is not turned red and the rows do not change. The same for a Boolean (True) and a variable of a user-defined type ({user defined type, 8 bytes}).\nControl: a name the debugger cannot evaluate turns the box red." +} diff --git a/bugs/memory-negative-address/repro.json b/bugs/memory-negative-address/repro.json new file mode 100644 index 000000000..7b33cdb92 --- /dev/null +++ b/bugs/memory-negative-address/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:memory", + "tests": [ + "neg, which is -8, gives a first row at &HFFFFFFF8 and a second at &H00000008" + ], + "steps": "1. Run a project whose Sub Main has Dim neg As Long: neg = -8: Stop, and wait for the Stop.\n2. View > MEMORY 1, type neg in the box and press Enter.\n3. The first row's address reads &HFFFFFFF8, the second &H00000008 and the third &H00000018." +} diff --git a/bugs/memory-unreadable-loading/repro.json b/bugs/memory-unreadable-loading/repro.json new file mode 100644 index 000000000..3f512774d --- /dev/null +++ b/bugs/memory-unreadable-loading/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:memory", + "tests": [ + "0 shows ?? in every value cell, as cells still being read, and never as unreadable memory" + ], + "steps": "1. Run any project to a Stop.\n2. View > MEMORY 1, type 0 in the box and press Enter.\n3. Every value cell shows ?? and every text cell a middle dot, the placeholders of a cell still being read, and they stay so. The pane's style for unreadable memory, blank grey cells, is never used." +} diff --git a/bugs/outline-no-members/repro.json b/bugs/outline-no-members/repro.json new file mode 100644 index 000000000..5ab0b8841 --- /dev/null +++ b/bugs/outline-no-members/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "the Outline lists the module Editing and none of its procedures" + ], + "steps": "1. Open any project and a module that holds procedures, and show the OUTLINE panel.\n2. The panel shows one row, the module's name, with no arrow, and none of the procedures. The symbols the compiler sends for the file have no children.\n3. The filters of the panel's ... menu (Show Procedures, Show Variables and the rest) change nothing." +} diff --git a/bugs/search-online-unencoded/repro.json b/bugs/search-online-unencoded/repro.json new file mode 100644 index 000000000..27b2a0da1 --- /dev/null +++ b/bugs/search-online-unencoded/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "Search Online passes the error's text into the search URL unencoded, so an & in it ends the query" + ], + "steps": "1. Run a program that executes Err.Raise 5, , \"Fails with A & B\".\n2. At the error panel, click Search Online.\n3. The IDE runs cmd.exe /c start \"link\" \"https://www.google.com/search?q=twinBASIC VB6 Run-time error 5 (00000005) Fails with A & B\": the text is not URL-encoded, so the browser's query ends at the &, and searches for twinBASIC VB6 Run-time error 5 (00000005) Fails with A.\nThe lane records the command line in place of root.Shell and opens no browser." +} diff --git a/bugs/shift-f10-menu-corner/repro.json b/bugs/shift-f10-menu-corner/repro.json new file mode 100644 index 000000000..021bddc3b --- /dev/null +++ b/bugs/shift-f10-menu-corner/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "Shift+F10 opens the editor's menu at the top-left corner of the IDE window, not at the caret" + ], + "steps": "1. Open any project and a module, and put the caret on a line in the middle of the code editor.\n2. Press Shift+F10. The editor's context menu (Go To Definition, Last Position, ...) opens at the top-left corner of the IDE window, over the menu bar, not at the caret.\nControl: a right-click in the code opens the same menu at the pointer." +} diff --git a/bugs/step-out-after-breakpoint/repro.json b/bugs/step-out-after-breakpoint/repro.json new file mode 100644 index 000000000..6ef58c302 --- /dev/null +++ b/bugs/step-out-after-breakpoint/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "after a stop at a breakpoint in a procedure, Step Out does not stop in its caller: the run goes on", + "after a stop at a breakpoint in a procedure, F8 off its End Sub does not stop in its caller, but in the next procedure the caller calls" + ], + "steps": "1. A Sub Main that calls Direct, then prints m2, then calls more procedures; Direct holds one Debug.Print.\n2. Put a breakpoint on Direct's Debug.Print line and press F5. The run stops there.\n3. Step Out (Ctrl+Shift+F8). The run does not stop in Main on the line after the call: m2 is printed and the run goes on to the next breakpoint, error or the end.\n4. Again, but press F8 twice, to End Sub and off it: the next stop is the Sub line of the next procedure Main calls, after m2.\nControl: a breakpoint on Main's call to Direct, F8 into Direct, then Step Out stops in Main on the m2 line." +} diff --git a/bugs/tab-tint-opened-later/repro.json b/bugs/tab-tint-opened-later/repro.json new file mode 100644 index 000000000..5fbe9536b --- /dev/null +++ b/bugs/tab-tint-opened-later/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:code-editor", + "tests": [ + "a tab opened on a file the compiler has already reported stays untinted, while its Project Explorer row is tinted" + ], + "steps": "1. Open a project with a module that has a warning, and do not open the module: its row in the Project Explorer is tinted.\n2. Double-click the row. The module's tab opens untinted, and stays so.\n3. Make any edit to the module, or make one and undo it. The compiler reports the file again, and the tab is now tinted.\nControl: step 3 is the lane's control test." +} diff --git a/bugs/theme-inherit-loop/repro.json b/bugs/theme-inherit-loop/repro.json new file mode 100644 index 000000000..7ae592837 --- /dev/null +++ b/bugs/theme-inherit-loop/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:customising", + "tests": [ + "two themes that inherit each other freeze the page, and choosing one fails with a stack overflow" + ], + "steps": "1. Start the twinBASIC IDE. In %APPDATA%\\twinBASIC\\themes write LoopA.theme with the lines inherits: LoopB; and MenuBackColor: rgb(1, 1, 1); and LoopB.theme with inherits: LoopA; and MenuBackColor: rgb(2, 2, 2);\n2. Choose Window > Theme > Reload from disk. The IDE stops responding for about seven seconds.\n3. Open Window > Theme. LoopB is listed as LoopB (undefined).\n4. Choose LoopA. Nothing on screen changes: the IDE's page raises an uncaught RangeError: Maximum call stack size exceeded, which nothing on screen reports, and the previous theme's colours stay.\n5. Remove the loop from the files and restart the IDE to recover." +} diff --git a/bugs/theme-missing-semicolon/repro.json b/bugs/theme-missing-semicolon/repro.json new file mode 100644 index 000000000..bca20bde1 --- /dev/null +++ b/bugs/theme-missing-semicolon/repro.json @@ -0,0 +1,13 @@ +{ + "mode": "probe", + "probe": ["scripts/probe_theme_semicolon.mjs"], + "expect": { + "exit": 1, + "output": [ + "^themes/Dark\\.theme line \\d+: dropped DiagnosticsGroupFontSize and DiagnosticsGroupFontWeight ", + "^themes/Light\\.theme line \\d+: dropped DiagnosticsGroupFontSize and DiagnosticsGroupFontWeight ", + "^[1-9]\\d* propert(y is|ies are) lost in [1-9]\\d* of \\d+ shipped themes \\(BETA \\d+\\)$" + ] + }, + "steps": "1. Open themes\\Dark.theme, in the folder of the twinBASIC installation, in a text editor. Line 63 is DiagnosticsGroupFontSize: 90% and ends without the semicolon that ends every other property. Line 64 is DiagnosticsGroupFontWeight: 500;\n2. Open themes\\Light.theme. Its lines 63 and 64 are the same.\n3. Open ide\\main.js and find applyThemeData, which loads a theme file. It removes the /* */ comments, cuts the text at each ';' followed by a space, a tab or a line end, removes the line breaks from each piece, cuts each piece at each ': ' and ':', and keeps a piece only if that gives exactly two parts. Lines 63 and 64 are one piece with three parts, so the piece is dropped and the loader writes no message.\nscripts/probe_theme_semicolon.mjs applies that rule to every .theme file in the themes folder of the installation (the newest twinBASIC_IDE_BETA_ folder on the Desktop, or --ide ) and prints one line for each piece it drops, then a summary. It starts no IDE. On BETA 1005, 997 and 957 it prints the same two lines, DiagnosticsGroupFontSize and DiagnosticsGroupFontWeight lost in Dark.theme and in Light.theme." +} diff --git a/bugs/theme-override-breaks-inheritance/repro.json b/bugs/theme-override-breaks-inheritance/repro.json new file mode 100644 index 000000000..e5b31f554 --- /dev/null +++ b/bugs/theme-override-breaks-inheritance/repro.json @@ -0,0 +1,8 @@ +{ + "mode": "lane", + "lane": "ide:customising", + "tests": [ + "a Dark.theme in the APPDATA themes folder unlinks every custom theme, and Reload from disk stops applying" + ], + "steps": "1. Close the twinBASIC IDE. In %APPDATA%\\twinBASIC\\themes, which the IDE creates, write AtStart.theme with the two lines inherits: light; and MenuBackColor: rgb(255, 0, 0); and Dark.theme with any text (its contents are ignored).\n2. Start the IDE and cancel the New / Open dialog. Open Window > Theme. Expected: AtStart (Light) and Classic (Light). Actual: AtStart (undefined) and Classic (undefined).\n3. Choose AtStart. Expected: the light base with a red menu bar. Actual: the dark base with a red menu bar.\n4. Change the colour in AtStart.theme to rgb(0, 128, 0) and choose Window > Theme > Reload from disk. Expected: the menu bar turns green. Actual: it stays red.\n5. Delete Dark.theme and restart the IDE: AtStart is listed as AtStart (Light) again, and Reload from disk applies an edit." +} diff --git a/bugs/variables-edit-apostrophe/repro.json b/bugs/variables-edit-apostrophe/repro.json new file mode 100644 index 000000000..924c37a81 --- /dev/null +++ b/bugs/variables-edit-apostrophe/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "the Variables edit box holds a String only up to an apostrophe, and Enter sets the variable to the empty string", + "a click away from the Variables edit box, with nothing typed, sets a String with an apostrophe to the empty string" + ], + "steps": "1. Stop at a breakpoint in a procedure with Dim s As String.\n2. In the DEBUG CONSOLE, run s = \"it's\". The VARIABLES panel shows \"it's\".\n3. Double-click the s row. The edit box holds \"it --- the text up to the apostrophe.\n4. Press Enter, or click another row, with nothing typed. The row shows \"\", and ? s prints an empty line.\nControl: Escape leaves s as it was." +} diff --git a/bugs/variables-edit-stale-display/repro.json b/bugs/variables-edit-stale-display/repro.json new file mode 100644 index 000000000..3528c83ae --- /dev/null +++ b/bugs/variables-edit-stale-display/repro.json @@ -0,0 +1,9 @@ +{ + "mode": "lane", + "lane": "ide:debugging", + "tests": [ + "a Double edited in the Variables panel shows 0 until the next refresh, while the variable holds the value", + "a Date edited in the Variables panel shows #12:00:00 AM# until the next refresh, while the variable holds the value" + ], + "steps": "1. Stop at a breakpoint in a procedure with Dim d As Double = 2.5 and Dim dt As Date.\n2. In the VARIABLES panel, double-click d, type 7.25 and press Enter. The row shows 0, and still does seconds later.\n3. In the DEBUG CONSOLE, run ? d. It prints 7.25, and the row now shows 7.25.\n4. The same for dt with #1/2/2020#: the row shows #12:00:00 AM# until the next console line.\nControl: a Long edited the same way shows its new value at once." +} diff --git a/builder/page-baseline.json b/builder/page-baseline.json index 641d7b811..72fa8f6e7 100644 --- a/builder/page-baseline.json +++ b/builder/page-baseline.json @@ -1,5 +1,5 @@ { "src": "docs", - "pages": 1131, - "staticFiles": 794 + "pages": 1137, + "staticFiles": 933 } diff --git a/docs/Documentation/Authoring.md b/docs/Documentation/Authoring.md index 7119e30eb..0a1527d77 100644 --- a/docs/Documentation/Authoring.md +++ b/docs/Documentation/Authoring.md @@ -367,6 +367,7 @@ about that sentence, not about the site. - twinBASIC code goes in a ` ```tb ` fenced block. Shiki highlights it with the vendored twinBASIC grammar; `twinbasic`, `vb` and `vba` select the same grammar. The other highlighted fence languages are `js`, `yaml`, `json`, `c`, `html`, `xml`, `sql` and `batch`. Anything else renders as plain text, and the build prints `highlight: unknown fence language ""` without failing, so check the build output rather than the page. - Parameter lists use the definition-list pattern (a term line, then a `: definition` line beneath it), not a markdown table. - **An inline code span cannot start or end with a single space.** CommonMark removes one space from each end of a span whose content is not all spaces, so `` ` 1 2 ` `` renders as `1 2`. That matters when the padding *is* the value shown --- what `Debug.Print` emits into its 14-column print zones, a fixed-width return such as [Partition](../tB/Modules/Interaction/Partition)'s. Write two spaces at each end to get one, and confirm against the built HTML rather than the preview. +- **The IDE's own words** are written as the IDE spells them, in bold: **Project Explorer**, **Break On All Errors**. A menu path is bold with an arrow, **File → Export Project**. A key is written `Ctrl+Shift+F`: each key in mixed case and by name, with no spaces around the `+`. Arrows are `Up`, `Down`, `Left` and `Right`, never a glyph. - For dashes, write `--` in the source (it renders as an en-dash) or `---` (an em-dash). Never paste a literal `–` or `—`. The build does not reject one, so a stray dash ships silently. `scripts/convert_em_dash_separators.mjs` is the normaliser, and it is run by hand. ### A code sample that holds a fence marker diff --git a/docs/Documentation/Images/Tools-Groups.dot b/docs/Documentation/Images/Tools-Groups.dot index 40912e10b..1ef5b79c0 100644 --- a/docs/Documentation/Images/Tools-Groups.dot +++ b/docs/Documentation/Images/Tools-Groups.dot @@ -162,13 +162,12 @@ digraph tools_groups { i1 [label="addin_test"] i2 [label="ide_test"] i3 [label="try_help_addin"] i4 [label="shoot_docs"] i5 [label="imagestrip"] i6 [label="probe_ide_image_labels"] - i7 [label="svgshot/replay"] - ip [style=invis label=""] + i7 [label="svgshot/replay"] i8 [label="probe_theme_semicolon"] i1 -> i3 -> i5 -> i7 - i2 -> i4 -> i6 -> ip + i2 -> i4 -> i6 -> i8 { rank=same; i3 i4 } { rank=same; i5 i6 } - { rank=same; i7 ip } + { rank=same; i7 i8 } } subgraph cluster_defects { margin=12 @@ -369,6 +368,8 @@ digraph tools_groups { tooltip="Lists the images in the style sheets of the IDE that are labelled with a media type their bytes do not have."] i7 [URL="Tools-IDE#replay" tooltip="Draws the SVG of each picture again from its saved bundle, with the converter as it is now. Starts no IDE and takes no picture."] + i8 [URL="Tools-IDE#probe-theme-semicolon" + tooltip="Lists the properties that the IDE silently drops when it loads the theme files it ships, because a property lacks its semicolon."] d1 [URL="Tools-Defects#bug-repro" tooltip="Builds, runs and verifies the reproducer projects for the entries of BUGS-TO-REPORT.md, one folder for each entry."] diff --git a/docs/Documentation/Images/Tools-Groups.svg b/docs/Documentation/Images/Tools-Groups.svg index e0e70c14c..82c5cece7 100644 --- a/docs/Documentation/Images/Tools-Groups.svg +++ b/docs/Documentation/Images/Tools-Groups.svg @@ -1,47 +1,47 @@ - + tools_groups cluster_wrappers - -Batch wrappers + +Batch wrappers cluster_build - -Build and Generation + +Build and Generation cluster_site - -Site Checks + +Site Checks cluster_compiler - -Compiler Harness + +Compiler Harness cluster_tests - -Toolchain Tests + +Toolchain Tests @@ -56,7 +56,7 @@ cluster_ide - + IDE Automation @@ -64,8 +64,8 @@ cluster_defects - -Defect Reproducers + +Defect Reproducers @@ -73,8 +73,8 @@ w1 - -build.bat + +build.bat @@ -82,8 +82,8 @@ w2 - -serve.bat + +serve.bat @@ -91,8 +91,8 @@ w3 - -check.bat + +check.bat @@ -100,8 +100,8 @@ w4 - -test.bat + +test.bat @@ -109,8 +109,8 @@ w5 - -book.bat + +book.bat @@ -118,8 +118,8 @@ w6 - -examples.bat + +examples.bat @@ -127,8 +127,8 @@ w7 - -addin-test.bat + +addin-test.bat @@ -136,8 +136,8 @@ w8 - -ide-test.bat + +ide-test.bat @@ -145,8 +145,8 @@ w9 - -try-help-addin.bat + +try-help-addin.bat @@ -155,8 +155,8 @@ b1 - -tbdocs + +tbdocs @@ -164,8 +164,8 @@ b2 - -render-book + +render-book @@ -173,8 +173,8 @@ b3 - -compare_trees + +compare_trees @@ -182,8 +182,8 @@ b4 - -survey_tooling + +survey_tooling @@ -191,8 +191,8 @@ b5 - -convert_em_dash_separators + +convert_em_dash_separators @@ -200,8 +200,8 @@ b6 - -build_fonts.py + +build_fonts.py @@ -209,8 +209,8 @@ b7 - -build_dot_metrics + +build_dot_metrics @@ -218,8 +218,8 @@ b8 - -twin-index + +twin-index @@ -227,8 +227,8 @@ s1 - -check_links + +check_links @@ -236,8 +236,8 @@ s2 - -crawl_check + +crawl_check @@ -245,8 +245,8 @@ s3 - -check_links_diff + +check_links_diff @@ -254,8 +254,8 @@ s4 - -check_tree_fresh + +check_tree_fresh @@ -263,8 +263,8 @@ s5 - -check_dot_fit + +check_dot_fit @@ -272,8 +272,8 @@ s6 - -check_a11y + +check_a11y @@ -281,8 +281,8 @@ s7 - -pick_a11y_sample + +pick_a11y_sample @@ -290,8 +290,8 @@ s8 - -axe-scan + +axe-scan @@ -299,8 +299,8 @@ s9 - -check_a11y_fingerprint + +check_a11y_fingerprint @@ -308,8 +308,8 @@ s10 - -sweep_a11y + +sweep_a11y @@ -317,8 +317,8 @@ c1 - -tbbuild + +tbbuild @@ -326,8 +326,8 @@ c2 - -tbrun + +tbrun @@ -335,8 +335,8 @@ c3 - -impexp.mjs, impexp.py + +impexp.mjs, impexp.py @@ -344,8 +344,8 @@ c4 - -check_tb_registry + +check_tb_registry @@ -353,8 +353,8 @@ c5 - -probe_shared_temp + +probe_shared_temp @@ -362,8 +362,8 @@ c6 - -probe_build_twice + +probe_build_twice @@ -373,8 +373,8 @@ t1 - -check_publish_policy + +check_publish_policy @@ -382,8 +382,8 @@ t2 - -check_regex_safety + +check_regex_safety @@ -391,8 +391,8 @@ t3 - -check_code_regions + +check_code_regions @@ -400,8 +400,8 @@ t4 - -check_gate_lists + +check_gate_lists @@ -409,8 +409,8 @@ t5 - -check_ci_workflows + +check_ci_workflows @@ -418,8 +418,8 @@ t6 - -check_lint + +check_lint @@ -427,8 +427,8 @@ t7 - -search.test + +search.test @@ -436,8 +436,8 @@ t8 - -render.test + +render.test @@ -445,8 +445,8 @@ t9 - -strftime.test + +strftime.test @@ -454,8 +454,8 @@ t10 - -png.test + +png.test @@ -463,8 +463,8 @@ t11 - -compact-image.test + +compact-image.test @@ -472,8 +472,8 @@ t12 - -svgshot.test + +svgshot.test @@ -481,8 +481,8 @@ t13 - -example-batches.test + +example-batches.test @@ -490,8 +490,8 @@ t14 - -ports.test + +ports.test @@ -499,8 +499,8 @@ t15 - -addin-project.test + +addin-project.test @@ -508,8 +508,8 @@ t16 - -check_page_baseline + +check_page_baseline @@ -517,8 +517,8 @@ t17 - -check_book_coverage + +check_book_coverage @@ -526,8 +526,8 @@ t18 - -check_symbol_index + +check_symbol_index @@ -535,8 +535,8 @@ t19 - -check_twin_parsers + +check_twin_parsers @@ -544,8 +544,8 @@ t20 - -check_attribute_sweep + +check_attribute_sweep @@ -553,8 +553,8 @@ t21 - -check_cli + +check_cli @@ -562,8 +562,8 @@ t22 - -check_pdf_shims_equiv + +check_pdf_shims_equiv @@ -571,8 +571,8 @@ t23 - -check_impexp_parity + +check_impexp_parity @@ -580,8 +580,8 @@ t24 - -check_axe_patch_equiv + +check_axe_patch_equiv @@ -589,8 +589,8 @@ t25 - -indexer.test + +indexer.test @@ -635,8 +635,8 @@ i1 - -addin_test + +addin_test @@ -644,8 +644,8 @@ i2 - -ide_test + +ide_test @@ -653,8 +653,8 @@ i3 - -try_help_addin + +try_help_addin @@ -662,8 +662,8 @@ i4 - -shoot_docs + +shoot_docs @@ -671,8 +671,8 @@ i5 - -imagestrip + +imagestrip @@ -680,8 +680,8 @@ i6 - -probe_ide_image_labels + +probe_ide_image_labels @@ -694,13 +694,21 @@ - + + +i8 + + +probe_theme_semicolon + + + d1 - -bug_repro + +bug_repro @@ -708,8 +716,8 @@ d2 - -vb6run + +vb6run @@ -779,7 +787,7 @@ - + diff --git a/docs/Documentation/Tools-Compiler.md b/docs/Documentation/Tools-Compiler.md index 53fa5c881..ccaeb2457 100644 --- a/docs/Documentation/Tools-Compiler.md +++ b/docs/Documentation/Tools-Compiler.md @@ -74,7 +74,7 @@ Exit codes: **0** the project compiled without errors; **1** the project has err [--llvm | --compiler-options S] [--exe | --tests] [--allow-name-clash] Builds a probe project and captures what it writes to the IDE's -[Debug Console](../../tB/IDE/Project/DebugConsole). Where [`tbbuild.mjs`](#tbbuild) answers +[Debug Console](../../tB/IDE/Debugging/Debug-Console). Where [`tbbuild.mjs`](#tbbuild) answers *does this compile*, this answers *what does this print*. It takes an **exported source tree** (the folder holding `Sources/` and `Settings`), not a diff --git a/docs/Documentation/Tools-IDE.md b/docs/Documentation/Tools-IDE.md index ec90b76cb..1a8b9f265 100644 --- a/docs/Documentation/Tools-IDE.md +++ b/docs/Documentation/Tools-IDE.md @@ -31,6 +31,23 @@ The output is one line for each mislabelled image, ` line : labelled ] + +Lists the properties that the twinBASIC IDE silently drops when it loads the theme files it ships. It is the check behind the `BUGS-TO-REPORT.md` entry whose reproducer is `bugs/theme-missing-semicolon/`: in BETA 1005, line 63 of `Dark.theme` and of `Light.theme` is `DiagnosticsGroupFontSize: 90%` with no `;`, so it runs into the next line, and the loader loses both `DiagnosticsGroupFontSize` and `DiagnosticsGroupFontWeight`. It starts no IDE and no program and writes nothing, but it needs an unpacked twinBASIC install. It is outside every gate and CI. + +It reads every `.theme` file in the install's `themes` folder and cuts each one as `applyThemeData` in the IDE's `ide\main.js` does. The loader removes the `/* */` comments, cuts the text at each `;` followed by a space, a tab or a line end, removes the line breaks from each piece, cuts each piece at each `: ` and `:` followed by a tab, and keeps the piece only if that gives exactly two parts. Any other piece is dropped without a message. + +| Flag | Effect | +|---|---| +| `--ide ` | Path to `twinBASIC.exe`, found as for `tbbuild`. The `.theme` files read are those in the `themes` folder beside it. | + +The output is one line for each dropped piece that holds text, ` line : dropped `, with the file relative to the install and the line where the piece starts. A piece made of several properties run together is shown as their names. The last line is a summary, ` properties are lost in of shipped themes (BETA )`, or `no shipped theme loses a property (BETA )`. + +Exit codes: **0** every piece of every shipped theme loads; **1** at least one is dropped; **2** a refused command line, no IDE, no `themes` folder or no `.theme` file in it, or a crash. + ## addin_test.mjs {: #addin-test } diff --git a/docs/Documentation/Tools.md b/docs/Documentation/Tools.md index 010eec136..741034ed9 100644 --- a/docs/Documentation/Tools.md +++ b/docs/Documentation/Tools.md @@ -17,7 +17,7 @@ Reference for every executable in the documentation repository. This page covers ## Tool groups -![Eight rounded boxes, one per group of tools, each holding a smaller rounded box for every tool in the group. Batch wrappers holds the nine .bat files at the repository root. Build and Generation holds tbdocs, render-book, compare_trees and the generators of committed files. Site Checks holds the link, freshness, diagram and accessibility checks. Toolchain Tests, the largest group, holds the gates test.bat runs. Compiler Harness holds tbbuild, tbrun, impexp and the probes of the harness itself. Code Samples and Attributes holds check_examples and the three attribute tools. IDE Automation holds the add-in and IDE test runners, try_help_addin, shoot_docs, imagestrip, probe_ide_image_labels and the SVG replay. Defect Reproducers holds bug_repro and vb6run. Each group's name links to its page, and each tool's box to its entry.](Images/Tools-Groups.svg) +![Eight rounded boxes, one per group of tools, each holding a smaller rounded box for every tool in the group. Batch wrappers holds the nine .bat files at the repository root. Build and Generation holds tbdocs, render-book, compare_trees and the generators of committed files. Site Checks holds the link, freshness, diagram and accessibility checks. Toolchain Tests, the largest group, holds the gates test.bat runs. Compiler Harness holds tbbuild, tbrun, impexp and the probes of the harness itself. Code Samples and Attributes holds check_examples and the three attribute tools. IDE Automation holds the add-in and IDE test runners, try_help_addin, shoot_docs, imagestrip, probe_ide_image_labels, probe_theme_semicolon and the SVG replay. Defect Reproducers holds bug_repro and vb6run. Each group's name links to its page, and each tool's box to its entry.](Images/Tools-Groups.svg) - [Build and Generation Tools](Tools-Build) -- `tbdocs`, the PDF render driver, the build comparison, and the generators of committed files. - [Site Checks](Tools-Site-Checks) -- the link, freshness, diagram and accessibility checks over a built site. diff --git a/docs/Features/64bit.md b/docs/Features/64bit.md index a4f0093a5..9aa18abaa 100644 --- a/docs/Features/64bit.md +++ b/docs/Features/64bit.md @@ -14,9 +14,9 @@ Using the [Fusion](Fusion) feature, it is also possible to use both 32bit and 64 ## Building a 64-bit Version -The [build configuration](../tB/IDE/Project/Toolbar#build-configuration) box on the toolbar chooses between a 32-bit and a 64-bit build: **win32** or **win64**. CTRL + F1 switches to **win64**, and CTRL + F2 switches back to **win32**. The IDE remembers the choice for each project. +The [build configuration](../tB/IDE/Reference/Toolbar#build-configuration) box on the toolbar chooses between a 32-bit and a 64-bit build: **win32** or **win64**. Ctrl+F1 switches to **win64**, and Ctrl+F2 switches back to **win32**. The IDE remembers the choice for each project. -The two builds write different files. The default [Build Output Path](../tB/IDE/Project/Settings#build-output-path) ends in `${ProjectName}_${Architecture}.${FileExtension}`, and `${Architecture}` is `win32` or `win64`. So a Standard DLL project named `MathGreetLib` builds `Build\MathGreetLib_win32.dll` in one mode and `Build\MathGreetLib_win64.dll` in the other. +The two builds write different files. The default [Build Output Path](../tB/IDE/Project-Settings#build-output-path) ends in `${ProjectName}_${Architecture}.${FileExtension}`, and `${Architecture}` is `win32` or `win64`. So a Standard DLL project named `MathGreetLib` builds `Build\MathGreetLib_win32.dll` in one mode and `Build\MathGreetLib_win64.dll` in the other. ## Example Syntax diff --git a/docs/Features/Advanced/Multithreading.md b/docs/Features/Advanced/Multithreading.md index 6679ba64d..9d7533fe8 100644 --- a/docs/Features/Advanced/Multithreading.md +++ b/docs/Features/Advanced/Multithreading.md @@ -123,7 +123,7 @@ Module Threads End Module ``` -Run from the IDE, in a 32-bit or a 64-bit build, **RunBoth** prints to the [Debug Console](../../tB/IDE/Project/DebugConsole): +Run from the IDE, in a 32-bit or a 64-bit build, **RunBoth** prints to the [Debug Console](../../tB/IDE/Debugging/Debug-Console): ```text Primes below 200,000: 17984 diff --git a/docs/Features/Compiler-IDE/CodeLens.md b/docs/Features/Compiler-IDE/CodeLens.md deleted file mode 100644 index 16c1c121a..000000000 --- a/docs/Features/Compiler-IDE/CodeLens.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: CodeLens -parent: Compiler and IDE Features -nav_order: 3 -permalink: /Features/Compiler-IDE/CodeLens ---- - -# Run Subs from the IDE - -The CodeLens feature allows running Subs and Functions, with no arguments and in modules (but not classes/Forms/UserControls) right from the editor without starting the full program. It has full access to your code; it can access constants, call other functions both intrinsic and user-defined, call APIs, and print to the Debug Console. - -Methods eligible to run with CodeLens (when enabled), have a bar above them that you can click to run: - -![The code editor with a run MyModule.CheckScale CodeLens bar above the CheckScale sub, and its output in the DEBUG CONSOLE below](../Images/351d0147-cad3-4e16-89e5-0a9e43496740.png){:width="640" height="669"} - -### Example - -A no-argument `Public Sub` in a module is eligible for CodeLens: - -```tb check_build -Public Sub RunTest() - Debug.Print "Hello from CodeLens" -End Sub -``` diff --git a/docs/Features/Compiler-IDE/Debugging.md b/docs/Features/Compiler-IDE/Debugging.md index 24dcc2d35..b8e6fc8dc 100644 --- a/docs/Features/Compiler-IDE/Debugging.md +++ b/docs/Features/Compiler-IDE/Debugging.md @@ -7,31 +7,15 @@ permalink: /Features/Compiler-IDE/Debugging # Debugging Features -twinBASIC includes several features to help with debugging. Breakpoints, stepping through code, and what the IDE shows when a run-time error stops a program are on the [Debug menu](../../tB/IDE/Project/Menu/Debug#when-a-run-time-error-stops-the-program) page. +twinBASIC adds three debugging features to those of VB6: a trace logger that also works in a compiled executable, the **Debug.Cls** statement, and detection of stale pointers. Breakpoints, stepping, the panel that opens at a run-time error and the Variables, Watches, Call Stack and Debug Console panels are described in [Running and debugging](../../tB/IDE/Debugging/). ## Debug Trace Logger -New to the debugging experience is a trace logging feature that automatically creates detailed logs to either the debug console or a file. Messages can be output with [**Debug.TracePrint**](../../tB/Modules/Debug#traceprint). The logger works both when running from the IDE and in compiled executables. - -![The Project Settings dialog scrolled to the Trace Flags row, a column of eleven unticked boxes from Trace Procedure Entry and Exit points to Trace RaiseEvent calls and arguments, and to the Trace Output row, an empty text box above a description of the ${SESSIONID}, ${DATE}, ${TIME} and ${DEBUG} placeholders.](../Images/4fc2bf99-2bec-4943-837d-21038d791574.png){:width="760" height="531"} - -```tb check_build -Public Sub ProcessOrder(ByVal orderId As Long) - Debug.TracePrint "ProcessOrder called, orderId=" & CStr(orderId) - ' ... processing ... -End Sub -``` +A trace logger creates detailed logs, to the debug console or to a file. Messages can be output with [**Debug.TracePrint**](../../tB/Modules/Debug#traceprint). The logger works both when running from the IDE and in compiled executables. [The trace logger](../../tB/IDE/Debugging/Debug-Console#the-trace-logger) explains the trace settings and shows an example. ## Clearing the Debug Console -[**Debug.Cls**](../../tB/Modules/Debug#cls) empties the [Debug Console](../../tB/IDE/Project/DebugConsole) from code, the same as its *Clear Debug Console* button. Calling it as a run begins separates that run's output from the previous one, which is the difference between reading a log and scrolling for where it started: - -```tb check_build -Private Sub Form_Load() - Debug.Cls - Debug.Print "--- run starting ---" -End Sub -``` +[**Debug.Cls**](../../tB/Modules/Debug#cls) empties the [Debug Console](../../tB/IDE/Debugging/Debug-Console) from code, the same as its *Clear Debug Console* button. Calling it as a run begins [separates that run's output from the previous one](../../tB/IDE/Debugging/Debug-Console#separate-one-run-from-the-next). ## Stale/Dangling Pointer Detection @@ -39,4 +23,4 @@ Bugs result from using Strings and Variants after they have been freed. It may n Below shows an example where the ListView ColumnHeader text had been set by previously-freed string and detected by this feature: -![A ListView whose column header text has been replaced by rows of warning symbols, marking the use of a freed string](../Images/021f6cbf-acce-445d-ade7-3fcad0af4927.png){:width="461" height="151"} \ No newline at end of file +![A ListView whose column header text has been replaced by rows of warning symbols, marking the use of a freed string](../Images/021f6cbf-acce-445d-ade7-3fcad0af4927.png){:width="461" height="151"} diff --git a/docs/Features/Compiler-IDE/IDE-Features.md b/docs/Features/Compiler-IDE/IDE-Features.md index 211821c81..3483af3eb 100644 --- a/docs/Features/Compiler-IDE/IDE-Features.md +++ b/docs/Features/Compiler-IDE/IDE-Features.md @@ -12,7 +12,7 @@ While the twinBASIC IDE still has a lot of work planned, it already includes a n ## Theme System {: index="dark mode" } -Fully theme-able, with Dark (default), Light, and Classic (Light) built in, and an easy inheritance-based system to add your own themes via CSS files. +Fully theme-able, with Dark (default), Light, and Classic (Light) built in. A theme is a short `.theme` text file of `Name: value;` pairs, which can inherit from another theme, so adding your own takes one small file. [Themes](../../tB/IDE/Customising/Themes) explains how. ## Code Navigation and Structure @@ -23,11 +23,11 @@ Fully theme-able, with Dark (default), Light, and Classic (Light) built in, and ## Editing Features -- **Fully customizable keyboard shortcuts** covering all commands, with ability to save and switch between different sets. -- **Auto-indent on paste**. -- **Paste as comment**. -- **Inline code hints**, which provide annotations at the end of blocks for what the block is (see picture). -- **Color-matching** for parentheses and brackets. +- **Fully customizable keyboard shortcuts** covering all commands. You keep your changes in groups that you switch on and off, and each group is layered on top of the built-in keys. See [Keyboard shortcuts](../../tB/IDE/Customising/Keyboard-Shortcuts). +- **Auto-indent on paste**, and **paste as comment**. See [Copy, cut and paste](../../tB/IDE/Code/Editor#copy-cut-and-paste). +- **Inline code hints**, which provide annotations at the end of blocks for what the block is (see picture). See [Inline code hints](../../tB/IDE/Code/IntelliSense#inline-code-hints). +- **Color-matching** for parentheses and brackets, which the theme sets. +- **Several cursors**, **bookmarks**, **a list of recently closed tabs** and **combo boxes that insert event handlers**. [Writing code](../../tB/IDE/Code/) describes the editor in full. ## Advanced Features @@ -37,9 +37,9 @@ Fully theme-able, with Dark (default), Light, and Classic (Light) built in, and ## Panels and Windows -- **A History panel** containing a list of recently modified methods. -- **An Outline panel** with selectable categories. -- **A Diagnostics panel** (the Problems panel), which lists all current errors and warnings (you can filter to show only one or the other). +- **A History panel** containing a list of the places you edited recently. See [History](../../tB/IDE/Code/Navigating#history). +- **An Outline panel** with selectable categories. See [Outline](../../tB/IDE/Code/Navigating#outline). +- **A Diagnostics panel** (the Problems panel), which lists all current errors, warnings, hints and information messages, and which you can filter by kind. See [Diagnostics](../../tB/IDE/Code/Diagnostics). ## Form Designer Enhancements diff --git a/docs/Features/Compiler-IDE/index.md b/docs/Features/Compiler-IDE/index.md index 501497773..d121f85d0 100644 --- a/docs/Features/Compiler-IDE/index.md +++ b/docs/Features/Compiler-IDE/index.md @@ -14,6 +14,6 @@ twinBASIC includes many compiler features and IDE enhancements to improve the de - [Compiler Warnings](Compiler-Warnings) - Compiler warnings and strict mode - [Debugging](Debugging) - Debug trace logger and stale pointer detection -- [CodeLens](CodeLens) - Run Subs from the IDE +- [CodeLens](../../tB/IDE/Code/IntelliSense#codelens) - Run Subs from the IDE - [IDE Features](IDE-Features) - Modern IDE capabilities - [Package Server](Package-Server) - Package management system diff --git a/docs/Features/Language/Inheritance.md b/docs/Features/Language/Inheritance.md index d64dfa624..bd708a78f 100644 --- a/docs/Features/Language/Inheritance.md +++ b/docs/Features/Language/Inheritance.md @@ -194,7 +194,7 @@ Module AnimalsDemoMod End Module ``` -Running `DemoAnimals` --- for example from its [CodeLens](../Compiler-IDE/CodeLens) bar --- prints this to the Debug Console: +Running `DemoAnimals` --- for example from its [CodeLens](../../tB/IDE/Code/IntelliSense#codelens) bar --- prints this to the Debug Console: ```text Rex says: woof diff --git a/docs/Features/Packages/Import-export tool.md b/docs/Features/Packages/Import-export tool.md index 83072a9c5..2e0f98fa6 100644 --- a/docs/Features/Packages/Import-export tool.md +++ b/docs/Features/Packages/Import-export tool.md @@ -130,7 +130,7 @@ stops at the first folder inside `Packages\`: it writes nothing, prints neither like this, among them the **WinNativeCommonCtls** package and the *Standard EXE (plus VBCCR v1.8)* template. The script packs them. An export made by the IDE's **File → Export Project** stops the same way, because it holds the compiler packages under `Packages\`; see [Packing -the export back into a project](../../tB/IDE/Project/Menu/File#packing-the-export-back-into-a-project). +the export back into a project](../../tB/IDE/Reference/Menu/File#packing-the-export-back-into-a-project). **The script's `import` adds the folders the IDE always writes.** Every project and package the IDE exports has `ImportedTypeLibraries`, `Miscellaneous`, `Packages`, `Resources` and `Sources` @@ -193,7 +193,7 @@ IDE](#keeping-a-project-in-git-from-the-ide). > folder of its own, as the next section does. Its export also holds the source of the > compiler packages, and the tB executable cannot pack that export back into a project while > any folder remains under its `Packages`. See -> [Export Project](../../tB/IDE/Project/Menu/File#export-project). +> [Export Project](../../tB/IDE/Reference/Menu/File#export-project). The steps, in order: @@ -243,15 +243,15 @@ tracks, so the repository is ready to commit after every save. Two project setti up, and a fresh clone is opened in the IDE too; the import/export tool is not needed. **Set these two project settings**, under *Export* in [Project -Settings](../../tB/IDE/Project/Settings#export) (**Project → Project Settings...**): +Settings](../../tB/IDE/Project-Settings#export) (**Project → Project Settings...**): | Setting | Value | |---------|-------| -| [*Export Path*](../../tB/IDE/Project/Settings#export-path) | `${SourcePath}\src` | -| [*Export After Save*](../../tB/IDE/Project/Settings#export-after-save) | **Yes** | +| [*Export Path*](../../tB/IDE/Project-Settings#export-path) | `${SourcePath}\src` | +| [*Export After Save*](../../tB/IDE/Project-Settings#export-after-save) | **Yes** | `${SourcePath}` is the folder that holds the `.twinproj` file, so every **Save Project** -(CTRL + S) now exports the project into a `src` folder beside it. The +(Ctrl+S) now exports the project into a `src` folder beside it. The `Settings` file that the export writes keeps both settings, so a clone of the repository has them too. @@ -259,7 +259,7 @@ them too. > Keep *Export Path* on a folder of its own. Every export deletes everything in its folder > before it writes, a `.git` folder included, so never set it to the repository's top folder > or to the folder that holds the `.twinproj`. See -> [Export Project](../../tB/IDE/Project/Menu/File#export-project). +> [Export Project](../../tB/IDE/Reference/Menu/File#export-project). **Make the folder that holds the `.twinproj` the repository's top folder**, with `git init` there. Once the project has been saved and the `.gitignore` below added, it holds: @@ -291,7 +291,7 @@ they become a copy inside the project that the compiler ignores, and every later writes that copy back into `src\Packages`. So after an IDE update, a repository that holds them keeps the package source of the IDE that first exported them, while the project compiles against the packages of the IDE in use. [Packing the export back into a -project](../../tB/IDE/Project/Menu/File#packing-the-export-back-into-a-project) has the +project](../../tB/IDE/Reference/Menu/File#packing-the-export-back-into-a-project) has the measurements. Those four are the compiler packages of a Standard EXE with the default references. Add a @@ -307,7 +307,7 @@ of `src`. **The fifth line leaves out the `.twinproj`.** `src` is what gets committed and merged, and the project file is rebuilt from it, as a fresh clone does below; the `.twinproj` itself is one binary file that Git cannot merge. The last line leaves out what -[**Build**](../../tB/IDE/Project/Menu/File#build) writes: the project templates build into a +[**Build**](../../tB/IDE/Reference/Menu/File#build) writes: the project templates build into a `Build` folder beside the `.twinproj`. **Commit after a save.** Once the save has finished, `src` matches the project: @@ -319,7 +319,7 @@ git commit -m "Describe the change" A file removed or renamed in the IDE disappears from `src` as well, because the export empties the folder first, and `git add -A` records that. An export can fail without a message box, so -check that the [Debug Console](../../tB/IDE/Project/DebugConsole) shows `[EXPORT] COMPLETED` +check that the [Debug Console](../../tB/IDE/Debugging/Debug-Console) shows `[EXPORT] COMPLETED` for the save before committing. **Open a fresh clone** in the IDE. It holds `src` and the `.gitignore`, but no `.twinproj`: @@ -340,7 +340,7 @@ for the save before committing. Project](../../tB/IDE/Project/New#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** +4. Save it with Ctrl+S. The project has no file, so **Save Project** opens the *Save As* dialog. Save the `.twinproj` in the clone's top folder, beside `src`, and not inside `src`: *Export Path* is relative to the folder that holds the `.twinproj`, so a project saved inside `src` exports into `src\src`, and the `src` that Git tracks is diff --git a/docs/Features/Packages/Updating a package.md b/docs/Features/Packages/Updating a package.md index 662bda9b5..2f2baffd0 100644 --- a/docs/Features/Packages/Updating a package.md +++ b/docs/Features/Packages/Updating a package.md @@ -57,7 +57,7 @@ The IDE refuses to import a package that the project already contains, whatever So the old copy has to be removed first: -1. Open [Project Settings](../../tB/IDE/Project/Settings#library-references). Under **Library References**, on the **Enabled Libraries** tab, untick the package, and press **Apply Changes**. The IDE saves the project and restarts the compiler. +1. Open [Project Settings](../../tB/IDE/Project-Settings#library-references). Under **Library References**, on the **Enabled Libraries** tab, untick the package, and press **Apply Changes**. The IDE saves the project and restarts the compiler. 2. Open Project Settings again. On the **Available Packages** tab, press **Import from file...** and choose the new `.twinpack` file. 3. Tick the package in the **Available Packages** list. BETA 1005 adds the imported package to the list but does not tick it. 4. Press **Apply Changes** again. The project now uses the new build. @@ -72,7 +72,7 @@ So the old copy has to be removed first: A [linked package](Linked) is stored once, in `%APPDATA%\twinBASIC\packages`, and every project that links it loads it from there. -To link a package in a project that uses it, open Project Settings, go to the **Available Packages** tab, and untick **Embedded** on the package's row. Press **Apply Changes**, then save the project with **File → Save Project** (CTRL + S). **Apply Changes** does not save this change. +To link a package in a project that uses it, open Project Settings, go to the **Available Packages** tab, and untick **Embedded** on the package's row. Press **Apply Changes**, then save the project with **File → Save Project** (Ctrl+S). **Apply Changes** does not save this change. - In the first project, the IDE writes the package to `%APPDATA%\twinBASIC\packages` as a `.twinpack` file named after the package, and removes the project's own copy. - In each further project the file already exists, so the IDE asks what to do. **Local Version** is the version of the shared file, and **Embedded Version** the version of this project's own copy. Choose **Use Existing Local Package**. **Overwrite Local Package (export it from this project)** replaces the shared file with this project's copy, for every project that links it. @@ -80,7 +80,7 @@ To link a package in a project that uses it, open Project Settings, go to the ** To deliver a new build after that, replace the file in `%APPDATA%\twinBASIC\packages` with the new `.twinpack` file. > [!IMPORTANT] -> The compiler reads a linked package only when it starts: when the project is opened, or when **Restart the compiler** is pressed on the [toolbar](../../tB/IDE/Project/Toolbar). A build does not read the file again. So after switching a project to the shared file, save the project and restart the compiler. After replacing the file, restart the compiler in each project that is open. +> The compiler reads a linked package only when it starts: when the project is opened, or when **Restart the compiler** is pressed on the [toolbar](../../tB/IDE/Reference/Toolbar). A build does not read the file again. So after switching a project to the shared file, save the project and restart the compiler. After replacing the file, restart the compiler in each project that is open. - The file is found by the package's project ID, not by its file name. - A project that still embeds the package is not affected by the shared file. @@ -88,5 +88,5 @@ To deliver a new build after that, replace the file in `%APPDATA%\twinBASIC\pack ### Checking which build a project uses -- After **Apply Changes**, the [DEBUG CONSOLE](../../tB/IDE/Project/DebugConsole) shows whether the compiler restarted. `[COMPILER] Project settings changes require compiler-restart` means the IDE saved the project and restarted the compiler. `[COMPILER] Project settings updated` means the compiler kept running, with the build it already had. +- After **Apply Changes**, the [DEBUG CONSOLE](../../tB/IDE/Debugging/Debug-Console) shows whether the compiler restarted. `[COMPILER] Project settings changes require compiler-restart` means the IDE saved the project and restarted the compiler. `[COMPILER] Project settings updated` means the compiler kept running, with the build it already had. - The **Version** column on the **Enabled Libraries** tab shows the version recorded when the package was imported or linked. It cannot tell apart two builds with the same Version, and for a linked package it does not change when the file is replaced. \ No newline at end of file diff --git a/docs/Features/Project-Configuration/Project-Types.md b/docs/Features/Project-Configuration/Project-Types.md index dada632ac..ae7c5f1c4 100644 --- a/docs/Features/Project-Configuration/Project-Types.md +++ b/docs/Features/Project-Configuration/Project-Types.md @@ -52,11 +52,11 @@ Three things stop the obvious VBA code from working: a new project builds a 32-b ### Build for the bitness of Office -64-bit Office --- the usual installation today --- loads only 64-bit DLLs, because Windows does not load a 32-bit DLL into a 64-bit process. Set the [build configuration](../../tB/IDE/Project/Toolbar#build-configuration) box on the toolbar to **win64** before building. Only 32-bit Office uses the **win32** build. +64-bit Office --- the usual installation today --- loads only 64-bit DLLs, because Windows does not load a 32-bit DLL into a 64-bit process. Set the [build configuration](../../tB/IDE/Reference/Toolbar#build-configuration) box on the toolbar to **win64** before building. Only 32-bit Office uses the **win32** build. ### Name the DLL by its full path -With the default [Build Output Path](../../tB/IDE/Project/Settings#build-output-path), the file goes into a `Build` folder beside the `.twinproj` file, and its name ends in the architecture: `Build\MathGreetLib_win64.dll` from a win64 build, `Build\MathGreetLib_win32.dll` from a win32 one. So `Lib "MathGreetLib.dll"` names a file that does not exist. Give `Lib` the full path. +With the default [Build Output Path](../../tB/IDE/Project-Settings#build-output-path), the file goes into a `Build` folder beside the `.twinproj` file, and its name ends in the architecture: `Build\MathGreetLib_win64.dll` from a win64 build, `Build\MathGreetLib_win32.dll` from a win32 one. So `Lib "MathGreetLib.dll"` names a file that does not exist. Give `Lib` the full path. ### Pass strings as pointers @@ -89,13 +89,13 @@ For 32-bit Office, change the two paths to the win32 build. The rest of the code ## Console Applications -This project type allows making a true console project rather than a GUI project: the program runs in the Command Prompt window it was started from, or in a console window of its own. Start one with **File → New Project → Standard EXE (Console App)**. The template turns on [*Is Console Application*](../../tB/IDE/Project/Settings#is-console-application), starts the program at `Sub Main` in its `MainModule`, and adds a `Console` class with `Cls`, `WriteLine` and `ReadLine` members for the console window. The class is a starting point, not a requirement; [Writing a command-line tool](#writing-a-command-line-tool-output-exit-code-and-arguments) replaces it with output that a batch file can capture. +This project type allows making a true console project rather than a GUI project: the program runs in the Command Prompt window it was started from, or in a console window of its own. Start one with **File → New Project → Standard EXE (Console App)**. The template turns on [*Is Console Application*](../../tB/IDE/Project-Settings#is-console-application), starts the program at `Sub Main` in its `MainModule`, and adds a `Console` class with `Cls`, `WriteLine` and `ReadLine` members for the console window. The class is a starting point, not a requirement; [Writing a command-line tool](#writing-a-command-line-tool-output-exit-code-and-arguments) replaces it with output that a batch file can capture. ## Writing a command-line tool: output, exit code and arguments A tool that a batch file runs must put its output where the batch file can redirect it, and report failure through its exit code, which a batch file tests with `if errorlevel`. Four things behave differently from what the template and VBA suggest: -- **`Debug.Print` writes only to the IDE's [Debug Console](../../tB/IDE/Project/DebugConsole).** The built `.exe` prints nothing with it. +- **`Debug.Print` writes only to the IDE's [Debug Console](../../tB/IDE/Debugging/Debug-Console).** The built `.exe` prints nothing with it. - **The template's `Console.WriteLine` writes only to a console window.** It calls `WriteConsoleW`, which writes nothing to a file or a pipe. With the program's output redirected, as in `mytool > out.txt`, it writes nothing and raises error 5. - **`End` stops the program with exit code 0.** To set the exit code, call the Windows `ExitProcess` function, after closing any files the program has open. - **`Command$` returns the arguments as they were typed.** For `mytool "my file.txt"` it returns `"my file.txt"`, quotes included. @@ -163,7 +163,7 @@ Module MainModule End Module ``` -With the default [Build Output Path](../../tB/IDE/Project/Settings#build-output-path), a project named `linecount` builds `Build\linecount_win32.exe`. Rename the file, or change the setting, to run it as `linecount`. At a command prompt, with `three.txt` holding three lines: +With the default [Build Output Path](../../tB/IDE/Project-Settings#build-output-path), a project named `linecount` builds `Build\linecount_win32.exe`. Rename the file, or change the setting, to run it as `linecount`. At a command prompt, with `three.txt` holding three lines: ```text C:\Tools>linecount three.txt diff --git a/docs/IDE/AddIns/GlobalSearch.md b/docs/IDE/AddIns/GlobalSearch.md index 2370ee488..8aa8cb414 100644 --- a/docs/IDE/AddIns/GlobalSearch.md +++ b/docs/IDE/AddIns/GlobalSearch.md @@ -15,7 +15,7 @@ Latest Release Developer : twinBASIC -The global search add in will contain a [Toolbar](../Project/Toolbar) item. +The global search add in will contain a [Toolbar](../Reference/Toolbar) item. ![A single toolbar button labelled Global Search.](Images/Toolbar_GlobalSearch.png){:width="101" height="35"} diff --git a/docs/IDE/AddIns/Help.md b/docs/IDE/AddIns/Help.md index 650a4ed28..d1062d3ef 100644 --- a/docs/IDE/AddIns/Help.md +++ b/docs/IDE/AddIns/Help.md @@ -28,7 +28,7 @@ The help pane is an ordinary IDE tool window: it can float, or be docked like th The gear : Opens the [settings](#settings). -The **Help** button the add-in adds to the IDE's [toolbar](../Project/Toolbar) shows the pane and puts the cursor in its search box, ready to type. +The **Help** button the add-in adds to the IDE's [toolbar](../Reference/Toolbar) shows the pane and puts the cursor in its search box, ready to type. ![The right end of the IDE's toolbar: the zoom box showing 100%, the PREVIEW button, and the Help button with a half-filled circle as its icon.](Images/Help_Toolbar.png){:width="313" height="36"} diff --git a/docs/IDE/Call Stack.md b/docs/IDE/Call Stack.md deleted file mode 100644 index f6c673ddf..000000000 --- a/docs/IDE/Call Stack.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Call Stack -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/CallStack -index: call stack window ---- - -# Call Stack - -![The Call Stack pane listing two active threads, each collapsed to a single row: a Win32 compiler thread with its version number, and MAIN_THREAD, marked idle. A label reading Active Threads sits below the rows, with an arrow rising from it to each row.](Images/CallStack.png){:width="376" height="206"} - -The Call Stack pane lists the active chain of procedure calls at the current execution point during a debugging session, with the most recent call at the top. Clicking an entry navigates the editor to that call site. - -Calls are grouped by thread rather than listed flat. Each active thread is a top-level row that expands to show that thread's own frames, and each row names the thread and its current state --- **COMPILER [Win32]** and **MAIN_THREAD [IDLE]** in the screenshot above. The threads are listed whether or not a debugging session is running, so a pane showing thread rows with nothing beneath them, as above, means no call frames are currently on any stack. diff --git a/docs/IDE/Code/Diagnostics.md b/docs/IDE/Code/Diagnostics.md new file mode 100644 index 000000000..e286bb99a --- /dev/null +++ b/docs/IDE/Code/Diagnostics.md @@ -0,0 +1,92 @@ +--- +title: Diagnostics +parent: Writing code +grand_parent: IDE +nav_order: 5 +permalink: /tB/IDE/Code/Diagnostics +redirect_from: + - /tB/IDE/Project/Diagnostics +index: diagnostics window +--- + +# Diagnostics + +The **DIAGNOSTICS** panel lists every problem the compiler reports in the project: errors, warnings, hints and information messages. The panel is docked at the bottom of the default layout. **View → DIAGNOSTICS** shows it. + +![The Diagnostics pane with no project open. Four counts, each 0, sit at the right of its title bar: Errors, Warnings, Hints and Information. A label at the left of the pane names each one, with an arrow that curves up to its count from below.](../Images/Diagnostics.png){:width="588" height="266"} + +## The four kinds + +| Kind | Colour | +|---|---| +| Error | Red | +| Warning | Yellow | +| Information | Blue | +| Hint | Green | + +At the right of the title bar, four counters show how many problems of each kind the project has. The counters always count everything, whatever is shown in the list below. + +Each counter is also a switch. Click it to show or hide the problems of that kind. Errors and warnings are shown at first. Hints and information are hidden. The pointer tooltip of a counter reads, for example, *Total Error Count -> click to toggle on/off*. The IDE keeps the four switches for each project, and brings them back when you reopen it. + +![The title bar of the Diagnostics pane with two errors, no warnings, no hints and three information messages, each count in a red box. A label reading Click to toggle ON/OFF sits below the bar, with an arrow rising from it to each of the four counts.](../Images/Diagnostics_Toggles.png){:width="446" height="121"} + +The commands `tbProblems_ToggleShowErrors`, `tbProblems_ToggleShowWarnings`, `tbProblems_ToggleShowHints` and `tbProblems_ToggleShowInfos` flip the same switches. They have no keys by default. + +## The list + +The list has one group for each file that has a problem to show. The group's header is the file's name, and pointing at it shows the path. Click the arrow at the left of the header, or double-click the header, to collapse or expand the group. + +Under a header, each row is one problem. A row shows an icon for its kind, the code and the message, and the position in square brackets as `[line,column]`. The code is `TB` and four digits, such as `TB0012`. When the compiler gives no code, the row shows `TB5000`. Pointing at a row shows its message in full. + +![The DIAGNOSTICS panel with its counts of 1 error, 4 warnings, 1 hint and 1 information message, and with Hints and Information switched on. The rows are grouped under three files, Helpers.twin, Inventory.twin and MainModule.twin. Each row shows its icon, its code such as TB0012 or TB5079, its message and its position. The error's row, TB5079 Unrecognized symbol 'Undefined1', is selected, with Jump to issue and Help under it.](Images/Diagnostics_Rows.png){:width="560" height="360"} + +### Go to a problem + +Click a row to select it. Two buttons appear under the row: + +**Jump to issue** +: Opens the file at the line and column of the problem, and puts the focus in the code. + +**Help** +: Shows only the message *Online help content coming soon.* + +A double-click on a row does the same as **Jump to issue**. + +### Copy problems + +Right-click in the list for a menu of five items. The menu is in English whatever the IDE language is. + +**Copy** +: Copies the row under the pointer. + +**Copy All** +: Copies the rows that are shown. A kind that is switched off is not copied. + +**Copy All Errors** +: Copies the errors only. + +**Collapse all**, **Expand all** +: Collapse or expand every group. + +![The right-click menu of the DIAGNOSTICS panel, open over the error's row, with Copy, Copy All and Copy All Errors above a separator and Collapse all and Expand all below it.](Images/Diagnostics_Menu.png){:width="576" height="376"} + +A copied row reads `{ERROR}` or `{WARNING}`, the path of the file, the position, and the code and message. **Copy All** and **Copy All Errors** begin with a line that names the IDE: `twinBASIC (` and the version, then `) compilation error report:`. + +> [!NOTE] +> In twinBASIC BETA 1005, each copied message contains the HTML tag `TBnnnn` around the code, and **Copy All** labels hints and information messages `{WARNING}`. Remove the tags from the pasted text by hand. + +## In the code + +A problem also shows in other places: + +- The editor underlines the part of the code the problem refers to with a wavy line. Hover over it to see the message, as `TBnnnn:` and the text. + + ![A ClassId attribute with empty parentheses in the editor, with a red wavy underline under ClassId and, above the line, the editor's text: insert a randomly generated GUID.](../../Tutorials/CustomControls/Images/ccClassIdInsert.png){:width="215" height="58"} + +- The tab of the file is tinted, and so is its row in the Project Explorer. The tint follows the worst problem in the file. +- Alt+F8 and Shift+Alt+F8 move through the problems of the current file; see [Navigating](Navigating#move-by-procedure-and-by-problem). + +## See Also + +- [Navigating](Navigating) -- move from problem to problem in the editor +- [IntelliSense](IntelliSense) -- hover shows a problem's message diff --git a/docs/IDE/Code/Editor.md b/docs/IDE/Code/Editor.md new file mode 100644 index 000000000..1c16a072c --- /dev/null +++ b/docs/IDE/Code/Editor.md @@ -0,0 +1,350 @@ +--- +title: Editor +parent: Writing code +grand_parent: IDE +nav_order: 1 +permalink: /tB/IDE/Code/Editor +redirect_from: + - /tB/IDE/Project/Editor +--- + +# Editor + +The editor panel holds one tab for each open file, three combo boxes that move you around the file, and the code itself. This page describes the tabs, the combo boxes and the commands that change text. [IntelliSense](IntelliSense), [Navigating](Navigating) and [Finding](Finding) have their own pages. + +The editor's fonts, the minimap, the indent guides, the line numbers and the other settings of how it looks are in [IDE Options](../Customising/IDE-Options). The **...** button in the header of the panel opens a menu of the same settings. + +![The EDITOR 1 pane with its two header buttons boxed and labelled Options and Tabs List. The Options menu is open, and the lists of its four dropdowns are drawn open beside it, each with an arrow from its dropdown: Show Code Folding set to when hovering, Render Whitespace to none, Font Size to 13px and Show Navigation Bar to above. Below the dropdowns sit the toggles, of which Show Indent Guides, Show Line Numbers, Show MiniMap, Auto Prettify Code and Show CodeLens Run Procedure are ticked.](../Images/Editor.png){:width="760" height="440"} + +## Tabs + +Each open file has a tab. The tab shows the file's icon, its name and a close button. + +- A file with changes you have not saved shows a dot in place of the close button. +- A file that has errors or warnings has a tinted row in the Project Explorer, and a tinted tab. +- Pointing at a tab for about a second and a half shows the full path of the file. +- Drag a tab to move it. A mark on the left or the right half of the tab under the pointer shows where it will land. +- Click the middle mouse button on a tab to close it. + +> [!NOTE] +> In twinBASIC BETA 1005, a tab is tinted only when the compiler reports its file while the tab is open. A tab opened after that report stays untinted until the compiler checks the file again, for example after the next edit to it. The Project Explorer row is tinted either way. + +A tab in italics is a *preview tab*. When **File Explorer: Single-Click Preview** is on, a single click on a file in the Project Explorer opens it in a preview tab, and the next single click replaces it. A double-click, or any edit, makes the tab permanent. The **Find / Replace** panel also opens the files it visits in a preview tab. [IDE Options](../Customising/IDE-Options) describes the option. + +![Four tabs, each named by a label with an arrow up to it. Types.twin is a normal tab. Inventory.twin is tinted, labelled Has warnings. Watcher.twin has a dot in place of its close button, labelled Unsaved changes. Ev.twin, the selected tab, has its name in italics, labelled Preview tab.](Images/Tabs_States.png){:width="465" height="135"} + +### Close a tab + +Choose the close button, press Ctrl+W or Ctrl+F4, or click the tab with the middle button. If the file has unsaved changes, a box asks **Do you want to save the changes you made to** the file, and adds that your changes will be lost if you do not save them. Its buttons are **Save**, **Discard Changes** and **Cancel**. + +After you close the active tab, the editor selects the tab you used before it, not its neighbour. + +Right-click a tab for a menu of four items: + +**Close** +: Closes the tab you clicked. + +**Close Others** +: Closes every tab except the one you clicked. It asks about each file that has unsaved changes, one after another. **Cancel** keeps that file only; the next file is still asked about. + +**Close Saved** +: Closes every tab whose file has no unsaved changes, and asks nothing. The files with changes stay open. + +**Close All** +: Closes every tab, and asks about each file that has unsaved changes. + +![The right-click menu of the tab Helpers.twin, open under the tab, with the items Close, Close Others, Close Saved and Close All.](Images/Tab_Menu.png){:width="499" height="142"} + +The tab menu is in English whatever the [language of the IDE](../Customising/#the-language-of-the-ide) is. + +### Reopen a closed tab + +Ctrl+Shift+T reopens the file you closed last. Press it again for the one before. The IDE remembers every file you closed since it started, with no limit, and forgets them when it closes. A file you open in any other way leaves the list. + +### Switch between tabs + +Ctrl+Tab selects the next tab, and Ctrl+Shift+Tab the previous one. They follow the order of the tabs, from left to right, and go round from the last tab to the first. They do not follow the order in which you used the tabs. + +### The Tabs List + +The **Tabs List** button, at the right end of the tab strip, opens a menu under the button. It holds: + +1. **Recently Closed**, with a submenu arrow. It appears only when a closed file can still be reopened. Its submenu lists the closed files, newest first. +2. A separator, when both parts exist. +3. Every open file, sorted by path, with the project's name left off. Choose one to select its tab. + +When there are no open files and nothing recently closed, the menu holds **(empty)**. + +While the menu is open, type to filter the open files. Each character you type is added to a filter, which matches any part of the path without regard to case, and the matching part is drawn in red. **Backspace** removes a character and Esc closes the menu. When nothing matches, the menu reads **(no matches for** and the text you typed**)**. The filter does not apply to **Recently Closed**. + +![The open Tabs List menu, holding a Recently Closed submenu entry above a separator and one open file beneath it, Sources/frmMain.tbform.](../Images/Editor_TabsList_Example.png){:width="187" height="66"} + +With no open file, the menu holds only **Recently Closed**: + +![The Tabs List button with its menu open directly beneath it, holding a single entry, Recently Closed, with a submenu arrow and no files listed under it.](../Images/Editor_TabsList_RecentlyClosed.png){:width="152" height="54"} + +Its submenu lists the closed files: + +![The Recently Closed entry highlighted with its submenu expanded to the right, listing three closed files: Resources/MANIFEST/#1.xml, Resources/STRING/Strings.json and Resources/MESSAGETABLE/Strings.json.](../Images/Editor_TabsList_RecentlyClosed_Example.png){:width="404" height="104"} + +The command `tbEditor_OpenTabsDropDown` opens the Tabs List too. It has no key by default. + +### Tabs that do not fit + +By default the tabs wrap onto more than one row. With **IDE: Single row of code panel tabs** on, the strip has two scroll buttons at its left and right ends. Hold one to scroll the strip, or turn the mouse wheel over the strip to scroll it sideways. When you select a tab, the strip scrolls it into view. The **Tabs List** button reaches every tab in either mode. + +## The navigation combo boxes + +Three combo boxes sit above the code, or below it, as **Show Code Editor Combo Selectors** in [IDE Options](../Customising/IDE-Options) decides. They show where the caret is, and they move it. + +![The three combo boxes above the code of a class named Inventory, numbered 1 to 3. With the caret inside the Sub Add, box 1 shows Inventory, box 2 shows (general) and box 3 shows Add.](Images/Combos.png){:width="811" height="234"} + +| Box | Shows | Its list | +|---|---|---| +| 1 | The component the caret is in: a module, a class, a form. | Every component in the file, sorted by name. | +| 2 | The object the caret is in, or **(general)**. | **(general)**, then the objects of the component, sorted. In a form they are its controls. In a class they are its `WithEvents` fields. A class and a form also have an entry for themselves, **Class** and **Form**. A module has only **(general)**. | +| 3 | The procedure the caret is in, or **(declaration)** when the caret is outside any procedure. | With **(general)** in box 2: the component's procedures, sorted, each with a description such as `[PropertyGet]`. With an object in box 2: that object's events. | + +An event handler shows in box 3 as the event followed by the handler's name in brackets, such as `Changed [src_Changed]`. + +The lists fill when you open them. Press Ctrl+1, Ctrl+2 or Ctrl+3 to move the focus to a box. After you choose an entry, the focus returns to the code. + +![A class named Watcher, with the WithEvents fields src and other, and the caret inside the handler src_Changed. The combo boxes show Watcher, src and Changed [src_Changed]. The third box is open, listing the events of src: Changed [src_Changed] in bold and selected, and Reset.](Images/Combo_Lists.png){:width="811" height="306"} + +What choosing an entry does: + +- **Box 1:** moves the caret to the first line of the component and unfolds it. +- **Box 2:** goes to the first existing event handler of the object. If the object has none, the editor inserts the skeleton of its default event's handler. +- **Box 3:** goes to the procedure or handler you choose. In a list of events, an event that already has a handler is in bold, followed by the handler's name in brackets. Choosing an event that has no handler inserts a skeleton for it. + +A skeleton is a blank line, the `Sub` line, an indented blank line with the caret on it, and `End Sub`. It goes before the `End Class` of the class. For a `WithEvents` variable `src` of a class with the event `Changed`, it is: + +```tb +Private Sub src_Changed(ByVal n As Long) + +End Sub +``` + +With **IDE: Use new Handles/Implements syntax** on, the `Sub` line ends with `Handles src.Changed`. For a control on a form, the skeleton names the control and the event, such as `Private Sub Text1_Change()`. The parameters come from the event. + +The [History](Navigating#history) panel names each of its entries from these three values. + +## Edit the text + +The commands in this part work on the line the caret is on, or on every line of the selection. The **Edit** menu holds the common ones. [Edit menu](../Reference/Menu/Edit) lists them with their keys. + +### Undo and redo + +Ctrl+Z undoes and Ctrl+Y redoes. Ctrl+Shift+Z does nothing. Each press undoes or redoes one step. + +### Comments + +| Command | Key | What it does | +|---|---|---| +| Comment lines | Ctrl+K | Puts `' ` at the indent of each selected line. A line that is already a comment gets another. Blank lines are left alone. The **Comment Selection** button of the toolbar does the same. | +| Uncomment lines | Ctrl+Shift+K | Removes one `' ` or `'` from each selected line. It does nothing on a `Rem` line. The **Uncomment Selection** button does the same. | +| Toggle line comment | Ctrl+/ | Comments every selected line if any of them is not a comment. If all are comments, it uncomments them. | +| Toggle block comment | Shift+Alt+A | Wraps the selection in `/* ... */`, from the start of its first line to the end of its last. The compiler accepts the result as a comment. | + +### Indent and outdent + +Ctrl+] indents the selected lines by one level, and Ctrl+[ outdents them. The toolbar buttons are **Indent Block** and **Outdent Block**. Tab and Shift+Tab do the same, except that Tab accepts an entry while the [completion list](IntelliSense#the-completion-list) is open. The width of a level is **Tab Size** in [IDE Options](../Customising/IDE-Options). + +### Lines + +| Command | Key | +|---|---| +| Insert a line below | Ctrl+Enter | +| Insert a line above | Ctrl+Shift+Enter | +| Delete the line | Ctrl+Shift+Delete | +| Delete the word to the left | Ctrl+Backspace | +| Delete the word to the right | Ctrl+Delete | +| Move the lines up or down | Alt+Up or Alt+Down | +| Copy the lines up or down | Shift+Alt+Up or Shift+Alt+Down | +| Grow the selection | Shift+Alt+Right | +| Shrink the selection | Shift+Alt+Left | +| Select the whole line | Ctrl+L | + +Ctrl+L also shows the **CALL STACK** panel, which takes the focus. A second press therefore does not extend the selection. + +Four commands have no key and no menu item. Give them a key under [Keyboard shortcuts](../Customising/Keyboard-Shortcuts), or run them from the [command palette](#the-command-palette): + +- `tbEditor_SortLinesAscending` and `tbEditor_SortLinesDescending` sort the selected lines. +- `tbEditor_ConvertIndentationToSpaces` and `tbEditor_ConvertIndentationToTabs` change the indentation of the file. + +### Several cursors at once + +Typing, deleting and most commands act at every cursor. One Ctrl+Z undoes the change at all of them. + +| To | Do this | +|---|---| +| Add a cursor at a point | Alt and click | +| Add a cursor above or below | Ctrl+Alt+Up or Ctrl+Alt+Down | +| Add the next match of the word at the caret | Ctrl+D | +| Remove the cursor added last | Ctrl+U | +| Select every occurrence of the word or selection | Ctrl+Shift+L | +| Put a cursor at the end of each selected line | Shift+Alt+I | +| Select a column of text | Shift+Alt and drag | + +Ctrl+D and Ctrl+Shift+L match whole words in the current file, including words inside strings and comments. Ctrl+Shift+L and **Select All Matches** (see [Finding](Finding#select-all-matches)) both give a cursor to each match. + +> [!NOTE] +> In twinBASIC BETA 1005, Esc does not remove the extra cursors. Click in the text, or run any command that places the caret, to go back to one cursor. + +### Change case + +**Edit → Transform To Uppercase**, **Transform To Lowercase**, **Transform To Titlecase** and **Transform To Snakecase** change the case of the selection. They have no keys. Their names are in English whatever the IDE language is. + +### Folding + +The fold arrows in the margin fold and unfold a block. **Show Code Folding** in [IDE Options](../Customising/IDE-Options) decides when the arrows show. The compiler decides the ranges. They include each procedure and each `#Region ... #End Region` block. + +| Command | Key | +|---|---| +| Fold the block at the caret | Ctrl+{ | +| Unfold the block at the caret | Ctrl+} | +| Fold Procedures | Ctrl+Alt+Left | +| Unfold Procedures | Ctrl+Alt+Right | +| Fold All, Unfold All | none | + +**Fold Procedures** folds every `Sub`, `Function` and `Property` in the file except the one the caret is in. A caret on a blank line between two procedures counts as being in the one above. **Fold All** folds every range, including the `Module` block that wraps a module file, so only its first line stays visible. The editor keeps the folded ranges when you close and reopen the project, as [IDE Options](../Customising/IDE-Options) describes for **Restore Editors State When Opening Project**. + +### Word wrap and zoom + +Alt+Z turns word wrap on and off for the editor. The setting is not saved: the next time the IDE starts, word wrap is off. `tbEditor_TurnWordWrapOn` and `tbEditor_TurnWordWrapOff` set it directly. + +Ctrl++ makes the editor's font 1px bigger, with no upper limit, and Ctrl+- makes it 1px smaller, down to 8px. Both save the new size at once, as the **Code Editor Font Size** option. The **View** menu has the same two commands while the editor has the focus. + +## Copy, cut and paste + +The editor does its own clipboard work, and it re-indents what you paste. + +- **Copy** and **Cut** replace each tab in the text with spaces, as many as **Tab Size**. They also store the column where the selection started. +- **Paste** puts the text where the caret is and re-indents it to the code around it. The first line takes the indent of the nearest code line above, plus one level if that line starts a block. A block that starts with `End`, `Next`, `Loop`, `Else`, `ElseIf`, `Wend` or `Case` goes one level out. Each other line keeps its indent relative to the first. A tab in text that comes from another program becomes spaces, and the text is then re-indented. +- A paste of a single line, such as `End If`, goes in as it is, at the caret. +- **Paste As Comment** (Ctrl+Alt+V) pastes the same way, and puts `' ` before each line that is not blank. It is on the **Edit** menu and on the right-click menu. A line that is already a comment gets a second `'`. +- **Plain** copy, cut and paste (Ctrl+Shift+C, Ctrl+Shift+X and Ctrl+Shift+V) skip the re-indenting. +- **Copy With Syntax Highlighting** (`tbEditor_ClipboardCopyWithSyntaxHighlighting`) puts the selection on the clipboard with its syntax colours. It has no key and no menu item. Run it from the command palette, or give it a key. + +Paste on a blank line, or with the caret at the end of the indent. Pasting at the end of a line that already has text joins the first pasted line to that text, and the later lines can end up indented too far. + +![A Sub named Third before and after Paste As Comment, side by side with an arrow from one to the other. Before, the Sub has a blank line under its Debug.Print line. After, three pasted lines take its place: ' Debug.Print Twice(6), a blank line, and ' Debug.Print Twice(7), indented to the code around them.](Images/Paste_As_Comment.png){:width="494" height="180"} + +## What Enter does + +Enter is a command of its own, not just a line break. If the [completion list](IntelliSense#the-completion-list) is open and an entry is selected, Enter accepts that entry instead. + +- **It indents.** The new line gets the indent of the nearest code line above it. A line that continues the one before it with ` _` takes the indent of the statement's first line. After a `Case` or `Else` line, the new line is one level in. +- **It closes the block.** If the line you left starts a block that has no End statement yet, the editor types the End statement for you and leaves the caret on the empty line between, one level in. The pairs are: + +| The block starts with | The editor types | +|---|---| +| `If ... Then` (block form) | `End If` | +| `For`, `For Each` | `Next` | +| `Do`, `Do While` | `Loop` | +| `While` | `Wend` | +| `Select Case` | `End Select` | +| `With` | `End With` | +| `#If ... Then` | `#End If` | +| `Sub`, `Function`, `Property` | `End Sub`, `End Function`, `End Property` | +| `Type`, `Enum` | `End Type`, `End Enum` | +| `Class`, `Module`, `Interface`, `CoClass` | `End Class`, `End Module`, `End Interface`, `End CoClass` | + +- **It adds no End statement** for a one-line `If`, for `#Region`, and for the members of an interface. When the block already has its End statement, the editor adds none, and the new line has the same indent as the line you left, with no extra level. +- **It adds the brackets.** On a `Sub` or `Function` line with no parameter list, Enter adds `()`. `Function Bar As Long` becomes `Function Bar() As Long`. +- **It lines up a continuation.** After a line that ends in ` _`, the next line is indented to the start of the statement. After `Dim a As Long, _` it is indented four spaces further, and after `ReDim arr(1), _` six. +- **It removes spaces.** Spaces and tabs after the caret are deleted. + +## Rename Symbol and Change All Occurrences + +**Rename Symbol** is on the right-click menu and has the key F2. It opens a box over the word with its name selected. + +> [!NOTE] +> In twinBASIC BETA 1005, Rename Symbol renames nothing. Typing a new name changes the box, but Enter and Esc do nothing, and the box stays open until you click in the code. To rename a name that is used in one file, use **Change All Occurrences**. For a name used in several files, use the [Find / Replace](Finding) panel with **Whole Word Only** and **Current Project**, and replace one match at a time, because **Replace All** does not work in project scope. + +**Change All Occurrences**, on the right-click menu only, gives a cursor to every occurrence of the word under the caret in the file. Type the new name to replace them all. The match is on text, not on meaning: the word is also changed inside strings and comments. A longer name that contains the word, such as `totalPrice` for `total`, is not changed. One Ctrl+Z restores all of them. Other files are not touched. + +## Format the code + +**Edit → Format Document** and **Format Selection** re-indent the code. They have no keys; Shift+Alt+F does nothing. + +The formatter changes only the indent of each line. It trims the line and sets the indent to four spaces for each level, whatever **Tab Size** is. It does not change the words, their case or the spacing inside the line. It adds a level after `Class`, `Module`, `Type`, `Enum`, `For`, `While`, `Do`, `With`, a block `If`, `#If`, `Sub`, `Function` and `Property`, and takes it away at the matching End. `ElseIf` and `Else` lines sit at the level of their `If`. A `Select Case` indents its `Case` lines one level and their bodies two. A continuation line is indented two levels deeper than its statement. The members of an interface get one level and no End. + +> [!WARNING] +> In twinBASIC BETA 1005, **Format Selection** fails when the selection starts on a continuation line, which is a line after one that ends in ` _`. Nothing is formatted, and a box opens with the text *IDE startup failure*, followed by *JS Promise Rejection(1): Assignment to constant variable.* If the IDE was started by opening the project's `.twinproj` file, it then closes. With unsaved changes, it first asks whether to save them: choose **Save**, or **Cancel** to keep the IDE open. Save before you format, start the selection on the first line of the statement, or use **Format Document**. + +## Auto Prettify + +The Project Setting **Auto Prettify Source Code** corrects the case of words and the spacing as you write. It is on by default. The **...** menu of the editor has the same setting as **Auto Prettify Code**. It belongs to the project, not to you. [Project Settings](../Project-Settings#auto-prettify-source-code) describes it. + +The compiler works out the corrections, and the editor applies them. A correction on the line the caret is on waits until the caret moves to another line. Then it applies: + +- case: `dim yy as long` becomes `Dim yy As Long`; +- spacing: `total=total+1` becomes `total = total + 1`, and extra spaces go. + +One correction does not wait. When you type a word in full and follow it with a space or a `.`, the completion list accepts it in its proper case: `dim ` becomes `Dim `, and `debug.print` becomes `debug.Print`. The rest of the line is corrected when you leave it. + +> [!NOTE] +> While the completion list is open, Down and Up move in the list, so the caret does not leave the line and nothing is corrected. Press Esc to close the list, then Down. + +A correction joins the typing before it in the undo history. One Ctrl+Z after `Dim ee As Long` removes the typed word and its correction together. With the setting off, the editor applies no corrections. + +## The right-click menu + +A right-click in the editor moves the caret to the point, unless you click inside the selection, which stays. It opens the editor's own menu at the pointer. Its labels are in English whatever the IDE language is. The groups, separated by lines, are: + +1. **Go To Definition** (F12) +2. **Last Position** (Ctrl+Shift+F2), **Toggle Bookmark** (Ctrl+B), **Next Bookmark**, **Previous Bookmark** (Ctrl+M), **Clear Bookmarks (Document)** and **Clear Bookmarks (Project)** +3. **Rename Symbol** (F2) and **Change All Occurrences** +4. **Cut**, **Copy**, **Paste** and **Paste As Comment** +5. **Quick Find...** (Alt+F) and **Quick Replace...** (Alt+H) +6. **Monaco Command Palette** and **Code Editor Options** + +**Code Editor Options** is a submenu with the items of the **...** menu in the header: **Show Code Folding**, **Render Whitespace**, **Font Size**, **Show Navigation Bar**, **Show Indent Guides**, **Show Line Numbers**, **Code Hints Always Visible**, **Sticky Scroll**, **Show MiniMap**, **Show Advanced Info On Hover**, **Show Single Row Of Tabs**, **Auto Prettify Code** and **Show CodeLens Run Procedure**. [IDE Options](../Customising/IDE-Options#the-code-editors-menu) describes them. + +![The editor's right-click menu open over the code, in six groups separated by lines: Go To Definition; Last Position and the bookmark commands; Rename Symbol and Change All Occurrences; Cut, Copy, Paste and Paste As Comment; Quick Find and Quick Replace; and Monaco Command Palette and Code Editor Options. The submenu of Code Editor Options is open beside it, with the four dropdowns Show Code Folding, Render Whitespace, Font Size and Show Navigation Bar above the toggles.](Images/Context_Menu.png){:width="672" height="772"} + +Shift+F10 opens the same menu from the keyboard. + +> [!NOTE] +> In twinBASIC BETA 1005, Shift+F10 opens the menu at the top-left corner of the IDE window, not at the caret. Right-click in the editor to open it at the pointer. + +## The command palette + +**Monaco Command Palette**, on the right-click menu, opens a list of the editor's actions, with a box to type in. It has no key. The IDE's editor is built on Monaco, and the palette is Monaco's own. It lists 124 actions in alphabetical order, among them many that have no key and no menu item in the IDE. Type to narrow the list, and press Enter to run the action. Esc closes the palette and returns to the code. + +These actions work from the palette: + +- **Sort Lines Ascending** +- **Trim Trailing Whitespace** +- **Convert Indentation to Tabs** and **Convert Indentation to Spaces** +- **Go to Bracket**, which moves the caret to the bracket that matches the one at the caret +- **Fold Recursively** +- **Join Lines**; one Ctrl+Z undoes it + +The palette also lists **Rename Symbol** (see [above](#rename-symbol-and-change-all-occurrences)), **Format Document** and **Show Hover**. It has no **Go to References**, **Peek Definition** or **Quick Fix**: the IDE offers no way to find all references, and the editor has no quick fixes. + +The key beside an action in the palette is the key Monaco gives it. It is not always a key that works in the IDE. The IDE stops every key press with Ctrl held from reaching the editor, apart from the commands it binds itself. So a palette action that shows a key with Ctrl does not respond to that key. The Monaco keys F1, F8, Shift+F8 and Shift+Alt+F do nothing. F1 does not open the palette, so open it from the menu. + +![The command palette open at the top of the editor, over the code. Its box holds only the prompt character, and the list under it starts with Add Cursor Above, Add Cursor Below, Add Cursors To Bottom and Add Cursors to Line Ends.](Images/Command_Palette.png){:width="735" height="235"} + +## Save often + +The IDE keeps no backup of your edits. If the IDE or the PC stops before you save, the edits made since the last save are lost. Nothing is offered when you open the project again. Save with [**File → Save Project**](../Reference/Menu/File) (Ctrl+S), and save often. The IDE saves the whole project at once. **Save Current Document**, on the same menu, is disabled. + +What the IDE does keep, every five seconds, is the state of the project's editors: which files are open, the cursors and folds, the bookmarks and the History list. It is kept with the project and brought back when you reopen it. It holds none of your unsaved text. + +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. + +## See Also + +- [Edit menu](../Reference/Menu/Edit) -- the menu of editing commands +- [IntelliSense](IntelliSense) -- the completion list, signature help and hover +- [Navigating](Navigating) -- Go To Definition, bookmarks and the side panels +- [Finding](Finding) -- the Find / Replace panel and Quick Find +- [IDE Options](../Customising/IDE-Options) -- the editor's settings diff --git a/docs/IDE/Code/Finding.md b/docs/IDE/Code/Finding.md new file mode 100644 index 000000000..04ecf3695 --- /dev/null +++ b/docs/IDE/Code/Finding.md @@ -0,0 +1,209 @@ +--- +title: Finding +parent: Writing code +grand_parent: IDE +nav_order: 4 +permalink: /tB/IDE/Code/Finding +redirect_from: + - /tB/IDE/Project/FindReplace +index: + - find and replace + - search and replace + - find in project +--- + +# Finding + +The editor has two ways to search. The **Find / Replace** panel searches the procedure, the file or the whole project, and replaces text. The Quick Find widget searches the file you are in, in a small box over the code. + +## The Find / Replace panel + +Open the panel with one of these: + +| To | Do this | +|---|---| +| Find | **Edit → Find...**, Ctrl+F | +| Find and replace | **Edit → Replace...**, Ctrl+H | +| Search the whole project | **Edit → Find In Project...**, Ctrl+Shift+F, or the **Find In Project...** button of the toolbar | +| Show the panel without searching | **View → FIND / REPLACE** | + +Ctrl+F and Ctrl+H do the same thing: both open the panel, and the panel has no separate mode for replacing. They fill **Find What** with the word at the caret, or with the selection, clear **Replace With**, and put the focus in **Find What** with its text selected. A selection of several lines fills **Find What** with the first line, and ticks **Selected Text**. + +The panel is in no built-in layout. The first time you show it, it floats. You can dock it like any other panel; see [Panel layouts](../Customising/Panel-Layouts). + +![The Find / Replace pane. Find What and Replace With are both empty. The scope radio group has Current File selected, with Current Procedure, Current Module and Selected Text greyed out. Direction is set to All, and the checkboxes Whole Word Only, Match Case, Pattern Matching and Match Regular Expressions are all clear, while Inside Packages is greyed out. Find Next, Cancel, Replace and Replace All run down the right edge.](../Images/FindReplace.png){:width="600" height="209"} + +The labels of the panel are in English whatever the IDE language is. + +### The controls + +**Find What** +: The text to find. Enter runs **Find Next**. Click the label to select the text in the box. + +**Replace With** +: The text that replaces a match. Enter runs **Find Next**. + +**Current Procedure** +: Searches the procedure the caret is in. It is available only while the caret is inside a procedure. + +**Current Module** +: This scope is always greyed out. Use **Current File**. + +**Current File** +: Searches the file you are editing. It is the scope the panel takes when you move the caret into a file and no scope is ticked. + +**Current Project** +: Searches every file of the project. See [Search the whole project](#search-the-whole-project). + +**Selected Text** +: Searches only the selection. It is available only while some text is selected. + +**Direction** +: **All**, **Down** or **Up**. With **All**, a search that reaches the end of its scope goes round to the other end without asking. + +**Whole Word Only** +: A match counts only when the characters before and after it are not letters or digits. An accented letter next to the match, such as *é*, also stops it counting. + +**Match Case** +: Tells capital and small letters apart. Without it, a plain search ignores case. Ticking it clears **Match Regular Expressions**. + +**Pattern Matching** +: Reads **Find What** as a pattern, like the `Like` operator. Ticking it clears **Match Regular Expressions**. + +**Match Regular Expressions** +: Reads **Find What** as a regular expression. Ticking it clears **Match Case** and **Pattern Matching**. + +**Inside Packages** +: Includes the packages the project references in a project search. It is available only with **Current Project**. + +**Find Next** +: Finds the next match in the direction, and selects it in the editor. + +**Replace** +: Replaces the selected match and finds the next one. The first press only finds a match; later presses replace. + +**Replace All** +: Replaces every match in the scope. A project search refuses it; see [Search the whole project](#search-the-whole-project). + +**Cancel** +: Closes the panel. It shows only while the panel floats. + +![The Direction dropdown with its list open below it, offering All, marked as the current choice, then Down and Up.](../Images/FindReplace_Direction.png){:width="232" height="99"} + +The panel keeps the text of **Find What** and **Replace With** that you searched with, and the options. Down in the **Find What** box shows the newest search, and each further Down an older one. Up goes back to newer ones. The IDE keeps this history and the options for each project. + +### Keys + +| Key | What it does | +|---|---| +| F3 | Find Next, from the editor or the panel. | +| Shift+F3 | Find Previous. | +| Esc | Closes the panel when it floats, and puts the focus in the code. | + +These keys work only while the focus is in the panel: + +| Key | What it does | +|---|---| +| Alt+N | Find Next. | +| Alt+R | Replace. | +| Alt+A | Replace All. | +| Alt+F, Alt+W | Go to **Find What**, go to **Replace With**. | +| Alt+P, Alt+C | Choose **Current Procedure**, **Current Project**. | +| Alt+D | Go to **Direction**. | +| Alt+O, Alt+S, Alt+U | Tick **Whole Word Only**, **Match Case**, **Pattern Matching**. | + +**Match Regular Expressions** and **Inside Packages** have no key. + +### What a search says + +A search that reaches the end of its scope with **Down** asks **End of search scope has been reached; do you want to continue from the beginning?** With **Up** it asks **Beginning of search scope has been reached; do you want to continue from the end?** The buttons are **Yes** and **No**. + +A search with **All**, a project search, and **Replace All** end with **The specified region has been searched.** After a **Replace All**, a second box says how many replacements were made: **N replacements were made.**, or **1 replacement was made.** With no scope ticked, the panel says **Scope not selected**. + +> [!NOTE] +> In twinBASIC BETA 1005, after a search of **Selected Text**, moving the caret leaves **Selected Text** ticked but greyed out, and **Current File** is no longer ticked. **Find Next** then says at once that the end of the search scope has been reached. Tick **Current File** again to search on. + +## Match text + +A plain search ignores case unless **Match Case** is ticked. + +> [!WARNING] +> In twinBASIC BETA 1005, **Whole Word Only** never finds a match that starts at the very first character of the file. For example, searching for `Module` with **Whole Word Only** skips a `Module` on line 1, column 1. A **Replace All** with **Whole Word Only** leaves that word unchanged. Search without **Whole Word Only**, and check the first line yourself. + +### Pattern matching + +With **Pattern Matching**, **Find What** is a pattern like the one the `Like` operator reads: + +| Pattern | Matches | +|---|---| +| `?` | Any one character. | +| `*` | Any run of characters, including none. | +| `#` | Any one digit. | +| `[abc]` | Any one of the characters in the brackets. | +| `[!abc]` | Any one character not in the brackets. | + +For example, `Tw?ce` finds `Twice`, `[AV]rea` finds `Area`, and `Area(#, #)` finds a call with two single-digit numbers. `*` takes as much as it can, so `W*Optional` finds from the first `W` to the last `Optional` on the line. + +### Regular expressions + +With **Match Regular Expressions**, **Find What** is a JavaScript regular expression, written between slashes with optional flags after the second slash: `/pattern/flags`. The flags can be `g`, `i`, `m`, `s`, `u` and `y`. The panel adds `m` itself. For example, `/to+tal/i` finds `total` in any case. + +If **Find What** is not in the `/pattern/flags` form, a box says **Invalid format. Expecting /pattern/flags**. If the pattern is not a valid expression, a box says **Invalid RegExp:**. + +> [!WARNING] +> In twinBASIC BETA 1005, a regular expression that has a capital letter finds nothing unless it has the `i` flag. The search runs on the text of the file turned into small letters, and the pattern is left as written. `/End Sub/` finds nothing and reports **The specified region has been searched.**, while `/end sub/` and `/End Sub/i` find every `End Sub`. Write the pattern in small letters, or add the `i` flag. + +> [!NOTE] +> In twinBASIC BETA 1005, a regular expression whose match spans a line break, such as `/^\s+end sub$/`, selects nothing. The selection is an empty one at the end of the line before the match, and **Find Next** stays there. Search for one line at a time. + +> [!NOTE] +> In twinBASIC BETA 1005, the **Invalid RegExp:** box leaves out the reason. It shows the words and nothing after them. + +## Search the whole project + +**Find In Project** has no dialog and no list of results of its own. It opens the **Find / Replace** panel with **Current Project** ticked, and you go through the matches with **Find Next**. + +![The Find / Replace panel floating over the editor, with Twice in Find What, Current Project ticked, and Inside Packages available but not ticked. Behind it, Find Next has gone on to a second file and selected the match Twice in its line Count = Count + Twice(n).](Images/Find_Project.png){:width="648" height="349"} + +A project search: + +- starts in the file you are editing, goes on through the files after it in the order of the Project Explorer tree, and then through the files before it, ending back in the first part of the start file; +- looks in `.twin`, `.bas`, `.cls` and `.md` files, and skips every file whose name starts with a dot; +- does not look in the packages unless **Inside Packages** is ticked; +- does not look in forms (`.tbform` files); +- always goes down, whatever **Direction** says; +- opens each file that holds a match in a tab. The tab is a preview tab when **File Explorer: Single-Click Preview** is on. + +**Replace All** does not work in a project search. The panel says **Sorry, Replace-All not yet supported in project-scope**. Use **Replace** and **Find Next** to go through the matches one at a time, or switch to **Current File** and repeat the replacement in each file. + +## Quick Find + +The Quick Find widget searches the file you are in, with a small box at the top right of the editor. + +| To | Do this | +|---|---| +| Open Quick Find | **Edit → Quick Find...**, Alt+F | +| Open it with the replace row | **Edit → Quick Replace...**, Alt+H | +| Close it | Esc, which returns the focus to the code | +| Go to the next match | Enter, or Alt+F3 | +| Go to the previous match | Shift+Alt+F3 | +| Show an earlier search | Up | +| Show a later search | Down | + +The widget opens with the selected text in its box, and with the count of the matches, such as *1 of 10*. Its toggles are **Match Case**, **Match Whole Word**, **Use Regular Expression** and **Find in Selection**, and the replace row has **Preserve Case**. Shift+Enter does nothing. The IDE does not remember the toggles between uses. + +![The Quick Find widget at the top right of the editor, opened with Quick Replace so that its replace row shows. Its box holds Twice, with the toggles beside it and the count 2 of 2, and both matches of Twice are marked in the code.](Images/Quick_Find.png){:width="751" height="430"} + +With a word selected, Ctrl+F3 in the code goes to its next occurrence and shows the widget. + +### Select all matches + +**Edit → Select All Matches** (Alt+A) opens the widget, gives a cursor to every match of the text in it, and closes the widget. You can then type to change every match at once; see [several cursors](Editor#several-cursors-at-once). Ctrl+Shift+L does the same with the word at the caret, with no widget. + +While the focus is in the Find / Replace panel, Alt+A is **Replace All** and Alt+F goes to **Find What**. In the editor they run Select All Matches and Quick Find. + +## See Also + +- [Edit menu](../Reference/Menu/Edit) -- the menu that holds the find commands +- [Editor](Editor) -- Change All Occurrences and the other editing commands +- [Navigating](Navigating) -- move by definition, bookmark or line diff --git a/docs/IDE/Code/Images/Bookmarks.light.png b/docs/IDE/Code/Images/Bookmarks.light.png new file mode 100644 index 000000000..36a7f09fe Binary files /dev/null and b/docs/IDE/Code/Images/Bookmarks.light.png differ diff --git a/docs/IDE/Code/Images/Bookmarks.light.svg b/docs/IDE/Code/Images/Bookmarks.light.svg new file mode 100644 index 000000000..b08ff4095 --- /dev/null +++ b/docs/IDE/Code/Images/Bookmarks.light.svg @@ -0,0 +1 @@ +13141516171819202122232425101112EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSubEndModulePublicFunctionTwice(ByValnAsLong)AsLongReturnn*2▶   run Helpers.WithWarning▶   run Helpers.ThirdBookmark diff --git a/docs/IDE/Code/Images/Bookmarks.png b/docs/IDE/Code/Images/Bookmarks.png new file mode 100644 index 000000000..306706a18 Binary files /dev/null and b/docs/IDE/Code/Images/Bookmarks.png differ diff --git a/docs/IDE/Code/Images/Bookmarks.svg b/docs/IDE/Code/Images/Bookmarks.svg new file mode 100644 index 000000000..f03323640 --- /dev/null +++ b/docs/IDE/Code/Images/Bookmarks.svg @@ -0,0 +1 @@ +10111213141516171819202122232425PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSubEndModule▶   run Helpers.WithWarning▶   run Helpers.ThirdBookmark diff --git a/docs/IDE/Code/Images/Combo_Lists.light.png b/docs/IDE/Code/Images/Combo_Lists.light.png new file mode 100644 index 000000000..652ea3516 Binary files /dev/null and b/docs/IDE/Code/Images/Combo_Lists.light.png differ diff --git a/docs/IDE/Code/Images/Combo_Lists.light.svg b/docs/IDE/Code/Images/Combo_Lists.light.svg new file mode 100644 index 000000000..cc5a76184 --- /dev/null +++ b/docs/IDE/Code/Images/Combo_Lists.light.svg @@ -0,0 +1 @@ +WatchersrcChanged [src_Changed]123456789101112131415ClassWatcherPrivateWithEventssrcAsEvPrivateWithEventsotherAsEv2PublicCountAsLongPublicSubAttach(ByValeAsEv)Setsrc=eEndSubPrivateSubsrc_Changed(ByValnAsLong)Count=Count+Twice(n)EndSubEndClassChanged [src_Changed]Reset diff --git a/docs/IDE/Code/Images/Combo_Lists.png b/docs/IDE/Code/Images/Combo_Lists.png new file mode 100644 index 000000000..9e1f7d217 Binary files /dev/null and b/docs/IDE/Code/Images/Combo_Lists.png differ diff --git a/docs/IDE/Code/Images/Combo_Lists.svg b/docs/IDE/Code/Images/Combo_Lists.svg new file mode 100644 index 000000000..c5618a635 --- /dev/null +++ b/docs/IDE/Code/Images/Combo_Lists.svg @@ -0,0 +1 @@ +WatchersrcChanged [src_Changed]123456789101112131415ClassWatcherPrivateWithEventssrcAsEvPrivateWithEventsotherAsEv2PublicCountAsLongPublicSubAttach(ByValeAsEv)Setsrc=eEndSubPrivateSubsrc_Changed(ByValnAsLong)Count=Count+Twice(n)EndSubEndClassChanged [src_Changed]Reset diff --git a/docs/IDE/Code/Images/Combos.light.png b/docs/IDE/Code/Images/Combos.light.png new file mode 100644 index 000000000..4496c48d7 Binary files /dev/null and b/docs/IDE/Code/Images/Combos.light.png differ diff --git a/docs/IDE/Code/Images/Combos.light.svg b/docs/IDE/Code/Images/Combos.light.svg new file mode 100644 index 000000000..57107611c --- /dev/null +++ b/docs/IDE/Code/Images/Combos.light.svg @@ -0,0 +1 @@ +Inventory(general)Add1234567891011ClassInventoryPrivateitems()AsStockItemPrivatecountAsLongPublicSubAdd(ByValnameAsString,ByValquantityAsLong,ByValpriceAsDouble)ReDimPreserveitems(count)Withitems(count)            .Name=name            .Quantity=quantity            .Price=price123 diff --git a/docs/IDE/Code/Images/Combos.png b/docs/IDE/Code/Images/Combos.png new file mode 100644 index 000000000..98f71e38b Binary files /dev/null and b/docs/IDE/Code/Images/Combos.png differ diff --git a/docs/IDE/Code/Images/Combos.svg b/docs/IDE/Code/Images/Combos.svg new file mode 100644 index 000000000..51eb93fcf --- /dev/null +++ b/docs/IDE/Code/Images/Combos.svg @@ -0,0 +1 @@ +Inventory(general)Add1234567891011ClassInventoryPrivateitems()AsStockItemPrivatecountAsLongPublicSubAdd(ByValnameAsString,ByValquantityAsLong,ByValpriceAsDouble)ReDimPreserveitems(count)Withitems(count)            .Name=name            .Quantity=quantity            .Price=price123 diff --git a/docs/IDE/Code/Images/Command_Palette.light.png b/docs/IDE/Code/Images/Command_Palette.light.png new file mode 100644 index 000000000..3665d8ca8 Binary files /dev/null and b/docs/IDE/Code/Images/Command_Palette.light.png differ diff --git a/docs/IDE/Code/Images/Command_Palette.light.svg b/docs/IDE/Code/Images/Command_Palette.light.svg new file mode 100644 index 000000000..01b3ea2c3 --- /dev/null +++ b/docs/IDE/Code/Images/Command_Palette.light.svg @@ -0,0 +1 @@ +12345678910111213ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunction>Add Cursor AboveAdd Cursor BelowAdd Cursors To BottomAdd Cursors to Line EndsAdd Cursors To TopAdd Line CommentAdd Selection To Next Find MatchAdd Selection To Previous Find Match diff --git a/docs/IDE/Code/Images/Command_Palette.png b/docs/IDE/Code/Images/Command_Palette.png new file mode 100644 index 000000000..7e8a0c179 Binary files /dev/null and b/docs/IDE/Code/Images/Command_Palette.png differ diff --git a/docs/IDE/Code/Images/Command_Palette.svg b/docs/IDE/Code/Images/Command_Palette.svg new file mode 100644 index 000000000..2e161f318 --- /dev/null +++ b/docs/IDE/Code/Images/Command_Palette.svg @@ -0,0 +1 @@ +12345678910111213ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunction>Add Cursor AboveAdd Cursor BelowAdd Cursors To BottomAdd Cursors to Line EndsAdd Cursors To TopAdd Line CommentAdd Selection To Next Find MatchAdd Selection To Previous Find Match diff --git a/docs/IDE/Code/Images/Context_Menu.light.png b/docs/IDE/Code/Images/Context_Menu.light.png new file mode 100644 index 000000000..1ffa95a0e Binary files /dev/null and b/docs/IDE/Code/Images/Context_Menu.light.png differ diff --git a/docs/IDE/Code/Images/Context_Menu.light.svg b/docs/IDE/Code/Images/Context_Menu.light.svg new file mode 100644 index 000000000..ed6297a02 --- /dev/null +++ b/docs/IDE/Code/Images/Context_Menu.light.svg @@ -0,0 +1 @@ +111213141516171819202122232425PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSubEndModule▶   run Helpers.WithWarning▶   run Helpers.ThirdDIAGNOSTICS0411Helpers.twinInventory.twinMainModule.twin[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[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 VariantCALL STACKCOMPILER [Win32] v0.15.1005MAIN_THREAD [IDLE]Go To DefinitionSHIFT+F2 / F12Last PositionCTRL+SHIFT+F2Toggle BookmarkCTRL+BNext BookmarkPrevious BookmarkCTRL+MClear Bookmarks (Document)Clear Bookmarks (Project)Rename SymbolF2Change All OccurrencesCutCTRL+X / SHIFT+DELETECopyCTRL+C / CTRL+INSERTPasteCTRL+VPaste As CommentCTRL+ALT+VQuick Find...ALT+FQuick Replace...ALT+HMonaco Command PaletteCode Editor OptionsShow Code FoldingRender WhitespaceFont SizeShow Navigation BarShow Indent GuidesShow Line NumbersCode Hints Always VisibleSticky ScrollShow MiniMapShow Advanced Info On HoverShow Single Row Of TabsAuto Prettify CodeShow CodeLens Run Procedurewhen hoveringnone13pxabove diff --git a/docs/IDE/Code/Images/Context_Menu.png b/docs/IDE/Code/Images/Context_Menu.png new file mode 100644 index 000000000..aba04d5ce Binary files /dev/null and b/docs/IDE/Code/Images/Context_Menu.png differ diff --git a/docs/IDE/Code/Images/Context_Menu.svg b/docs/IDE/Code/Images/Context_Menu.svg new file mode 100644 index 000000000..2b8574b47 --- /dev/null +++ b/docs/IDE/Code/Images/Context_Menu.svg @@ -0,0 +1 @@ +111213141516171819202122232425PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSubEndModule▶   run Helpers.WithWarning▶   run Helpers.ThirdDIAGNOSTICS0411Helpers.twinInventory.twinMainModule.twin[16,15]TB0003Datatype has not been explicitly declared so will default to Variant[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 VariantCALL STACKCOMPILER [Win32] v0.15.1005MAIN_THREAD [IDLE]Go To DefinitionSHIFT+F2 / F12Last PositionCTRL+SHIFT+F2Toggle BookmarkCTRL+BNext BookmarkPrevious BookmarkCTRL+MClear Bookmarks (Document)Clear Bookmarks (Project)Rename SymbolF2Change All OccurrencesCutCTRL+X / SHIFT+DELETECopyCTRL+C / CTRL+INSERTPasteCTRL+VPaste As CommentCTRL+ALT+VQuick Find...ALT+FQuick Replace...ALT+HMonaco Command PaletteCode Editor OptionsShow Code FoldingRender WhitespaceFont SizeShow Navigation BarShow Indent GuidesShow Line NumbersCode Hints Always VisibleSticky ScrollShow MiniMapShow Advanced Info On HoverShow Single Row Of TabsAuto Prettify CodeShow CodeLens Run Procedurewhen hoveringnone13pxabove diff --git a/docs/IDE/Code/Images/Diagnostics_Menu.light.png b/docs/IDE/Code/Images/Diagnostics_Menu.light.png new file mode 100644 index 000000000..88092135a Binary files /dev/null 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 new file mode 100644 index 000000000..624b13161 --- /dev/null +++ b/docs/IDE/Code/Images/Diagnostics_Menu.light.svg @@ -0,0 +1 @@ +DIAGNOSTICS1411Helpers.twinInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpretted as an Integer value of -32768 (&HFFFF8000). The value is also being converted to a wider type, and so this is likely a mistake. Use &H8000& to force the literal into the correct type and prevent unwanted side-effects.[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 new file mode 100644 index 000000000..617e5eb8a Binary files /dev/null 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 new file mode 100644 index 000000000..b482ab417 --- /dev/null +++ b/docs/IDE/Code/Images/Diagnostics_Menu.svg @@ -0,0 +1 @@ +DIAGNOSTICS1411Helpers.twinInventory.twinMainModule.twin[9,37]TB0012Possible misuse of hexadecimal literal notation. This literal is interpretted as an Integer value of -32768 (&HFFFF8000). The value is also being converted to a wider type, and so this is likely a mistake. Use &H8000& to force the literal into the correct type and prevent unwanted side-effects.[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 new file mode 100644 index 000000000..bf9334447 Binary files /dev/null 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 new file mode 100644 index 000000000..038b1e97d --- /dev/null +++ b/docs/IDE/Code/Images/Diagnostics_Rows.light.svg @@ -0,0 +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 an Integer value of -32768 (&HFFFF8000). The value is also being converted to a wider type, and so this is likely a mistake. Use &H8000& to force the literal into the correct type and prevent unwanted side-effects.[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 new file mode 100644 index 000000000..143ef498d Binary files /dev/null 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 new file mode 100644 index 000000000..f0dee1083 --- /dev/null +++ b/docs/IDE/Code/Images/Diagnostics_Rows.svg @@ -0,0 +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 an Integer value of -32768 (&HFFFF8000). The value is also being converted to a wider type, and so this is likely a mistake. Use &H8000& to force the literal into the correct type and prevent unwanted side-effects.[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/Editor_Panel.light.png b/docs/IDE/Code/Images/Editor_Panel.light.png new file mode 100644 index 000000000..52876b324 Binary files /dev/null and b/docs/IDE/Code/Images/Editor_Panel.light.png differ diff --git a/docs/IDE/Code/Images/Editor_Panel.light.svg b/docs/IDE/Code/Images/Editor_Panel.light.svg new file mode 100644 index 000000000..127da2485 --- /dev/null +++ b/docs/IDE/Code/Images/Editor_Panel.light.svg @@ -0,0 +1 @@ +EDITOR 1Helpers(general)TwiceTypes.twinInventory.twinHelpers.twin12345678910111213141516171819202122232425ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSubEndModule▶   run Helpers.WithWarning▶   run Helpers.ThirdOptionsTabs ListCombo boxesMinimapTab stripMargin diff --git a/docs/IDE/Code/Images/Editor_Panel.png b/docs/IDE/Code/Images/Editor_Panel.png new file mode 100644 index 000000000..0808b9cac Binary files /dev/null and b/docs/IDE/Code/Images/Editor_Panel.png differ diff --git a/docs/IDE/Code/Images/Editor_Panel.svg b/docs/IDE/Code/Images/Editor_Panel.svg new file mode 100644 index 000000000..0b49eed63 --- /dev/null +++ b/docs/IDE/Code/Images/Editor_Panel.svg @@ -0,0 +1 @@ +EDITOR 1Helpers(general)TwiceTypes.twinInventory.twinHelpers.twin12345678910111213141516171819202122232425ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSubEndModule▶   run Helpers.WithWarning▶   run Helpers.ThirdOptionsTabs ListCombo boxesMinimapTab stripMargin diff --git a/docs/IDE/Code/Images/Find_Project.light.png b/docs/IDE/Code/Images/Find_Project.light.png new file mode 100644 index 000000000..51082f5a9 Binary files /dev/null and b/docs/IDE/Code/Images/Find_Project.light.png differ diff --git a/docs/IDE/Code/Images/Find_Project.light.svg b/docs/IDE/Code/Images/Find_Project.light.svg new file mode 100644 index 000000000..2467d4932 --- /dev/null +++ b/docs/IDE/Code/Images/Find_Project.light.svg @@ -0,0 +1 @@ +EDITOR 1WatchersrcChanged [src_Changed]Helpers.twinWatcher.twin123456789101112131415ClassWatcherPrivateWithEventssrcAsEvPrivateWithEventsotherAsEv2PublicCountAsLongPublicSubAttach(ByValeAsEv)Setsrc=eEndSubPrivateSubsrc_Changed(ByValnAsLong)Count=Count+Twice(n)EndSubEndClass2 of 2TwiceReplaceFIND / REPLACEFind What:Replace With:Direction:Find NextCancelReplaceReplace AllTwiceCurrent ProcedureCurrent FileCurrent ProjectSelected TextAllWhole Word OnlyMatch CasePattern MatchingMatch Regular ExpressionsInside PackagesCurrent Module diff --git a/docs/IDE/Code/Images/Find_Project.png b/docs/IDE/Code/Images/Find_Project.png new file mode 100644 index 000000000..73dd65639 Binary files /dev/null and b/docs/IDE/Code/Images/Find_Project.png differ diff --git a/docs/IDE/Code/Images/Find_Project.svg b/docs/IDE/Code/Images/Find_Project.svg new file mode 100644 index 000000000..d9b09df2a --- /dev/null +++ b/docs/IDE/Code/Images/Find_Project.svg @@ -0,0 +1 @@ +EDITOR 1WatchersrcChanged [src_Changed]Helpers.twinWatcher.twin123456789101112131415ClassWatcherPrivateWithEventssrcAsEvPrivateWithEventsotherAsEv2PublicCountAsLongPublicSubAttach(ByValeAsEv)Setsrc=eEndSubPrivateSubsrc_Changed(ByValnAsLong)Count=Count+Twice(n)EndSubEndClassFIND / REPLACEFind What:Replace With:Direction:Find NextCancelReplaceReplace AllTwiceCurrent ProcedureCurrent FileCurrent ProjectSelected TextAllWhole Word OnlyMatch CasePattern MatchingMatch Regular ExpressionsInside PackagesCurrent Module diff --git a/docs/IDE/Code/Images/Hints.light.png b/docs/IDE/Code/Images/Hints.light.png new file mode 100644 index 000000000..f9cb925cb Binary files /dev/null and b/docs/IDE/Code/Images/Hints.light.png differ diff --git a/docs/IDE/Code/Images/Hints.light.svg b/docs/IDE/Code/Images/Hints.light.svg new file mode 100644 index 000000000..dc0f8b01e --- /dev/null +++ b/docs/IDE/Code/Images/Hints.light.svg @@ -0,0 +1 @@ +15161718192021222324252627282930313233PublicFunctionTotal(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).NameEndIf🠈 items(i).Quantity > 100EndIf🠈 items(i).Quantity > 0Next🠈 i = 0 To count - 1Total=subtotalEndFunction🠈 Total diff --git a/docs/IDE/Code/Images/Hints.png b/docs/IDE/Code/Images/Hints.png new file mode 100644 index 000000000..ad638271c Binary files /dev/null and b/docs/IDE/Code/Images/Hints.png differ diff --git a/docs/IDE/Code/Images/Hints.svg b/docs/IDE/Code/Images/Hints.svg new file mode 100644 index 000000000..2c53bd003 --- /dev/null +++ b/docs/IDE/Code/Images/Hints.svg @@ -0,0 +1 @@ +15161718192021222324252627282930313233PublicFunctionTotal(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).NameEndIf🠈 items(i).Quantity > 100EndIf🠈 items(i).Quantity > 0Next🠈 i = 0 To count - 1Total=subtotalEndFunction🠈 Total diff --git a/docs/IDE/Code/Images/Hover.light.png b/docs/IDE/Code/Images/Hover.light.png new file mode 100644 index 000000000..fe0e5200a Binary files /dev/null and b/docs/IDE/Code/Images/Hover.light.png differ diff --git a/docs/IDE/Code/Images/Hover.light.svg b/docs/IDE/Code/Images/Hover.light.svg new file mode 100644 index 000000000..27185bc67 --- /dev/null +++ b/docs/IDE/Code/Images/Hover.light.svg @@ -0,0 +1 @@ +2345678910PublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)first=stock.ItemByIndex(stock.Find("Widget"))▶   run MainModule.MainFunction Total ( ByVal taxRate As Double ) As DoubleTotalin FeatureTour._Inventoryno further info available.   Tip: use [Description("")] attribute above the procedure definition. diff --git a/docs/IDE/Code/Images/Hover.png b/docs/IDE/Code/Images/Hover.png new file mode 100644 index 000000000..8a7957614 Binary files /dev/null and b/docs/IDE/Code/Images/Hover.png differ diff --git a/docs/IDE/Code/Images/Hover.svg b/docs/IDE/Code/Images/Hover.svg new file mode 100644 index 000000000..89c84dec0 --- /dev/null +++ b/docs/IDE/Code/Images/Hover.svg @@ -0,0 +1 @@ +2345678910PublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)first=stock.ItemByIndex(stock.Find("Widget"))▶   run MainModule.MainFunction Total ( ByVal taxRate As Double ) As DoubleTotalin FeatureTour._Inventoryno further info available.   Tip: use [Description("")] attribute above the procedure definition. diff --git a/docs/IDE/Code/Images/Outline_Menu.light.png b/docs/IDE/Code/Images/Outline_Menu.light.png new file mode 100644 index 000000000..f256d0b8c Binary files /dev/null and b/docs/IDE/Code/Images/Outline_Menu.light.png differ diff --git a/docs/IDE/Code/Images/Outline_Menu.light.svg b/docs/IDE/Code/Images/Outline_Menu.light.svg new file mode 100644 index 000000000..a17e0fa38 --- /dev/null +++ b/docs/IDE/Code/Images/Outline_Menu.light.svg @@ -0,0 +1 @@ +OUTLINEHelpersSort AlphabeticallyShow Class FieldsShow Module VariablesShow EnumerationsShow User Defined TypesShow DLL DeclaresShow ConstructorsShow Properties (Get/Let/Set)Show ProceduresShow VariablesShow Parameters diff --git a/docs/IDE/Code/Images/Outline_Menu.png b/docs/IDE/Code/Images/Outline_Menu.png new file mode 100644 index 000000000..0ff524e0d Binary files /dev/null and b/docs/IDE/Code/Images/Outline_Menu.png differ diff --git a/docs/IDE/Code/Images/Outline_Menu.svg b/docs/IDE/Code/Images/Outline_Menu.svg new file mode 100644 index 000000000..8044f461b --- /dev/null +++ b/docs/IDE/Code/Images/Outline_Menu.svg @@ -0,0 +1 @@ +OUTLINEHelpersSort AlphabeticallyShow Class FieldsShow Module VariablesShow EnumerationsShow User Defined TypesShow DLL DeclaresShow ConstructorsShow Properties (Get/Let/Set)Show ProceduresShow VariablesShow Parameters diff --git a/docs/IDE/Code/Images/Paste_As_Comment.light.png b/docs/IDE/Code/Images/Paste_As_Comment.light.png new file mode 100644 index 000000000..8b6a33c44 Binary files /dev/null and b/docs/IDE/Code/Images/Paste_As_Comment.light.png differ diff --git a/docs/IDE/Code/Images/Paste_As_Comment.light.svg b/docs/IDE/Code/Images/Paste_As_Comment.light.svg new file mode 100644 index 000000000..d85d0f50d --- /dev/null +++ b/docs/IDE/Code/Images/Paste_As_Comment.light.svg @@ -0,0 +1 @@ +FileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpwin32TOOLBOXEDITOR 1Helpers(general)Helpers.twin12ModuleHelpersTwiceReplace diff --git a/docs/IDE/Code/Images/Paste_As_Comment.png b/docs/IDE/Code/Images/Paste_As_Comment.png new file mode 100644 index 000000000..a101e235a Binary files /dev/null and b/docs/IDE/Code/Images/Paste_As_Comment.png differ diff --git a/docs/IDE/Code/Images/Paste_As_Comment.svg b/docs/IDE/Code/Images/Paste_As_Comment.svg new file mode 100644 index 000000000..b2065245e --- /dev/null +++ b/docs/IDE/Code/Images/Paste_As_Comment.svg @@ -0,0 +1 @@ +FileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelpwin32TOOLBOXEDITOR 1Helpers(general)Helpers.twin12ModuleHelpers diff --git a/docs/IDE/Code/Images/Quick_Find.light.png b/docs/IDE/Code/Images/Quick_Find.light.png new file mode 100644 index 000000000..44824752d Binary files /dev/null and b/docs/IDE/Code/Images/Quick_Find.light.png differ diff --git a/docs/IDE/Code/Images/Quick_Find.light.svg b/docs/IDE/Code/Images/Quick_Find.light.svg new file mode 100644 index 000000000..bf307e3da --- /dev/null +++ b/docs/IDE/Code/Images/Quick_Find.light.svg @@ -0,0 +1 @@ +12345678910111213141516171819202122ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSub▶   run Helpers.WithWarning▶   run Helpers.Third2 of 2TwiceReplace diff --git a/docs/IDE/Code/Images/Quick_Find.png b/docs/IDE/Code/Images/Quick_Find.png new file mode 100644 index 000000000..af8702449 Binary files /dev/null and b/docs/IDE/Code/Images/Quick_Find.png differ diff --git a/docs/IDE/Code/Images/Quick_Find.svg b/docs/IDE/Code/Images/Quick_Find.svg new file mode 100644 index 000000000..7d271c017 --- /dev/null +++ b/docs/IDE/Code/Images/Quick_Find.svg @@ -0,0 +1 @@ +12345678910111213141516171819202122ModuleHelpersPublicEnumColourRed=1Green=2Blue=4EndEnumPublicConstBigValueAsLong=&H8000PublicFunctionTwice(ByValnAsLong)AsLongReturnn*2EndFunctionPublicSubWithWarning()ReDimmade(3)Debug.PrintUBound(made)EndSubPublicSubThird()Debug.PrintTwice(5)EndSub▶   run Helpers.WithWarning▶   run Helpers.Third2 of 2TwiceReplace diff --git a/docs/IDE/Code/Images/SignatureHelp.light.png b/docs/IDE/Code/Images/SignatureHelp.light.png new file mode 100644 index 000000000..de5447ab7 Binary files /dev/null and b/docs/IDE/Code/Images/SignatureHelp.light.png differ diff --git a/docs/IDE/Code/Images/SignatureHelp.light.svg b/docs/IDE/Code/Images/SignatureHelp.light.svg new file mode 100644 index 000000000..ded2f3ff7 --- /dev/null +++ b/docs/IDE/Code/Images/SignatureHelp.light.svg @@ -0,0 +1 @@ +32333435363738Total=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongi= InStr()Returns a Variant (typically Long) specifying the position of the first occurrence of one string within another, allowing for arguments to be passed as Variant.Function InStr(ByRef String1 As Variant, ByRef String2 As Variant, [ByVal Compare As VbCompareMethod = TB_BUILTIN_CURRENT_MODULE_TEXTCOMPARE_MODE]) As VariantFunction InStr(ByRef Start As Variant, ByRef String1 As Variant, ByRef String2 As Variant, [ByVal Compare As VbCompareMethod = TB_BUILTIN_CURRENT_MODULE_TEXTCOMPARE_MODE]) As Variant diff --git a/docs/IDE/Code/Images/SignatureHelp.png b/docs/IDE/Code/Images/SignatureHelp.png new file mode 100644 index 000000000..0d98155a9 Binary files /dev/null and b/docs/IDE/Code/Images/SignatureHelp.png differ diff --git a/docs/IDE/Code/Images/SignatureHelp.svg b/docs/IDE/Code/Images/SignatureHelp.svg new file mode 100644 index 000000000..ae685913a --- /dev/null +++ b/docs/IDE/Code/Images/SignatureHelp.svg @@ -0,0 +1 @@ +32333435363738Total=subtotalEndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongi= InStr()Returns a Variant (typically Long) specifying the position of the first occurrence of one string within another, allowing for arguments to be passed as Variant.Function InStr(ByRef String1 As Variant, ByRef String2 As Variant, [ByVal Compare As VbCompareMethod = TB_BUILTIN_CURRENT_MODULE_TEXTCOMPARE_MODE]) As VariantFunction InStr(ByRef Start As Variant, ByRef String1 As Variant, ByRef String2 As Variant, [ByVal Compare As VbCompareMethod = TB_BUILTIN_CURRENT_MODULE_TEXTCOMPARE_MODE]) As Variant diff --git a/docs/IDE/Code/Images/SignatureHelp_Expanded.light.png b/docs/IDE/Code/Images/SignatureHelp_Expanded.light.png new file mode 100644 index 000000000..c078f7543 Binary files /dev/null and b/docs/IDE/Code/Images/SignatureHelp_Expanded.light.png differ diff --git a/docs/IDE/Code/Images/SignatureHelp_Expanded.light.svg b/docs/IDE/Code/Images/SignatureHelp_Expanded.light.svg new file mode 100644 index 000000000..0ae9aa944 --- /dev/null +++ b/docs/IDE/Code/Images/SignatureHelp_Expanded.light.svg @@ -0,0 +1 @@ +3334353637383940414243444546474849EndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenFind=iExitForEndIfNextEndFunctionPublicSubClear()Eraseitemscount=0MsgBox()MsgBoxDisplays a dialog box with a message, set of buttons, and optional title, help file, and context information, and returns the user's selection as a VbMsgBoxResult enumeration.Syntaxresult = MsgBox(prompt[, buttons][, title][, helpfile][, context])Parameters• prompt: The message text displayed in the dialog box.• buttons: (Optional) A VbMsgBoxStyle enumeration that specifies the buttons, icon style, default button, and modality of the message box.• title: (Optional) The title bar text. If omitted, the application name is used.• helpfile: (Optional) The path to a help file for context-sensitive help.• context: (Optional) The help context number associated with the help file.in VBA.InteractionFunction MsgBox(ByRef Prompt As Variant, [ByVal Buttons As VbMsgBoxStyle = vbOKOnly], [ByRef Title As Variant], [ByRef HelpFile As Variant], [ByRef Context As Variant]) As VbMsgBoxResult diff --git a/docs/IDE/Code/Images/SignatureHelp_Expanded.png b/docs/IDE/Code/Images/SignatureHelp_Expanded.png new file mode 100644 index 000000000..b2f545e3d Binary files /dev/null and b/docs/IDE/Code/Images/SignatureHelp_Expanded.png differ diff --git a/docs/IDE/Code/Images/SignatureHelp_Expanded.svg b/docs/IDE/Code/Images/SignatureHelp_Expanded.svg new file mode 100644 index 000000000..f1c403c6a --- /dev/null +++ b/docs/IDE/Code/Images/SignatureHelp_Expanded.svg @@ -0,0 +1 @@ +3334353637383940414243444546474849EndFunctionPublicFunctionFind(ByValnameAsString)AsLongFind=-1DimiAsLongFori=0Tocount-1Ifitems(i).Name=nameThenFind=iExitForEndIfNextEndFunctionPublicSubClear()Eraseitemscount=0MsgBox()MsgBoxDisplays a dialog box with a message, set of buttons, and optional title, help file, and context information, and returns the user's selection as a VbMsgBoxResult enumeration.Syntaxresult = MsgBox(prompt[, buttons][, title][, helpfile][, context])Parameters• prompt: The message text displayed in the dialog box.• buttons: (Optional) A VbMsgBoxStyle enumeration that specifies the buttons, icon style, default button, and modality of the message box.• title: (Optional) The title bar text. If omitted, the application name is used.• helpfile: (Optional) The path to a help file for context-sensitive help.• context: (Optional) The help context number associated with the help file.in VBA.InteractionFunction MsgBox(ByRef Prompt As Variant, [ByVal Buttons As VbMsgBoxStyle = vbOKOnly], [ByRef Title As Variant], [ByRef HelpFile As Variant], [ByRef Context As Variant]) As VbMsgBoxResult diff --git a/docs/IDE/Code/Images/Tab_Menu.light.png b/docs/IDE/Code/Images/Tab_Menu.light.png new file mode 100644 index 000000000..9d350da4f Binary files /dev/null and b/docs/IDE/Code/Images/Tab_Menu.light.png differ diff --git a/docs/IDE/Code/Images/Tab_Menu.light.svg b/docs/IDE/Code/Images/Tab_Menu.light.svg new file mode 100644 index 000000000..3257c9158 --- /dev/null +++ b/docs/IDE/Code/Images/Tab_Menu.light.svg @@ -0,0 +1 @@ +EDITOR 1Helpers(general)Types.twinInventory.twinHelpers.twin1234ModuleHelpersPublicEnumColourRed=1CloseClose OthersClose SavedClose All diff --git a/docs/IDE/Code/Images/Tab_Menu.png b/docs/IDE/Code/Images/Tab_Menu.png new file mode 100644 index 000000000..55456a316 Binary files /dev/null and b/docs/IDE/Code/Images/Tab_Menu.png differ diff --git a/docs/IDE/Code/Images/Tab_Menu.svg b/docs/IDE/Code/Images/Tab_Menu.svg new file mode 100644 index 000000000..bae5618b1 --- /dev/null +++ b/docs/IDE/Code/Images/Tab_Menu.svg @@ -0,0 +1 @@ +EDITOR 1Helpers(general)Types.twinInventory.twinHelpers.twin1234ModuleHelpersPublicEnumColourRed=1CloseClose OthersClose SavedClose All diff --git a/docs/IDE/Code/Images/Tabs_States.light.png b/docs/IDE/Code/Images/Tabs_States.light.png new file mode 100644 index 000000000..f379c9c5c Binary files /dev/null and b/docs/IDE/Code/Images/Tabs_States.light.png differ diff --git a/docs/IDE/Code/Images/Tabs_States.light.svg b/docs/IDE/Code/Images/Tabs_States.light.svg new file mode 100644 index 000000000..d12b24c26 --- /dev/null +++ b/docs/IDE/Code/Images/Tabs_States.light.svg @@ -0,0 +1 @@ +Types.twinInventory.twinWatcher.twinEv.twinNormalHas warningsUnsaved changesPreview tab diff --git a/docs/IDE/Code/Images/Tabs_States.png b/docs/IDE/Code/Images/Tabs_States.png new file mode 100644 index 000000000..807bd59bc Binary files /dev/null and b/docs/IDE/Code/Images/Tabs_States.png differ diff --git a/docs/IDE/Code/Images/Tabs_States.svg b/docs/IDE/Code/Images/Tabs_States.svg new file mode 100644 index 000000000..bf6cd56ef --- /dev/null +++ b/docs/IDE/Code/Images/Tabs_States.svg @@ -0,0 +1 @@ +Types.twinInventory.twinWatcher.twinEv.twinNormalHas warningsUnsaved changesPreview tab diff --git a/docs/IDE/Code/IntelliSense.md b/docs/IDE/Code/IntelliSense.md new file mode 100644 index 000000000..4c13e8800 --- /dev/null +++ b/docs/IDE/Code/IntelliSense.md @@ -0,0 +1,131 @@ +--- +title: IntelliSense +parent: Writing code +grand_parent: IDE +nav_order: 2 +permalink: /tB/IDE/Code/IntelliSense +redirect_from: + - /Features/Compiler-IDE/CodeLens +index: + - completion list + - signature help + - code hints +--- + +# IntelliSense + +IntelliSense is the group of helpers that tell you what you can write next: the completion list, the signature-help box, the hover popup and the inline code hints. CodeLens, described at the end of this page, runs a procedure from the editor. + +The compiler supplies all of them. The editor asks it as you type and shows the answer. + +[IDE Options](../Customising/IDE-Options) holds the settings: the **IDE: Intellisense mode** option decides when the completion list opens by itself, and a few options switch the hover popup, the first line of documentation in signature help and the inline hints. [Intellisense modes](../Customising/IDE-Options#intellisense-modes) lists the three modes. + +## The completion list + +The completion list is a list of the names that fit at the caret. It opens under the caret, as you type or when you ask for it with Ctrl+Space. Ctrl+Space opens it in every mode. If only one name fits, the editor accepts it at once without showing the list. + +![A code editor where typing fmt. has opened the completion list, offering the package's members: AsciiEscapeBase, CopyCapitalisation, CurrencySFI, DateTimeSFI, DecimalSFI, EscapeSequence and more.](../../Features/Packages/Images/e2a65dfe-4a9d-4524-b6d6-7a6d1bc35cdb.png){:width="592" height="252"} + +The list holds the names that **start with** the word you have typed. The match ignores case and is on the start of the name only, so `rea` does not find `Area`. The names are sorted by the priority the compiler gives them, and then by name. Each row has: + +- an icon for the kind of name: a method, function, property, field, variable, class, interface, module, enum, enum member, constant, user-defined type, event, keyword, library or reference; +- the name, with the part you typed highlighted; +- a short text on the right: for a function, `returns` and its type; for the other kinds, a word such as `field`, `variable`, `class`, `interface`, `module`, `property`, `enum`, `enum-value`, `constant`, `event`, `user defined type`, `library` or `reference`. + +A row can show `...` for a moment in place of a type. The compiler sends the types a little after the names. + +### Choose an entry + +Press Up or Down to select a row. When the list opens, no row is selected. + +| Key | With a row selected | With no row selected | +|---|---|---| +| Tab | Accepts the row. | Accepts the first row. If the word you typed is already complete, it inserts a tab. | +| Enter | Accepts the row, and adds no new line. | Does not accept: it starts a new line. | +| Space | Accepts the row. | Accepts the first row, without typing the space, when the first row starts with the word you have typed. If the word is complete, it types the space and fixes the word's case: `debug ` becomes `Debug `. | +| `,` `.` `+` `-` `&` `(` `)` `!` `*` `\` `/` `:` `=` | Accepts the row, and then types the character. | Types the character. | + +What an accepted entry adds: + +- A function or an event gets `()`, and the caret goes between the brackets. After a complete function name, Space does the same: `Area` becomes `Area()`. +- A library, such as `VBA`, gets a `.` at its end, and the list opens again for the names inside it. +- A name that starts with an underscore is inserted in square brackets, such as `[_Name]`. + +Esc, Left, Right and Backspace close the list. Left also moves the caret, and Backspace also deletes. Scrolling the editor with the mouse wheel closes the list too. + +> [!NOTE] +> In twinBASIC BETA 1005, typing `(` to accept a function from the list doubles the bracket: `Area` becomes `Area(()`. Accept the function with Tab or Enter instead. It gives `Area()` with the caret between the brackets, so type the arguments without the opening bracket. + +The Debug Console has the same list in its entry box. See [Debug Console](../Debugging/Debug-Console). + +## Signature help + +Signature help is a box above the line that shows the parameters of the procedure you are calling. It opens when you type `(` after a procedure's name, after each `,`, and on a space after a procedure called without brackets, such as `MsgBox `. In the MANUAL mode only Ctrl+Space opens it. + +The box shows the whole signature: `Function Area(ByVal width As Long, ByVal height As Long) As Long`. An optional parameter is in square brackets, with its default value. The parameter you are writing is highlighted. It is the first after the `(` and the next one after each `,`. When a procedure has several overloads, the box lists them one under the other, each with its own highlighted parameter. `InStr(` shows both forms of `InStr`. + +![The signature-help box open above a line reading i = InStr(), listing the two forms of InStr one under the other. In the first, ByRef String1 As Variant is highlighted, and in the second, ByRef Start As Variant. Under them is the first line of the documentation of InStr.](Images/SignatureHelp.png){:width="1430" height="126"} + +Under the signature is the first line of the procedure's documentation. For a procedure of your own that has none, the box says that no further information is available, and suggests the `Description` attribute above the procedure. The option **IDE: Signature-Help Show First Line Of Docs** turns that line off, and then only the signature shows. + +F1 expands the box to the whole documentation, shown as formatted text. F1 again collapses it. The fold icon at the left of the box does the same when you click it. + +![The signature-help box above a line reading MsgBox(), expanded with F1. Under the signature of MsgBox, with Prompt highlighted, the documentation scrolls in the box as formatted text: the name MsgBox and VBA.Interaction, a description, and the Syntax and Parameters sections.](Images/SignatureHelp_Expanded.png){:width="1399" height="306"} + +Esc, Up, Down and Backspace close the box. Typing `)` over the closing bracket that the editor added with `(` leaves the box open, and so does Enter. + +## Hover + +Rest the pointer on a word for a second and a popup shows what the compiler knows about it. The popup stays open when you move the pointer onto it. **IDE: Show Information When Hovering** turns it off. + +- For a function, the popup shows its declaration and the module that holds it. Without documentation it suggests the `Description` attribute. +- For a local variable, it shows `local variable` and the variable's `Dim` line. +- For a class, it shows the instance size, the members, the fields, the implemented interfaces and the events. +- For a keyword, a string and the functions of the VBA library, it shows nothing. + +![The hover popup over Total in the line cost = stock.Total(0.2). It shows the declaration Function Total(ByVal taxRate As Double) As Double, then Total in FeatureTour._Inventory, the class that holds it, and the suggestion to add a Description attribute above the procedure.](Images/Hover.png){:width="818" height="166"} + +**IDE: Show Advanced Info On Hover** adds the offset of each member in the class's virtual table. [IDE Options](../Customising/IDE-Options) describes it, and [Modern IDE Features](../../../Features/Compiler-IDE/IDE-Features) lists the rest of what the popup shows. + +Hovering over a wavy underline shows the compiler's message, as `TBnnnn:` followed by the text. See [Diagnostics](Diagnostics#in-the-code). + +While a program is stopped in the debugger, the popup also evaluates the expression under the pointer. + +The hover text is in English whatever the [language of the IDE](../Customising/#the-language-of-the-ide) is. + +## Inline code hints + +The compiler sends a short note for some lines, and the editor draws it after the end of the line, in italics. Most are the name of the block that an `End` line closes, such as `End Function` followed by an arrow and `Twice`. On an enum member that has no explicit value, the note is the value, such as `= 1 << 0`. + +![An Enum named MyFlags marked with the Flags attribute, with four members flag1 to flag4. The editor's inline hints after the members read 1 << 0 to 1 << 3, each with the value in hexadecimal, from &H00000001& to &H00000008&.](../../Reference/Images/flags-attribute.png){:width="357" height="142"} + +![The function Total of a class with its inline code hints shown in grey italics after the ends of its blocks: each End If followed by an arrow and the condition of its If, Next by i = 0 To count - 1, and End Function by Total.](Images/Hints.png){:width="562" height="328"} + +The notes are hidden. They show while you hold Ctrl with the focus in the code, and hide again when you let go. With **Always show IDE Inline Code Hints** on, they stay on screen. The command `tbEditor_ToggleShowCodeHints` switches them on and off without a key. + +## CodeLens + +A CodeLens bar above a procedure runs the procedure from the editor, without starting the whole program. The procedure has full access to your code: it can use constants, call other functions both intrinsic and user-defined, call APIs, and print to the Debug Console. + +A bar appears above a procedure in a standard module when the procedure has no required parameters. That includes a `Public` or `Private` `Sub`, a `Function`, a `Property Get`, and a procedure whose only parameters are `Optional`. A procedure in a class, a form or a UserControl has no bar. The bar reads **run** with the module and procedure name, such as **▶ run MyModule.CheckScale**. + +![The code editor with a run MyModule.CheckScale CodeLens bar above the CheckScale sub, and its output in the DEBUG CONSOLE below](../../Features/Images/351d0147-cad3-4e16-89e5-0a9e43496740.png){:width="640" height="669"} + +Click the bar to run the procedure. The DEBUG CONSOLE prints `Executing 'Project.Module.Procedure'...`, whatever the procedure prints, and the time it took. The focus does not go back to the code. If the project has a compile error, nothing runs, and the console says so: *(compile error: codegen error; check for compilation errors)*. + +For example, this `Public Sub` in a module gets a bar: + +```tb check_build +Public Sub RunTest() + Debug.Print "Hello from CodeLens" +End Sub +``` + +The Project Setting **CodeLens - Show Run Procedure** turns the bars on and off for the project. It is on by default. [Project Settings](../Project-Settings#show-run-procedure) describes it. The editor's **...** menu has the same setting as **Show CodeLens Run Procedure**. + +## See Also + +- [IDE Options](../Customising/IDE-Options) -- the IntelliSense mode and the hover options +- [Editor](Editor) -- Enter, Auto Prettify and the editing commands +- [Diagnostics](Diagnostics) -- the problems the compiler reports +- [Debug Console](../Debugging/Debug-Console) -- where CodeLens output appears diff --git a/docs/IDE/Code/Navigating.md b/docs/IDE/Code/Navigating.md new file mode 100644 index 000000000..cd0e5a176 --- /dev/null +++ b/docs/IDE/Code/Navigating.md @@ -0,0 +1,151 @@ +--- +title: Navigating +parent: Writing code +grand_parent: IDE +nav_order: 3 +permalink: /tB/IDE/Code/Navigating +redirect_from: + - /tB/IDE/Project/Outline + - /tB/IDE/Project/OpenEditors + - /tB/IDE/Project/History +index: + - outline view + - outline window + - outline panel + - history panel + - history window + - bookmarks +--- + +# Navigating + +This page covers the commands that take the caret somewhere else in the code, and the three panels that list where you can go: Outline, Open Editors and History. The three combo boxes above the code are another way to move, described under [Editor](Editor#the-navigation-combo-boxes). + +## Go To Definition + +**Go To Definition** takes the caret to where the name under it is declared. Put the caret on the name and press F12 or Shift+F2, or choose **Go To Definition** from the right-click menu or **Definition** from the **View** menu. + +Where the caret lands: + +| The name is | The caret goes to | +|---|---| +| A local variable | Its `Dim` line. | +| A procedure in another module | That file, on the line after the procedure's declaration. | +| A member of a class, reached through an object variable | The class's file, on the line after the member's declaration. | +| A class | The line after its `Class` line. | +| An enum member | Its line. | +| A control, in a form's code | The top of the form's code file, not the form designer. | +| A member of a package, or of the VBA library | A new tab with the package's source. The tab is read-only. | +| A keyword, or text in a string | Nowhere. | + +> [!NOTE] +> In twinBASIC BETA 1005, Ctrl+Up and Ctrl+Down, and Go To Definition for most names, land on the line after the declaration, not on the declaration itself. Press Up once to reach it. + +Ctrl+click does not follow a name, and the editor has no Peek Definition. + +## Last Position + +**Last Position** takes the caret back to where it was before. Press Ctrl+Shift+F2, choose **Last Position** from the **View** menu or the right-click menu, or press the "back" button of the mouse while the pointer is over the editor. It puts the caret at the start of the line. + +The editor remembers 20 positions, and each use takes one off, newest first, so repeating the command walks back. A position is recorded: + +- when you use **Go To Definition**, even if it found nothing; +- when you edit a line. Edits on the same line, or on the next line, extend the newest position instead of adding one. + +Moving the caret alone records nothing. The positions are not saved with the project. + +## Bookmarks + +A bookmark marks a line so that you can return to it. A bookmarked line shows a light-blue box with a black border in the margin. + +![A module with two bookmarked lines, the first lines of the procedures Twice and Third. Each shows a light-blue box with a black border in the margin, left of its line number, and a label reading Bookmark has an arrow up to the lower one.](Images/Bookmarks.png){:width="565" height="372"} + +| Command | Key | +|---|---| +| Toggle Bookmark | Ctrl+B | +| Previous Bookmark | Ctrl+M | +| Next Bookmark | none | +| Clear Bookmarks (Document) | none | +| Clear Bookmarks (Project) | none | + +The five commands are on the right-click menu of the editor. Next Bookmark has no default key, so give it one under [Keyboard shortcuts](../Customising/Keyboard-Shortcuts). The commands are `tbEditor_NextBookmark`, `tbEditor_ClearBookmarks` and `tbEditor_ClearBookmarksProject`. + +**Previous Bookmark** and **Next Bookmark** go to the nearest bookmark above or below the caret, then on through the other files of the project in the order of the Project Explorer tree, and round to the start again. They open the file that holds the bookmark, and put the caret at the start of its line. + +A bookmark moves with its line when you insert or delete lines above it. The project keeps its bookmarks when you close and reopen it. + +## Go to a line + +**Edit → Go To Line/Column...** opens a small box over the editor. Click the **Ln**, **Col** field in the status bar to open the same box. The box tells you how many lines the file has. Type a line number, or `line:column`, such as `12:5`, and press Enter. Text that is not a position, or a line number beyond the end, leaves the box as it was. + +The command has no key. Ctrl+G is the key of the Debug Console. + +## Move by procedure and by problem + +| Command | Key | What it does | +|---|---|---| +| Previous procedure | Ctrl+Up | Moves the caret to the first line of the body of the previous procedure, and scrolls so that it is near the top. | +| Next procedure | Ctrl+Down | The same for the next procedure. | +| Next problem | Alt+F8 | Opens a small view of the next problem in the current file, with a count such as *1 of 4 problems*. | +| Previous problem | Shift+Alt+F8 | The same for the previous problem. | + +The procedure commands stop at the first and the last procedure. The problem commands go through the problems of the current file in the order of their severity, errors first, and wrap round. They do not count hints. + +Ctrl+Alt+Left and Ctrl+Alt+Right fold and unfold every procedure; see [Folding](Editor#folding). + +## Outline + +The **OUTLINE** panel lists the symbols of the file you are editing. It is empty when no file is open. Click a row to move the caret to that symbol. A row shows the symbol's name and the compiler's note about it. + +![The Outline pane listing the two top level items of the open file, the module MainModule and the class GlobalSearchAddIn.](../Images/Outline_1.png){:width="300" height="96"} + +> [!NOTE] +> In twinBASIC BETA 1005, the Outline lists only the top-level types of a file, such as the module or the classes. The procedures, fields and properties inside them are missing, rows have no expand arrow, and the filters of the **...** menu below change nothing you can see. To go to a procedure, use the [combo boxes](Editor#the-navigation-combo-boxes). + +The **...** button in the header of the panel opens a menu of eleven items that you tick. The IDE keeps them for each project. + +**Sort Alphabetically** +: Sorts the rows by name. It is off by default. + +**Show Class Fields**, **Show Module Variables**, **Show Enumerations**, **Show User Defined Types**, **Show DLL Declares**, **Show Constructors**, **Show Properties (Get/Let/Set)**, **Show Procedures**, **Show Variables** and **Show Parameters** +: Each shows or hides the symbols of that kind. All are on by default. + +![The OUTLINE panel listing the module Helpers, with its ... menu open: Sort Alphabetically, not ticked, then a separator and the ten Show items from Show Class Fields to Show Parameters, all ticked.](Images/Outline_Menu.png){:width="518" height="301"} + +The menu is in English whatever the IDE language is. + +## Open Editors + +The **OPEN EDITORS** panel lists the files that are open in the editor, one row for each tab. Each row shows the file's icon and its full path. The rows are sorted by path, not in the order of the tabs. Click a row to select that tab and put the focus in the code. The panel is empty when no project is open. + +![The Open Editors pane listing two open files of SampleProject, each with its own file type icon and its full path: /SampleProject/Sources/frmMain.tbform and /SampleProject/Sources/frmMain.twin.](../Images/OpenEditors_1.png){:width="360" height="92"} + +## History + +The **HISTORY** panel lists the places where you edited, so that you can go back to them. The panel is empty when no project is open. + +![The same panel after the form and the code of a project have been edited, listing two entries both named frmMain, timestamped 18:02 and 16:41 at the right edge.](../Images/History_1.png){:width="300" height="96"} + +An entry has a name and a time such as `18:02` at its right. The name is built from the three combo boxes at the moment of the edit: the component, the object and the procedure, joined by dots, such as `Watcher.src.Changed`. Outside a procedure, the name is the component alone. + +- Editing in a place that has no entry adds one at the bottom of the list. +- Editing in the place of the newest entry only updates its time and line. +- Editing in a place that has an older entry removes the older one, so each place is listed once and the newest edit is at the bottom. +- The list holds at most 10 entries. The oldest goes first. + +Click an entry to open its file at the line, in column 1. An entry follows the edits above it, so it keeps pointing at the same line. An entry whose file no longer exists is removed. The IDE keeps the list with the project and brings it back when you reopen it. + +Point at an entry to see its tooltip: the path of the file, the line as `[line: n]`, and the full date and time. For a form, which has no lines of code, the tooltip reads `[line: undefined]`. + +![The pointer resting on the 18:02 frmMain entry, which raises a tooltip reading /SampleProject/Sources/frmMain.tbform [line: undefined] above a second line giving Fri Jan 09 2026 18:02:51 GMT+0000 (GMT+00:00).](../Images/History_2.png){:width="360" height="120"} + +![The pointer on the 16:41 entry instead, its tooltip reading /SampleProject/Sources/frmMain.twin [line: 4] above Fri Jan 09 2026 16:41:58 GMT+0000 (GMT+00:00).](../Images/History_3.png){:width="360" height="120"} + +The three panels are in no built-in layout, so each comes up floating the first time you show it from the **View** menu. See [Panel layouts](../Customising/Panel-Layouts). + +## See Also + +- [View menu](../Reference/Menu/View) -- the menu that shows the panels +- [Editor](Editor) -- the combo boxes, tabs and editing commands +- [Finding](Finding) -- search the file or the project +- [Diagnostics](Diagnostics) -- the list of problems diff --git a/docs/IDE/Code/index.md b/docs/IDE/Code/index.md new file mode 100644 index 000000000..775b00e01 --- /dev/null +++ b/docs/IDE/Code/index.md @@ -0,0 +1,65 @@ +--- +title: Writing code +parent: IDE +nav_order: 4 +permalink: /tB/IDE/Code/ +has_toc: false +--- + +# Writing code + +The code editor and the panels you use beside it: tabs, editing commands, IntelliSense, navigation, searching and the list of problems. + +- [**Editor**](Editor) --- the editor panel and its tabs, the three navigation combo boxes, editing commands, formatting, Auto Prettify and the command palette. +- [**IntelliSense**](IntelliSense) --- the completion list, signature help, hover, inline code hints and CodeLens. +- [**Navigating**](Navigating) --- Go To Definition, Last Position, bookmarks, Go To Line/Column, and the Outline, Open Editors and History panels. +- [**Finding**](Finding) --- the **Find / Replace** panel, searching the whole project, the Quick Find widget and regular expressions. +- [**Diagnostics**](Diagnostics) --- the panel that lists the errors and warnings the compiler finds, and how to filter and copy them. + +The commands of these pages are listed by menu in the [Edit menu](../Reference/Menu/Edit) and [View menu](../Reference/Menu/View) reference pages. Everything that sets how the editor looks, such as its fonts, the minimap and the IntelliSense mode, is in [IDE Options](../Customising/IDE-Options). + +## The editor panel + +The editor is a panel titled **EDITOR**. **View → EDITOR** shows it. From top to bottom, it holds: + +1. The header, with the **...** button at the right. The button opens the menu of editor options, described in [IDE Options](../Customising/IDE-Options#the-code-editors-menu). +2. The tab strip, with one tab for each open file and the **Tabs List** button at its right end. +3. The three navigation combo boxes, which you can place above the code, below it or hide. +4. The code. A margin at its left holds line numbers, fold arrows, bookmarks and the marks for breakpoints. A minimap can sit at its right. + +![The EDITOR panel with the module Helpers open and two more files, Types.twin and Inventory.twin, as tabs. Labels with arrows name its parts: Options, the ... button at the right of the header; Tab strip; Tabs List, the button at the right end of the tab strip; Combo boxes, the three boxes above the code; Margin, with the line numbers and the fold arrows; and Minimap, at the right of the code.](Images/Editor_Panel.png){:width="1165" height="644"} + +The status bar shows the position of the caret, **Ln** and **Col**, at its right end while a code editor is active. Clicking it opens [Go To Line/Column](Navigating#go-to-a-line). + +### What the compiler supplies + +The editor does not understand twinBASIC by itself. The compiler runs beside the IDE, and the editor shows what the compiler reports about the file you are editing: + +- the colours of the code, which follow the meaning of each word; +- the problems, as wavy underlines and in the [Diagnostics](Diagnostics) panel; +- the completion list, signature help and hover text; +- the contents of the combo boxes and of the Outline; +- the fold ranges, the inline hints and the CodeLens bars; +- the corrections of Auto Prettify. + +The editor itself does the text work: selecting, moving by word and line, several cursors at once, commenting, folding, word wrap and finding. + +### The panels you use while writing + +Five panels belong with the editor. All of them are in the **View** menu. + +| Panel | Page | +|---|---| +| **DIAGNOSTICS** | [Diagnostics](Diagnostics). Docked in the default layout. | +| **FIND / REPLACE** | [Finding](Finding). | +| **OUTLINE** | [Navigating](Navigating#outline). | +| **OPEN EDITORS** | [Navigating](Navigating#open-editors). | +| **HISTORY** | [Navigating](Navigating#history). | + +Only **DIAGNOSTICS** is docked in the default layout. The other four are in no built-in layout, so the first time you show one, it comes up as a floating panel. You can dock it, and save the arrangement as a [panel layout](../Customising/Panel-Layouts). + +## See Also + +- [Edit menu](../Reference/Menu/Edit) -- the menu of editing commands +- [View menu](../Reference/Menu/View) -- the menu that shows the panels +- [Customising the IDE](../Customising/) -- fonts, themes, shortcuts and layouts diff --git a/docs/IDE/Customising/IDE-Options.md b/docs/IDE/Customising/IDE-Options.md new file mode 100644 index 000000000..3f09a64ea --- /dev/null +++ b/docs/IDE/Customising/IDE-Options.md @@ -0,0 +1,236 @@ +--- +title: IDE Options +parent: Customising the IDE +grand_parent: IDE +nav_order: 1 +permalink: /tB/IDE/Customising/IDE-Options +has_toc: false +--- + +# IDE Options + +The **IDE Options** dialog sets the IDE's fonts, the behaviour of the code editor, IntelliSense and the Project Explorer, and a few settings for the compiler. + +Every option is kept per user, in the registry, never in a project. [Where the IDE keeps its settings](.#where-the-ide-keeps-its-settings) gives the details. + +## Open the dialog + +Choose **Tools → IDE Options...**. The command has no default key. + +![The IDE Options dialog at its default settings, scrolled to its top. The first rows set the sizes and fonts of the IDE and the code editor, then two drop-downs that place the editor's combo boxes and show white space. Three LLVM Compiler rows follow, and a run of tick boxes. Change Licence sits at the bottom left, with a greyed-out Save Changes and a Close button at the right.](../Menu/Images/Menu_Tools_IDEOptions.png){:width="628" height="594"} + +The dialog is titled **twinBASIC IDE Options**. It is a scrolling list. Each row has the option's label at the left and its control at the right. Rows are one of four kinds: + +- a tick box; +- a drop-down list; +- a number box, which accepts any number and sets no limits; +- a font list, which holds the fonts installed on the PC, in ascending order. + +The labels are in English whatever language the IDE uses. The dialog has no tooltips. + +The buttons are, from left to right: + +**Change Licence...** +: Changes the twinBASIC licence. It is shown only while a project is open. + +**Save Changes** +: Saves the options and applies them. It is greyed out until you change an option. + +**Close** +: Closes the dialog. Esc does the same. The dialog edits a copy of the options, so **Close** discards every change made since you opened it. + +When the IDE starts for the first time and has no saved options, it opens this dialog by itself, after the licence agreement and the question about error reporting. The **Close** button is hidden then. An option that has no saved value takes its default. + +> [!NOTE] +> The font lists need a project. With no project open, the **IDE Font** and **Code Editor Font** rows show the grey text *<font list unavailable: load a project first>* in place of a list. **Code Editor Font** reads empty even with a project open, because its default, Monaco, is not a font that Windows installs. + +## When a change applies + +**Save Changes** applies every change at once. No restart is needed, with two exceptions. + +**Eight options restart the compiler.** The compiler is the same one that **Tools → Restart the compiler** restarts. These options take effect when it starts again: + +- **IDE: Show Advanced Info On Hover** +- **IDE: Show Hidden Members** +- **IDE: Use new Handles/Implements syntax** +- **IDE: Show memory and resource usage** +- **IDE: Auto refresh Explorer icon cache after build** +- the three **LLVM Compiler** options + +No question is asked. About two seconds after **Save Changes**, the **tB Services** field of the status bar goes from **OPERATIONAL** to **UNAVAILABLE**, then **LIMITED**, then **OPERATIONAL** again. A **Please wait...** box with a percentage shows for a moment, and the DEBUG CONSOLE prints `restarting from MEMORY` and the path of the project. The restart starts from the project in memory, so unsaved edits are kept. A change to an **LLVM Compiler** option also ends the LLVM cache process first. + +**The real title bar needs a restart of the IDE.** See [Window and status bar](#window-and-status-bar). + +## Fonts and text + +**IDE Font Size** +: Default 13px. The size of the IDE's own text. The list runs from 8px to 30px in steps of 1px, and 13px is the normal size: the whole IDE is scaled in proportion. Ctrl+Alt++ makes the text 1px bigger with no upper limit, so it can pass 30px, and Ctrl+Alt+- makes it 1px smaller, down to 8px. Both keys save the new size at once. + +**IDE Font** +: Default Segoe UI. The typeface of the IDE's own text. Segoe UI is also the fallback. + +**Code Editor Font Size** +: Default 13px. The size of the text in the code editor, from 8px to 30px. The **Font Size** item of the editor's [menu](#the-code-editors-menu) sets the same option. + +**Code Editor Font** +: Default Monaco. The typeface of the code editor. With the default, the editor's list of typefaces is Monaco, Consolas, Courier New and then the system's monospace font. On a PC without Monaco, the editor draws its text in Consolas. + +**Code Editor Font: Use Ligatures** +: Default off. Turns the font's ligatures on in the code editor. A ligature draws a run of characters, such as `->`, as one mark. + +## Code editor + +**Tab Size** +: Default 4. The width of a tab in the code editor. A change applies at once to the editor you are in. A file open in another tab takes the new size when you select its tab. + +**Show Code Editor Combo Selectors** +: Default above. Where the editor's three navigation combo boxes sit: **above** the code, **below** it, or **none**, which hides them. The **Show Navigation Bar** item of the editor's [menu](#the-code-editors-menu) sets the same option. + +**Monaco: Render Whitespace** +: Default none. Which white-space characters the editor draws as visible marks: **all**, **none**, **boundary**, **selection** or **trailing**. The **Render Whitespace** item of the editor's [menu](#the-code-editors-menu) sets the same option. + +**IDE: Sticky scrolling in code editor** +: Default off. Keeps lines at the top of the editor that name the section you are in, such as the module, a region, a procedure or a **With** block, while you scroll. The **Sticky Scroll** item of the editor's [menu](#the-code-editors-menu) sets the same option. + +**Always show IDE Inline Code Hints** +: Default off. The hints the editor draws inside the code, such as the annotation at the end of a block. With this option off, the hints show only while you hold Ctrl. With it on, they always show. The **Code Hints Always Visible** item of the editor's [menu](#the-code-editors-menu) sets the same option. + +**IDE: Single row of code panel tabs** +: Default off. Keeps the editor's tabs on one row. The **Tabs List** button and the commands that scroll the tabs reach the tabs that do not fit. The **Show Single Row Of Tabs** item of the editor's [menu](#the-code-editors-menu) sets the same option. + +## IntelliSense and hover + +**IDE: Intellisense mode** +: Default MODERN. When the IntelliSense list opens. See [Intellisense modes](#intellisense-modes). + +**IDE: Signature-Help Show First Line Of Docs** +: Default on. The collapsed signature-help box includes the first line of the procedure's documentation. F1 expands the box. + +**IDE: Show Information When Hovering** +: Default on. Shows information in a popup when you point at code in the editor. + +**IDE: Show Advanced Info On Hover** +: Default off. Adds detail to the hover popup. Pointing at a class lists each member with its offset in the virtual table, such as `[voffset &H001C] Function Item(...)`, for a class from a package and for a class of your own. [Modern IDE Features](../../../Features/Compiler-IDE/IDE-Features) lists the rest of what the popup shows. The **Show Advanced Info On Hover** item of the editor's [menu](#the-code-editors-menu) sets the same option. A change restarts the compiler. + +### Intellisense modes + +The mode applies to the code editor and to the entry box of the Debug Console. Ctrl+Space opens the list in all three modes. Once a list or a signature-help box is open, every key refreshes it. + +| Mode | The list opens | +|---|---| +| **MODERN** | As you type a letter, a `.`, a `(` or an `=`, and after `As `. A signature-help box opens on `(`. In the Debug Console, the list opens on the first letter and on `.`. | +| **LEGACY** | After a `.`, a `(`, an `=`, a `,` or a space. Letters do not open it. In the Debug Console, the list opens only on `.`. | +| **MANUAL** | Only when you ask for it with Ctrl+Space. No key opens the list or the signature-help box, not even `.` or `(`. In the Debug Console, typing opens nothing. | + +## Project Explorer, tabs and start-up + +**Restore Editors State When Opening Project** +: 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. + +**File Explorer: 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. + +**File Explorer: Single-Click Preview** +: Default off. With the option on, one click on a file in the Project Explorer opens it in a *preview tab*, whose title is in italics. There is one preview tab at a time: a single click on another file replaces it. A double-click makes the tab permanent, and so does editing the file. With the option off, one click only selects the file, and a double-click opens it in an ordinary tab. The option also decides whether the files that the debugger steps into, and the commands that switch between a form and its code, open as preview tabs. + +**IDE: Check referenced packages are up-to-date** +: Default on. Half a second after a project loads, the IDE asks the package server, over HTTPS, whether each package the project references has a newer version. It does so for every package that has a publisher and is not a compiler package. For each package, the DEBUG CONSOLE prints a line that starts `[PACKAGE CHECK]`, followed by the package's name and version. A tick after the line means the package is current. A cross means it is not, and the line ends with `-- v is available`. If the request fails, the line `[PACKAGES] version checks failed` appears, with a hint to check the connection. + +## Form designer and Toolbox + +**IDE: Less transparency for invisible controls** +: Default off. A control whose **Visible** property is **False** is drawn faded in the form designer. With this option on, it is drawn greyed but almost opaque, at 91 per cent. With it off, it is drawn at 70 per cent opacity. + +**IDE: Show toolbox icons only** +: Default on. The Toolbox shows icons without control names. The **Show Control Names** item on the Toolbox's **...** button turns the names on and saves that choice at once. + +**IDE: Show in-place editing toolbar in the form designer** +: Default on. The toolbar for editing a control in place. The command `tbFormDesigner_ToggleInPlaceEditingToolbar` hides or shows it for the current session, and does not change the option. + +## Compiler and LLVM + +Changing any of these options restarts the compiler. The three **LLVM Compiler** options are also described in [Getting Started with LLVM](../../../LLVM/Getting-Started#general-llvm-options). + +**LLVM Compiler: Maximum number of threads** +: Default 1. The number of threads the LLVM compiler can use. An empty value or 0 means 1. + +**LLVM Compiler: Complex procedure reporting threshold (milliseconds)** +: Default 10000. A procedure that takes at least this many milliseconds to compile is listed in the Debug Console. An empty value or 0 means 10000. + +**LLVM Compiler: Keep cache process alive after exiting the IDE** +: Default on. Keeps the LLVM cache process running after the IDE closes. + +**IDE: Show Hidden Members** +: Default off. Shows members marked `[Hidden]` in tooltips, hovers and the other information the editor shows about code. It is not the same switch as the Project Settings **Intellisense: Show Hidden Members** and **Debugger: Show Hidden Members**. + +**IDE: Use new Handles/Implements syntax** +: Default off. When you choose an event of a **WithEvents** variable in the editor's procedure combo box, the handler that the editor inserts has a **Handles** clause. With the option off, the handler is `Private Sub src_Changed(ByVal n As Long)`. With it on, it is `Private Sub src_Changed(ByVal n As Long) Handles src.Changed`. The name of the procedure is the same in both. + +**IDE: Auto refresh Explorer icon cache after build** +: Default on. After each build, tells Windows Explorer to refresh its cache of icons. Without the refresh, Explorer can go on showing a program's old icon after a build that changed it. + +## Window and status bar + +**IDE: Show memory and resource usage** +: Default off. Adds a block of figures at the right-hand end of the status bar, after the social icons, for example `174.5MB in use | 96 ui objects | 16 gdi objects`. The figures are the memory in use, in megabytes, and the numbers of USER objects and GDI objects. Until the first figures arrive, the block reads `?MB in use | ? ui objects | ? gdi objects`. The block shows once a project has loaded, and it is hidden again when the project closes. The Debug Console prints a critical error when the limit of GDI handles is exceeded, and advises you to restart the compiler. A change restarts the compiler. + +**IDE: Show real OS titlebar** +: Default off. Gives the IDE window the title bar of Windows, in place of the IDE's own title text and its minimise, maximise and close buttons. **Save Changes** does not change the window at once. The new title bar appears the next time the IDE starts, and the IDE's own buttons stay until then. Choosing the option off again also takes a restart. The choice is also saved in the registry value `Window\RealTitlebar`, under the IDE's key, and **Save Changes** keeps the two in step. + +## Settings with no row in the dialog + +Some IDE settings are saved with the options but have their own commands. + +- The theme is chosen under **Window → Theme**. See [Themes](Themes). +- The language is chosen under **Window → Language**. See [The language of the IDE](.#the-language-of-the-ide). +- The editor's minimap, its folding controls, its indent guides and its line numbers are set from the editor's menu, below. +- The Project Explorer's file or object view is set with its toggle button. It is kept between sessions. +- The panel layout and the Panel Features switches are described in [Panel layouts](Panel-Layouts). + +### The code editor's menu + +The **...** icon in the header of the editor panel opens a menu of options. The first eleven items are IDE options, and each is saved at once. A combo item lists its values. + +**Show Code Folding** +: **never**, **when hovering** or **always**. The default is when hovering. + +**Render Whitespace** +: The same option as **Monaco: Render Whitespace**. + +**Font Size** +: The same option as **Code Editor Font Size**. + +**Show Navigation Bar** +: The same option as **Show Code Editor Combo Selectors**. + +**Show Indent Guides** +: Shown by default. + +**Show Line Numbers** +: Shown by default. + +**Code Hints Always Visible** +: The same option as **Always show IDE Inline Code Hints**. + +**Sticky Scroll** +: The same option as **IDE: Sticky scrolling in code editor**. + +**Show MiniMap** +: Shown by default. + +**Show Advanced Info On Hover** +: The same option as **IDE: Show Advanced Info On Hover**. A change restarts the compiler. + +**Show Single Row Of Tabs** +: The same option as **IDE: Single row of code panel tabs**. + +Two more items, **Auto Prettify Code** and **Show CodeLens Run Procedure**, are not IDE options. They are Project Settings, so they belong to the project, not to you. Both are on unless the project turns them off. + +## See Also + +- [Customising the IDE](.) -- where the settings are kept, and the language of the IDE +- [Tools menu](../Reference/Menu/Tools) -- the menu that opens the dialog +- [Getting Started with LLVM](../../../LLVM/Getting-Started) -- the three LLVM options in use diff --git a/docs/IDE/Customising/Keyboard-Shortcuts.md b/docs/IDE/Customising/Keyboard-Shortcuts.md new file mode 100644 index 000000000..c097fb8d0 --- /dev/null +++ b/docs/IDE/Customising/Keyboard-Shortcuts.md @@ -0,0 +1,129 @@ +--- +title: Keyboard Shortcuts +parent: Customising the IDE +grand_parent: IDE +nav_order: 4 +permalink: /tB/IDE/Customising/Keyboard-Shortcuts +has_toc: false +--- + +# Keyboard shortcuts + +Every IDE command can have keys, and you can change them. You write your changes in *groups*, and the IDE layers the groups that are switched on on top of the built-in keys. + +## How shortcuts are layered + +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. +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. + +To remove a default key, override the command in a group of your own with no keys. Clicking the built-in group does not remove it: the IDE answers that you cannot switch off the built-in shortcuts, and that you should create a group to override or disable single commands. + +## Switch groups on and off + +Point at **Window → Keyboard Shortcuts**. The submenu lists the built-in group, which is always ticked, then your groups as **Custom** *n*. *name*, then **Manage Keyboard Shortcuts...**. + +![The Window menu with Keyboard Shortcuts highlighted and its submenu open to the right: a ticked Default Built-in Keyboard Shortcuts above Manage Keyboard Shortcuts.](../Menu/Images/Menu_Window_KeyboardShortcuts.png){:width="404" height="159"} + +A tick means the group is on. Click a group to switch it on or off. The change applies at once: menu items show their new keys. The IDE keeps the set of groups that are on. + +The first ten groups have commands that switch them: `tbKeyboardShortcuts_ToggleActiveKeyboardShortcutGroupCustom1` to `...Custom10`. **Manage Keyboard Shortcuts...** has the command `tbKeyboardShortcuts_ShowManageKeyboardShortcuts`. None of these has a default key. + +Menu items show their keys in the order Ctrl+Shift+Alt+key. The key names are in the language of the IDE. + +## Manage keyboard shortcuts + +Choose **Window → Keyboard Shortcuts → Manage Keyboard Shortcuts...** to create, edit and delete groups. + +![The Manage Keyboard Shortcuts dialog. A paragraph across the top explains that groups are merged in the order listed, and how to override an existing shortcut or add one alongside it. The left pane holds the single built-in group, DEFAULT; the right pane shows its JSON with the Edit as JSON box ticked, mapping names such as tbEditor_SelectAll and tbEditor_ClipboardCopy to KEYDOWN sequences. Save Changes is greyed out.](../Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png){:width="1016" height="736"} + +The text across the top of the dialog describes the layering, and how to override a shortcut and how to add one. The list on the left holds ` (built-in group)`, which is read-only, and then your groups. Each of yours has a close icon that deletes it. The pane on the right holds the selected group as JSON in a monospace text area. + +The **Edit as JSON** box is ticked. If you clear it, the pane shows only the text *Keyboard editor coming soon....*, so keep it ticked. + +![The same dialog with the Edit as JSON box cleared: the editing pane is empty apart from the line Keyboard editor coming soon.](../Menu/Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png){:width="1016" height="736"} + +**Add New...** +: Asks for a name, then creates a group that holds one sample entry, `{"tbMisc_ShowTestMessage":["{KEYDOWN}{CTRL}0"]}`. Ctrl+0 then opens a test dialog that reads *This is just a test modal dialog.* A name that starts with `<` is refused with a message that says *Custom panel name cannot begin with '<'*, although it means a group name. + +**Save Changes** +: Greyed out until you change something. It saves the groups, switches on the groups you added, and applies the result. + +**Cancel** +: Discards every change made in the dialog. + +Text that is not valid JSON turns red. The IDE ignores a group whose text is not valid when it merges the groups. + +> [!WARNING] +> In twinBASIC BETA 1005, a group whose name contains a vertical bar (`|`) can break the list of groups that are on. The IDE saves the list as the names, each between two bars, such as `|Mine||Other|`. A name with a bar inside it can match part of another name. For example, with groups named `A|B` and `B` both on, clicking `B` in the menu leaves it ticked, and clicking it again switches off both groups, and leaves a broken entry in the saved list. Nothing warns you. Do not use `|` in a group name. + +## 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. + +| Entry | Meaning | +|---|---| +| `"command": ["..."]` | Replaces every key that earlier groups gave the command. | +| `"+command": ["..."]` | Adds keys, and keeps the ones the earlier groups gave. | +| `"command": []` | Gives the command no key. `[""]` does the same. | +| `"command('argument')": ["..."]` | Binds the key to the command called with the argument. | + +An entry with an argument is always added to the keys of the command, with or without the `+`. The single quotes around the argument are optional. In the IDE, only `tbDebugConsole_ExecuteCode` takes an argument. + +This group adds a second key for word wrap, keeps only Ctrl+X for **Cut**, and binds Ctrl+Shift+J to run an expression in the Debug Console: + +```json +{ + "+tbEditor_ToggleWordWrap": ["{KEYDOWN}{CTRL}{SHIFT}Y"], + "tbEditor_ClipboardCut": ["{KEYDOWN}{CTRL}X"], + "tbDebugConsole_ExecuteCode('? 6*7')": ["{KEYDOWN}{CTRL}{SHIFT}J"] +} +``` + +The built-in group gives **Cut** two keys, Ctrl+X and Shift+Delete. The entry without a `+` replaces both with the one. Alt+Z still toggles word wrap, because the entry for it begins with `+`. + +## Shortcut strings + +A key string describes **one key press** with its modifiers. The IDE has no way to write a sequence of two key presses. + +The IDE changes the string to upper case. It then reads these parts, in this order: + +`{KEYDOWN}` +: The binding runs when the key goes down. A string without it, such as `CONTROL`, which the default group uses to hide the inline code hints, runs when the key is released. + +`{KEYDOWNREPEAT}` +: The binding also runs again while the key is held down. It is always written beside `{KEYDOWN}`. + +`{CTRL}`, `{ALT}` and `{SHIFT}` +: The modifier keys that must be held. + +The key +: One key name, such as `A`, `F5` or `ARROWLEFT`. + +For example, `{KEYDOWN}{KEYDOWNREPEAT}{CTRL}{SHIFT}ARROWLEFT` runs on Ctrl+Shift+Left, and again for as long as you hold the keys. + +The key names are `A` to `Z`, `0` to `9`, `F1` to `F19`, `ARROWLEFT`, `ARROWUP`, `ARROWRIGHT`, `ARROWDOWN`, `ENTER`, `SPACE`, `BACKSPACE`, `DELETE`, `TAB`, `ESCAPE`, `HOME`, `END`, `PAGEUP`, `PAGEDOWN`, `INSERT`, `CAPSLOCK`, `CANCEL`, `CONTROL`, `SHIFT`, `ALT`, `OTHERKEY` and these punctuation keys: `[ ] / + - ! , . & ( ) * : = \ { } #`. A name that is not in the list gives no binding. A backslash is written `\\` in JSON. + +A punctuation key written without `{SHIFT}` matches both the plain and the shifted form of the key. + +A digit with `{SHIFT}` never matches a key press. With Shift held, the key arrives as its shifted symbol, and the IDE looks that up in place of the digit. `{CTRL}{SHIFT}0` is therefore a string that cannot be pressed. + +The form `{ALLEXCEPT}KEY|KEY|...` binds every key in the table except the ones listed. Only the built-in command `tbIntellisense_ShowAfterKeyPress` uses it. + +## When two commands share a key + +Nothing warns you when a key is bound to more than one command. The IDE tries the commands in the order of its command table. A command runs only if its own context allows it, for example while a text editor has the focus. The IDE stops after the first command that has handled the key press completely. + +For example, with `tbMisc_ShowTestMessage` and `tbTheme_SwitchToggle` both on Ctrl+Shift+Y, both run: the theme switches and the test dialog opens. + +When a command fails, the Debug Console prints `command failed:` and the command's name in quotes. + +## See Also + +- [Window menu](../Reference/Menu/Window#keyboard-shortcuts) -- the **Keyboard Shortcuts** entry, and the list of the default shortcuts +- [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 new file mode 100644 index 000000000..cc6e16ffd --- /dev/null +++ b/docs/IDE/Customising/Panel-Layouts.md @@ -0,0 +1,308 @@ +--- +title: Panel Layouts +parent: Customising the IDE +grand_parent: IDE +nav_order: 3 +permalink: /tB/IDE/Customising/Panel-Layouts +has_toc: false +--- + +# Panel layouts + +A panel layout is a saved arrangement of the IDE's panels. You can rearrange the panels freely, save the result under a name, and switch back to it later. + +## What a layout keeps + +A layout keeps two things: + +- the **docked** panels: which panels are docked, where, and how much room each takes; +- the **floating** panels: the position and size of each one, in pixels. A floating panel that is hidden is not kept. + +The IDE also keeps the arrangement you are using now, and brings it back the next time it starts. Every change you make to the panels updates it. + +## Choose a layout + +Point at **Window → Panel Layouts**. The submenu lists, in this order: + +1. **Default Built-in Layout**, with the key Ctrl+#. +2. **Full Screen Editor Layout**, which has no key. +3. **Custom Layout (Unsaved)**, shown and ticked only while no named layout is in use. +4. One item for each layout you saved, labelled **Custom** *n*. *name*, in the order you created them. +5. **Save Current Panel Layout As...** and **Manage Panel Layouts...**. + +![The Window menu with Panel Layouts highlighted and its submenu open to the right: a ticked Default Built-in Layout with CTRL+#, Full Screen Editor Layout, then Save Current Panel Layout As and Manage Panel Layouts.](../Menu/Images/Menu_Window_PanelLayouts.png){:width="389" height="159"} + +A tick marks the layout in use. Choosing a layout applies it at once: the IDE closes every floating panel, builds the docked arrangement again and opens the floating panels the layout holds. + +Any change to the panels, such as dragging a splitter or moving a panel, puts the arrangement outside every named layout. The tick moves to **Custom Layout (Unsaved)**. The arrangement is not lost: save it, or leave it as it is. + +The first ten saved layouts have commands that switch to them: `tbPanels_SetActiveLayoutCustom1` to `tbPanels_SetActiveLayoutCustom10`. The built-in layouts have `tbPanels_SetActiveLayoutDefault` and `tbPanels_SetActiveLayoutFullScreen`. Only `tbPanels_SetActiveLayoutDefault` has a default key. You can give the others keys under [Keyboard shortcuts](Keyboard-Shortcuts). + +### 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. + +**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)**. + +You cannot change or delete a built-in layout. + +## Save a layout + +Choose **Window → Panel Layouts → Save Current Panel Layout As...**. A box titled *Save current IDE panel layout...* asks for a **Name**. Press Enter to save the arrangement under that name, and the new layout becomes the one in use. An empty name saves nothing. + +A name may be any text. If a layout of that name exists, the new one replaces it without asking. Do not start a name with `<`: **Manage Panel Layouts** refuses such names, and a layout saved as `` can never be applied, because the IDE looks for the built-in layout of that name first. + +## Manage layouts + +Choose **Window → Panel Layouts → Manage Panel Layouts...** to add and delete layouts and to edit them as text. + +![The Manage Panel Layouts dialog. Its left pane lists two built-in layouts, DEFAULT and FULLSCREEN, with DEFAULT selected; the right pane shows that layout JSON --- a docked tree of TOOLBAR, TOOLBOX and EDITOR entries with percentage sizes. Add New sits at the bottom left, with a greyed-out Save Changes and a Cancel button at the right.](../Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png){:width="1016" height="638"} + +The list on the left holds ` (built-in)` and ` (built-in)`, which are read-only, and then your layouts. Each of yours has a close icon that removes it. The pane on the right shows the selected layout as text, which you can edit unless it is a built-in layout. + +**Add New...** +: Asks for a name in a box that reads *<new name>*. Enter creates a layout that is a copy of the Default layout, and Esc cancels. A name that starts with `<` is refused with the message *Custom panel name cannot begin with '<'*. + +**Save Changes** +: Greyed out until you change something. It saves the layouts and, if a named layout is in use, applies it again. + +**Cancel** +: Discards every change made in the dialog. + +### The text of a layout + +A layout is one JSON object with two members, `docked` and `floating`. + +`docked` is a tree. A group holds other groups and panels, laid out in a row or a column: + +```json +{ + "type": "horizontal", + "variableSize": true, + "size": "0%", + "content": [ ... ] +} +``` + +`type` is `horizontal` or `vertical`. `size` is a CSS flex-basis: a percentage such as `79.0109%`, or `fit-content`. `variableSize` is `true` or `false`; copy the value from a layout that works. `content` lists the members of the group. + +A panel is a member with an `id` in place of `type` and `content`: + +```json +{ + "id": "EDITOR", + "variableSize": true, + "size": "0%" +} +``` + +`floating` is a list of floating panels, each as `{"id": ..., "left": ..., "top": ..., "width": ..., "height": ...}`, in pixels. The built-in layouts have an empty list. + +The panel ids are `TOOLBAR`, `TOOLBOX`, `EDITOR`, `DEBUG CONSOLE`, `PROBLEMS`, `CALL STACK`, `VARIABLES`, `PROJECT EXPLORER`, `PROPERTIES`, `FIND REPLACE`, `PACKAGE PUBLISHING`, `WEBPAGE1`, `MEMORY 1`, `IDE TRACE OUTPUT`, `OPEN EDITORS`, `HISTORY`, `OUTLINE` and `WATCHES`. A tool window that an add-in creates has an id that starts `ADDIN_`. + +Text that is not valid JSON turns red. If you save it anyway, the layout holds nothing. When you choose it, the IDE shows the warning *WARNING: bad panel layout data in '<name>'. Reverting to <DEFAULT> instead.* and applies the Default layout. A bad `floating` list shows *WARNING: bad floating panel layout data in '<name>'.* + +![The same dialog with FULLSCREEN selected instead, its JSON showing a TOOLBAR sized to fit its content, a TOOLBOX at about 8.5 per cent, and an EDITOR and PROPERTIES pair filling the rest.](../Menu/Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png){:width="1016" height="638"} + +The two built-in layouts, as text: + +
+<DEFAULT> (built-in) + +```json +{ + "docked": { + "type": "horizontal", + "variableSize": true, + "size": "0%", + "content": [ + { + "id": "TOOLBAR", + "variableSize": false, + "size": "fit-content" + }, + { + "type": "vertical", + "variableSize": true, + "size": "0%", + "content": [ + { + "type": "horizontal", + "variableSize": false, + "size": "79.0109%", + "content": [ + { + "type": "vertical", + "variableSize": false, + "size": "73.8657%", + "content": [ + { + "id": "TOOLBOX", + "variableSize": false, + "size": "13.0192%" + }, + { + "id": "EDITOR", + "variableSize": true, + "size": "0%" + } + ] + }, + { + "type": "vertical", + "variableSize": true, + "size": "0%", + "content": [ + { + "type": "vertical", + "variableSize": false, + "size": "67.9395%", + "content": [ + { + "id": "DEBUG CONSOLE", + "variableSize": false, + "size": "53.0193%" + }, + { + "id": "PROBLEMS", + "variableSize": true, + "size": "0%" + } + ] + }, + { + "type": "vertical", + "variableSize": true, + "size": "0%", + "content": [ + { + "id": "CALL STACK", + "variableSize": true, + "size": "0%" + }, + { + "id": "VARIABLES", + "variableSize": true, + "size": "0%" + } + ] + } + ] + } + ] + }, + { + "type": "horizontal", + "variableSize": true, + "size": "0%", + "content": [ + { + "id": "PROJECT EXPLORER", + "variableSize": false, + "size": "60.4237%" + }, + { + "id": "PROPERTIES", + "variableSize": true, + "size": "0%" + } + ] + } + ] + } + ] + }, + "floating": [] +} +``` + +
+ +
+<FULLSCREEN> (built-in) + +```json +{ + "docked": { + "type": "horizontal", + "variableSize": true, + "size": "0%", + "content": [ + { + "id": "TOOLBAR", + "variableSize": false, + "size": "fit-content" + }, + { + "type": "vertical", + "variableSize": true, + "size": "0%", + "content": [ + { + "id": "TOOLBOX", + "variableSize": false, + "size": "8.49705%" + }, + { + "type": "vertical", + "variableSize": true, + "size": "0%", + "content": [ + { + "id": "EDITOR", + "variableSize": false, + "size": "81.8591%" + }, + { + "id": "PROPERTIES", + "variableSize": true, + "size": "0%" + } + ] + } + ] + } + ] + }, + "floating": [] +} +``` + +
+ +## Show, hide and focus a panel + +Every panel has three commands: `tb_ShowPanel`, `tb_HidePanel` and `tb_FocusPanel`, where `` is the panel's name, as in `tbDebugConsole_ShowPanel`. Two have default keys: Ctrl+G focuses the Debug Console, and Ctrl+L focuses Call Stack. You can give the others keys under [Keyboard shortcuts](Keyboard-Shortcuts). + +A panel that you show, and that is in no layout in use, comes up floating, 300 pixels wide and 500 high, at 100 pixels from the left and 100 from the top. + +## Panel Features + +Five switches under **Window → Panel Features** control what you can do to panels. A tick means the action is allowed, and all five are ticked at first. + +![The Window menu with Panel Features highlighted and its submenu open to the right, holding five options that are all ticked: resizing, rearrangement and tear-out of docked panels, then resizing and movement of floating panels.](../Menu/Images/Menu_Window_PanelFeatures.png){:width="421" height="186"} + +**Allow resizing of docked panels** +: Clear it, and the splitters between docked panels disappear. + +**Allow rearrangement of docked panels** +: Clear it, and the grip icon on each panel's header disappears. You can no longer drag a docked panel to another place. + +**Allow tear-out of docked panels** +: Clear it, and dragging a docked panel out of the dock does nothing. + +**Allow resizing of floating panels** +: Clear it, and the resize edges and corners of a floating panel disappear. + +**Allow movement of floating panels** +: Clear it, and dragging a floating panel does nothing. + +Choosing an item applies it at once. The IDE keeps the five switches. + +## See Also + +- [Window menu](../Reference/Menu/Window#panel-layouts) -- the **Panel Layouts** and **Panel Features** entries +- [Keyboard shortcuts](Keyboard-Shortcuts) -- give a layout a key +- [Customising the IDE](.) -- where the settings are kept diff --git a/docs/IDE/Customising/Themes.md b/docs/IDE/Customising/Themes.md new file mode 100644 index 000000000..45745259d --- /dev/null +++ b/docs/IDE/Customising/Themes.md @@ -0,0 +1,121 @@ +--- +title: Themes +parent: Customising the IDE +grand_parent: IDE +nav_order: 2 +permalink: /tB/IDE/Customising/Themes +has_toc: false +index_also: dark mode +--- + +# Themes + +A theme sets the colours, fonts and borders of the whole IDE, including the colours of the code. Three themes ship with the IDE, and you can add your own by writing a short text file. + +## Choose a theme + +Point at **Window → Theme**. The submenu lists the themes, and a tick marks the one in use. + +![The Window menu with Theme highlighted and its submenu open to the right, listing Classic (Light), a ticked Dark, and Light, with Reload from disk below a separator.](../Menu/Images/Menu_Window_Theme.png){:width="312" height="219"} + +You can also click the icon with the tooltip **Change IDE Theme** in the menu bar. It opens the same list. + +The IDE ships with these themes: + +**Dark** +: The default. + +**Light** +: A light theme. + +**Classic (Light)** +: The Light theme with the semantic highlighting of the code effectively turned off, so the code is plainer. + +A theme you add is listed as its file name, followed by **Light** or **Dark** in brackets: **Red (Light)**. The word in brackets is the family of the theme that it inherits from. + +The choice applies at once, and the IDE keeps it. No restart is needed. If the IDE has no saved choice, or cannot find the theme saved, it uses Dark. + +Four commands switch themes, and you can give them keys under [Keyboard shortcuts](Keyboard-Shortcuts). None has a key by default: + +- `tbTheme_SwitchToDarkMode` +- `tbTheme_SwitchToLightMode` +- `tbTheme_SwitchToLightClassicMode` +- `tbTheme_SwitchToggle`, which switches between Dark and Light. From any other theme, it switches to Dark. + +## Write your own theme + +A theme is a text file whose name ends in `.theme`. The theme's name is the file name without `.theme`. Names are compared without regard to case. + +The IDE looks for theme files in two folders: + +- the `themes` folder in the IDE's own folder, next to `twinBASIC.exe`, which holds the three shipped themes; +- `%APPDATA%\twinBASIC\themes`, which the IDE creates at start-up. This is the place for your own themes, because it survives deleting an old IDE folder. + +The IDE reads the files only when it starts and when you choose **Window → Theme → Reload from disk**. A new file shows in the list after **Reload from disk**. If you edit the file of the theme in use, **Reload from disk** applies the edit at once. + +If a theme of the same name is in both folders, both are listed, and choosing either one applies the file in the IDE's own folder. + +Start from a small file that inherits from a shipped theme, and add only the properties you want to change. This file makes the Light theme's menu red and underlines class names. Save it as `Red.theme` in `%APPDATA%\twinBASIC\themes`, then choose **Reload from disk**: + +```text +/* The Light theme, with a red menu and underlined classes. */ +inherits: light; +MenuBackColor: red; +SymbolClassTextDecoration: underline; +``` + +> [!WARNING] +> In twinBASIC BETA 1005, a file named `Dark.theme` or `Light.theme` in `%APPDATA%\twinBASIC\themes` breaks the inheritance of every custom theme. The IDE ignores the file's contents, but still counts it, and so it never links the themes to their parents. Every custom theme, and **Classic (Light)** with them, is listed as `(undefined)` in place of **Light** or **Dark**. Choosing one gives the dark base with only that theme's own properties changed, and **Reload from disk** no longer applies the edits you make. No error is shown. To change one of the shipped themes, give your file another name and inherit from the shipped theme: `inherits: dark;`. + +> [!WARNING] +> In twinBASIC BETA 1005, two themes that inherit from each other freeze the IDE. For example, `LoopA` has the line `inherits: LoopB;`, and `LoopB` has `inherits: LoopA;`. When the IDE starts, or when you choose **Reload from disk**, it stops responding for several seconds. Choosing one of the two themes afterwards changes nothing on screen, because the IDE fails before it applies the theme. Never let a chain of `inherits:` lines return to a theme in it. To recover, remove the loop from the files and restart the IDE. + +### The format of a theme file + +A theme file is plain text. It is like CSS but is not CSS: it is a list of `Name: value;` pairs. + +- A comment starts with `/*` and ends with `*/`. The IDE removes comments first. +- Each property ends in a semicolon followed by a space, a tab or the end of the line. +- The name and the value are separated by a colon followed by a space or a tab. The value goes to the semicolon. Leading spaces and tabs in front of a name are ignored, so you can line the values up. +- A piece of text that does not split into a name and a value is dropped without a message. A property with no closing `;` runs into the next one, and both are lost. A property written `Name:value`, with no space after the colon, is lost too. +- A value may be empty (`Name: ;`). + +A property becomes a CSS variable on the IDE's page, named `--theme` followed by the property's name: `MenuBackColor` sets `--themeMenuBackColor`. The value is used as the CSS value. Any CSS colour, length or filter works, and so does `!important`. A property named `TextDecoration` is matched to the CSS property `text-decoration`, for example. + +One property is read by the IDE itself and not by CSS: `CodeBracketPairColorization: 1` colours matching pairs of brackets in the code. The shipped themes set it to 0. + +The shipped files `Dark.theme` and `Light.theme` define the same properties, about 500 of them, and are the list of properties you can set. They start with a comment that tells you not to modify them, so copy a property's name from them and put it in your own file. The names group by their first word: + +- `Symbol`, 188 properties: the colour, font style, font weight and text decoration of each kind of code. +- `Toolbox`, 33. +- `Panel` and `Status`, 25 each. +- `Designer`, 19. +- `Icon`, 16. +- `Dialog` and `Tab`, 15 each. +- `Code` and `Property`, 13 each. + +Smaller groups follow, such as `Context`, `General`, `Diagnostics`, `Debug`, `Dock`, `FrontPage` and `Menu`. The names within each shipped file are in alphabetical order. + +Each of the 47 kinds of code has four `Symbol` properties: its name between `Symbol` and `Color`, `FontStyle`, `FontWeight` or `TextDecoration`. `SymbolClassTextDecoration` in the example above is the text decoration of the kind *Class*. + +
+The 47 kinds of code + +Attribute, BuiltInDataType, Class, Comment, ConditionalCompilationDirective, ConditionalCompilationExcludedCode, Constant, ContinuationCharacter, DeclareFunction, DeclareSub, Enum, EnumMember, Field, Function, GenericDataType, GenericValue, GlobalVariablePrivate, GlobalVariablePublic, Interface, Keyword, LateBoundFunction, Library, LineLabel, LineNumber, LiteralBoolean, LiteralDate, LiteralEmpty, LiteralNothing, LiteralNull, LiteralNumeric, LiteralString, Me, Module, MultiLineSeperator, NamedArgument, NamedOperator, Operator, ParamByRef, ParamByVal, PropertyGet, PropertyLet, PropertySet, ReturnValue, Sub, UDT, Variable, VariableUndeclared. + +The names are spelled as the IDE spells them, including `MultiLineSeperator`. + +
+ +### Inheritance + +A line `inherits: ;` makes the theme start from the values of another theme, and then apply its own on top. The name is not case-sensitive, and spaces in it are removed. A chain works: a theme may inherit from a theme that itself inherits. + +A theme with no `inherits:` line inherits from Dark, unless it is itself named Dark or Light. The family of the theme, Light or Dark, is the family of the theme at the end of its chain. It sets the IDE's base colours and the code editor's base theme. + +If the theme named in `inherits:` does not exist, the theme is listed with `(undefined)` in place of the family. Choosing it gives the dark base, with only the properties the file defines changed. Every other property keeps the value it had in the theme that was in use before. After Light, the result is a white panel background under a dark code editor. Always name a theme that exists. + +## See Also + +- [Window menu](../Reference/Menu/Window#theme) -- the **Theme** entry +- [Customising the IDE](.) -- where the settings are kept diff --git a/docs/IDE/Customising/index.md b/docs/IDE/Customising/index.md new file mode 100644 index 000000000..f468ee282 --- /dev/null +++ b/docs/IDE/Customising/index.md @@ -0,0 +1,116 @@ +--- +title: Customising the IDE +parent: IDE +nav_order: 8 +permalink: /tB/IDE/Customising/ +has_toc: false +--- + +# Customising the IDE + +The IDE lets you change its fonts and behaviour, its colours, the arrangement of its panels, the keys that run its commands and the language of its menus. + +- [**IDE Options**](IDE-Options) --- the 31 options in the **IDE Options** dialog, and what each one changes. +- [**Themes**](Themes) --- choose a theme, or write your own `.theme` file. +- [**Panel layouts**](Panel-Layouts) --- save and switch the arrangement of the panels, and control how panels can be moved. +- [**Keyboard shortcuts**](Keyboard-Shortcuts) --- add, replace or remove the keys of any IDE command. + +The language of the IDE is described below, on this page. + +## Where the IDE keeps its settings + +Every setting is kept per user. No project file holds an IDE setting. A project opened on another PC, or by another user, shows the IDE as that user has set it up. + +The settings are in the registry, under this key: + +```text +HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE\IDESettings +``` + +Each setting is one text value under the key. These values hold the settings this chapter describes: + +| Value | Holds | +|---|---| +| `GENERAL` | All the IDE Options, as one JSON object, and the chosen theme and language. | +| `GENERAL2` | The five [Panel Features](Panel-Layouts#panel-features) switches, and whether the Project Explorer shows its file view or its object view. | +| `LAYOUTv2` | The current arrangement of the panels. | +| `ACTIVE_PANEL_LAYOUT_NAME` | The name of the active panel layout. | +| `CUSTOM_PANEL_LAYOUTS` | Your saved [panel layouts](Panel-Layouts). | +| `CUSTOM_KEYBOARD_SHORTCUT_GROUPS` | Your [keyboard shortcut groups](Keyboard-Shortcuts). | +| `ACTIVE_KEYBOARD_SHORTCUT_GROUPS` | The names of the shortcut groups that are switched on. | + +The same key also holds the licence, the publisher name and other values that are not customising. + +The folder `%APPDATA%\twinBASIC` holds the files you add yourself, and it survives deleting an old IDE folder. The IDE creates these folders in it at start-up: + +| Folder | Holds | +|---|---| +| `themes` | Your [themes](Themes). | +| `locale` | Your [language packs](#the-language-of-the-ide). | +| `packages` | Linked packages. | +| `addins\win32` and `addins\win64` | [Add-ins](../AddIns/). | + +The [FAQ](../../../FAQ#ide-data-storage) lists the other folders the IDE uses. + +## The language of the IDE + +Choose the language of the menus and dialogs under **Window → Language**. The submenu lists every language pack, and a tick marks the one in use. + +![The Window menu with Language highlighted and its submenu open to the right, listing nineteen languages in one tall column from Catalan and German through to Chinese Simplified and Chinese Traditional. Each is named in English with its own native spelling beside it, and English, British English, carries the tick.](../Menu/Images/Menu_Window_Language.png){:width="390" height="577"} + +The submenu shows these names, in this order. Each name is the one its pack gives itself, so some differ from the usual English spelling: the Indonesian entry begins *Indonesia*, and the Italian one ends in a brace. The entries for Hindi, Japanese, Malayalam, Russian and the two Chinese packs also give the language's own name, in its own script, in brackets. The table shows only their English names. + +| Menu entry | Pack file | +|---|---| +| Catalan (Català [Espanya]) | `ca-es.lang` | +| German (Deutsch [Deutschland]) | `de-de.lang` | +| Greek (Ελληνικά Ελλάδος) | `el_gr.lang` | +| English (British English) | `en-gb.lang` | +| Spanish (Español [España]) | `es-es.lang` | +| French (Français [France]) | `fr-fr.lang` | +| Hindi | `hi-in.lang` | +| Hungarian (Magyar) | `hu-hu.lang` | +| Indonesia (bahasa [Indonesia]) | `id-id.lang` | +| Italian (Italiano [Italy}) | `it-it.lang` | +| Japanese | `ja-jp.lang` | +| Malayalam | `ml-in.lang` | +| Polish (Polski) | `pl-pl.lang` | +| Portuguese (Português [Brasil]) | `pt-br.lang` | +| Russian | `ru-ru.lang` | +| Swedish (Svenska) | `sv-sv.lang` | +| Turkish (Türkçe [Turkey]) | `tr-tr.lang` | +| Chinese Simplified | `zh-cn.lang` | +| Chinese Traditional | `zh-tw.lang` | + +The default is British English. Its pack, `en-gb.lang`, is the base pack and holds every string. A string that another pack does not translate is shown in British English. Hindi, Indonesian, Russian and Malayalam translate only part of the strings. + +### What changes when you switch + +Choosing a language applies at once, and the IDE keeps the choice. No restart is needed. The menu bar, most menu items, the panel titles and the dialogs change language. + +These parts stay in English: + +- the **IDE Options** dialog, including its title, labels, values and buttons, and its **Change Licence...** button; +- the menu items **Quick Find...**, **Quick Replace...**, **Select All Matches**, the Fold and Unfold items, **Go To Line/Column...** and the Transform To items on the **Edit** menu; +- **Debugger Options** on the **Debug** menu; +- **Restart the compiler** and **Flush the LLVM compiler cache** on the **Tools** menu; +- **Automatic IDE Error Reporting...** on the **Help** menu; +- most text in the Debug Console; +- the compiler's messages and the hover text: they stay in English after a switch, and after a restart of the compiler. + +### Language packs + +The IDE reads its language packs from two `locale` folders: the one in the IDE's own folder, which holds the packs that ship with the IDE, and `%APPDATA%\twinBASIC\locale`, which starts empty. It reads them **only at start-up**. A pack you add or edit shows in the submenu after you restart the IDE. There is no **Reload from disk** for languages. + +To write a pack, copy `en-gb.lang` and translate it. The `README.txt` in the IDE's `locale` folder gives the rules: + +- Name the file by the IETF language tag, such as `en-us.lang`. +- Save it as UTF-8. +- Start it with a `FriendlyName:` line. Its text is the name the submenu shows. Without the line, the submenu shows the file name. +- Follow with the properties, as in `en-gb.lang`. You need only the strings you translate: the rest come from `en-gb.lang`. + +A pack is a text file of `Name: value;` pairs, as a [theme](Themes#the-format-of-a-theme-file) is, with `/* ... */` comments. Each value is a string in double quotes. Write two double quotes for one inside a string. A line break inside the quotes is kept. In a string, `{%Name}` stands for another string and `{%1}` and `{%2}` mark the places where the IDE fills in a value. + +A line `inherits: ;` makes the pack take its missing strings from another pack instead of from `en-gb.lang`. + +Translators share new and updated packs in the [#langpacks channel on the twinBASIC Discord server](https://discord.com/channels/927638153546829845/1329533568376115282). diff --git a/docs/IDE/Debug Console.md b/docs/IDE/Debug Console.md deleted file mode 100644 index e5f51bc0e..000000000 --- a/docs/IDE/Debug Console.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -title: Debug Console -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/DebugConsole -index: immediate window ---- - -# Debug Console - -![The Debug Console pane with its three header buttons outlined at the top right. Labels above the pane, with arrows, name the first two buttons Auto Scroll and Clear Debug Console. The Options menu of the third button is open below it, offering Invert Output Direction and a ticked Show Timestamps, and a label at its right, also with an arrow, names it Options. The input row is outlined along the bottom edge, and a label with an arrow names it Input.](Images/DebugConsole.png){:width="914" height="326"} - -The Debug Console captures output from [**Debug.Print**](../../Modules/Debug#print) statements and other debug-layer messages written at runtime, displaying them in a scrollable log. [**Debug.TracePrint**](../../Modules/Debug#traceprint) writes here too when the *Trace Output* project setting is `${DEBUG}` rather than the path of a log file. - -## ![](Images/DebugConsole_AutoScroll.png){:width="16" height="16"} Auto Scroll - -Keeps the newest output in view. While Auto Scroll is on, the console scrolls to each new line as it arrives: to the bottom, or to the top when **Invert Output Direction** is ticked. It is on each time the IDE starts, and the button is highlighted while it is on. - -Scrolling away from the newest line turns Auto Scroll off, so earlier output stays in view while the program goes on writing. Scrolling back until the newest line shows turns it on again. - -Clicking the button turns Auto Scroll off until it is clicked again. Scrolling back to the newest line then leaves it off. - -## ![](Images/DebugConsole_Clear.png){:width="16" height="16"} Clear Debug Console - -Empties the console. [**Debug.Cls**](../../Modules/Debug#cls) does the same thing from code. - -## ![](Images/DebugConsole_Options.png){:width="16" height="16"} Options - -- Invert Output Direction -- Show Timestamps - -## ![](Images/DebugConsole_Input.png){:width="20" height="15"} Input - -The row along the bottom of the console runs one line of code when ENTER is pressed. CTRL + G moves the keyboard focus to it, and the up arrow key (ARROWUP) brings back earlier lines, the most recent first. - -- **`?` and an expression** prints the value, the same way [**Debug.Print**](../../Modules/Debug#print) does, and `Debug.Print` itself works too. Several expressions separated by commas or semicolons are spaced as **Debug.Print** spaces them. -- **A statement** runs: `idx = 5` gives a variable a new value, and a procedure's name, such as `Module1.Main` or just `Main`, calls it. -- **An expression on its own** is refused: `i` gives `(compile error: Expression is neither used nor assigned)`. - -The console repeats each line, then shows its output and how long it took, as `(time taken: …)`. A line that is refused shows the compile error instead. - -While the program is stopped --- at a breakpoint, after a step, or at a run-time error --- the line runs inside the procedure selected in the [Call Stack](CallStack) pane. It can read and change that procedure's local variables, and the [Variables](Variables) pane shows the change. With the calling procedure selected instead, the failing procedure's variables are unknown: `? i` gives `(compile error: Unrecognized symbol 'i')`. With nothing running, the row still evaluates expressions and calls procedures. - -At a run-time error, running a line in the console removes the error panel. [When a run-time error stops the program](Menu/Debug#when-a-run-time-error-stops-the-program) describes what to do then. diff --git a/docs/IDE/Debugging/Breakpoints-and-Stepping.md b/docs/IDE/Debugging/Breakpoints-and-Stepping.md new file mode 100644 index 000000000..0e711cada --- /dev/null +++ b/docs/IDE/Debugging/Breakpoints-and-Stepping.md @@ -0,0 +1,205 @@ +--- +title: Breakpoints and stepping +parent: Running and debugging +grand_parent: IDE +nav_order: 2 +permalink: /tB/IDE/Debugging/Breakpoints-and-Stepping +index: + - breakpoint + - step into + - step over + - step out + - set next statement +--- + +# Breakpoints and stepping + +A breakpoint pauses the program on a line you choose. Once it is paused, the step commands run it one statement at a time, and Set Next Statement moves the point where it goes on. This page also describes the panel that opens when a run-time error stops the program. + +## Breakpoints + +### Set and clear a breakpoint + +Click in the margin of the code editor beside a line, or put the caret in the line and press F9 (**Debug → Toggle Breakpoint**). Either one sets the breakpoint at once, or clears it if the line has one. Nothing is asked, and no line is selected first. A breakpoint belongs to a code editor: a form designer has no margin, and F9 does nothing there. + +![The margin of the code editor beside the loop of a function. Line 24 has a red dot. The mouse pointer rests in the margin of line 23, above it, which shows a fainter dot. A label with an arrow names each dot.](Images/Breakpoint_Glyphs.png){:width="722" height="180"} + +The margin shows four marks: + +| Mark | Meaning | +|---|---| +| A red dot | A breakpoint. | +| A faint dot | The pointer is over the margin of this line, and a click would set a breakpoint. It shows on every line except the one with the arrow. | +| A yellow arrow | The program is paused on this line. | +| A yellow arrow over a red dot | The program is paused on a line that has a breakpoint. | + +To clear every breakpoint of the project, choose **Debug → Clear All Breakpoints** (Ctrl+Shift+F9). It clears the breakpoints in every file, open or not. It asks for no confirmation. + +On the line that has the arrow, a click without dragging toggles a breakpoint too. A press that lasts longer than a fifth of a second starts a drag of the arrow instead; see [Set Next Statement](#set-next-statement). + +### What a breakpoint does + +A breakpoint is a line number and nothing else. There are no conditions, no hit counts, no log points and no disabled breakpoints, and there is no list of breakpoints: the margin of an open file is the only place that shows them. There is also no Run to Cursor command. F6 runs the procedure at the caret, which [Running](Running#run-or-preview) describes. + +A breakpoint moves with the text. If you insert lines above it, the dot stays with its line, and the next run stops at the moved line. + +The margin takes a breakpoint on any line, including a blank line, a comment and the `Module` line. The glyph stays, but a line that does nothing never stops the program. A `Sub` or `Function` line, a `Dim` with an initializer such as `Dim n As Long = 7`, a `Next` (at every pass) and an `End Sub` do stop it. A breakpoint in the `Click` procedure of a form stops the program when the button is clicked. + +When a stop is in a file that is not open, the IDE opens the file. With **Single-Click Preview** on in [IDE Options](../Customising/IDE-Options), it opens as a preview tab. + +### Where breakpoints are kept + +The compiler keeps the breakpoints of each file. A breakpoint survives **Restart the compiler**, whether or not you saved the project, because the IDE sends its list to the new compiler. A breakpoint is not part of the source: **Export Project** writes no trace of one. Setting or clearing one marks the project as changed, so closing the project asks to save it. + +The **Allow Breakpoints (Debuggable)** item of **Debug → Debugger Options** turns breakpoints off for the whole project. While it is off, you can still set breakpoints and the margin shows them, but none stops the program. Turn it on, and they stop it again from the next run. [Debuggable](../Project-Settings#debuggable) describes the project setting, and the [**Debuggable**](../../Core/Attributes#debuggable) attribute changes it for one module, class or procedure. + +## What happens at a stop + +A breakpoint, a step, a `Stop` statement, the **Break** command and a run-time error all stop the program in the same way. At the stop: + +- The editor opens the file, if it is not open, and puts the caret at the line and column of the stop. +- The whole line gets a pale yellow background, and the yellow arrow appears in the margin. [Themes](../Customising/Themes) can change the background, which is the theme property `DebugLineBackColor`. +- The panels fill: [Call Stack, Variables and the watches](Inspecting), and the [Memory](Memory) pane if it is shown. The IDE evaluates every watch. +- The IDE shows its own window and asks for the keyboard focus. + +![The editor stopped at a breakpoint in a function named Total. The line subtotal = subtotal + items(i).Quantity * price is highlighted, and the yellow arrow in its margin covers the breakpoint's red dot. A second breakpoint, a red dot with no arrow, marks the line Total = subtotal further down. Under the editor, CALL STACK lists Total, marked with the arrow, and Main under it, and VARIABLES shows the locals of Total.](Images/Paused_Line.png){:width="1100" height="610"} + +The arrow and the background belong to the top frame, the procedure that is running. When you click another row in the Call Stack, the background moves to that frame's line, in that frame's file, and no arrow is drawn in the margin of any editor. The Variables panel, the watches, the console and the hover then all use that frame. + +The marks, and what the panels hold, are cleared the moment the program continues, steps or ends. + +A `Stop` statement shows the arrow and the background, and no error panel. A `Debug.Assert` with a false condition shows the error panel, as [below](#when-a-run-time-error-stops-the-program) describes. + +## Step commands + +While the program is paused, three commands run it one step at a time. The panels empty while the program runs the step, and fill again at the next stop. + +**Step Into** (F8 or F11) +: Runs the next statement. When the statement calls a procedure of the project, the program stops on the `Sub` or `Function` line of that procedure. + +**Step Over** (Shift+F8 or F10) +: Runs the next statement as one step, including any procedure it calls. A `RaiseEvent` runs its handlers. + +**Step Out** (Ctrl+Shift+F8 or Shift+F11) +: Runs the rest of the current procedure, and stops in its caller on the line after the call; the note below describes a case in which it does not. Step Out from `Main` ends the run. The **Debug** menu has no Step Out item: use the toolbar button or the keys. + +A procedure in another file opens in the editor when you step into it, as a preview tab when **Single-Click Preview** is on. + +At the `End Sub` of a procedure whose local variables hold objects, a step goes into a frame named `{class_final_destruction}` and then into the `Class_Terminate` procedures of those objects. A step off the `End Sub` of the `Click` procedure of a form lets the program run on, and the form waits for input again. + +> [!NOTE] +> In twinBASIC BETA 1005, once the program has stopped at a breakpoint, no step stops in a caller of the procedure it stopped in. Step Out, F8 at the `End Sub` and Step Over all run the caller on until the next breakpoint or the end of the program. Steps inside the procedure, and into the procedures it calls and back out to it, work. To stop in the caller, put a breakpoint there, or stop on the line of the call and step into the procedure. + +## Set Next Statement + +**Debug → Set Next Statement (Jump To Line)** (Ctrl+F9) makes the line that has the caret the next one to run. The arrow moves to it, and the lines in between do not run: a `Debug.Print` that the jump skips prints nothing. The jump can go to a later line or to an earlier one. + +You can also drag the arrow. Press on it and hold for a fifth of a second. The pointer becomes a grabbing hand and the arrow shrinks. Move over the margin, where a faint arrow follows the pointer, and let go over the line you want. The result is the same as the command. A shorter press is a click, which toggles a breakpoint. + +The jump works inside the paused procedure only. A blank line, a line of another procedure and a line of another file give no result: the arrow stays, and nothing is written to the console. The IDE sends the compiler the line number and no file name, so with the caret in another file than the paused one, the number is taken in the paused file. + +## Break On All Errors and Debuggable + +The two items of **Debug → Debugger Options** turn project settings on and off. Each writes its setting into the project's `Settings` file. A change to **Allow Breakpoints (Debuggable)** marks the project as changed, applies from the next run, and needs no restart of the compiler. + +**Break On All Errors** +: Off by default. Then an error that is raised while `On Error Resume Next` or `On Error GoTo` is in effect goes to that handler, as usual. When it is on, the program stops at the failing line even there, with the same error panel as [an error that nothing handles](#when-a-run-time-error-stops-the-program). **Ignore (Resume Next)** then skips the line. Under `On Error Resume Next`, `Err.Number` still holds the error afterwards, so code that checks it sees the error. F5 runs the failing line again, and the program stops on it again. Under `On Error GoTo`, the handler never runs. The project setting is [Break On All Errors](../Project-Settings#break-on-all-errors). + +**Allow Breakpoints (Debuggable)** +: On by default; the item shows its tick when the setting is absent from the `Settings` file. See [Where breakpoints are kept](#where-breakpoints-are-kept) and [Debuggable](../Project-Settings#debuggable). + +## When a run-time error stops the program +{: index="break mode" } + +A run-time error that no `On Error` statement handles stops the program on the line that raised it. It stops the same way whether the program was started with **Run → Start** (F5), from the **▶ run** link above a procedure, or by a `[RunAfterBuild]` procedure. With **Break On All Errors** on, a handled error stops it too. + +The examples below use this procedure. On the fourth pass through the loop, `idx` is 7, past the last index of `a`, which is 5: + +```tb check_build +Public Sub FillTable() + Dim a(5) As Long + Dim i As Long + Dim idx As Long + Dim total As Long + For i = 1 To 6 + idx = Choose(i, 1, 3, 5, 7, 2, 4) + a(idx) = i * 10 + total = total + i + Debug.Print "i = " & i & ", idx = " & idx & ", total = " & total + Next i + Debug.Print "loop finished, i = " & i & ", total = " & total +End Sub +``` + +### Where it stopped, and what the variables hold + +The editor opens the module, highlights the whole failing line, `a(idx) = i * 10`, and puts a yellow arrow in the margin beside it. Under the line, an error panel shows: + +- **Run-time error 9 (00000009)**: the error number, then the same number in hexadecimal; +- **DESCRIPTION:** and the error's description, here *Subscript out of range*; +- four buttons: **Try Again (Resume)**, **Ignore (Resume Next)**, **Stop** and **Search Online**. + +![The editor stopped at the error in FillTable. The line a(idx) = i * 10 is highlighted, with the yellow arrow in its margin, and the error panel under it shows Run-time error 9 (00000009), the description Subscript out of range and the four buttons. Under the editor, CALL STACK lists one frame, FillTable, at 12:1, marked with the arrow, and VARIABLES shows Locals expanded: a, an array of 6 elements, then i 4, idx 7 and total 6.](Images/Error_Panel.png){:width="1100" height="610"} + +The number is the one `Err.Number` holds. When the error was raised with `Err.Raise` and a source, a **SOURCE:** row with the source text shows below the buttons. When the compiler gives no header, the panel is titled **ERROR OCCURRED**. Nothing about the error is written to the [Debug Console](Debug-Console). The panel's **×** only hides the panel. + +A false `Debug.Assert` opens the same panel, with the header `Run-time error -353703420 (EAEAEA04)` and the description `Assertion FAILED`. F5 there runs on past the assertion. A `Stop` statement opens no panel. + +The other panes show the state of the program at the failing line: + +- **[Call Stack](Inspecting#call-stack)** lists the chain of calls, the failing procedure first, each with its file and its `line:column`. Here that is `FillTable`, then the procedure that called it, if there is one. Clicking a row opens that file at that line, and **Variables** then shows that procedure's variables. +- **[Variables](Inspecting#variables)** shows the failing procedure's *Locals*, already expanded: `i` is 4, `idx` is 7 and `total` is 6. The array shows as `{array of 6 elements}` until it is expanded. +- Holding the mouse pointer over a variable in the editor shows its value; see [Hover](Inspecting#hover). + +### Reading and changing values in the Debug Console + +The input row at the bottom of the [Debug Console](Debug-Console) runs a line of code inside the stopped procedure: + +- `? i` prints `4`, with a `(time taken: …)` line after it. `Debug.Print i` does the same. +- `i` on its own is refused, with `(compile error: Expression is neither used nor assigned)`. +- `idx = 5` gives `idx` a new value, and **Variables** shows it. + +Running any line in the console removes the error panel. The failing line stays highlighted, and the keys below still work. + +### The four buttons + +- **Try Again (Resume)** runs the failing line again. Unless a value it uses has changed, the error happens again at once. F5 does the same, and still works after the panel has gone. +- **Ignore (Resume Next)** skips the failing line and goes on from the statement after it. Here the loop runs to the end, and prints `i = 4, idx = 7, total = 10` for the pass that failed. No menu command or key does this. At the panel of a failed `Debug.Assert` it does nothing. +- **Stop** does not end the program at an error. See [Traps](#traps). +- **Search Online** opens a Google search in the default browser. The search text is `twinBASIC VB6`, the header and the description of the error. + +> [!NOTE] +> In twinBASIC BETA 1005, **Search Online** puts the error text into the address as it is, without encoding it. An `&` in the description ends the search text at that point, so the search loses the rest, and a `"` ends the address. Copy the description from the panel and search for it in your browser instead. + +### Stepping on through the rest of the loop + +**Step Into** (F8 / F11) and **Step Over** (Shift+F8 / F10) do not move past the failing line. They run it again, and the error happens again at once. To go on one line at a time, first do one of these, then step: + +- **Correct the value in the console.** After `idx = 5`, F8 runs the failing line without an error, and stops on the next one, `total = total + i`. +- **Move past the line with Set Next Statement.** Click in the next line and press Ctrl+F9. The yellow arrow moves there without running the failing line, and the panel closes. F8 then runs the line the arrow is on. + +To run on to the end instead, correct the value and press F5, or press **Ignore (Resume Next)** while the panel is still there. + +### Editing at the error panel + +Edit and Continue is not supported at the error panel either: when you change the code, the console writes `[EDIT & CONTINUE] not yet supported. Aborting.`. After you change the failing line, F5 starts a new run that runs the changed line. + +> [!WARNING] +> In twinBASIC BETA 1005, an edit at the error panel does not abort the program, although the console says `Aborting`. The rest of the failing procedure is skipped, and the code after its call runs: its output appears in the console, and the run then ends. At a breakpoint the same edit does abort the program. Do not edit the code while the error panel is showing. End the run first, as the **Stop** warning under [Traps](#traps) describes, and then edit. + +### Traps + +- **The panel disappears, and Ignore with it.** Running a line in the Debug Console, or clicking another procedure in the Call Stack, removes the panel. F5 brings it back only by running the failing line again. Set Next Statement onto the next line, then F5, does what **Ignore (Resume Next)** would have done. + +> [!NOTE] +> In twinBASIC BETA 1005, a step key pressed on the failing line leaves a step waiting. After F8 on the failing line, **Ignore (Resume Next)** stops again on the next line instead of going on, and the next F5 is used up as well. Press F5 to go on, or use Set Next Statement as above instead of a step key. + +> [!WARNING] +> In twinBASIC BETA 1005, **Stop** at an error ends only the procedure that failed. The procedure that called it goes on from its next line: if `Sub Main` called `FillTable`, the rest of `Sub Main` still runs. The panel's **Stop**, the toolbar's **Stop** and **Run → End** all do this. At a failed `Debug.Assert`, **Stop** ends only the check, and the test goes on past it as if the check had passed. To end the whole run, first move the arrow to a later line with Ctrl+F9, then press **Stop**. Away from an error --- at a breakpoint, after a step, or after Set Next Statement --- **Stop** ends the run. + +## See Also + +- [Running](Running) -- Start, Break and End, and what ends a run +- [Inspecting a paused program](Inspecting) -- the panels that fill at a stop +- [Debug menu](../Reference/Menu/Debug) -- the menu commands of this page +- [Testing with Assert](../../../Tutorials/Testing-with-Assert) -- a failed assertion stops the program as an error does diff --git a/docs/IDE/Debugging/Debug-Console.md b/docs/IDE/Debugging/Debug-Console.md new file mode 100644 index 000000000..75f41511a --- /dev/null +++ b/docs/IDE/Debugging/Debug-Console.md @@ -0,0 +1,158 @@ +--- +title: Debug Console +parent: Running and debugging +grand_parent: IDE +nav_order: 4 +permalink: /tB/IDE/Debugging/Debug-Console +redirect_from: + - /tB/IDE/Project/DebugConsole +index: immediate window +--- + +# Debug Console + +The **DEBUG CONSOLE** panel is a scrolling log of what a run writes, and an input row that runs one line of code at a time. + +![The Debug Console pane with its three header buttons outlined at the top right. Labels above the pane, with arrows, name the first two buttons Auto Scroll and Clear Debug Console. The Options menu of the third button is open below it, offering Invert Output Direction and a ticked Show Timestamps, and a label at its right, also with an arrow, names it Options. The input row is outlined along the bottom edge, and a label with an arrow names it Input.](../Images/DebugConsole.png){:width="914" height="326"} + +The panel is docked at the bottom of the default layout. Ctrl+G shows it, and moves the keyboard focus to the input row. + +## What the console shows + +**Debug.Print** +: [**Debug.Print**](../../Modules/Debug#print) writes here. The console is monospaced, and the output of several expressions is spaced as **Debug.Print** spaces it. A built executable writes nothing with **Debug.Print**: the console is for a run in the IDE. + +**Debug.TracePrint** +: [**Debug.TracePrint**](../../Modules/Debug#traceprint) writes here when the *Trace Output* project setting is `${DEBUG}`, and to a file when it is a path. With no trace settings at all, it writes nothing. See [The trace logger](#the-trace-logger). + +**Debug.Cls** +: [**Debug.Cls**](../../Modules/Debug#cls) empties the console from code. It does the same as the **Clear Debug Console** button. + +**Messages from the IDE** +: The IDE writes its own lines in the same list: `Executing 'Main'...` at the start of a run, `(time taken: …)` at its end, `aborted` for a run that you stop, `[DEBUGGER] …` lines, `[SHELL] process … started.` for a program that your program starts, `restarting from MEMORY []` when the compiler restarts, and lines tagged `[COMPILER]`, `[EXPORT]` and `[LINKER]` from other features. [Running](Running) describes the run lines. + +A line that the compiler marks as a native exception is red. The first one in a session also makes the IDE turn on TRACE mode, and the console says so, and that the compiler is slower until TRACE mode is turned off from the Help menu. + +Every line starts with the time at which it was written, as `HH:MM:SS.mmm`. The lines are not links: a line that names a file and a line does not take you there. + +## The buttons + +The header of the panel has three buttons. + +### Auto Scroll + +The ![Auto Scroll button, an arrow pointing down at a bar](../Images/DebugConsole_AutoScroll.png){:width="16" height="16"} button keeps the newest output in view. While Auto Scroll is on, the console scrolls to each new line as it arrives: to the bottom, or to the top when **Invert Output Direction** is ticked. It is on each time the IDE starts, and the button is highlighted while it is on. + +Scrolling away from the newest line turns Auto Scroll off, so earlier output stays in view while the program goes on writing. Scrolling back until the newest line shows turns it on again. + +Clicking the button turns Auto Scroll off until it is clicked again. Scrolling back to the newest line then leaves it off. + +### Clear Debug Console + +The ![Clear Debug Console button](../Images/DebugConsole_Clear.png){:width="16" height="16"} button empties the console. It asks for no confirmation. + +### Options + +The ![Options button, three dots](../Images/DebugConsole_Options.png){:width="16" height="16"} button opens a menu of two items, each with a tick when it is on. + +**Invert Output Direction** +: Off by default. Puts the newest line at the top. + +**Show Timestamps** +: On by default. It only hides the times: every line keeps its time, and **Copy All (With Timestamps)** still includes it. + +The IDE saves both settings with the project. + +## The input row + +The row along the bottom of the console has a `>` at its left. It takes one line of code. Enter runs it. The console repeats the line, then shows its output and how long it took, as `(time taken: …)`. A line that is refused shows the compile error instead. + +- **`?` and an expression** prints the value, the same way **Debug.Print** does, and `Debug.Print` itself works too. Several expressions separated by commas or semicolons are spaced as **Debug.Print** spaces them. +- **A statement** runs: `idx = 5` gives a variable a new value, and a procedure's name, such as `Module1.Main` or just `Main`, calls it. +- **An expression on its own** is refused: `i` gives `(compile error: Expression is neither used nor assigned)`. + +![The DEBUG CONSOLE while the program is paused. Above, the line that started the run and the line the program printed. Then the line ? subtotal, repeated, its output 360 and a (time taken: ...) line, and the line subtotal = 5, repeated, with only a (time taken: ...) line after it. The input row at the bottom is empty.](Images/Console_Run.png){:width="560" height="236"} + +What the line can see depends on the state of the program: + +- **Paused.** The line runs inside the procedure selected in the [Call Stack](Inspecting#call-stack). It can read and change that procedure's local variables, and the [Variables](Inspecting#variables) panel shows the change. With the calling procedure selected instead, the failing procedure's variables are unknown: `? i` gives `(compile error: Unrecognized symbol 'i')`. +- **Idle.** The line can call the procedures and functions of the modules of the project: `? Twice(4)` prints `8`. It cannot see local variables, because none exist: `? n` gives `(compile error: Unrecognized symbol 'n')`. +- **Running, not paused.** The line is refused: `main thread is busy. wait for it to finish, or prepend the expression with '~' to execute it in a new thread.` +- **The compiler is not running.** The line is refused with `ERROR: DEBUGGER IS NOT CONNECTED.` + +At a run-time error, running a line in the console removes the error panel. [When a run-time error stops the program](Breakpoints-and-Stepping#when-a-run-time-error-stops-the-program) describes what to do then. + +The input row is one line. There is no way to enter a multi-line construct. + +### History + +Up brings back earlier lines, the most recent first, and Down moves forward again. Down at the newest line does nothing: the box is not cleared. A line is added to the history only when it differs from the line before it, so entering the same line twice in a row stores it once. The IDE saves the history with the project and restores it when the project opens. + +### Keys and completion + +- Ctrl+A selects the whole text of the row. +- F12 and Shift+F2 go to the definition of the word at the caret. +- The row has the IntelliSense of the editor, in the mode that [IDE Options](../Customising/IDE-Options#intellisense-modes) sets, and Ctrl+Space opens the list. It has a line for signature help of its own. +- A [keyboard shortcut](../Customising/Keyboard-Shortcuts#the-text-of-a-group) can run a line of code in the console: `tbDebugConsole_ExecuteCode('? 6*7')` is the one command that takes an argument. + +> [!NOTE] +> In twinBASIC BETA 1005, the input row offers no completion for a name typed after `?` and a space, and Ctrl+Space there opens no list. Type the name without the space, as `?total`, or start the line with the name. + +> [!NOTE] +> In twinBASIC BETA 1005, a space typed while the completion list is open accepts the list's first entry, and the space is lost. Typing `Print total` gives `Printertotal`, because the list offers `Printer` but not the keyword `Print`; `Call Touch(total)` and `Set obj = items` are changed in the same way. The console refuses the changed line. Press Escape to close the list before you type the space: the text you typed stays. + +> [!NOTE] +> In twinBASIC BETA 1005, `? p`, where `p` is a variable of a user-defined type, fails with `(compile error: codegen error; check for compilation errors)`, and the console prints a `[LINKER] compilation (codegen) error` line. The [Variables](Inspecting#variables) panel and a watch show the same variable, so read it there. + +## Copy the output + +Right-click anywhere in the panel for a menu of two items. The menu is in English whatever the language of the IDE is. + +![The DEBUG CONSOLE after a run, each line with its time, and the menu that a right-click opens over it, holding Copy All and Copy All (With Timestamps).](Images/Console_Menu.png){:width="640" height="180"} + +**Copy All** +: Puts every line of the console on the clipboard, one line to a row, without the times. + +**Copy All (With Timestamps)** +: The same, with the time and a space before each line. + +Both include the `(time taken: …)` lines, and neither depends on the **Show Timestamps** setting. There is no command to copy a selection. + +## Commands and keys + +Each button and toggle of the header has a command that a [keyboard group](../Customising/Keyboard-Shortcuts) can bind to a key. `tbDebugConsole_Clear` clears the console. `tbDebugConsole_ToggleAutoScroll` toggles Auto Scroll, and `tbDebugConsole_TurnAutoScrollOn` and `tbDebugConsole_TurnAutoScrollOff` set it. The same three forms exist for Invert Output Direction (`tbDebugConsole_TurnInvertOutputDirectionOn` and `…Off`) and for Show Timestamps (`tbDebugConsole_TurnShowTimestampsOn` and `…Off`). The two copy commands are `tbDebugConsole_ClipboardCopyAll` and `tbDebugConsole_ClipboardCopyAll_WithTimestamps`. None has a key by default. + +## The trace logger + +twinBASIC can write a log of what the program does, to this console or to a file. The logger works when the program runs in the IDE and in a compiled executable. Use [**Debug.TracePrint**](../../Modules/Debug#traceprint) to write your own messages into it: + +```tb check_build +Public Sub ProcessOrder(ByVal orderId As Long) + Debug.TracePrint "ProcessOrder called, orderId=" & CStr(orderId) + ' ... processing ... +End Sub +``` + +Two project settings control the log. **Trace Flags** choose what the compiler records, with a box for each of eleven flags, and **Trace Output** chooses where the log goes: the path of a file, or `${DEBUG}` for this console only. + +![The Project Settings dialog scrolled to the Trace Flags row, a column of eleven unticked boxes from Trace Procedure Entry and Exit points to Trace RaiseEvent calls and arguments, and to the Trace Output row, an empty text box above a description of the ${SESSIONID}, ${DATE}, ${TIME} and ${DEBUG} placeholders.](../../Features/Images/4fc2bf99-2bec-4943-837d-21038d791574.png){:width="760" height="531"} + +[Trace Flags](../Project-Settings#trace-flags) and [Trace Output](../Project-Settings#trace-output) describe each flag and the placeholders. + +### Separate one run from the next + +Calling **Debug.Cls** as a run begins separates that run's output from the previous one, which is the difference between reading a log and scrolling for where it started: + +```tb check_build +Private Sub Form_Load() + Debug.Cls + Debug.Print "--- run starting ---" +End Sub +``` + +## See Also + +- [Running](Running) -- the lines the IDE writes at the start and end of a run +- [Inspecting a paused program](Inspecting) -- the Variables, Watches and Call Stack panels, which the console shares a frame with +- [Debug.Print](../../Modules/Debug#print) -- the statement that writes here +- [Project Settings](../Project-Settings) -- Trace Flags and Trace Output diff --git a/docs/IDE/Debugging/Images/Breakpoint_Glyphs.light.png b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.light.png new file mode 100644 index 000000000..1114ce30f Binary files /dev/null and b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.light.png differ diff --git a/docs/IDE/Debugging/Images/Breakpoint_Glyphs.light.svg b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.light.svg new file mode 100644 index 000000000..af1ac567e --- /dev/null +++ b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.light.svg @@ -0,0 +1 @@ +19202122232425262728DimnoteDimpriceAsDoubleFori=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).NameEndIfBreakpointFaint dot diff --git a/docs/IDE/Debugging/Images/Breakpoint_Glyphs.png b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.png new file mode 100644 index 000000000..b782b1742 Binary files /dev/null and b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.png differ diff --git a/docs/IDE/Debugging/Images/Breakpoint_Glyphs.svg b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.svg new file mode 100644 index 000000000..f1c0f8ea7 --- /dev/null +++ b/docs/IDE/Debugging/Images/Breakpoint_Glyphs.svg @@ -0,0 +1 @@ +19202122232425262728DimnoteDimpriceAsDoubleFori=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).NameEndIfBreakpointFaint dot diff --git a/docs/IDE/Debugging/Images/CallStack_Frames.light.png b/docs/IDE/Debugging/Images/CallStack_Frames.light.png new file mode 100644 index 000000000..120f6a674 Binary files /dev/null and b/docs/IDE/Debugging/Images/CallStack_Frames.light.png differ diff --git a/docs/IDE/Debugging/Images/CallStack_Frames.light.svg b/docs/IDE/Debugging/Images/CallStack_Frames.light.svg new file mode 100644 index 000000000..ea8c72954 --- /dev/null +++ b/docs/IDE/Debugging/Images/CallStack_Frames.light.svg @@ -0,0 +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.1005MAIN_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 new file mode 100644 index 000000000..e981a825d Binary files /dev/null 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 new file mode 100644 index 000000000..20694b7a5 --- /dev/null +++ b/docs/IDE/Debugging/Images/CallStack_Frames.svg @@ -0,0 +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.1005MAIN_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/Console_Menu.light.png b/docs/IDE/Debugging/Images/Console_Menu.light.png new file mode 100644 index 000000000..25ca06462 Binary files /dev/null and b/docs/IDE/Debugging/Images/Console_Menu.light.png differ diff --git a/docs/IDE/Debugging/Images/Console_Menu.light.svg b/docs/IDE/Debugging/Images/Console_Menu.light.svg new file mode 100644 index 000000000..12c714882 --- /dev/null +++ b/docs/IDE/Debugging/Images/Console_Menu.light.svg @@ -0,0 +1 @@ +DEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: Widget16:41:58.204120 501.6 16:41:58.204(time taken: 0.0021175s)16:41:58.204[DEBUGGER] global variables have been clearedCopy AllCopy All (With Timestamps) diff --git a/docs/IDE/Debugging/Images/Console_Menu.png b/docs/IDE/Debugging/Images/Console_Menu.png new file mode 100644 index 000000000..de7e3a597 Binary files /dev/null and b/docs/IDE/Debugging/Images/Console_Menu.png differ diff --git a/docs/IDE/Debugging/Images/Console_Menu.svg b/docs/IDE/Debugging/Images/Console_Menu.svg new file mode 100644 index 000000000..1c455729c --- /dev/null +++ b/docs/IDE/Debugging/Images/Console_Menu.svg @@ -0,0 +1 @@ +DEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: Widget16:41:58.204120 501.6 16:41:58.204(time taken: 0.0021175s)16:41:58.204[DEBUGGER] global variables have been clearedCopy AllCopy All (With Timestamps) diff --git a/docs/IDE/Debugging/Images/Console_Run.light.png b/docs/IDE/Debugging/Images/Console_Run.light.png new file mode 100644 index 000000000..76848c5c5 Binary files /dev/null and b/docs/IDE/Debugging/Images/Console_Run.light.png differ diff --git a/docs/IDE/Debugging/Images/Console_Run.light.svg b/docs/IDE/Debugging/Images/Console_Run.light.svg new file mode 100644 index 000000000..e8c853ddd --- /dev/null +++ b/docs/IDE/Debugging/Images/Console_Run.light.svg @@ -0,0 +1 @@ +Inventory(general)Total10111213141516171819202122            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0ThenDEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: Widget16:41:58.204? subtotal16:41:58.204360 16:41:58.204(time taken: 0.0001023s)16:41:58.204subtotal = 516:41:58.204(time taken: 0.0001023s) diff --git a/docs/IDE/Debugging/Images/Console_Run.png b/docs/IDE/Debugging/Images/Console_Run.png new file mode 100644 index 000000000..9fea85aaf Binary files /dev/null and b/docs/IDE/Debugging/Images/Console_Run.png differ diff --git a/docs/IDE/Debugging/Images/Console_Run.svg b/docs/IDE/Debugging/Images/Console_Run.svg new file mode 100644 index 000000000..52befa073 --- /dev/null +++ b/docs/IDE/Debugging/Images/Console_Run.svg @@ -0,0 +1 @@ +Inventory(general)Total101112131415161718192021            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1DEBUG CONSOLE>16:41:58.204Executing 'Main'...16:41:58.204Grüße 👋 こんにちは: Widget16:41:58.204? subtotal16:41:58.204360 16:41:58.204(time taken: 0.0001023s)16:41:58.204subtotal = 516:41:58.204(time taken: 0.0001023s) diff --git a/docs/IDE/Debugging/Images/Error_Panel.light.png b/docs/IDE/Debugging/Images/Error_Panel.light.png new file mode 100644 index 000000000..e5e5420b8 Binary files /dev/null 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 new file mode 100644 index 000000000..a30320e0e --- /dev/null +++ b/docs/IDE/Debugging/Images/Error_Panel.light.svg @@ -0,0 +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.1005MAIN_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 new file mode 100644 index 000000000..7b23d937c Binary files /dev/null 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 new file mode 100644 index 000000000..71b1c8ae7 --- /dev/null +++ b/docs/IDE/Debugging/Images/Error_Panel.svg @@ -0,0 +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.1005MAIN_THREAD [EXECUTING]12:1FillTableChecks.twinVARIABLESLocalsa:{array of 6 elements}Long()i:4Longidx:7Longtotal:6Long diff --git a/docs/IDE/Debugging/Images/Hover_Paused.light.png b/docs/IDE/Debugging/Images/Hover_Paused.light.png new file mode 100644 index 000000000..2b112df64 Binary files /dev/null and b/docs/IDE/Debugging/Images/Hover_Paused.light.png differ diff --git a/docs/IDE/Debugging/Images/Hover_Paused.light.svg b/docs/IDE/Debugging/Images/Hover_Paused.light.svg new file mode 100644 index 000000000..7bb3c99b7 --- /dev/null +++ b/docs/IDE/Debugging/Images/Hover_Paused.light.svg @@ -0,0 +1 @@ +202122232425DimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thensubtotal:   360 diff --git a/docs/IDE/Debugging/Images/Hover_Paused.png b/docs/IDE/Debugging/Images/Hover_Paused.png new file mode 100644 index 000000000..0312a3140 Binary files /dev/null and b/docs/IDE/Debugging/Images/Hover_Paused.png differ diff --git a/docs/IDE/Debugging/Images/Hover_Paused.svg b/docs/IDE/Debugging/Images/Hover_Paused.svg new file mode 100644 index 000000000..14de8e834 --- /dev/null +++ b/docs/IDE/Debugging/Images/Hover_Paused.svg @@ -0,0 +1 @@ +202122232425DimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thensubtotal:   360 diff --git a/docs/IDE/Debugging/Images/Hover_Selection.light.png b/docs/IDE/Debugging/Images/Hover_Selection.light.png new file mode 100644 index 000000000..d84aa2a8c Binary files /dev/null and b/docs/IDE/Debugging/Images/Hover_Selection.light.png differ diff --git a/docs/IDE/Debugging/Images/Hover_Selection.light.svg b/docs/IDE/Debugging/Images/Hover_Selection.light.svg new file mode 100644 index 000000000..cb232101c --- /dev/null +++ b/docs/IDE/Debugging/Images/Hover_Selection.light.svg @@ -0,0 +1 @@ +202122232425DimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thenitems(i).Quantity * price:   141.6 diff --git a/docs/IDE/Debugging/Images/Hover_Selection.png b/docs/IDE/Debugging/Images/Hover_Selection.png new file mode 100644 index 000000000..d5deb11a1 Binary files /dev/null and b/docs/IDE/Debugging/Images/Hover_Selection.png differ diff --git a/docs/IDE/Debugging/Images/Hover_Selection.svg b/docs/IDE/Debugging/Images/Hover_Selection.svg new file mode 100644 index 000000000..205c8367c --- /dev/null +++ b/docs/IDE/Debugging/Images/Hover_Selection.svg @@ -0,0 +1 @@ +202122232425DimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0Thenprice=Round(items(i).Price*(1+taxRate),2)subtotal=subtotal+items(i).Quantity*priceIfitems(i).Quantity>100Thenitems(i).Quantity * price:   141.6 diff --git a/docs/IDE/Debugging/Images/Paused_Line.light.png b/docs/IDE/Debugging/Images/Paused_Line.light.png new file mode 100644 index 000000000..8503f7f5a Binary files /dev/null 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 new file mode 100644 index 000000000..1fee76d6c --- /dev/null +++ b/docs/IDE/Debugging/Images/Paused_Line.light.svg @@ -0,0 +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.1005MAIN_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 new file mode 100644 index 000000000..a826fb0a1 Binary files /dev/null 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 new file mode 100644 index 000000000..4157a3077 --- /dev/null +++ b/docs/IDE/Debugging/Images/Paused_Line.svg @@ -0,0 +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.1005MAIN_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 new file mode 100644 index 000000000..cf5bc4231 Binary files /dev/null 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 new file mode 100644 index 000000000..92a0d0d9a --- /dev/null +++ b/docs/IDE/Debugging/Images/Paused_Panels.light.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALULTIMATE EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1005)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.1005MAIN_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 new file mode 100644 index 000000000..db255ef21 Binary files /dev/null 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 new file mode 100644 index 000000000..a40b3f561 --- /dev/null +++ b/docs/IDE/Debugging/Images/Paused_Panels.svg @@ -0,0 +1 @@ +tB Services: OPERATIONALULTIMATE EDITIONFileEditViewProjectFormatDebugRunToolsAdd-InsWindowHelp• project.twinproj - twinBASIC (twinBASIC IDE BETA 1005)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.1005MAIN_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/Process_Box.light.png b/docs/IDE/Debugging/Images/Process_Box.light.png new file mode 100644 index 000000000..f3702a640 Binary files /dev/null and b/docs/IDE/Debugging/Images/Process_Box.light.png differ diff --git a/docs/IDE/Debugging/Images/Process_Box.light.svg b/docs/IDE/Debugging/Images/Process_Box.light.svg new file mode 100644 index 000000000..b25257e8a --- /dev/null +++ b/docs/IDE/Debugging/Images/Process_Box.light.svg @@ -0,0 +1 @@ +PROCESS 10432: RUNNINGTerminateBring To Frontping -n 20 127.0.0.1 diff --git a/docs/IDE/Debugging/Images/Process_Box.png b/docs/IDE/Debugging/Images/Process_Box.png new file mode 100644 index 000000000..d68386ba8 Binary files /dev/null and b/docs/IDE/Debugging/Images/Process_Box.png differ diff --git a/docs/IDE/Debugging/Images/Process_Box.svg b/docs/IDE/Debugging/Images/Process_Box.svg new file mode 100644 index 000000000..58eaf99c1 --- /dev/null +++ b/docs/IDE/Debugging/Images/Process_Box.svg @@ -0,0 +1 @@ +PROCESS 10432: RUNNINGTerminateBring To Frontping -n 20 127.0.0.1 diff --git a/docs/IDE/Debugging/Images/Toolbar_Paused.light.png b/docs/IDE/Debugging/Images/Toolbar_Paused.light.png new file mode 100644 index 000000000..5faee5e1c Binary files /dev/null and b/docs/IDE/Debugging/Images/Toolbar_Paused.light.png differ diff --git a/docs/IDE/Debugging/Images/Toolbar_Paused.light.svg b/docs/IDE/Debugging/Images/Toolbar_Paused.light.svg new file mode 100644 index 000000000..24e9af9d1 --- /dev/null +++ b/docs/IDE/Debugging/Images/Toolbar_Paused.light.svg @@ -0,0 +1 @@ +Step OutStep IntoStep OverStopContinue diff --git a/docs/IDE/Debugging/Images/Toolbar_Paused.png b/docs/IDE/Debugging/Images/Toolbar_Paused.png new file mode 100644 index 000000000..355f2405b Binary files /dev/null and b/docs/IDE/Debugging/Images/Toolbar_Paused.png differ diff --git a/docs/IDE/Debugging/Images/Toolbar_Paused.svg b/docs/IDE/Debugging/Images/Toolbar_Paused.svg new file mode 100644 index 000000000..88e6165a3 --- /dev/null +++ b/docs/IDE/Debugging/Images/Toolbar_Paused.svg @@ -0,0 +1 @@ +Step OutStep IntoStep OverStopContinue diff --git a/docs/IDE/Debugging/Images/Variables_Edit.light.png b/docs/IDE/Debugging/Images/Variables_Edit.light.png new file mode 100644 index 000000000..033258aae Binary files /dev/null and b/docs/IDE/Debugging/Images/Variables_Edit.light.png differ diff --git a/docs/IDE/Debugging/Images/Variables_Edit.light.svg b/docs/IDE/Debugging/Images/Variables_Edit.light.svg new file mode 100644 index 000000000..4219c7253 --- /dev/null +++ b/docs/IDE/Debugging/Images/Variables_Edit.light.svg @@ -0,0 +1 @@ +Inventory(general)10111213141516171819202122            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDoubleDimiAsLongDimsubtotalDimnoteDimpriceAsDoubleFori=0Tocount-1Ifitems(i).Quantity>0ThenVARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Double360taxRate:0.2Double diff --git a/docs/IDE/Debugging/Images/Variables_Edit.png b/docs/IDE/Debugging/Images/Variables_Edit.png new file mode 100644 index 000000000..9b7d4fd8c Binary files /dev/null and b/docs/IDE/Debugging/Images/Variables_Edit.png differ diff --git a/docs/IDE/Debugging/Images/Variables_Expanded.light.png b/docs/IDE/Debugging/Images/Variables_Expanded.light.png new file mode 100644 index 000000000..32c1807df Binary files /dev/null and b/docs/IDE/Debugging/Images/Variables_Expanded.light.png differ diff --git a/docs/IDE/Debugging/Images/Variables_Expanded.light.svg b/docs/IDE/Debugging/Images/Variables_Expanded.light.svg new file mode 100644 index 000000000..64693cd14 --- /dev/null +++ b/docs/IDE/Debugging/Images/Variables_Expanded.light.svg @@ -0,0 +1 @@ +MainModule(general)123456789ModuleMainModulePublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)▶   run MainModule.MainVARIABLESLocalsfirst:{user defined type, 16 bytes}StockItemName:"Widget"StringPrice:2.5DoubleQuantity:120Longstock:{object: 441954992}Inventorycost:501.6Variant [Double] diff --git a/docs/IDE/Debugging/Images/Variables_Expanded.png b/docs/IDE/Debugging/Images/Variables_Expanded.png new file mode 100644 index 000000000..13f5a832a Binary files /dev/null and b/docs/IDE/Debugging/Images/Variables_Expanded.png differ diff --git a/docs/IDE/Debugging/Images/Variables_Expanded.svg b/docs/IDE/Debugging/Images/Variables_Expanded.svg new file mode 100644 index 000000000..421f5df1c --- /dev/null +++ b/docs/IDE/Debugging/Images/Variables_Expanded.svg @@ -0,0 +1 @@ +MainModule(general)123456789ModuleMainModulePublicSubMain()DimstockAsNewInventoryDimfirstAsStockItemDimcoststock.Add"Widget",120,2.5stock.Add"Gadget",8,14.75cost=stock.Total(0.2)▶   run MainModule.MainVARIABLESLocalsfirst:{user defined type, 16 bytes}StockItemName:"Widget"StringPrice:2.5DoubleQuantity:120Longstock:{object: 441954992}Inventorycost:501.6Variant [Double] diff --git a/docs/IDE/Debugging/Images/Variables_Tooltip.light.png b/docs/IDE/Debugging/Images/Variables_Tooltip.light.png new file mode 100644 index 000000000..a942fc593 Binary files /dev/null and b/docs/IDE/Debugging/Images/Variables_Tooltip.light.png differ diff --git a/docs/IDE/Debugging/Images/Variables_Tooltip.light.svg b/docs/IDE/Debugging/Images/Variables_Tooltip.light.svg new file mode 100644 index 000000000..f0819a095 --- /dev/null +++ b/docs/IDE/Debugging/Images/Variables_Tooltip.light.svg @@ -0,0 +1 @@ +VARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Doublesubtotal:360Variant [Double]taxRate:0.2DoublesubtotalTypeVariant [Double]ValuePointer360(&H0019F3A4) diff --git a/docs/IDE/Debugging/Images/Variables_Tooltip.png b/docs/IDE/Debugging/Images/Variables_Tooltip.png new file mode 100644 index 000000000..6410d9c36 Binary files /dev/null and b/docs/IDE/Debugging/Images/Variables_Tooltip.png differ diff --git a/docs/IDE/Debugging/Images/Variables_Tooltip.svg b/docs/IDE/Debugging/Images/Variables_Tooltip.svg new file mode 100644 index 000000000..78a91ffd1 --- /dev/null +++ b/docs/IDE/Debugging/Images/Variables_Tooltip.svg @@ -0,0 +1 @@ +VARIABLESLocalsMe:{object: 441954992}InventoryTotal:0Doublei:1Longnote:"Grüße 👋 こんにちは"Variant [String]price:17.7Doublesubtotal:360Variant [Double]taxRate:0.2DoublesubtotalTypeVariant [Double]ValuePointer360(&H0019F3A4) diff --git a/docs/IDE/Debugging/Images/Watches_NotAvailable.light.png b/docs/IDE/Debugging/Images/Watches_NotAvailable.light.png new file mode 100644 index 000000000..57ef5bf76 Binary files /dev/null and b/docs/IDE/Debugging/Images/Watches_NotAvailable.light.png differ diff --git a/docs/IDE/Debugging/Images/Watches_NotAvailable.light.svg b/docs/IDE/Debugging/Images/Watches_NotAvailable.light.svg new file mode 100644 index 000000000..352f36026 --- /dev/null +++ b/docs/IDE/Debugging/Images/Watches_NotAvailable.light.svg @@ -0,0 +1 @@ +Inventory(general)10111213141516            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleWATCHESitems(i).Name:not availableitems(i).Quantity:not availablesubtotal:not availablesubtotal *:not available diff --git a/docs/IDE/Debugging/Images/Watches_NotAvailable.png b/docs/IDE/Debugging/Images/Watches_NotAvailable.png new file mode 100644 index 000000000..016818b57 Binary files /dev/null and b/docs/IDE/Debugging/Images/Watches_NotAvailable.png differ diff --git a/docs/IDE/Debugging/Images/Watches_NotAvailable.svg b/docs/IDE/Debugging/Images/Watches_NotAvailable.svg new file mode 100644 index 000000000..10ffba5d8 --- /dev/null +++ b/docs/IDE/Debugging/Images/Watches_NotAvailable.svg @@ -0,0 +1 @@ +Inventory(general)101112131415            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubWATCHESitems(i).Name:not availableitems(i).Quantity:not availablesubtotal:not availablesubtotal *:not available diff --git a/docs/IDE/Debugging/Images/Watches_Values.light.png b/docs/IDE/Debugging/Images/Watches_Values.light.png new file mode 100644 index 000000000..d698e693b Binary files /dev/null and b/docs/IDE/Debugging/Images/Watches_Values.light.png differ diff --git a/docs/IDE/Debugging/Images/Watches_Values.light.svg b/docs/IDE/Debugging/Images/Watches_Values.light.svg new file mode 100644 index 000000000..5b8ee3a25 --- /dev/null +++ b/docs/IDE/Debugging/Images/Watches_Values.light.svg @@ -0,0 +1 @@ +Inventory(general)10111213141516            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubPublicFunctionTotal(ByValtaxRateAsDouble)AsDoubleWATCHESitems(i).Name:"Gadget"Stringitems(i).Quantity:8Longsubtotal:360Variant [Double]subtotal *:(compile error: Unexpected end of expression)ERROR diff --git a/docs/IDE/Debugging/Images/Watches_Values.png b/docs/IDE/Debugging/Images/Watches_Values.png new file mode 100644 index 000000000..fd22c9ed5 Binary files /dev/null and b/docs/IDE/Debugging/Images/Watches_Values.png differ diff --git a/docs/IDE/Debugging/Images/Watches_Values.svg b/docs/IDE/Debugging/Images/Watches_Values.svg new file mode 100644 index 000000000..d1fe2994f --- /dev/null +++ b/docs/IDE/Debugging/Images/Watches_Values.svg @@ -0,0 +1 @@ +Inventory(general)101112131415            .Quantity=quantity            .Price=priceEndWithcount=count+1EndSubWATCHESitems(i).Name:"Gadget"Stringitems(i).Quantity:8Longsubtotal:360Variant [Double]subtotal *:(compile error: Unexpected end of expression)ERROR diff --git a/docs/IDE/Debugging/Inspecting.md b/docs/IDE/Debugging/Inspecting.md new file mode 100644 index 000000000..402e25705 --- /dev/null +++ b/docs/IDE/Debugging/Inspecting.md @@ -0,0 +1,161 @@ +--- +title: Inspecting a paused program +parent: Running and debugging +grand_parent: IDE +nav_order: 3 +permalink: /tB/IDE/Debugging/Inspecting +redirect_from: + - /tB/IDE/Project/Variables + - /tB/IDE/Project/Watches + - /tB/IDE/Project/CallStack +index: + - variables panel + - locals window + - watch window + - call stack window +--- + +# Inspecting a paused program + +When the program is paused, three panels show its state: **VARIABLES** holds the values in scope, **WATCHES** holds expressions that you chose, and **CALL STACK** holds the chain of calls that led to the stop. Pointing at a variable in the editor shows its value as well. + +![The IDE paused in a function named Total, on the second pass of its loop. The yellow arrow over a red dot marks the paused line, which is highlighted. Under the editor, four panels: DEBUG CONSOLE with the line that announced the run and the line the first pass printed; CALL STACK with the main thread expanded to two frames, Total, marked with the arrow, and Main, which called it; VARIABLES with Locals expanded, each row with its name, value and type; and WATCHES with two expressions and their values.](Images/Paused_Panels.png){:width="1280" height="880"} + +All three show the **active frame**. At a stop it is the top frame, the procedure that is running. Click a row of the Call Stack to make another frame the active one. The Variables panel, the watches, the Debug Console and the hover then all work in that frame. + +The panels are empty when nothing is paused. Each one is cleared the moment the program continues, steps or ends. + +## Variables + +The **VARIABLES** panel is a tree. Each *scope* of the active frame is a folder at the top level. The folder is named **Locals** in a procedure of a module, and it holds the procedure's arguments and local variables. In a **Sub**, **Function** or **Property Get** of a class, and in an event procedure of a form, it holds `Me` as well. In a **Function** or a **Property Get**, of a module or of a class, it also holds a variable with the procedure's name, which holds the value that the procedure returns. Module-level variables are not listed there. Add a watch for one, or point at it in the editor. + +![The Variables pane, empty apart from its title bar, with no variables listed.](../Images/Variables.png){:width="240" height="248"} + +Each variable is a row with three columns: its name, its value and its type. A value that has parts, such as an array, a user-defined type or an object, is a folder you can expand. The panel reads the parts when you first expand it. Folders come first in each list, and every group is sorted by name. The IDE remembers which folders you expanded and saves them with the project. + +![The VARIABLES panel while the program is paused in a procedure named Main. Locals is expanded. Its first row, first, a user-defined type of 16 bytes, is expanded to three fields: Name "Widget", Price 2.5 and Quantity 120, with the type of each at the right. Next comes stock, an object, collapsed, and last cost, a Variant holding a Double.](Images/Variables_Expanded.png){:width="420" height="214"} + +The value column shows these forms: + +| Variable | Value shown | Type column | +|---|---|---| +| **Long** 7, **Double** 2.5, **Currency** 12.34, **Boolean** True | `7`, `2.5`, `12.34`, `True` | the type | +| **String** | `"hello"`, in quotes | **String** | +| **Date** | `#10/11/2026 12:30:00 PM#` | **Date** | +| **Variant** | the value, as above | `Variant [Long]`, the subtype in brackets | +| An array | `{array of 3 elements}`; its elements are `(1)`, `(2)` and so on, and `(0,0)` in a two-dimensional array | `Long()` | +| An empty dynamic array | `{dynamic array, no elements}`; it cannot be expanded | | +| A user-defined type | `{user defined type, 8 bytes}`; its fields are the children | | +| An object | `{object:
}`, the address in decimal; its public fields and its **Property Get** values are the children | | +| A **Collection** | the object form; the children include `Count`, `KeyCountHint` and `KeyCompareMode` | `_Collection` | +| An enumeration value | the member's name, such as `Green` | | + +The panel runs the property procedures of an object to show their values, so a property that has side effects runs when the object is expanded. + +### Change a value + +Double-click a value to edit it. A text box replaces the value. Enter, or a click anywhere else, applies the text, and Escape leaves the value as it was. The row then shows the value the program holds, and the IDE evaluates the watches again. The program uses the new value from then on. + +![The VARIABLES panel with the locals of a function. The row of subtotal is a white text box across the panel, holding its value, 360, selected.](Images/Variables_Edit.png){:width="400" height="236"} + +- A **Long**, a **String**, an enumeration member, a field of a user-defined type and an element of an array can all be edited. Type a string with its quotes. Type an enumeration member by name, such as `Red`. +- A value that the program refuses, such as `abc` for a **Long**, is ignored, with no message. +- A row that is a folder, an object, a user-defined type or an array, has no text box. Expand it and edit its parts. +- A line typed in the Debug Console, such as `s = "it's"`, changes a variable too, and the panel shows the change. + +> [!WARNING] +> In twinBASIC BETA 1005, the edit box cuts a string at an apostrophe, and leaving the box changes the variable. With `s` holding `it's`, double-click `s`, and the box holds only `"it`. A click on another row, or Enter, with nothing typed, then makes `s` the empty string. Escape leaves it as it was. Do not edit a string that contains an apostrophe in the panel. Assign it in the [Debug Console](Debug-Console) instead. + +> [!WARNING] +> In twinBASIC BETA 1005, a **Date** or **Double** row shows a wrong value for a while after you edit it. A **Date** edited to `#1/2/2020#` shows `#12:00:00 AM#`, and a **Double** edited to `7.25` shows `0`. The variable holds the value you typed. The row shows it correctly after the next line you run in the console, or at the next stop. To check the value now, run `? d` in the console, where `d` is the variable. + +### What the tooltip shows + +Hold the pointer over a row to see its tooltip: the name, as a dotted path from the top-level variable, then the **Type** and the **Value**. A pointer value has a line labelled **Pointer**. A **Copy Value To Clipboard** icon copies the value. The panel has no context menu and no search box. + +![The VARIABLES panel with the row of subtotal highlighted under the pointer, and its tooltip below it: the name subtotal, then Type Variant [Double], Value 360 and Pointer with an address, and a copy icon at the lower right.](Images/Variables_Tooltip.png){:width="429" height="335"} + +## Watches + +A watch is an expression that the IDE evaluates at every stop. The **WATCHES** panel is not in the default layout, so open it from **View → WATCHES**, or add a watch, which opens it. [Saving a layout](../Customising/Panel-Layouts) keeps it open. + +![The WATCHES panel with a project open, two more buttons now in its title bar: a plus for adding a watch, and a stack of lines with a small x for clearing the list.](../Images/Watches_1.png){:width="300" height="76"} + +### Add and change a watch + +To add a watch, use any of these: + +- **Debug → Add Watch...** (Shift+F9). If the editor has a selection, the box starts with the selected text. +- The ![a thin grey plus sign](../Images/Add.png){:width="16" height="16"} button, titled **Add Watch...**. +- A double-click in the empty area of the panel. + +Each opens a box that says *Expression to watch*. Enter, or a click elsewhere, adds the watch. Escape adds nothing, and an empty box adds nothing. + +![The panel after the plus is clicked, a white text box across the top of its body holding the grey placeholder text Expression to watch.](../Images/Watches_2.png){:width="300" height="76"} + +Double-click a watch to change its expression in place; an empty expression removes it. Select a watch and press Delete to remove it. The ![four stacked lines with a small x at their lower right](../Images/Clear.png){:width="16" height="16"} button, or **Debug → Clear Watches**, removes every watch with no confirmation. Adding an expression that is already there changes nothing. + +### What a watch shows + +![The WATCHES panel while the program is paused, with four watches: items(i).Name reads "Gadget", a String; items(i).Quantity reads 8, a Long; subtotal reads 360, a Variant holding a Double; and subtotal * reads (compile error: Unexpected end of expression), with the type ERROR.](Images/Watches_Values.png){:width="520" height="128"} + +Each row holds the expression, its value and its type, in the same three columns as the Variables panel, and it can be expanded in the same way. The rows are sorted by expression, capitals first. Hold the pointer over a row for a tooltip with the expression, the type and the value. + +- While the program is not paused, every value reads `not available`. +- An expression that fails shows the compiler's message in place of the value, such as `(compile error: Unexpected end of expression)`, and the type **ERROR**. +- The expression can be anything the Debug Console takes, including a call to a procedure. The call runs at every stop, and when you choose another frame, so a watch can change the state of the program. +- A watch on a variable of a user-defined type shows `{user defined type, 8 bytes}`, as the Variables panel does. + +![The same four watches once the program has ended: each reads not available, and the type column is empty.](Images/Watches_NotAvailable.png){:width="520" height="128"} + +The watches are saved with the project and come back when you open it. They also survive **Restart the compiler**, which keeps the expanded state of each one. + +## Call Stack + +The **CALL STACK** panel (Ctrl+L) lists the chain of calls. It groups them by thread. Each thread is a row at the top level, named with the thread and its state, such as **MAIN_THREAD [IDLE]** and **COMPILER [Win32]** in the picture. The threads are listed whether or not anything is paused, so a panel with only these rows means that no call frames are on any stack. + +![The Call Stack pane listing two active threads, each collapsed to a single row: a Win32 compiler thread with its version number, and MAIN_THREAD, marked idle. A label reading Active Threads sits below the rows, with an arrow rising from it to each row.](../Images/CallStack.png){:width="376" height="206"} + +The row of the program's thread reads **[EXECUTING]** while the program runs, and it still does while the program is paused. The IDE shows no break state there. + +Expand a thread to see its frames, the most recent call first. A frame has three columns: the procedure, the file and `line:column`. The top frame has a yellow arrow. A frame in code of a package, such as `RaiseClickEvent` in `CommandButton.twin`, shows `-1:1` for its position. + +![The IDE paused in Total after a click on the Main row of CALL STACK. The editor shows MainModule.twin with the line cost = stock.Total(0.2), the call to Total, highlighted and no arrow in its margin. CALL STACK still marks Total with the arrow, and VARIABLES shows the locals of Main: first, stock and cost.](Images/CallStack_Frames.png){:width="1100" height="610"} + +Click a frame to make it the active frame. The editor opens the frame's file at that line and highlights it, without the arrow, and the Variables panel, the watches, the console and the hover all work in that frame. The watches are evaluated again. The error panel is hidden. A click opens the file in a preview tab when **Single-Click Preview** is on in [IDE Options](../Customising/IDE-Options). A double-click always opens it in a normal tab. + +The panel has no tooltips and no context menu. When you edit the code, the `line:column` of the frames follows the text. + +The Call Stack shows the frames of the other threads of the program as well. A frame named `{class_final_destruction}` appears when a procedure ends and releases objects that its variables hold. + +## Hover + +While the program is paused, pointing at an expression in the editor evaluates it in the active frame, and the popup shows `expression: value`. The popup appears about a second after the pointer stops. + +![The paused line subtotal = subtotal + items(i).Quantity * price, with a popup above it reading subtotal: 360.](Images/Hover_Paused.png){:width="544" height="90"} + +The IDE builds the expression like this: + +- If the pointer is inside a selection, the expression is the selected text. Select `x + y`, point at it, and the popup shows its value. +- Otherwise it is the word under the pointer, extended to the left over `.` and `!` and over an argument list in brackets, and to the right over an argument list that follows. Pointing at `Items` in `obj.Items(i).Name` evaluates the whole `obj.Items(i).Name`. + +![The same line with items(i).Quantity * price selected, and a popup reading items(i).Quantity * price: 141.6.](Images/Hover_Selection.png){:width="603" height="90"} + +A user-defined type shows `{user defined type, 8 bytes}` and one line for each field. An object shows its address. A word that is not an expression, such as a keyword or a type name, shows `(compile error: Unrecognized symbol 'For')`. A name that is not in the active frame shows the same message. + +Pointing at a function call or at a property runs it, so a function with side effects changes the state of the program. + +> [!WARNING] +> In twinBASIC BETA 1005, pointing at a call to a **Sub** runs the Sub, and the popup shows `t.Bump: 0`, a value that means nothing. The Sub, and any event handlers it raises, runs once for each hover. Do not point at the name of a procedure that changes the program's state while the program is paused. To read a variable that such a procedure uses, point at the variable. + +When the program is not paused, the same pointing shows the compiler's usual hover text, which [IntelliSense](../Code/IntelliSense#hover) describes. + +## The Memory pane + +The raw bytes of the paused program are in the [Memory](Memory) pane. The expression box of the pane evaluates its text as a watch does. + +## See Also + +- [Breakpoints and stepping](Breakpoints-and-Stepping) -- how the program gets paused +- [Debug Console](Debug-Console) -- run a line of code in the active frame +- [Memory](Memory) -- the bytes behind a variable diff --git a/docs/IDE/Debugging/Memory.md b/docs/IDE/Debugging/Memory.md new file mode 100644 index 000000000..667277ac8 --- /dev/null +++ b/docs/IDE/Debugging/Memory.md @@ -0,0 +1,139 @@ +--- +title: Memory +parent: Running and debugging +grand_parent: IDE +nav_order: 5 +permalink: /tB/IDE/Debugging/Memory +redirect_from: + - /tB/IDE/Project/Memory +index: + - memory window + - memory panel + - hex view, of memory +--- + +# Memory + +![The Memory 1 pane showing only its toolbar row: a button of two curved arrows, an empty box prompting for an expression to watch, a display format dropdown set to Byte (Hex), and a column count dropdown set to Cols 16. No memory contents are listed.](../Images/Memory.png){:width="696" height="82"} + +The Memory pane displays the raw contents of process memory while the program is paused in the debugger, with addresses in the left column, the values in the middle and the same bytes as text on the right. It is useful for inspecting data structures at the byte level, and it can also change them. + +**View → MEMORY 1** opens the pane. There is one Memory pane, and its title is always **MEMORY 1**. The pane is not in the default layout. It is one of the [panels used while the program is paused](../Debugging/#the-panels-that-show-a-paused-program). + +A toolbar runs along the top of the pane: + +- The button of two curved arrows on the left, **Auto-refresh whilst step debugging**, is a toggle. When it is on, the pane evaluates its expression again at every stop. See [When the pane updates](#when-the-pane-updates). +- The box marked **enter expression** takes the expression that gives the address to show. Press Enter to show it. +- The first list sets the display format --- **Byte (Hex)** above. +- The second list sets the number of values in each row --- **Cols 16** above. + +While the program runs, and when it is not running at all, the pane is dimmed, ignores the mouse and hides its rows, as in the picture above. Once the program pauses --- at a breakpoint, a `Stop` statement, an error or a step --- the rows appear, starting at address 0 until an expression is entered. + +## What to enter in the expression box + +The debugger evaluates the expression in the box as it does a watch, so the box takes anything a watch takes: a variable, an element, a field, a function call such as [**VarPtr**](../../Modules/Information/VarPtr), [**StrPtr**](../../Modules/Information/StrPtr) or [**ObjPtr**](../../Modules/Information/ObjPtr), arithmetic, and literal addresses such as `&H0F423ACC`. The result then becomes an address: + +- A **String**, or a **Variant** that holds one: the box's text is replaced with `StrPtr(`*expression*`)`, and the pane shows the string's characters. +- An **object**: the text is replaced with `ObjPtr(`*expression*`)`, and the pane shows the object. +- An **array**: the text is replaced with `VarPtr(`*expression*`)`, and the pane shows the array variable. +- Any other result is a number, and **the number itself is the address**. The name of a **Long** variable therefore shows the memory at the address the variable *holds*, not the memory of the variable. That is what a **LongPtr** holding an address needs. For a variable's own bytes, enter `VarPtr(`*name*`)`. + +> [!NOTE] +> In twinBASIC BETA 1005, a result that is not a whole number --- the name of a **Double**, a **Boolean** or a variable of a user-defined type --- gives no address and does not turn the box red. The rows stay as they were, and the DEBUG CONSOLE prints an *IDE internal error* such as `Uncaught SyntaxError: Cannot convert 1.5 to a BigInt`. Enter `VarPtr(`*name*`)` for such a variable. + +The pane starts its first row at that address exactly, and the cell at the address takes the keyboard focus. An expression that cannot be evaluated --- an unknown name, an empty box, anything entered while the program is not paused --- turns the box red and leaves the rows as they were. + +> [!NOTE] +> In twinBASIC BETA 1005, a negative number is taken as an address too. With -8, the first row is labelled `&HFFFFFFF8` and the rows after it `&H00000008`, `&H00000018` and so on. None of them can be read. + +The table shows what each kind of expression gives in a 32-bit program paused after these lines: + +```tb +Dim n As Long, d As Double, s As String, v As Variant +Dim arr() As Long, udt As Rec, obj As Thing, p As LongPtr +n = 1234: d = 1.5: s = "ABCD": v = "Hi" +ReDim arr(0 To 3): arr(0) = &HA1: arr(1) = &HB2: arr(2) = &HC3: arr(3) = &HD4 +udt.A = &H11223344: udt.B = 5: udt.C = 6 ' Type Rec: A As Long, B As Integer, C As Integer +Set obj = New Thing +p = VarPtr(n) +``` + +| Expression | The pane starts at | The bytes shown | +|---|---|---| +| `VarPtr(n)` | the variable `n` | `D2 04 00 00`, which is 1234 as a **Long** (least significant byte first), then the variables stored after `n` | +| `n` | address 1234, `&H000004D2`: the value of `n` | `??` in every cell, because the program has no memory there | +| `p` | the address `p` holds, the same as `VarPtr(n)` | `D2 04 00 00` | +| `VarPtr(d)` | the variable `d` | `00 00 00 00 00 00 F8 3F`, which is 1.5 as a **Double** | +| `s` (the box then reads `StrPtr(s)`) | the first character of `s` | `41 00 42 00 43 00 44 00 00 00`: A, B, C and D, two bytes each (UTF-16), then two zero bytes. The text column reads `A.B.C.D..` | +| `StrPtr(s) + 4` | the third character of `s` | `43 00 44 00 00 00` | +| `VarPtr(s)` | the variable `s` | the pointer to the characters: the number `StrPtr(s)` returns, in 4 bytes | +| `v` (the box then reads `StrPtr(v)`) | the string held in `v` | `48 00 69 00 00 00`, "Hi" | +| `VarPtr(v)` | the **Variant** itself | the type first, `08 00` for **vbString**, and the pointer to the string 8 bytes in | +| `arr` (the box then reads `VarPtr(arr)`) | the array variable | the pointer to the array's descriptor, not the elements | +| `VarPtr(arr(0))` | the first element | `A1 00 00 00 B2 00 00 00 C3 00 00 00 D4 00 00 00` | +| `arr(1)` | address `&H000000B2`: the element's value | `??` | +| `VarPtr(udt)` | the variable `udt` | the fields in order: `44 33 22 11` for **A**, `05 00` for **B**, `06 00` for **C** | +| `VarPtr(udt.B)` | the field **B**, 4 bytes into `udt` | `05 00 06 00` | +| `obj` (the box then reads `ObjPtr(obj)`) | the object | the object's memory, starting with the pointer to its table of methods | +| `VarPtr(obj)` | the variable `obj` | the pointer to the object: the number `ObjPtr(obj)` returns | +| `&H0F423ACC` | that address | whatever is there | + +In a 64-bit program each pointer takes 8 bytes instead of 4. + +Below, `s` holds "Hello twinBASIC". After `s` is entered, the box reads `StrPtr(s)`. Each character takes two bytes, the second of them `00`, and the last two zero bytes end the string: + +![The MEMORY 1 pane with StrPtr(s) in its box, Byte (Hex) and Cols 16 chosen, and two rows. The first row, at address &H0F423AC8, holds 48 00 65 00 6C 00 6C 00 6F 00 20 00 74 00 77 00, and its text column reads H.e.l.l.o. .t.w. The second row, at &H0F423AD8, holds 69 00 6E 00 42 00 41 00 53 00 49 00 43 00 00 00 and reads i.n.B.A.S.I.C...](../Images/Memory_String.png){:width="696" height="120"} + +`??` in the value cells, and `·` in the text column, mark memory the program cannot read. + +> [!NOTE] +> In twinBASIC BETA 1005, `??` and `·` are also what a cell shows while its bytes are still being read, so unreadable memory looks the same as memory not read yet. Memory that keeps showing `??` cannot be read. + +## Display formats and columns + +The first list sets how the bytes are grouped and written: + +| Format | Each value | `VarPtr(n)`, with `n` = 1234, starts with | +|---|---|---| +| **Byte (Hex)** | one byte, two hexadecimal digits | `D2` | +| **Byte (Decimal)** | one byte, unsigned, three digits | `210` | +| **Integer (Hex)** | two bytes, four hexadecimal digits | `04D2` | +| **Integer (Decimal)** | two bytes, signed, a sign and five digits | `+01234` | +| **Long (Hex)** | four bytes, eight hexadecimal digits | `000004D2` | +| **Long (Decimal)** | four bytes, signed, a sign and ten digits | `+0000001234` | + +The formats of two and four bytes show each value as a number, so the hexadecimal formats write the bytes in the reverse of their order in memory: the bytes `D2 04` are the **Integer** `04D2`. + +The second list, **Cols 8**, **Cols 12**, **Cols 16**, **Cols 24** or **Cols 32**, sets the number of values in a row. A row therefore covers more bytes in the larger formats: 16 in **Byte (Hex)** at **Cols 16**, 32 in an **Integer** format and 64 in a **Long** format. The address column steps by the same amount, and is written as `&H` and eight hexadecimal digits. + +> [!WARNING] +> In twinBASIC BETA 1005, the address column of a 64-bit program shows only the low 32 bits of each address. The bytes shown are those at the whole address, but an address of `&H4232CFF438` is labelled `&H32CFF438`, which is another address. Do not copy an address from the column in a 64-bit program; print it with `Hex(VarPtr(`*name*`))` instead. + +The text column on the right always shows every byte of the row, one character each. A byte from 32 to 126 shows as its ASCII character, and any other byte as a dot. + +The mouse wheel moves the rows by one row at a time, and by eight with Shift. Double-click an address to select it for copying. + +## Changing memory + +Every value cell and every character of the text column can be edited, and an edit writes the program's memory at once. In **Byte (Hex)**, typing a hexadecimal digit replaces the digit at the caret and moves on, into the next cell at the end of one. In the text column, typing a character writes its code. The **Variables** pane and the watches are refreshed after each write. + +- In the decimal formats, digits replace digits, and a value outside the format's range is limited to it. The signed formats take `+` and `-` for the sign. +- Backspace and Delete set a digit, or a character, to zero. +- The arrow keys, Home and End move the caret; moving past the first or last row moves the rows. +- Escape puts back what the cell showed. Enter and pasting do nothing. + +For example, with `VarPtr(n)` entered, typing `FF` over the first cell makes `n` 1279 (`&H04FF`), and with `s` entered, typing `Q` over the first character in the text column makes `s` "QBCD". + +> [!WARNING] +> The pane writes whatever is typed, wherever it is typed. A wrong byte in a string's length, an object's pointer or an array's descriptor can crash the program or corrupt its data. + +## When the pane updates + +The pane reads its memory again every time the program stops: at a breakpoint, a `Stop` statement, an error, and after each step. What it reads depends on **Auto-refresh whilst step debugging**: + +- With the button **off**, the default, the pane reads the same address again. After a step that runs `n = 5678`, `VarPtr(n)` shows `2E 16 00 00`. With `p` in the box, the pane stays at the address `p` held when it was entered, even after `p` changes. +- With the button **on**, the pane evaluates the expression again at every stop and moves to the address it gives now. With `p` in the box, the pane follows `p`. Turning the button on also evaluates the expression at once. + +The expression stays in the box when the program continues, at the next stop, and in the next run. + +The box keeps a history of the expressions entered. With the box selected, Down shows the most recent one, and each press after that an earlier one; Up goes back toward the most recent. The history is saved with the project. diff --git a/docs/IDE/Debugging/Running.md b/docs/IDE/Debugging/Running.md new file mode 100644 index 000000000..1eaafd606 --- /dev/null +++ b/docs/IDE/Debugging/Running.md @@ -0,0 +1,144 @@ +--- +title: Running +parent: Running and debugging +grand_parent: IDE +nav_order: 1 +permalink: /tB/IDE/Debugging/Running +index: + - start, a program + - run a program + - F5 +--- + +# Running + +Start runs the project inside the compiler, Break pauses it, and End stops it. This page describes what Start runs, the second way to run code (F6), the boxes the status bar shows for programs your program starts, and everything that ends a run. + +## Start, Break and End + +Three commands control a run. Each is in the **Run** menu and has a button in the [toolbar](../Reference/Toolbar). + +**Start** (F5) +: Starts the program when nothing runs, and continues it when it is paused. The menu calls it **Start**, and the toolbar button is titled **Start / Continue**. It does not ask to save the project, and it does not save it. + +**Break** (Ctrl+Break) +: Pauses a running program. The toolbar button is titled **Break Into Code**. + +**End** +: Stops the program, whether it is running or paused. It has no key. The toolbar button is titled **Stop**. + +Which of them work depends on the [state of the program](../Debugging/#the-three-states-of-a-program): + +- While the program is idle, only **Start** works. +- While it runs, **Break** and **End** work. **Start** is greyed out. +- While it is paused, **Start** (now continuing), **End**, the step buttons and **Set Next Statement** work. **Break** is greyed out. + +The toolbar buttons and the menu items follow the same rules. [Breakpoints and stepping](Breakpoints-and-Stepping) describes the paused state. + +![The run and step buttons of the toolbar while the program is paused: a green triangle, a grey pause sign, a red square and three step arrows. The pause sign alone is greyed out. A label with an arrow names each of the other five buttons.](Images/Toolbar_Paused.png){:width="256" height="175"} + +At a run-time error, **End** ends only the procedure that failed; see [Traps](Breakpoints-and-Stepping#traps). + +### Break + +**Break** stops the program at the line it is executing. In a loop that has a statement of its own, that line is in your code, and the DEBUG CONSOLE works as at any other stop. + +In a form that is waiting for input, and in a loop that holds only `DoEvents`, there is no line of yours to stop at. The IDE opens a file of the VB package, `IdleMessageLoopBreak.twin`, and stops in its procedure `IdleMessageLoopBreakpoint`. With a form waiting for input, a line typed in the console works there. Press F5 and the program stops again in the same file. Press it once more to run on. + +> [!WARNING] +> In twinBASIC BETA 1005, a program that is stopped by **Break** in a loop that holds only `DoEvents` ends with a native access violation as soon as the IDE evaluates anything. A line typed in the console does it, even `? 1`, and so does any watch, because the IDE evaluates every watch at every stop. The DEBUG CONSOLE prints `NATIVE EXCEPTION: ACCESS_VIOLATION`, and the IDE stays in the paused state, with the arrow on the VB package's line and **Stop** and the step commands enabled, although the program has ended. Do not use **Break** on a loop that holds only `DoEvents`. Put a statement in the loop and a [breakpoint](Breakpoints-and-Stepping#breakpoints) on it instead. If the IDE is already in that state, **Restart the compiler** in the toolbar puts it right. + +## What Start runs + +Start asks the compiler to run the project. The program runs inside the compiler's own process, not as an executable of its own, and it is the debug build of the project: [Compiler Options (DEBUG)](../Project-Settings#compiler-options-debug) says what the IDE can do with code that LLVM compiles. + +What runs first is the project's [Startup Object](../Project-Settings#startup-object): + +- **Sub Main**, the default for an EXE project. The console shows `Executing 'Main'...`, then the output of the program. +- **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. + +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. + +### When the project does not compile + +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. + +> [!NOTE] +> In twinBASIC BETA 1005, 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. + +## Run Or Preview + +F6 runs one procedure without starting the whole project, or previews a form. It has no menu item and no toolbar button. It works when a code editor or a form designer is active, and what it does depends on where the caret is. + +- **In a procedure of a module.** The procedure runs, and the console shows `Executing '..'...` first. A `Private` procedure runs too. It runs as a debug session: a [breakpoint](Breakpoints-and-Stepping#breakpoints) in a procedure it calls stops it, a `Stop` statement stops it, and F5 then continues. The run ends with `(time taken: …)`. The Call Stack starts at the procedure, with no frame for `Main`. +- **In the code of a form.** The form is shown instead of a procedure being run. The console shows no `Executing` line, and the form's `Form_Load` runs. +- **In a form designer.** The form is previewed. The **PREVIEW** button of the designer's toolbar, titled *Launch the form in isolation to test its functionality*, does the same. A UserControl or a property page designer does not support a preview. + +A preview is also a debug session. A breakpoint in `Form_Load` stops it, and a click on a button of the previewed form stops at a breakpoint in the button's `Click` procedure. Closing the window runs `Form_Unload` and `Form_Terminate` and writes `[DEBUGGER] done.` to the console. **End** during a preview of the form's code runs `Form_Terminate` but not `Form_Unload`. + +F6 refuses what it cannot run, and says so in the console, with nothing else happening: + +- A procedure with a required argument: `(compile error: Too few arguments, expected 1 in call to 'Greet')`. When the procedure also has an **Optional** argument, the message reads `expected at least 1`. +- A method of a class: `(compile error: Unrecognized member 'Thing' on type '' [non-extensible object])`. +- A caret at module level, outside a procedure: `RunCodeUnderCursor: failed to identify the procedure at the cursor position.` +- With no editor active: `runCodeUnderCursor failed: no active text editor.` + +> [!NOTE] +> In twinBASIC BETA 1005, F6 also gives `failed to identify the procedure at the cursor position.` when the caret is on the procedure's `End Sub` or `End Function` line, or on another of its lines at or past the column where that `End` line ends. A comment at the end of the `End` line moves that limit to where the comment starts. Nothing runs. Put the caret nearer the start of a line of the procedure. + +The CodeLens **▶ run** link above a procedure runs it by another route: [CodeLens](../Code/IntelliSense#codelens) describes it. [Testing with Assert](../../../Tutorials/Testing-with-Assert) uses both. + +## Programs that your program starts + +A program that your program starts with `Shell` shows in the status bar as a green box reading **PROCESS** *pid* **: RUNNING**, one box for each process. A run that you start with F5 or F6 makes no box of its own. The box appears when the console writes `[SHELL] process started.` and goes away when the process ends, with `[SHELL] process exited with code 0`. + +![Part of the status bar with one green box reading PROCESS 10432: RUNNING, and the box's menu open above it: the command line ping -n 20 127.0.0.1, a line under it, then Terminate and Bring To Front.](Images/Process_Box.png){:width="351" height="125"} + +Click a box to open its menu. The first line of the menu is the command line of the process. Under it are two commands: + +**Terminate** +: Ends the process. The console writes `exited with code 1`. + +**Bring To Front** +: Brings the window of the process to the front, and shows it if it was hidden. It writes nothing to the console. + +A `Shell` that runs from a line you type in the console makes a box too, with no program running. **Restart the compiler** removes the boxes and ends the processes. + +## What ends a run + +- **End**, or **Stop** on the toolbar. The console writes `aborted`, and the objects held by the procedure's local variables are released, so their `Class_Terminate` procedures run. A form program closes its windows and runs `Form_Terminate`, but not `Form_Unload`. +- **The program ends by itself.** After its last statement, or at an `End` statement, the run ends. Closing the window of a form program does the same: `Form_Unload` and `Form_Terminate` run, and the IDE is idle again. +- **A step out of `Main`.** Step Out from `Main` ends the run. +- **Restart the compiler**, from the toolbar or **Tools → Restart the compiler**, and **closing the project.** Both end the compiler process at once. A program that is running or paused dies without running `Form_Unload`, `Class_Terminate` or any other of its code, and the IDE asks for no confirmation. [Restarting the compiler](#restarting-the-compiler) says what the IDE keeps. +- **Opening a form designer.** The IDE tells the compiler to end the program if it is busy. A form program that is waiting for input ends, with `form terminate` and no `Form_Unload`. A loop with no `DoEvents` keeps running, and so does a program that is paused. +- **Editing the code.** See the next section. + +> [!NOTE] +> In twinBASIC BETA 1005, an `End` statement in a procedure that LLVM compiles ends the run, and then the console shows `restarting from MEMORY []`: the IDE restarts the compiler and compiles the project again. The IDE recovers by itself. Remove the `End` statement, or compile the procedure without LLVM, to avoid the restart. + +## Editing while the program runs + +Edit and Continue is not supported. The IDE does not make the editor read-only while a program runs or is paused, but any edit of the code ends the run: + +- **While the program is paused**, an edit anywhere, including a line below the arrow, a line above it, another procedure and a line that does not compile, writes `[EDIT & CONTINUE] not yet supported. Aborting.` to the console. The objects held by the local variables are released, then `aborted` and `[DEBUGGER] global variables have been cleared`. No dialog opens, and the tab of the file shows its unsaved dot. A breakpoint moves with the text, as it does in any edit. +- **While a form program runs**, an edit ends it too, with `(time taken: …)` and `form terminate` and no `Aborting` line. + +The next F5 starts a new run of the edited code. At the panel that a run-time error opens, an edit does not end the program as it should; see [Editing at the error panel](Breakpoints-and-Stepping#editing-at-the-error-panel). + +## 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. + +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. + +## See Also + +- [Breakpoints and stepping](Breakpoints-and-Stepping) -- what happens when the program pauses +- [Run menu](../Reference/Menu/Run) -- the three commands of the menu +- [Project Settings](../Project-Settings) -- Startup Object, Runtime Command Line Args and Compiler Options (DEBUG) diff --git a/docs/IDE/Debugging/index.md b/docs/IDE/Debugging/index.md new file mode 100644 index 000000000..9494983f7 --- /dev/null +++ b/docs/IDE/Debugging/index.md @@ -0,0 +1,70 @@ +--- +title: Running and debugging +parent: IDE +nav_order: 6 +permalink: /tB/IDE/Debugging/ +has_toc: false +--- + +# Running and debugging + +The IDE runs your program inside the compiler, so you can stop it, look at its variables and step through its code without building an executable first. + +- [**Running**](Running) --- Start, Break and End, what Start runs, Run Or Preview (F6), the **PROCESS** boxes in the status bar, and what ends a run. +- [**Breakpoints and stepping**](Breakpoints-and-Stepping) --- breakpoints, the current-line marker, the step commands, Set Next Statement, and the panel that opens when a run-time error stops the program. +- [**Inspecting a paused program**](Inspecting) --- the **VARIABLES**, **WATCHES** and **CALL STACK** panels, and the value that appears when you point at a variable. +- [**Debug Console**](Debug-Console) --- program output, the input row that runs a line of code, and the trace logger. +- [**Memory**](Memory) --- the raw bytes of the paused program. + +The commands of these pages are listed by menu in the [Run menu](../Reference/Menu/Run) and [Debug menu](../Reference/Menu/Debug) reference pages, and as buttons in the [Toolbar](../Reference/Toolbar). + +## The three states of a program + +A program started from the IDE is in one of three states, and the state decides which commands work. + +- **Idle.** Nothing is running. **Start** works, and so do the commands that need only a project. +- **Running.** The program is executing. **Break** and **End** work. The step commands do not. +- **Paused.** The program has stopped at a breakpoint, after a step, at a `Stop` statement, at a run-time error, or because you pressed **Break**. **Start** (now continuing), **End**, the step commands and **Set Next Statement** work. The editor shows where the program stopped. + +The IDE does not make a paused or running program read-only. You can type in the editor in any state. [Running](Running#editing-while-the-program-runs) says what happens to the program when you do. + +At every stop, the IDE shows its own window and asks for the keyboard focus. + +## The panels that show a paused program + +Five panels show the state of a paused program. All of them are in the **View** menu. + +| Panel | Page | In the default layout | +|---|---|---| +| **CALL STACK** | [Call Stack](Inspecting#call-stack) | Yes. Ctrl+L shows it. | +| **VARIABLES** | [Variables](Inspecting#variables) | Yes. | +| **WATCHES** | [Watches](Inspecting#watches) | No. **Add Watch...** opens it. | +| **DEBUG CONSOLE** | [Debug Console](Debug-Console) | Yes. Ctrl+G shows it and moves the focus to its input row. | +| **MEMORY 1** | [Memory](Memory) | No. | + +Nothing opens a panel when the program stops. If you closed a panel, or your [panel layout](../Customising/Panel-Layouts) never had it, open it from the **View** menu before you run. The panels are empty, or dimmed, while the program is idle or running. + +## Keys + +| Key | Command | Works when | +|---|---|---| +| F5 | **Start** or continue | A project is open, and the program is idle or paused. | +| Ctrl+Break | **Break** | The program is running and not paused. | +| F6 | **Run Or Preview** | A code editor or a form designer is active. | +| F8, F11 | **Step Into** | The program is paused. | +| Shift+F8, F10 | **Step Over** | The program is paused. | +| Ctrl+Shift+F8, Shift+F11 | **Step Out** | The program is paused. | +| Ctrl+F9 | **Set Next Statement** | The program is paused. | +| F9 | **Toggle Breakpoint** | The focus is in a code editor. | +| Ctrl+Shift+F9 | **Clear All Breakpoints** | A project is open. | +| Shift+F9 | **Add Watch...** | A project is open. | +| Ctrl+L | Show **CALL STACK** | In any state of the program. | +| Ctrl+G | Show **DEBUG CONSOLE** | In any state of the program. | + +**End**, **Clear Watches** and the two **Debugger Options** items have no key. A key held down repeats, so holding F8 steps over and over. [Manage Keyboard Shortcuts](../Customising/Keyboard-Shortcuts) changes any of the keys. The Debug Console's input row has keys of its own, listed in [Debug Console](Debug-Console#the-input-row). + +## See Also + +- [Writing code](../Code/) -- the editor, where the breakpoints and the current line show +- [Project Settings](../Project-Settings) -- Startup Object, Break On All Errors, Debuggable and the trace settings +- [Testing with Assert](../../../Tutorials/Testing-with-Assert) -- run a procedure with F6 and read its failures diff --git a/docs/IDE/Diagnostics.md b/docs/IDE/Diagnostics.md deleted file mode 100644 index c5f401f5a..000000000 --- a/docs/IDE/Diagnostics.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Diagnostics -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/Diagnostics -index: diagnostics window ---- - -# Diagnostics - -![The Diagnostics pane with no project open. Four counts, each 0, sit at the right of its title bar: Errors, Warnings, Hints and Information. A label at the left of the pane names each one, with an arrow that curves up to its count from below.](Images/Diagnostics.png){:width="588" height="266"} - -![The title bar of the Diagnostics pane with two errors, no warnings, no hints and three information messages, each count in a red box. A label reading Click to toggle ON/OFF sits below the bar, with an arrow rising from it to each of the four counts.](Images/Diagnostics_Toggles.png){:width="446" height="121"} - -![The same title bar with only the number of each count in a red box. A label reading Total counts for each category sits below the bar, with an arrow rising from it to each of the four numbers.](Images/Diagnostics_Totals.png){:width="446" height="118"} - - - -## Categories - -- 🟥 Error -- 🟨 Warning -- 🟩 Hints -- 🟦 Info diff --git a/docs/IDE/Editor.md b/docs/IDE/Editor.md deleted file mode 100644 index 16c9a7cba..000000000 --- a/docs/IDE/Editor.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -title: Editor -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/Editor ---- - -# Editor - -![The EDITOR 1 pane with its two header buttons boxed and labelled Options and Tabs List. The Options menu is open, and the lists of its four dropdowns are drawn open beside it, each with an arrow from its dropdown: Show Code Folding set to when hovering, Render Whitespace to none, Font Size to 13px and Show Navigation Bar to above. Below the dropdowns sit the toggles, of which Show Indent Guides, Show Line Numbers, Show MiniMap, Auto Prettify Code and Show CodeLens Run Procedure are ticked.](Images/Editor.png){:width="760" height="440"} - -## Options - -- Show Code Folding - - never - - *when hovering* - - always - -- Render Whitespace - - all - - *none* - - boundary - - selection - - trailing - -- Font Size - - 8px - - ... - - *13px* - - ... - - 30px -- Show Navigation Bar - - above - - below - - none -- ✓ Show Indent Guides -- ✓ Show Line Numbers -- Code Hints Always Visible -- Sticky Scroll -- ✓ Show MiniMap -- Show Advanced Info On Hover -- Show Single Row Of Tabs -- ✓ Auto Prettify Code -- ✓ Show CodeLens Run Procedure - -## Tabs List - -When a file is open in the _Editor_ it will be listed in the _Tabs List_ and you can jump between them here. - -![The open Tabs List menu, holding a Recently Closed submenu entry above a separator and one open file beneath it, Sources/frmMain.tbform.](Images/Editor_TabsList_Example.png){:width="187" height="66"} - -_Recently Closed_. - -![The Tabs List button with its menu open directly beneath it, holding a single entry, Recently Closed, with a submenu arrow and no files listed under it.](Images/Editor_TabsList_RecentlyClosed.png){:width="152" height="54"} - -_Recently Closed - List_ - -![The Recently Closed entry highlighted with its submenu expanded to the right, listing three closed files: Resources/MANIFEST/#1.xml, Resources/STRING/Strings.json and Resources/MESSAGETABLE/Strings.json.](Images/Editor_TabsList_RecentlyClosed_Example.png){:width="404" height="104"} diff --git a/docs/IDE/FindReplace.md b/docs/IDE/FindReplace.md deleted file mode 100644 index be002ce2a..000000000 --- a/docs/IDE/FindReplace.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Find / Replace -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/FindReplace -index: - - find and replace - - search and replace ---- - -# Find / Replace - -![The Find / Replace pane. Find What and Replace With are both empty. The scope radio group has Current File selected, with Current Procedure, Current Module and Selected Text greyed out. Direction is set to All, and the checkboxes Whole Word Only, Match Case, Pattern Matching and Match Regular Expressions are all clear, while Inside Packages is greyed out. Find Next, Cancel, Replace and Replace All run down the right edge.](Images/FindReplace.png){:width="600" height="209"} - -Find What - -Replace With - -- Current Procedure -- Current Module -- Current File -- Current Project -- Selected Text - -Direction: All / Down / Up - -![The Direction dropdown with its list open below it, offering All, marked as the current choice, then Down and Up.](Images/FindReplace_Direction.png){:width="232" height="99"} - -- Whole Word Only -- Match Case -- Pattern Matching -- Match Regular Expressions -- Inside Packages - -Find Next - -Cancel - -Replace - -Replace All diff --git a/docs/IDE/History.md b/docs/IDE/History.md deleted file mode 100644 index a839f9f41..000000000 --- a/docs/IDE/History.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: History -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/History -index: - - history panel - - history window ---- - -# History - -When a project isn't open this will be empty. - -![The HISTORY panel with no project open: a title bar with the drag grip and close button, above an empty body.](Images/History.png){:width="300" height="96"} - -Once a project is open, editing a file adds an entry. - -![The same panel after the form and the code of a project have been edited, listing two entries both named frmMain, timestamped 18:02 and 16:41 at the right edge.](Images/History_1.png){:width="300" height="96"} - -If you hover on an item it will show you the path of the file. - -![The pointer resting on the 18:02 frmMain entry, which raises a tooltip reading /SampleProject/Sources/frmMain.tbform [line: undefined] above a second line giving Fri Jan 09 2026 18:02:51 GMT+0000 (GMT+00:00).](Images/History_2.png){:width="360" height="120"} - -If it's a code file (i.e. `.twin`) it will show the "line: #". - -![The pointer on the 16:41 entry instead, its tooltip reading /SampleProject/Sources/frmMain.twin [line: 4] above Fri Jan 09 2026 16:41:58 GMT+0000 (GMT+00:00).](Images/History_3.png){:width="360" height="120"} - -You can also click on an item to open it. diff --git a/docs/IDE/Images/Memory_String.light.png b/docs/IDE/Images/Memory_String.light.png new file mode 100644 index 000000000..fe34fff37 Binary files /dev/null and b/docs/IDE/Images/Memory_String.light.png differ diff --git a/docs/IDE/Images/Memory_String.light.svg b/docs/IDE/Images/Memory_String.light.svg new file mode 100644 index 000000000..a52b67f33 --- /dev/null +++ b/docs/IDE/Images/Memory_String.light.svg @@ -0,0 +1 @@ +Startup(general)Main12345' The project shoot_docs.mjs runs for the Memory pane's picture (setup memory). Main' stops with s holding fifteen characters: two bytes each and two for the terminating' null fill two rows of the pane at Cols 16, so nothing else in memory is shown.ModuleStartupMEMORY 1&H0F423AC8480065006C006C006F00200074007700H.e.l.l.o..t.w.&H0F423AD869006E00420041005300490043000000i.n.B.A.S.I.C...StrPtr(s)Byte (Hex)Cols 16 diff --git a/docs/IDE/Images/Memory_String.png b/docs/IDE/Images/Memory_String.png new file mode 100644 index 000000000..806539f2c Binary files /dev/null and b/docs/IDE/Images/Memory_String.png differ diff --git a/docs/IDE/Images/Memory_String.svg b/docs/IDE/Images/Memory_String.svg new file mode 100644 index 000000000..65af27b7a --- /dev/null +++ b/docs/IDE/Images/Memory_String.svg @@ -0,0 +1 @@ +Startup(general)Main12345' The project shoot_docs.mjs runs for the Memory pane's picture (setup memory). Main' stops with s holding fifteen characters: two bytes each and two for the terminating' null fill two rows of the pane at Cols 16, so nothing else in memory is shown.ModuleStartupMEMORY 1&H0F423AC8480065006C006C006F00200074007700H.e.l.l.o..t.w.&H0F423AD869006E00420041005300490043000000i.n.B.A.S.I.C...StrPtr(s)Byte (Hex)Cols 16 diff --git a/docs/IDE/Memory.md b/docs/IDE/Memory.md deleted file mode 100644 index f9b899266..000000000 --- a/docs/IDE/Memory.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Memory -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/Memory -index: - - memory window - - memory panel ---- - -# Memory - -![The Memory 1 pane showing only its toolbar row: a refresh button, an empty box prompting for an expression to watch, a display format dropdown set to Byte (Hex), and a column count dropdown set to Cols 16. No memory contents are listed.](Images/Memory.png){:width="696" height="82"} - -The Memory pane displays the raw contents of process memory during a paused debugging session, with addresses in the left column and byte values on the right. It is useful for inspecting data structures at the byte level. - -A toolbar runs along the top of the pane. The box marked **enter expression** takes the expression whose address is to be watched; the two dropdowns beside it set the display format --- **Byte (Hex)** above --- and the number of columns --- **Cols 16**. The refresh button on the left re-reads the memory at that address. - -The pane lists nothing until an expression is entered during a paused debugging session, which is the state shown above. More than one Memory pane can be open at once, and the title bar numbers them; the pane above is **MEMORY 1**. diff --git a/docs/IDE/Menu/Add-Ins.md b/docs/IDE/Menu/Add-Ins.md index 0637c2a32..bf16e2d74 100644 --- a/docs/IDE/Menu/Add-Ins.md +++ b/docs/IDE/Menu/Add-Ins.md @@ -1,9 +1,11 @@ --- title: Add-Ins parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 9 -permalink: /tB/IDE/Project/Menu/AddIns +permalink: /tB/IDE/Reference/Menu/AddIns +redirect_from: + - /tB/IDE/Project/Menu/AddIns --- # Add-Ins Menu diff --git a/docs/IDE/Menu/Debug.md b/docs/IDE/Menu/Debug.md index 1e0365d47..5993e8b10 100644 --- a/docs/IDE/Menu/Debug.md +++ b/docs/IDE/Menu/Debug.md @@ -1,112 +1,51 @@ --- title: Debug parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 6 -permalink: /tB/IDE/Project/Menu/Debug +permalink: /tB/IDE/Reference/Menu/Debug +redirect_from: + - /tB/IDE/Project/Menu/Debug --- # Debug Menu -![The Debug menu open with every stepping, watch and breakpoint command greyed out --- Step Into, Step Over, Add Watch, Clear Watches, Toggle Breakpoint, Clear All Breakpoints and Set Next Statement --- leaving Debugger Options at the foot of the menu as the only available entry, marked with a submenu arrow.](Images/Menu_Debug.png){:width="291" height="258"} +The Debug menu holds the commands for stepping through a paused program, for watches and for breakpoints. [Running and debugging](../../Debugging/) explains them. -- Step Into F8 / F11 -- Step Over SHIFT + F8 / F10 +![The Debug menu open while a program is paused and the code editor has the focus, every entry available: Step Into, Step Over, Add Watch..., Clear Watches, Toggle Breakpoint, Clear All Breakpoints, Set Next Statement (Jump To Line), and Debugger Options at the foot, marked with a submenu arrow.](Images/Menu_Debug.png){:width="291" height="258"} ---- -- Add Watch... SHIFT + F9 -- Clear Watches - ---- -- Toggle Breakpoint F9 -- Clear All Breakpoints CTRL + SHIFT + F9 - ---- -- Set Next Statement (Jump To Line) CTRL + F9 - ---- -- Debugger Options - -## When a run-time error stops the program -{: index="break mode" } - -A run-time error that no `On Error` statement handles stops the program on the line that raised it. It stops the same way whether the program was started with **Run → Start** (F5), from the **▶ run** link above a procedure, or by a `[RunAfterBuild]` procedure. With **Break On All Errors** on, a handled error stops it too; see [Debugger Options](#debugger-options). - -The examples below use this procedure. On the fourth pass through the loop, `idx` is 7, past the last index of `a`, which is 5: - -```tb check_build -Public Sub FillTable() - Dim a(5) As Long - Dim i As Long - Dim idx As Long - Dim total As Long - For i = 1 To 6 - idx = Choose(i, 1, 3, 5, 7, 2, 4) - a(idx) = i * 10 - total = total + i - Debug.Print "i = " & i & ", idx = " & idx & ", total = " & total - Next i - Debug.Print "loop finished, i = " & i & ", total = " & total -End Sub -``` - -### Where it stopped, and what the variables hold +## Stepping -The editor opens the module, highlights the whole failing line, `a(idx) = i * 10`, and puts a yellow arrow in the margin beside it. Under the line, an error panel shows: +**Step Into** (F8, F11) and **Step Over** (Shift+F8, F10) +: Run the program one statement at a time. They work only while the program is paused. The menu shows the first key of each. There is no **Step Out** item: the toolbar button and the keys Ctrl+Shift+F8 and Shift+F11 do it. See [Step commands](../../Debugging/Breakpoints-and-Stepping#step-commands). -- **Run-time error 9 (00000009)**: the error number, then the same number in hexadecimal; -- **DESCRIPTION:** and the error's description, here *Subscript out of range*; -- four buttons: **Try Again (Resume)**, **Ignore (Resume Next)**, **Stop** and **Search Online**. +**Set Next Statement (Jump To Line)** (Ctrl+F9) +: Makes the line that has the caret the next one to run. It works only while the program is paused. See [Set Next Statement](../../Debugging/Breakpoints-and-Stepping#set-next-statement). -The number is the one `Err.Number` holds. Nothing about the error is written to the [Debug Console](../DebugConsole). The panel's **×** only hides the panel. +## Watches -The other panes show the state of the program at the failing line: +**Add Watch...** (Shift+F9) +: Opens the **WATCHES** panel with a box for a new expression, filled with the selected text of the editor. It works whenever a project is open. See [Watches](../../Debugging/Inspecting#watches). -- **[Call Stack](../CallStack)** lists the chain of calls, the failing procedure first, each with its file and its `line:column`. Here that is `FillTable`, then the procedure that called it, if there is one. Clicking a row opens that file at that line, and **Variables** then shows that procedure's variables. -- **[Variables](../Variables)** shows the failing procedure's *Locals*, already expanded: `i` is 4, `idx` is 7 and `total` is 6. The array shows as `{array of 6 elements}` until it is expanded. -- Holding the mouse pointer over a variable in the editor shows its value. +**Clear Watches** +: Removes every watch, with no confirmation. It has no key. -### Reading and changing values in the Debug Console +## Breakpoints -The input row at the bottom of the [Debug Console](../DebugConsole) runs a line of code inside the stopped procedure: +**Toggle Breakpoint** (F9) +: Sets or clears a breakpoint at the caret's line. It works when the focus is in a code editor. See [Breakpoints](../../Debugging/Breakpoints-and-Stepping#breakpoints). -- `? i` prints `4`, with a `(time taken: …)` line after it. `Debug.Print i` does the same. -- `i` on its own is refused, with `(compile error: Expression is neither used nor assigned)`. -- `idx = 5` gives `idx` a new value, and **Variables** shows it. - -Running any line in the console removes the error panel. The failing line stays highlighted, and the keys below still work. - -### The four buttons - -- **Try Again (Resume)** runs the failing line again. Unless a value it uses has changed, the error happens again at once. F5 does the same, and still works after the panel has gone. -- **Ignore (Resume Next)** skips the failing line and goes on from the statement after it. Here the loop runs to the end, and prints `i = 4, idx = 7, total = 10` for the pass that failed. No menu command or key does this. -- **Stop** does not end the program at an error. See [Traps](#traps). -- **Search Online** opens a web search for the error's number and description in the default browser. - -### Stepping on through the rest of the loop - -**Step Into** (F8 / F11) and **Step Over** (SHIFT + F8 / F10) do not move past the failing line. They run it again, and the error happens again at once. To go on one line at a time, first do one of these, then step: - -- **Correct the value in the console.** After `idx = 5`, F8 runs the failing line without an error, and stops on the next one, `total = total + i`. -- **Move past the line with Set Next Statement.** Click in the next line and press CTRL + F9. The yellow arrow moves there without running the failing line, and the panel closes. F8 then runs the line the arrow is on. - -To run on to the end instead, correct the value and press F5, or press **Ignore (Resume Next)** while the panel is still there. - -### Traps - -- **The panel disappears, and Ignore with it.** Running a line in the Debug Console, or clicking another procedure in the Call Stack, removes the panel. F5 brings it back only by running the failing line again. Set Next Statement onto the next line, then F5, does what **Ignore (Resume Next)** would have done. -- **A step key leaves a step waiting.** After F8 on the failing line, **Ignore (Resume Next)** stops again on the next line instead of going on. Press F5 to go on. -- **At an error, Stop ends only the procedure that failed.** The procedure that called it goes on from its next line: if `Sub Main` called `FillTable`, the rest of `Sub Main` still runs. The panel's **Stop**, the toolbar's **Stop** and **Run → End** all do this. At a failed **Assert** check, **Stop** ends only the check, and the test goes on past it as if the check had passed. To end the whole run, first move the arrow to a later line with CTRL + F9, then press **Stop**. Away from an error --- at a breakpoint, after a step, or after Set Next Statement --- **Stop** ends the run. - -The last two are defects, as of BETA 1005. +**Clear All Breakpoints** (Ctrl+Shift+F9) +: Clears the breakpoints in every file of the project, open or not. It works whenever a project is open. ## Debugger Options -![The same Debug menu with Debugger Options highlighted and its submenu open to the right, holding two entries: Break On All Errors, unticked, and Allow Breakpoints (Debuggable), ticked.](Images/Menu_Debug_DebuggerOptions.png){:width="534" height="285"} - -- Break On All Errors -- ✓ Allow Breakpoints (Debuggable) +![The Debug menu with no project open, its other entries greyed out, Debugger Options highlighted and its submenu open to the right, holding two entries: Break On All Errors, unticked, and Allow Breakpoints (Debuggable), ticked.](Images/Menu_Debug_DebuggerOptions.png){:width="534" height="285"} ![A close-up of that submenu on its own: a tick against Allow Breakpoints (Debuggable) and none against Break On All Errors.](Images/Menu_Debug_DebuggerOptions_2.png){:width="252" height="56"} -**Break On All Errors** turns the project setting of the [same name](../Settings#break-on-all-errors) on and off. It is off by default, and then an error raised while `On Error Resume Next` or `On Error GoTo` is in effect goes to that handler, as usual. When it is on, the program stops at the failing line even there, with the same error panel as [an error that nothing handles](#when-a-run-time-error-stops-the-program). **Ignore (Resume Next)** then skips the line. Under `On Error Resume Next`, `Err.Number` still holds the error afterwards, so code that checks it sees the error. Under `On Error GoTo`, the handler never runs. +**Break On All Errors** +: Turns the project setting of the [same name](../../Project-Settings#break-on-all-errors) on and off. It is off by default. When it is on, the program stops at an error that an `On Error` statement would handle. See [Break On All Errors and Debuggable](../../Debugging/Breakpoints-and-Stepping#break-on-all-errors-and-debuggable). + +**Allow Breakpoints (Debuggable)** +: Turns the project setting [Debuggable](../../Project-Settings#debuggable) on and off. It is on by default. While it is off, no breakpoint stops the program. diff --git a/docs/IDE/Menu/Edit.md b/docs/IDE/Menu/Edit.md index 7758721d7..b632b141c 100644 --- a/docs/IDE/Menu/Edit.md +++ b/docs/IDE/Menu/Edit.md @@ -1,54 +1,66 @@ --- title: Edit parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 2 -permalink: /tB/IDE/Project/Menu/Edit +permalink: /tB/IDE/Reference/Menu/Edit +redirect_from: + - /tB/IDE/Project/Menu/Edit --- # Edit Menu -![The Edit menu open with every command greyed out. Its groups run from Undo and Redo through the clipboard commands, Find, Replace and Find In Project, the indent and format commands, the Quick Find group, the fold and unfold commands, and finally Go To Line/Column and four Transform To case commands. Most entries show a keyboard shortcut at the right.](Images/Menu_Edit.png){:width="297" height="748"} +The Edit menu holds the commands that change and search text. [Writing code](../../Code/) explains them. -- Undo CTRL + Z -- Redo CTRL + Y +![The Edit menu open while the code editor has the focus, with no command greyed out. Its groups run from Undo and Redo through the clipboard commands, with Paste As Comment between Paste and Delete, then Find, Replace and Find In Project, the indent and format commands, the Quick Find group, the fold and unfold commands, and finally Go To Line/Column and four Transform To case commands. Most entries show a keyboard shortcut at the right.](Images/Menu_Edit.png){:width="297" height="771"} ---- -- Cut CTRL + X / SHIFT + DELETE -- Copy CTRL + C / CTRL + INSERT -- Paste CTRL + V -- Delete DELETE -- Select All CTRL + A +The picture shows the menu while the code editor has the focus. The commands act on the code editor, so which ones are available depends on what has the focus. With nothing in focus, **Paste As Comment** is missing and every command is greyed out. While the form designer has the focus, **Paste As Comment** is missing and the text commands, from **Cut** and **Copy** to the **Transform To** items, are greyed out. ---- -- Find... CTRL + F -- Replace... CTRL + H -- Find In Project... CTRL + SHIFT + F +## Undo and the clipboard ---- -- Indent CTRL + ] -- Outdent CTRL + [ -- Format Selection -- Format Document +**Undo** (Ctrl+Z) and **Redo** (Ctrl+Y) +: Step back and forward through the changes to the file. There is no second key for Redo. See [Undo and redo](../../Code/Editor#undo-and-redo). ---- -- Quick Find... ALT + F -- Quick Replace... ALT + H -- Select All Matches ALT + A +**Cut**, **Copy** and **Paste** +: The editor's own clipboard commands, with the keys Shift+Delete, Ctrl+Insert and Ctrl+V as well as the usual ones. **Paste** re-indents the text to the code around it. See [Copy, cut and paste](../../Code/Editor#copy-cut-and-paste). ---- -- Fold CTRL + { -- Fold Procedures CTRL + ALT + ARROWLEFT -- Fold All -- Unfold CTRL + } -- Unfold Procedures CTRL + ALT + ARROWRIGHT -- Unfold All +**Paste As Comment** (Ctrl+Alt+V) +: Pastes the clipboard text with `' ` before each line that is not blank. ---- -- Go To Line/Column... +## Find and replace ---- -- Transform To Uppercase -- Transform To Lowercase -- Transform To Titlecase -- Transform To Snakecase \ No newline at end of file +**Find...** (Ctrl+F) and **Replace...** (Ctrl+H) +: Both open the **FIND / REPLACE** panel and fill it with the word at the caret. See [Finding](../../Code/Finding). + +**Find In Project...** (Ctrl+Shift+F) +: Opens the same panel with **Current Project** ticked. It has no list of results. See [Search the whole project](../../Code/Finding#search-the-whole-project). + +**Quick Find...** (Alt+F), **Quick Replace...** (Alt+H) and **Select All Matches** (Alt+A) +: Search the current file with a small box over the code, and give a cursor to every match. Their names are in English whatever the IDE language is. See [Quick Find](../../Code/Finding#quick-find). + +## Indent and format + +**Indent** (Ctrl+]) and **Outdent** (Ctrl+[) +: Move the selected lines one level in or out. See [Indent and outdent](../../Code/Editor#indent-and-outdent). + +**Format Selection** and **Format Document** +: Re-indent the selection or the file, four spaces to a level. They have no keys. See [Format the code](../../Code/Editor#format-the-code). + +## Fold and unfold + +**Fold** (Ctrl+{) and **Unfold** (Ctrl+}) +: Fold and unfold the block at the caret. + +**Fold Procedures** (Ctrl+Alt+Left) and **Unfold Procedures** (Ctrl+Alt+Right) +: Fold or unfold every procedure except the one the caret is in. + +**Fold All** and **Unfold All** +: Fold or unfold every block in the file. These two have no keys. See [Folding](../../Code/Editor#folding). + +## Go to and transform + +**Go To Line/Column...** +: Opens a box that takes a line number, or `line:column`. It has no key. See [Go to a line](../../Code/Navigating#go-to-a-line). + +**Transform To Uppercase**, **Transform To Lowercase**, **Transform To Titlecase** and **Transform To Snakecase** +: Change the case of the selected text. They have no keys. See [Change case](../../Code/Editor#change-case). diff --git a/docs/IDE/Menu/File.md b/docs/IDE/Menu/File.md index 9ac64c5c8..011d36541 100644 --- a/docs/IDE/Menu/File.md +++ b/docs/IDE/Menu/File.md @@ -1,38 +1,20 @@ --- title: File parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 1 -permalink: /tB/IDE/Project/Menu/File +permalink: /tB/IDE/Reference/Menu/File +redirect_from: + - /tB/IDE/Project/Menu/File --- # File Menu ![The File menu open below the menu bar. New Project with CTRL+N, Open Project with CTRL+O, Open Recent, and Exit with ALT+F4 are available; Close Project, Save Project, Save Project As, Export Project with CTRL+E, Save Current Document, Build and Clean are all greyed out.](Images/Menu_File.png){:width="190" height="337"} -- New Project... CTRL + N -- Open Project... CTRL + O -- Open Recent... -- Close Project - ---- -- Save Project CTRL + S -- Save Project As... - ---- -- Export Project... CTRL + E -- Save Current Document - ---- -- Build -- Clean - ---- -- Exit ALT + F4 - ## Export Project -Writes the open project out to a folder as separate files, instead of the single `.twinproj` file. The folder gets the project's `Settings` file, its `Sources`, `Resources` and other folders, and a `Packages` folder with the full source of every package the project references. For a small project that uses the **VB** package, the export held 477 files: two of the project's own, and 475 under `Packages`. CTRL + E runs the same command. +Writes the open project out to a folder as separate files, instead of the single `.twinproj` file. The folder gets the project's `Settings` file, its `Sources`, `Resources` and other folders, and a `Packages` folder with the full source of every package the project references. For a small project that uses the **VB** package, the export held 477 files: two of the project's own, and 475 under `Packages`. Ctrl+E runs the same command. **Before it writes anything, the command deletes everything already in the target folder.** It does not ask first, and the deleted files do not go to the Recycle Bin. Files that have nothing to do with the project are deleted too: hidden files, a `.git` folder, anything else there. @@ -41,14 +23,14 @@ Writes the open project out to a folder as separate files, instead of the single ### Where it writes -- **With [*Export Path*](../Settings#export-path) set**, the command exports there straight away, with no dialog. +- **With [*Export Path*](../../Project-Settings#export-path) set**, the command exports there straight away, with no dialog. - **With no *Export Path***, it opens a *Browse For Folder* dialog, which starts in the folder that holds the `.twinproj`. The export goes into a subfolder of the chosen folder named after the project, and it is that subfolder that is emptied. For a project named `MyApp`, choosing `C:\Work` exports into `C:\Work\MyApp\`. If the project itself is kept in `C:\Work\MyApp\`, choosing `C:\Work` empties the project's own folder. -With [*Export After Save*](../Settings#export-after-save) set, every save runs the export again, into *Export Path*, and empties that folder again. +With [*Export After Save*](../../Project-Settings#export-after-save) set, every save runs the export again, into *Export Path*, and empties that folder again. ### When a file cannot be deleted -A read-only file stops the export part-way. Everything before it in alphabetical order is already deleted by then, everything after it is left, and nothing is written. No message appears. The only record is in the [Debug Console](../DebugConsole): +A read-only file stops the export part-way. Everything before it in alphabetical order is already deleted by then, everything after it is left, and nothing is written. No message appears. The only record is in the [Debug Console](../../Debugging/Debug-Console): ```text [EXPORT] DELETE FAILED: \\?\C:\Work\MyApp\notes.txt @@ -60,7 +42,7 @@ Git makes the files in `.git\objects` read-only. When the target is the top fold ### Exporting into the project's own folder -When the target is the folder that holds the `.twinproj`, the command deletes the `.twinproj` file too. The project then exists only in the open IDE, until **Save Project** (CTRL + S) writes the file back. For this reason the Project Settings dialog refuses `${SourcePath}` on its own as the *Export Path*, but it accepts the same folder written out in full. +When the target is the folder that holds the `.twinproj`, the command deletes the `.twinproj` file too. The project then exists only in the open IDE, until **Save Project** (Ctrl+S) writes the file back. For this reason the Project Settings dialog refuses `${SourcePath}` on its own as the *Export Path*, but it accepts the same folder written out in full. ### Packing the export back into a project @@ -68,7 +50,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](../New#import-from-folder) dialog, which opens the project unsaved; the first save asks for a file name. The import/export script'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](../../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. 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. @@ -76,10 +58,10 @@ The tB executable's `import` cannot pack a folder while any folder remains under ### What the Debug Console shows -Each export writes a first line, `[EXPORT] exporting current project to "…"`, and a last line: `[EXPORT] COMPLETED (72 folders, 478 files)`, or the failure lines above. With [*Export Verbose*](../Settings#export-verbose) set, it also writes a line for each file and folder it deletes, `[EXPORT] DELETED: …`, and for each file it writes, `[EXPORT] DONE: …`. +Each export writes a first line, `[EXPORT] exporting current project to "…"`, and a last line: `[EXPORT] COMPLETED (72 folders, 478 files)`, or the failure lines above. With [*Export Verbose*](../../Project-Settings#export-verbose) set, it also writes a line for each file and folder it deletes, `[EXPORT] DELETED: …`, and for each file it writes, `[EXPORT] DONE: …`. The `export` command of the tB executable and of the import/export script deletes nothing. See [What both programs do](../../../../Features/Packages/Import-Export-Tool#what-both-programs-do). ## Build -Compiles the project into the file that the [*Build Output Path*](../Settings#build-output-path) setting names. The project templates set it to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`, a file in a `Build` folder beside the `.twinproj`. `${Architecture}` is `win32` or `win64`, as chosen in the [build configuration](../Toolbar#build-configuration) box on the toolbar. +Compiles the project into the file that the [*Build Output Path*](../../Project-Settings#build-output-path) setting names. The project templates set it to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`, a file in a `Build` folder beside the `.twinproj`. `${Architecture}` is `win32` or `win64`, as chosen in the [build configuration](../Toolbar#build-configuration) box on the toolbar. diff --git a/docs/IDE/Menu/Format.md b/docs/IDE/Menu/Format.md index 57fff8fc5..4fa767328 100644 --- a/docs/IDE/Menu/Format.md +++ b/docs/IDE/Menu/Format.md @@ -1,9 +1,11 @@ --- title: Format parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 5 -permalink: /tB/IDE/Project/Menu/Format +permalink: /tB/IDE/Reference/Menu/Format +redirect_from: + - /tB/IDE/Project/Menu/Format --- # Format Menu @@ -14,55 +16,10 @@ Every command on this menu acts on the controls selected in the form designer, s ![The same Format menu with two controls selected in the form designer: all of its commands are available rather than greyed out.](Images/Menu_Format_1.png){:width="232" height="281"} -- Align -- Make Same Size - ---- -- Horizontal Spacing -- Vertical Spacing - ---- -- Center In Container (Horizontally) -- Center In Container (Vertically) - ---- -- Bring To Front -- Send To Back - ---- -- Lock Controls - ![The Format menu with Align highlighted and its submenu open to the right. Left, Center (Horizontal) and Right sit above Top, Center (Vertical) and Bottom, with To Grid on its own below; the four edge commands carry ALT plus arrow-key shortcuts. All are available.](Images/Menu_Format_Align.png){:width="436" height="281"} -- Left ALT + ARROWLEFT -- Center (Horizontal) -- Right ALT + ARROWRIGHT - ---- -- Top ALT + ARROWUP -- Center (Vertical) -- Bottom ALT + ARROWDOWN - ---- -- To Grid - ![The Format menu with Make Same Size highlighted and its submenu open to the right, offering Width (Widest), Width (Narrowest), Height (Tallest) and Height (Shortest), each with a CTRL plus SHIFT plus arrow-key shortcut.](Images/Menu_Format_MakeSameSize.png){:width="523" height="281"} -- Width (Widest) CTRL + SHIFT + ARROWRIGHT -- Width (Narrowest) CTRL + SHIFT + ARROWLEFT -- Height (Tallest) CTRL + SHIFT + ARROWDOWN -- Height (Shortest) CTRL + SHIFT + ARROWUP - ![The Format menu with Horizontal Spacing highlighted and its submenu open to the right. Make Equal is greyed out; Increase, Decrease and Remove are available.](Images/Menu_Format_HorizontalSpacing.png){:width="345" height="281"} -- Make Equal -- Increase -- Decrease -- Remove - ![The Format menu with Vertical Spacing highlighted and its submenu open to the right. Make Equal is greyed out; Increase, Decrease and Remove are available.](Images/Menu_Format_VerticalSpacing.png){:width="345" height="281"} - -- Make Equal -- Increase -- Decrease -- Remove diff --git a/docs/IDE/Menu/Help.md b/docs/IDE/Menu/Help.md index d2e656edb..71a1e2d2e 100644 --- a/docs/IDE/Menu/Help.md +++ b/docs/IDE/Menu/Help.md @@ -1,32 +1,17 @@ --- title: Help parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 11 -permalink: /tB/IDE/Project/Menu/Help +permalink: /tB/IDE/Reference/Menu/Help +redirect_from: + - /tB/IDE/Project/Menu/Help --- # Help Menu ![The Help menu open with every entry available, each beside its own icon: About twinBASIC, Licence Agreement and Automatic IDE Error Reporting; the Discord, GitHub and Twitter support links; Purchase A Licence, Enter Licence Key and Buy us a Coffee; and a closing status line reading Compiler services TRACE mode: Disabled.](Images/Menu_Help.png){:width="268" height="294"} -- About 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: Disabled - ## 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="506" height="880"} diff --git a/docs/IDE/Menu/Images/Menu_Debug.light.png b/docs/IDE/Menu/Images/Menu_Debug.light.png index c645b3c67..a7ee11b21 100644 Binary files a/docs/IDE/Menu/Images/Menu_Debug.light.png and b/docs/IDE/Menu/Images/Menu_Debug.light.png differ diff --git a/docs/IDE/Menu/Images/Menu_Debug.light.svg b/docs/IDE/Menu/Images/Menu_Debug.light.svg index 76342759f..81919d1ab 100644 --- a/docs/IDE/Menu/Images/Menu_Debug.light.svg +++ b/docs/IDE/Menu/Images/Menu_Debug.light.svg @@ -1 +1 @@ -DebugStep IntoF8 / F11Step OverSHIFT+F8 / F10Add Watch...SHIFT+F9Clear WatchesToggle BreakpointF9Clear All BreakpointsCTRL+SHIFT+F9Set Next Statement (Jump To Line)CTRL+F9Debugger Options +DebugStep IntoF8 / F11Step OverSHIFT+F8 / F10Add Watch...SHIFT+F9Clear WatchesToggle BreakpointF9Clear All BreakpointsCTRL+SHIFT+F9Set Next Statement (Jump To Line)CTRL+F9Debugger Options diff --git a/docs/IDE/Menu/Images/Menu_Debug.png b/docs/IDE/Menu/Images/Menu_Debug.png index d015fe041..3cdddbac1 100644 Binary files a/docs/IDE/Menu/Images/Menu_Debug.png and b/docs/IDE/Menu/Images/Menu_Debug.png differ diff --git a/docs/IDE/Menu/Images/Menu_Debug.svg b/docs/IDE/Menu/Images/Menu_Debug.svg index 076fbf99c..da9f41a6c 100644 --- a/docs/IDE/Menu/Images/Menu_Debug.svg +++ b/docs/IDE/Menu/Images/Menu_Debug.svg @@ -1 +1 @@ -DebugStep IntoF8 / F11Step OverSHIFT+F8 / F10Add Watch...SHIFT+F9Clear WatchesToggle BreakpointF9Clear All BreakpointsCTRL+SHIFT+F9Set Next Statement (Jump To Line)CTRL+F9Debugger Options +DebugStep IntoF8 / F11Step OverSHIFT+F8 / F10Add Watch...SHIFT+F9Clear WatchesToggle BreakpointF9Clear All BreakpointsCTRL+SHIFT+F9Set Next Statement (Jump To Line)CTRL+F9Debugger Options diff --git a/docs/IDE/Menu/Images/Menu_Edit.light.png b/docs/IDE/Menu/Images/Menu_Edit.light.png index 5653bf437..43b2bf79b 100644 Binary files a/docs/IDE/Menu/Images/Menu_Edit.light.png and b/docs/IDE/Menu/Images/Menu_Edit.light.png differ diff --git a/docs/IDE/Menu/Images/Menu_Edit.light.svg b/docs/IDE/Menu/Images/Menu_Edit.light.svg index 27800147d..9d8fcb06f 100644 --- a/docs/IDE/Menu/Images/Menu_Edit.light.svg +++ b/docs/IDE/Menu/Images/Menu_Edit.light.svg @@ -1 +1 @@ -EditUndoCTRL+ZRedoCTRL+YCutCTRL+X / SHIFT+DELETECopyCTRL+C / CTRL+INSERTPasteCTRL+VDeleteDELETESelect AllCTRL+AFind...CTRL+FReplace...CTRL+HFind In Project...CTRL+SHIFT+FIndentCTRL+]OutdentCTRL+[Format SelectionFormat DocumentQuick Find...ALT+FQuick Replace...ALT+HSelect All MatchesALT+AFoldCTRL+{Fold ProceduresCTRL+ALT+ARROWLEFTFold AllUnfoldCTRL+}Unfold ProceduresCTRL+ALT+ARROWRIGHTUnfold AllGo To Line/Column...Transform To UppercaseTransform To LowercaseTransform To TitlecaseTransform To Snakecase +EditUndoCTRL+ZRedoCTRL+YCutCTRL+X / SHIFT+DELETECopyCTRL+C / CTRL+INSERTPasteCTRL+VPaste As CommentCTRL+ALT+VDeleteDELETESelect AllCTRL+AFind...CTRL+FReplace...CTRL+HFind In Project...CTRL+SHIFT+FIndentCTRL+]OutdentCTRL+[Format SelectionFormat DocumentQuick Find...ALT+FQuick Replace...ALT+HSelect All MatchesALT+AFoldCTRL+{Fold ProceduresCTRL+ALT+ARROWLEFTFold AllUnfoldCTRL+}Unfold ProceduresCTRL+ALT+ARROWRIGHTUnfold AllGo To Line/Column...Transform To UppercaseTransform To LowercaseTransform To TitlecaseTransform To Snakecase diff --git a/docs/IDE/Menu/Images/Menu_Edit.png b/docs/IDE/Menu/Images/Menu_Edit.png index 393f684d7..c4d65ff6b 100644 Binary files a/docs/IDE/Menu/Images/Menu_Edit.png and b/docs/IDE/Menu/Images/Menu_Edit.png differ diff --git a/docs/IDE/Menu/Images/Menu_Edit.svg b/docs/IDE/Menu/Images/Menu_Edit.svg index 15dd4f09b..d37a664e5 100644 --- a/docs/IDE/Menu/Images/Menu_Edit.svg +++ b/docs/IDE/Menu/Images/Menu_Edit.svg @@ -1 +1 @@ -EditUndoCTRL+ZRedoCTRL+YCutCTRL+X / SHIFT+DELETECopyCTRL+C / CTRL+INSERTPasteCTRL+VDeleteDELETESelect AllCTRL+AFind...CTRL+FReplace...CTRL+HFind In Project...CTRL+SHIFT+FIndentCTRL+]OutdentCTRL+[Format SelectionFormat DocumentQuick Find...ALT+FQuick Replace...ALT+HSelect All MatchesALT+AFoldCTRL+{Fold ProceduresCTRL+ALT+ARROWLEFTFold AllUnfoldCTRL+}Unfold ProceduresCTRL+ALT+ARROWRIGHTUnfold AllGo To Line/Column...Transform To UppercaseTransform To LowercaseTransform To TitlecaseTransform To Snakecase +EditUndoCTRL+ZRedoCTRL+YCutCTRL+X / SHIFT+DELETECopyCTRL+C / CTRL+INSERTPasteCTRL+VPaste As CommentCTRL+ALT+VDeleteDELETESelect AllCTRL+AFind...CTRL+FReplace...CTRL+HFind In Project...CTRL+SHIFT+FIndentCTRL+]OutdentCTRL+[Format SelectionFormat DocumentQuick Find...ALT+FQuick Replace...ALT+HSelect All MatchesALT+AFoldCTRL+{Fold ProceduresCTRL+ALT+ARROWLEFTFold AllUnfoldCTRL+}Unfold ProceduresCTRL+ALT+ARROWRIGHTUnfold AllGo To Line/Column...Transform To UppercaseTransform To LowercaseTransform To TitlecaseTransform To Snakecase diff --git a/docs/IDE/Menu/Images/Menu_Run.light.png b/docs/IDE/Menu/Images/Menu_Run.light.png index e6d42a0c3..70a3db055 100644 Binary files a/docs/IDE/Menu/Images/Menu_Run.light.png and b/docs/IDE/Menu/Images/Menu_Run.light.png differ diff --git a/docs/IDE/Menu/Images/Menu_Run.light.svg b/docs/IDE/Menu/Images/Menu_Run.light.svg index eba30ed85..1e3496787 100644 --- a/docs/IDE/Menu/Images/Menu_Run.light.svg +++ b/docs/IDE/Menu/Images/Menu_Run.light.svg @@ -1 +1 @@ -RunStartF5BreakCTRL+BREAKEnd +RunStartF5BreakCTRL+BREAKEnd diff --git a/docs/IDE/Menu/Images/Menu_Run.png b/docs/IDE/Menu/Images/Menu_Run.png index e5d941e83..b04d2bbfb 100644 Binary files a/docs/IDE/Menu/Images/Menu_Run.png and b/docs/IDE/Menu/Images/Menu_Run.png differ diff --git a/docs/IDE/Menu/Images/Menu_Run.svg b/docs/IDE/Menu/Images/Menu_Run.svg index 954d1369c..09d93cfa1 100644 --- a/docs/IDE/Menu/Images/Menu_Run.svg +++ b/docs/IDE/Menu/Images/Menu_Run.svg @@ -1 +1 @@ -RunStartF5BreakCTRL+BREAKEnd +RunStartF5BreakCTRL+BREAKEnd diff --git a/docs/IDE/Menu/Images/Menu_View.light.png b/docs/IDE/Menu/Images/Menu_View.light.png index 70261ad0e..85bb1dc30 100644 Binary files a/docs/IDE/Menu/Images/Menu_View.light.png and b/docs/IDE/Menu/Images/Menu_View.light.png differ diff --git a/docs/IDE/Menu/Images/Menu_View.light.svg b/docs/IDE/Menu/Images/Menu_View.light.svg index 65d2cccbc..e1d079779 100644 --- a/docs/IDE/Menu/Images/Menu_View.light.svg +++ b/docs/IDE/Menu/Images/Menu_View.light.svg @@ -1 +1 @@ -ViewCode EditorObject DesignerSHIFT+F7DefinitionSHIFT+F2 / F12Last PositionCTRL+SHIFT+F2Object BrowserZoom InZoom OutEDITORPROJECT EXPLOREROPEN EDITORSHISTORYOUTLINECALL STACKCTRL+LVARIABLESWATCHESDEBUG CONSOLECTRL+GDIAGNOSTICSPACKAGE PUBLISHINGTOOLBARTOOLBOXPROPERTIESWEBPAGEFIND / REPLACEMEMORY 1 +ViewCode EditorObject DesignerSHIFT+F7DefinitionSHIFT+F2 / F12Last PositionCTRL+SHIFT+F2Object BrowserIncrease Font SizeCTRL++Decrease Font SizeCTRL+-EDITORPROJECT EXPLOREROPEN EDITORSHISTORYOUTLINECALL STACKCTRL+LVARIABLESWATCHESDEBUG CONSOLECTRL+GDIAGNOSTICSPACKAGE PUBLISHINGTOOLBARTOOLBOXPROPERTIESWEBPAGEFIND / REPLACEMEMORY 1 diff --git a/docs/IDE/Menu/Images/Menu_View.png b/docs/IDE/Menu/Images/Menu_View.png index f2baf414a..b2cfff8bc 100644 Binary files a/docs/IDE/Menu/Images/Menu_View.png and b/docs/IDE/Menu/Images/Menu_View.png differ diff --git a/docs/IDE/Menu/Images/Menu_View.svg b/docs/IDE/Menu/Images/Menu_View.svg index 4e7f37afb..66d88543d 100644 --- a/docs/IDE/Menu/Images/Menu_View.svg +++ b/docs/IDE/Menu/Images/Menu_View.svg @@ -1 +1 @@ -ViewCode EditorObject DesignerSHIFT+F7DefinitionSHIFT+F2 / F12Last PositionCTRL+SHIFT+F2Object BrowserZoom InZoom OutEDITORPROJECT EXPLOREROPEN EDITORSHISTORYOUTLINECALL STACKCTRL+LVARIABLESWATCHESDEBUG CONSOLECTRL+GDIAGNOSTICSPACKAGE PUBLISHINGTOOLBARTOOLBOXPROPERTIESWEBPAGEFIND / REPLACEMEMORY 1 +ViewCode EditorObject DesignerSHIFT+F7DefinitionSHIFT+F2 / F12Last PositionCTRL+SHIFT+F2Object BrowserIncrease Font SizeCTRL++Decrease Font SizeCTRL+-EDITORPROJECT EXPLOREROPEN EDITORSHISTORYOUTLINECALL STACKCTRL+LVARIABLESWATCHESDEBUG CONSOLECTRL+GDIAGNOSTICSPACKAGE PUBLISHINGTOOLBARTOOLBOXPROPERTIESWEBPAGEFIND / REPLACEMEMORY 1 diff --git a/docs/IDE/Menu/Project.md b/docs/IDE/Menu/Project.md index c3b38dc25..19d14c531 100644 --- a/docs/IDE/Menu/Project.md +++ b/docs/IDE/Menu/Project.md @@ -1,38 +1,30 @@ --- title: Project parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 4 -permalink: /tB/IDE/Project/Menu/Project +permalink: /tB/IDE/Reference/Menu/Project +redirect_from: + - /tB/IDE/Project/Menu/Project --- # Project Menu ![The Project menu open with every command greyed out: Add, which carries a submenu arrow, References with CTRL+T, Project Settings, Open Project Folder and Open Build Output Folder.](Images/Menu_Project.png){:width="200" height="169"} -- Add - ---- -- References... CTRL + T -- Project Settings... - ---- -- Open Project Folder... -- Open Build Output Folder... - ## Add -**Add** is the same as Right Click Add on the [Project Explorer](../Explorer) +**Add** is the same as Right Click Add on the [Project Explorer](../../Project/Explorer) ![The Project Explorer context menu with Add highlighted and its submenu opened out to the left. The submenu lists Add Folder; five Windows item types from Form through MDI Form, UserControl and PropertyPage to Report; Add CustomControls Form; the Unicode-capable .TWIN module and class; the .BAS module and .CLS class; Add Other File; Import; and three Add Resource entries for a Visual Styles Manifest, a String Table and a MESSAGETABLE. On the parent menu, Cut, Copy, Paste, View As JSON and View As Markdown Preview are greyed out.](../Images/RightClick-Add.png){:width="510" height="492"} ## References {: index_also="add reference" } -See [Project Settings](../Settings) filtered by "project.references". +See [Project Settings](../../Project-Settings) filtered by "project.references". ![The Project Settings dialog with project.references in its filter box, showing the Library References section on its Enabled Libraries tab. Four ticked rows are listed in priority order against Library Symbol and Version columns: the twinBASIC VBA and VBRUN compatibility packages, OLE Automation as stdole, and the IDE Extensibility package as tbIDE.](../Images/ProjectSettings_LibraryReferences.png){:width="1102" height="774"} ## Project Settings -See [Project Settings](../Settings). +See [Project Settings](../../Project-Settings). diff --git a/docs/IDE/Menu/Run.md b/docs/IDE/Menu/Run.md index e3a6d0844..133e55c4e 100644 --- a/docs/IDE/Menu/Run.md +++ b/docs/IDE/Menu/Run.md @@ -1,15 +1,26 @@ --- title: Run parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 7 -permalink: /tB/IDE/Project/Menu/Run +permalink: /tB/IDE/Reference/Menu/Run +redirect_from: + - /tB/IDE/Project/Menu/Run --- # Run Menu -![The Run menu open with all three commands greyed out: Start with F5 beside a play triangle, Break with CTRL+BREAK beside a pause icon, and End beside a stop icon.](Images/Menu_Run.png){:width="168" height="103"} +The Run menu holds the three commands that start, pause and end a run. [Running](../../Debugging/Running) explains them. -- Start F5 -- Break CTRL + BREAK -- End +![The Run menu open while a program runs: Start, with F5 beside a play triangle, is greyed out; Break, with CTRL+BREAK beside a pause icon, and End, beside a stop icon, are available.](Images/Menu_Run.png){:width="168" height="103"} + +**Start** (F5) +: Starts the program when nothing runs, and continues it when it is paused. The toolbar button is titled **Start / Continue**. It works when a project is open and the program is idle or paused. See [Start, Break and End](../../Debugging/Running#start-break-and-end). + +**Break** (Ctrl+Break) +: Pauses a running program. It works only while the program runs and is not paused. See [Break](../../Debugging/Running#break). + +**End** +: Stops the program, whether it is running or paused. It has no key. At a run-time error it ends only the procedure that failed; see [Traps](../../Debugging/Breakpoints-and-Stepping#traps). + +The menu has no other items. F6 (**Run Or Preview**) has no menu item; [Run Or Preview](../../Debugging/Running#run-or-preview) describes it. The programs that your program starts with `Shell` are listed in the [status bar](../../Debugging/Running#programs-that-your-program-starts), not in this menu. diff --git a/docs/IDE/Menu/Tools.md b/docs/IDE/Menu/Tools.md index fd5e58108..f19f577d2 100644 --- a/docs/IDE/Menu/Tools.md +++ b/docs/IDE/Menu/Tools.md @@ -1,27 +1,17 @@ --- title: Tools parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 8 -permalink: /tB/IDE/Project/Menu/Tools +permalink: /tB/IDE/Reference/Menu/Tools +redirect_from: + - /tB/IDE/Project/Menu/Tools --- # Tools Menu ![The Tools menu open with no project loaded: IDE Options beside a sliders icon, then, below a separator, the greyed-out Restart the compiler and Flush the LLVM compiler cache.](Images/Menu_Tools.png){:width="217" height="113"} -- IDE Options... -- Restart the compiler --- ends the compiler and starts a new one, which compiles the project again and loads the add-ins afresh. -- Flush the LLVM compiler cache --- clears the cache of code compiled with [LLVM](../../../../LLVM/Getting-Started), so that the next build compiles everything. - -![The twinBASIC IDE Options dialog: a scrolling list of right-aligned dark red labels against their controls. Tab Size reads 4, IDE Font Size and Code Editor Font Size both read 13px, IDE Font reads Segoe UI and Code Editor Font is empty, and the two drop-downs below them read above and none. Three LLVM Compiler settings follow: a maximum of 1 thread, a complex procedure reporting threshold of 10000 milliseconds, and a ticked box to keep the cache process alive after exiting the IDE. Then comes a run of tick boxes, of which only Restore Editors State When Opening Project and File Explorer: Ask Before Delete Files are ticked. Change Licence sits at the bottom left, with a greyed-out Save Changes and a Close button at the right.](Images/Menu_Tools_IDEOptions.png){:width="628" height="594"} - -> [!NOTE] -> -> TODO: Add each IDE Option item. - -| Option | Value | -| -------- | ----- | -| Tab Size | 4 | - -The three **LLVM Compiler** options are described in [Getting Started with LLVM](../../../../LLVM/Getting-Started#general-llvm-options). +- **IDE Options...** --- opens the **IDE Options** dialog, which holds the IDE's 31 options. The command has no default key. [IDE Options](../../Customising/IDE-Options) describes the dialog, with its picture, and every option. +- **Restart the compiler** --- ends the compiler and starts a new one, which compiles the project again and loads the add-ins afresh. +- **Flush the LLVM compiler cache** --- clears the cache of code compiled with [LLVM](../../../../LLVM/Getting-Started), so that the next build compiles everything. diff --git a/docs/IDE/Menu/View.md b/docs/IDE/Menu/View.md index f88e61e13..ef803de41 100644 --- a/docs/IDE/Menu/View.md +++ b/docs/IDE/Menu/View.md @@ -1,42 +1,76 @@ --- title: View parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 3 -permalink: /tB/IDE/Project/Menu/View +permalink: /tB/IDE/Reference/Menu/View +redirect_from: + - /tB/IDE/Project/Menu/View --- # View Menu -![The View menu open. The upper commands --- Code Editor, Object Designer, Definition, Last Position, Object Browser, Zoom In and Zoom Out --- are greyed out. Below them the seventeen panel names from EDITOR down to MEMORY 1 are all available, each beside its own icon, with CTRL+L against CALL STACK and CTRL+G against DEBUG CONSOLE.](Images/Menu_View.png){:width="218" height="626"} +The View menu switches between a file's code and its designer, moves around the code, changes the zoom, and shows the panels of the IDE. -- Code Editor -- Object Designer SHIFT + F7 +![The View menu open while the code editor has the focus. Code Editor and Object Browser are greyed out. Object Designer, Definition and Last Position are available, and so are Increase Font Size and Decrease Font Size, with CTRL++ and CTRL+-. Below them the seventeen panel names from EDITOR down to MEMORY 1 are all available, each beside its own icon, with CTRL+L against CALL STACK and CTRL+G against DEBUG CONSOLE.](Images/Menu_View.png){:width="218" height="626"} ---- -- Definition SHIFT + F2 / F12 -- Last Position CTRL + SHIFT + F2 +The picture shows the menu while the code editor has the focus. The upper commands depend on what has the focus; the panel names are always available. ---- -- Object Browser -- Zoom In -- Zoom Out +## Code and designer ---- -- EDITOR -- PROJECT EXPLORER -- OPEN EDITORS -- HISTORY -- OUTLINE -- CALL STACK CTRL + L -- VARIABLES -- WATCHES -- DEBUG CONSOLE CTRL + G -- DIAGNOSTICS -- PACKAGE PUBLISHING -- TOOLBAR -- TOOLBOX -- PROPERTIES -- WEBPAGE -- FIND / REPLACE -- MEMORY 1 +**Code Editor** +: Shows the code of the current form or control. It is greyed out while the code has the focus. + +**Object Designer** (Shift+F7) +: Shows the designer of the current form or control. + +## Navigation + +**Definition** (F12 or Shift+F2) +: Goes to where the name at the caret is declared. See [Go To Definition](../../Code/Navigating#go-to-definition). + +**Last Position** (Ctrl+Shift+F2) +: Goes back to the place the caret was before. See [Last Position](../../Code/Navigating#last-position). + +**Object Browser** +: Always greyed out. It has no function. + +## Zoom + +While the code editor has the focus, the menu shows **Increase Font Size** (Ctrl++) and **Decrease Font Size** (Ctrl+-) in place of the zoom commands. They change the editor's font by 1px and save the new size at once. See [Word wrap and zoom](../../Code/Editor#word-wrap-and-zoom). + +While the form designer has the focus, the menu shows **Zoom In** and **Zoom Out**, which scale the form designer. + +## Panels + +The last seventeen items show a panel. A panel that is in no layout in use comes up floating. See [Show, hide and focus a panel](../../Customising/Panel-Layouts#show-hide-and-focus-a-panel). + +**EDITOR** +: The code editor. See [Writing code](../../Code/). + +**PROJECT EXPLORER** +: The files and objects of the project. See [Project Explorer](../../Project/Explorer). + +**OPEN EDITORS**, **HISTORY** and **OUTLINE** +: The open files, the places where you edited, and the symbols of the current file. See [Navigating](../../Code/Navigating). + +**CALL STACK** (Ctrl+L), **VARIABLES**, **WATCHES**, **DEBUG CONSOLE** (Ctrl+G) and **MEMORY 1** +: The debugging panels: the chain of calls, the variables in scope, your watch expressions, the program's output and commands, and raw memory. See [Call Stack](../../Debugging/Inspecting#call-stack), [Variables](../../Debugging/Inspecting#variables), [Watches](../../Debugging/Inspecting#watches), [Debug Console](../../Debugging/Debug-Console) and [Memory](../../Debugging/Memory). The Watches and Memory panels are not in the default layout. + +**DIAGNOSTICS** +: The errors and warnings of the project. See [Diagnostics](../../Code/Diagnostics). + +**FIND / REPLACE** +: The search panel. See [Finding](../../Code/Finding). + +**PACKAGE PUBLISHING** +: The details of a project built as a package. See [Package Publishing](../../Project/PackagePublishing). + +**TOOLBAR** +: The row of buttons under the menu. See [Toolbar](../Toolbar). + +**TOOLBOX** and **PROPERTIES** +: The controls you can place on a form, and the properties of the selected one. See [Toolbox](../../Project/Toolbox) and [Properties](../../Project/Properties). + +**WEBPAGE** +: A web browser pane inside the IDE. See [Webpage](../../Project/Webpage). diff --git a/docs/IDE/Menu/Window.md b/docs/IDE/Menu/Window.md index 8f18af149..aa2a90e39 100644 --- a/docs/IDE/Menu/Window.md +++ b/docs/IDE/Menu/Window.md @@ -1,238 +1,54 @@ --- title: Window parent: Menu -grand_parent: IDE +grand_parent: IDE Reference nav_order: 10 -permalink: /tB/IDE/Project/Menu/Window +permalink: /tB/IDE/Reference/Menu/Window +redirect_from: + - /tB/IDE/Project/Menu/Window --- # Window Menu -![The Window menu open with five available entries, each carrying a submenu arrow: Panel Layouts, Panel Features and Keyboard Shortcuts, then Theme beside a palette icon and Language beside a globe icon.](Images/Menu_Window.png){:width="172" height="159"} - -- Panel Layouts -- Panel Features -- Keyboard Shortcuts +The Window menu sets how the IDE is arranged and how it looks: its panel layouts, what can be done to panels, its keyboard shortcuts, its theme and its language. [Customising the IDE](../../Customising/) explains each entry. ---- -- Theme -- Language +![The Window menu open with five available entries, each carrying a submenu arrow: Panel Layouts, Panel Features and Keyboard Shortcuts, then Theme beside a palette icon and Language beside a globe icon.](Images/Menu_Window.png){:width="172" height="159"} ## Panel Layouts -![The Window menu with Panel Layouts highlighted and its submenu open to the right: a ticked Default Built-in Layout with CTRL+#, Full Screen Editor Layout, then Save Current Panel Layout As and Manage Panel Layouts.](Images/Menu_Window_PanelLayouts.png){:width="389" height="159"} - -- Default Built-in Layout CTRL + # -- Full Screen Editor Layout - ---- -- ✓ Custom Layout (Unsaved) --- shown, and ticked, while none of the named layouts is the active one -- Custom Layout *n*. *name* --- one for each saved layout - ---- -- Save Current Panel Layout As... -- Manage Panel Layouts... +Switches the arrangement of the panels, and saves and manages arrangements. See [Panel layouts](../../Customising/Panel-Layouts). +- **Default Built-in Layout** --- Ctrl+#. Restores the arrangement the IDE starts with. +- **Full Screen Editor Layout** --- docks only the toolbar, the Toolbox, the Editor and Properties, and gives the Editor most of the room. +- **Custom Layout (Unsaved)** --- shown, and ticked, while none of the named layouts is in use. +- **Custom** *n*. *name* --- one item for each saved layout. A tick marks the layout in use. +- **Save Current Panel Layout As...** --- saves the current arrangement under a name. +- **Manage Panel Layouts...** --- adds layouts, deletes them and edits them as text. ### Manage Panel Layouts... -![The Manage Panel Layouts dialog. Its left pane lists two built-in layouts, DEFAULT and FULLSCREEN, with DEFAULT selected; the right pane shows that layout JSON --- a docked tree of TOOLBAR, TOOLBOX and EDITOR entries with percentage sizes. Add New sits at the bottom left, with a greyed-out Save Changes and a Cancel button at the right.](Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Default.png){:width="1016" height="638"} - -
-<DEFAULT> (built-in) - -```json -{ - "docked": { - "type": "horizontal", - "variableSize": true, - "size": "0%", - "content": [ - { - "id": "TOOLBAR", - "variableSize": false, - "size": "fit-content" - }, - { - "type": "vertical", - "variableSize": true, - "size": "0%", - "content": [ - { - "type": "horizontal", - "variableSize": false, - "size": "79.0109%", - "content": [ - { - "type": "vertical", - "variableSize": false, - "size": "73.8657%", - "content": [ - { - "id": "TOOLBOX", - "variableSize": false, - "size": "13.0192%" - }, - { - "id": "EDITOR", - "variableSize": true, - "size": "0%" - } - ] - }, - { - "type": "vertical", - "variableSize": true, - "size": "0%", - "content": [ - { - "type": "vertical", - "variableSize": false, - "size": "67.9395%", - "content": [ - { - "id": "DEBUG CONSOLE", - "variableSize": false, - "size": "53.0193%" - }, - { - "id": "PROBLEMS", - "variableSize": true, - "size": "0%" - } - ] - }, - { - "type": "vertical", - "variableSize": true, - "size": "0%", - "content": [ - { - "id": "CALL STACK", - "variableSize": true, - "size": "0%" - }, - { - "id": "VARIABLES", - "variableSize": true, - "size": "0%" - } - ] - } - ] - } - ] - }, - { - "type": "horizontal", - "variableSize": true, - "size": "0%", - "content": [ - { - "id": "PROJECT EXPLORER", - "variableSize": false, - "size": "60.4237%" - }, - { - "id": "PROPERTIES", - "variableSize": true, - "size": "0%" - } - ] - } - ] - } - ] - }, - "floating": [] -} -``` - -
- -![The same dialog with FULLSCREEN selected instead, its JSON showing a TOOLBAR sized to fit its content, a TOOLBOX at about 8.5 per cent, and an EDITOR and PROPERTIES pair filling the rest.](Images/Menu_Window_PanelLayouts_ManagePanelLayouts_Fullscreen.png){:width="1016" height="638"} - -
-<FULLSCREEN> (built-in) - -```json -{ - "docked": { - "type": "horizontal", - "variableSize": true, - "size": "0%", - "content": [ - { - "id": "TOOLBAR", - "variableSize": false, - "size": "fit-content" - }, - { - "type": "vertical", - "variableSize": true, - "size": "0%", - "content": [ - { - "id": "TOOLBOX", - "variableSize": false, - "size": "8.49705%" - }, - { - "type": "vertical", - "variableSize": true, - "size": "0%", - "content": [ - { - "id": "EDITOR", - "variableSize": false, - "size": "81.8591%" - }, - { - "id": "PROPERTIES", - "variableSize": true, - "size": "0%" - } - ] - } - ] - } - ] - }, - "floating": [] -} -``` - -
+Opens a dialog that lists the built-in layouts, which are read-only, and the saved ones. See [Manage layouts](../../Customising/Panel-Layouts#manage-layouts). ## Panel Features -![The Window menu with Panel Features highlighted and its submenu open to the right, holding five options that are all ticked: resizing, rearrangement and tear-out of docked panels, then resizing and movement of floating panels.](Images/Menu_Window_PanelFeatures.png){:width="421" height="186"} - -- ✓ Allow resizing of docked panels -- ✓ Allow rearrangement of docked panels -- ✓ Allow tear-out of docked panels - ---- -- ✓ Allow resizing of floating panels -- ✓ Allow movement of floating panels +Five switches decide what can be done to panels. A tick means the action is allowed. See [Panel Features](../../Customising/Panel-Layouts#panel-features). ## Keyboard Shortcuts -![The Window menu with Keyboard Shortcuts highlighted and its submenu open to the right: a ticked Default Built-in Keyboard Shortcuts above Manage Keyboard Shortcuts.](Images/Menu_Window_KeyboardShortcuts.png){:width="404" height="159"} +Switches groups of keyboard shortcuts on and off. See [Keyboard shortcuts](../../Customising/Keyboard-Shortcuts). -- ✓ Default Built-in Keyboard Shortcuts - ---- -- Manage Keyboard Shortcuts +- **Default Built-in Keyboard Shortcuts** --- always ticked. The group cannot be switched off. +- **Custom** *n*. *name* --- one item for each group that has been created. A tick means the group is on. +- **Manage Keyboard Shortcuts...** --- creates, edits and deletes groups. ### Manage Keyboard Shortcuts -![The Manage Keyboard Shortcuts dialog. A paragraph across the top explains that groups are merged in the order listed, and how to override an existing shortcut or add one alongside it. The left pane holds the single built-in group, DEFAULT; the right pane shows its JSON with the Edit as JSON box ticked, mapping names such as tbEditor_SelectAll and tbEditor_ClipboardCopy to KEYDOWN sequences. Save Changes is greyed out.](Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts.png){:width="1016" height="736"} +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 same dialog with the Edit as JSON box cleared: the editing pane is empty apart from the line Keyboard editor coming soon.](Images/Menu_Window_KeyboardShortcuts_ManageKeyboardShortcuts_1.png){:width="1016" height="736"} +The built-in group follows. The names are the IDE's command names, and each is followed by its keys.
-Options +Default keyboard shortcuts ```json { @@ -934,7 +750,7 @@ permalink: /tB/IDE/Project/Menu/Window "{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|&|+|-|*|/|\\|^" + "{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" @@ -952,6 +768,9 @@ permalink: /tB/IDE/Project/Menu/Window "tbToolbox_SelectPrevTool": [ "{KEYDOWN}{KEYDOWNREPEAT}ARROWUP", "{KEYDOWN}{KEYDOWNREPEAT}ARROWLEFT" + ], + "tbBuild_Fusion": [ + "{KEYDOWN}{CTRL}{SHIFT}B" ] } ``` @@ -961,37 +780,12 @@ permalink: /tB/IDE/Project/Menu/Window ## Theme {: index_also="dark mode" } -![The Window menu with Theme highlighted and its submenu open to the right, listing Classic (Light), a ticked Dark, and Light, with Reload from disk below a separator.](Images/Menu_Window_Theme.png){:width="312" height="219"} +Lists the themes and switches between them. See [Themes](../../Customising/Themes). -- Classic (Light) -- ✓ Dark -- Light - ---- -- Reload from disk +- **Classic (Light)**, **Dark** and **Light** --- the three themes that ship with the IDE. A tick marks the theme in use. +- Any theme added as a `.theme` file is listed after these, as its file name and its family, such as **Red (Light)**. +- **Reload from disk** --- reads the theme files again, so that a new or edited file takes effect without restarting the IDE. ## Language -![The Window menu with Language highlighted and its submenu open to the right, listing nineteen languages in one tall column from Catalan and German through to Chinese Simplified and Chinese Traditional. Each is named in English with its own native spelling beside it, and English, British English, carries the tick.](Images/Menu_Window_Language.png){:width="390" height="577"} - -Each language is listed by its English name, followed by its native name. British English is the base language; a string that another language pack does not translate is shown in British English. - -- Catalan -- German -- Greek -- English (British English) -- Spanish -- French -- Hindi -- Hungarian -- Indonesian -- Italian -- Japanese -- Malayalam -- Polish -- Portuguese (Brazil) -- Russian -- Swedish -- Turkish -- Chinese Simplified -- Chinese Traditional +Lists the language packs and switches between them. The choice applies at once. The names are listed, with what changes and what stays in English, under [The language of the IDE](../../Customising/#the-language-of-the-ide). diff --git a/docs/IDE/Menu/index.md b/docs/IDE/Menu/index.md index 0cca22572..406609dd2 100644 --- a/docs/IDE/Menu/index.md +++ b/docs/IDE/Menu/index.md @@ -1,8 +1,11 @@ --- title: Menu -parent: IDE +parent: IDE Reference +grand_parent: IDE # nav_order: 1 -permalink: /tB/IDE/Project/Menu +permalink: /tB/IDE/Reference/Menu +redirect_from: + - /tB/IDE/Project/Menu --- # Menu diff --git a/docs/IDE/New Project.md b/docs/IDE/New Project.md index 0f3ed453f..0ec4fd42a 100644 --- a/docs/IDE/New Project.md +++ b/docs/IDE/New Project.md @@ -12,7 +12,7 @@ index: new project dialog ![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 +Shortcut: Ctrl+N ## 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" } @@ -32,9 +32,9 @@ Browse \| Open \| Cancel 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*](Settings#export-path) may use. +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](Menu/File#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. +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](../Reference/Menu/File#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 diff --git a/docs/IDE/Open Editors.md b/docs/IDE/Open Editors.md deleted file mode 100644 index e7c90124b..000000000 --- a/docs/IDE/Open Editors.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Open Editors -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/OpenEditors ---- - -# Open Editors - -When a project isn't open this will be empty. - -![The Open Editors pane with nothing listed beneath its title bar.](Images/OpenEditors.png){:width="360" height="92"} - -When a project is open it will list the files that are currently open in your [Editor](Editor) - -![The Open Editors pane listing two open files of SampleProject, each with its own file type icon and its full path: /SampleProject/Sources/frmMain.tbform and /SampleProject/Sources/frmMain.twin.](Images/OpenEditors_1.png){:width="360" height="92"} - -Clicking a file in the list brings it into focus in the editor. diff --git a/docs/IDE/Outline.md b/docs/IDE/Outline.md deleted file mode 100644 index 551abc28f..000000000 --- a/docs/IDE/Outline.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: Outline -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/Outline -index: - - outline view - - outline window - - outline panel ---- - -# Outline - -The Outline pane shows a structural overview of the declarations in the active source file---modules, classes, procedures, and properties. - -When a project isn't open this will be empty. - -![The Outline pane with nothing listed beneath its title bar.](Images/Outline.png){:width="300" height="96"} - -Once you open a source file, it lists the `Modules`/`Classes` etc. that the file declares. - -![The Outline pane listing the two top level items of the open file, the module MainModule and the class GlobalSearchAddIn.](Images/Outline_1.png){:width="300" height="96"} - -You can click on an item to navigate to that point in the code file. diff --git a/docs/IDE/Package Publishing.md b/docs/IDE/Package Publishing.md index 982c542c0..30bac9aa2 100644 --- a/docs/IDE/Package Publishing.md +++ b/docs/IDE/Package Publishing.md @@ -17,4 +17,4 @@ Once you open a project whose build type is Package TWINPACK, the pane shows its ![The Package Publishing pane with a package project open and its values filled in: Namespace SampleProject, Description SampleProject description, Licence MIT, Visibility PRIVATE and Version 1.0.0.0. Publisher still offers Sign In, and the Publish This Package button is now enabled.](Images/PackagePublishing_1.png){:width="300" height="330"} -Click the "EDIT" and this will open the [Project Settings](Settings) +Click the "EDIT" and this will open the [Project Settings](../Project-Settings) diff --git a/docs/IDE/Project Explorer.md b/docs/IDE/Project Explorer.md index 2a71f8e37..89b5040e4 100644 --- a/docs/IDE/Project Explorer.md +++ b/docs/IDE/Project Explorer.md @@ -24,9 +24,9 @@ When a Project is open contextual icons will appear. ## ![](Images/Settings.png){:width="16" height="16"} Project Settings -- [Info](Settings) +- [Info](../Project-Settings) -## ![](Images/Toggle.png){:width="16" height="16"} Toggle file view (CTRL + R) +## ![](Images/Toggle.png){:width="16" height="16"} Toggle file view (Ctrl+R) ## ![](Images/Add.png){:width="16" height="16"} Add... @@ -116,7 +116,7 @@ Same as Right-Click An application manifest is an XML file that Windows reads when it starts the program. The one this command adds declares a dependency on version 6 of the Windows Common Controls, so that the program's buttons, lists and other controls are drawn in the current Windows visual style rather than the classic one. It is stored as resource `#1` in the project's `MANIFEST` resource folder, and the build embeds it in the program. -A manifest can also declare the program's DPI awareness. A program whose manifest does should set [Force DPI Awareness At Startup](Settings#dpi-awareness) to **NONE**. +A manifest can also declare the program's DPI awareness. A program whose manifest does should set [Force DPI Awareness At Startup](../Project-Settings#dpi-awareness) to **NONE**. See ![A small yellow folder icon](Images/Folder.png){:width="16" height="11"} `/.../Resources/MANIFEST/#1.xml` diff --git a/docs/IDE/Project Settings.md b/docs/IDE/Project Settings.md index 49ca724f2..7492ea745 100644 --- a/docs/IDE/Project Settings.md +++ b/docs/IDE/Project Settings.md @@ -2,7 +2,9 @@ title: Project Settings parent: IDE # nav_order: 1 -permalink: /tB/IDE/Project/Settings +permalink: /tB/IDE/Project-Settings +redirect_from: + - /tB/IDE/Project/Settings --- # Project Settings @@ -11,7 +13,7 @@ The Project Settings dialog, **Project → Project Settings...**, holds the sett ![The Option Explicit On setting in the Project Settings dialog, set to Yes, with an arrow pointing at the description under it](Images/project settings description text.png){:width="900" height="67"} -The drop-down list of a setting ends with a **COMPILER DEFAULT** entry, which names the value used when the project does not set one. Choosing it removes the setting from the project's `Settings` file, and so does emptying a box. [**File → Export Project**](Menu/File#export-project) writes that file beside the project's `Sources` folder, and the entries below give the settings' keys in it. +The drop-down list of a setting ends with a **COMPILER DEFAULT** entry, which names the value used when the project does not set one. Choosing it removes the setting from the project's `Settings` file, and so does emptying a box. [**File → Export Project**](Reference/Menu/File#export-project) writes that file beside the project's `Sources` folder, and the entries below give the settings' keys in it. {: .toc } @@ -25,11 +27,11 @@ The description of the type library, when the build produces one. It can include ## Application Title -The title that [**MsgBox**](../../Modules/Interaction/MsgBox) and [**InputBox**](../../Modules/Interaction/InputBox) show when the call gives none, and the value that [**App.Title**](../../Packages/AppGlobalClassObject/_App/Title) returns. When [Product Name](#product-name) is not set, the build also writes it into the file's version resource as the product name. In the `Settings` file it is `project.appTitle`. +The title that [**MsgBox**](../Modules/Interaction/MsgBox) and [**InputBox**](../Modules/Interaction/InputBox) show when the call gives none, and the value that [**App.Title**](../Packages/AppGlobalClassObject/_App/Title) returns. When [Product Name](#product-name) is not set, the build also writes it into the file's version resource as the product name. In the `Settings` file it is `project.appTitle`. ## Application HelpFile -The help file of the program. [**App.HelpFile**](../../Packages/AppGlobalClassObject/_App/HelpFile) returns it at run time, and forms use it for **F1** and *What's This* help. A new project does not set it. In the `Settings` file it is `project.appHelpFile`. +The help file of the program. [**App.HelpFile**](../Packages/AppGlobalClassObject/_App/HelpFile) returns it at run time, and forms use it for **F1** and *What's This* help. A new project does not set it. In the `Settings` file it is `project.appHelpFile`. ## Startup Object @@ -50,7 +52,7 @@ To add a reference, tick it on the **Available COM References** tab, which lists ![The same dialog on its Available COM References tab. A search box sits above an alphabetical list of unticked type libraries registered on the machine --- on this machine ABBYY FineReader 16 SharePoint Integration Component, AccessibilityCplAdmin, Acrobat, Acrobat Access and so on --- against Library Symbol, Version and Publisher columns.](Images/ProjectSettings_AvailableCOMReferences.png){:width="1102" height="774"} -See [Packages](../../../Features/Packages/) +See [Packages](../../Features/Packages/) ![The Project Settings dialog open over the IDE, with red numbers marking the route: 1 on the Project Explorer's Project Settings gear, 2 on the Library References heading, 3 on the Available Packages tab. That tab lists the built-in and published packages with tick boxes and Embedded and Library Symbol columns.](../Features/Packages/Images/e749e10f-e361-4f15-a977-d756fcb3b5dd.png){:width="1162" height="529"} @@ -58,7 +60,7 @@ See [Packages](../../../Features/Packages/) How the compiler reports each of its warnings in this project. The dialog lists every warning by its code and message, and each one can be set to **WARNING**, **HINT**, **INFO**, **IGNORE** or **ERROR**. In a new project, TB0015 and TB0030 are at **HINT**; TB0018 to TB0021, TB0024, TB0025 and TB0029 are at **IGNORE**; and every other warning is at **WARNING**. -The [**IgnoreWarnings**](../../Core/Attributes#ignorewarnings), [**EnforceWarnings**](../../Core/Attributes#enforcewarnings) and [**EnforceErrors**](../../Core/Attributes#enforceerrors) attributes override these settings in a class, a module or a procedure. [Compiler Warnings](../../../Features/Compiler-IDE/Compiler-Warnings) describes some of the warnings. In the `Settings` file the setting is `project.warnings`, which holds a list of codes for each level: `errors`, `hints`, `ignored`, `info` and `warnings`. +The [**IgnoreWarnings**](../Core/Attributes#ignorewarnings), [**EnforceWarnings**](../Core/Attributes#enforcewarnings) and [**EnforceErrors**](../Core/Attributes#enforceerrors) attributes override these settings in a class, a module or a procedure. [Compiler Warnings](../../Features/Compiler-IDE/Compiler-Warnings) describes some of the warnings. In the `Settings` file the setting is `project.warnings`, which holds a list of codes for each level: `errors`, `hints`, `ignored`, `info` and `warnings`. ## Project ID @@ -70,22 +72,22 @@ When set to **Yes**, the type library that the build produces takes the [Project ## Build Output Path -The full path of the file the compiler creates. Its description in the dialog lists these variables: `${SourcePath}`, the folder that holds the `.twinproj` file, and `${ProjectName}`, `${ProjectID}`, `${FileExtension}`, `${Architecture}`, `${VersionMajor}`, `${VersionMinor}`, `${VersionBuild}` and `${VersionRevision}`. `${Architecture}` is `win32` or `win64`, whichever the toolbar's [build configuration](Toolbar#build-configuration) box is set to. The IDE add-in samples use one more that the description leaves out, `${IdePath}`, the folder the IDE is installed in: their `${IdePath}\addins\${Architecture}\${ProjectName}.${FileExtension}` builds straight into the IDE's own `addins` folder, which works until the IDE has loaded the add-in --- see [Rebuilding an addin the IDE has loaded](../../Packages/tbIDE/#rebuilding-an-addin-the-ide-has-loaded). +The full path of the file the compiler creates. Its description in the dialog lists these variables: `${SourcePath}`, the folder that holds the `.twinproj` file, and `${ProjectName}`, `${ProjectID}`, `${FileExtension}`, `${Architecture}`, `${VersionMajor}`, `${VersionMinor}`, `${VersionBuild}` and `${VersionRevision}`. `${Architecture}` is `win32` or `win64`, whichever the toolbar's [build configuration](Reference/Toolbar#build-configuration) box is set to. The IDE add-in samples use one more that the description leaves out, `${IdePath}`, the folder the IDE is installed in: their `${IdePath}\addins\${Architecture}\${ProjectName}.${FileExtension}` builds straight into the IDE's own `addins` folder, which works until the IDE has loaded the add-in --- see [Rebuilding an addin the IDE has loaded](../Packages/tbIDE/#rebuilding-an-addin-the-ide-has-loaded). -Every project template sets it to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`: a `Build` folder beside the `.twinproj` file, and a different file name for each architecture. A Standard DLL project named `MathGreetLib` builds `Build\MathGreetLib_win32.dll` or `Build\MathGreetLib_win64.dll`, and a `Declare` that calls it has to name that file --- see [Calling a Standard DLL from VBA or Excel](../../../Features/Project-Configuration/Project-Types#calling-a-standard-dll-from-vba-or-excel). In the `Settings` file it is `project.buildPath`. +Every project template sets it to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`: a `Build` folder beside the `.twinproj` file, and a different file name for each architecture. A Standard DLL project named `MathGreetLib` builds `Build\MathGreetLib_win32.dll` or `Build\MathGreetLib_win64.dll`, and a `Declare` that calls it has to name that file --- see [Calling a Standard DLL from VBA or Excel](../../Features/Project-Configuration/Project-Types#calling-a-standard-dll-from-vba-or-excel). In the `Settings` file it is `project.buildPath`. ## ActiveX Fusion Host EXE Output Path -The full path of the host EXE that the compiler creates for [Fusion](../../../Features/Fusion): the separate program that ActiveX controls run in. It can use the same variables as [Build Output Path](#build-output-path), except that `${Architecture}` is `win32host` or `win64host`. When it is not set, the compiler uses *Build Output Path* when it needs the file. A built program expects the host EXE in its own folder. For a host EXE kept anywhere else, the program has to set `App.FusionHostEXEPath` as it starts --- see [Runtime Behaviour and Deployment](../../../Features/Fusion#runtime-behaviour-and-deployment). In the `Settings` file it is `project.fusionBuildPath`. +The full path of the host EXE that the compiler creates for [Fusion](../../Features/Fusion): the separate program that ActiveX controls run in. It can use the same variables as [Build Output Path](#build-output-path), except that `${Architecture}` is `win32host` or `win64host`. When it is not set, the compiler uses *Build Output Path* when it needs the file. A built program expects the host EXE in its own folder. For a host EXE kept anywhere else, the program has to set `App.FusionHostEXEPath` as it starts --- see [Runtime Behaviour and Deployment](../../Features/Fusion#runtime-behaviour-and-deployment). In the `Settings` file it is `project.fusionBuildPath`. ## Build Type {: index_also="standard exe; create an ActiveX DLL; create ActiveX DLL" } -The type of file the compiler creates: **Standard EXE**, **ActiveX DLL**, **ActiveX Control**, **Standard DLL** or **Package TWINPACK**. [Project Types](../../../Features/Project-Configuration/Project-Types) describes the Standard DLL, and code can test the setting with the [`TWINBASIC_BUILD_TYPE`](../../../Reference/Compiler-Constants#twinbasic_build_type) compiler constant. In the `Settings` file it is `project.buildType`. +The type of file the compiler creates: **Standard EXE**, **ActiveX DLL**, **ActiveX Control**, **Standard DLL** or **Package TWINPACK**. [Project Types](../../Features/Project-Configuration/Project-Types) describes the Standard DLL, and code can test the setting with the [`TWINBASIC_BUILD_TYPE`](../../Reference/Compiler-Constants#twinbasic_build_type) compiler constant. In the `Settings` file it is `project.buildType`. ## Licence Type -For a package published to the package database: its licence, shown to the people who use the package. A new project does not set it. [Creating a TWINPACK Package](../../../Features/Packages/Creating-TWINPACK) describes the settings of a package. In the `Settings` file it is `project.licence`. +For a package published to the package database: its licence, shown to the people who use the package. A new project does not set it. [Creating a TWINPACK Package](../../Features/Packages/Creating-TWINPACK) describes the settings of a package. In the `Settings` file it is `project.licence`. ## Package Visibility @@ -97,31 +99,31 @@ The version number of the build, and the text that the build writes into the fil ### Major/Minor/Build/Revision -The four parts of the version number: *Major*, *Minor*, *Build* and *Revision*. The build writes them into the file's version resource, and [**App.Major**](../../Packages/AppGlobalClassObject/_App/Major), [**App.Minor**](../../Packages/AppGlobalClassObject/_App/Minor), [**App.Build**](../../Packages/AppGlobalClassObject/_App/Build) and [**App.Revision**](../../Packages/AppGlobalClassObject/_App/Revision) return them. When they are not set, the version is 1.0.0.0. In the `Settings` file they are `project.versionMajor`, `project.versionMinor`, `project.versionBuild` and `project.versionRevision`. +The four parts of the version number: *Major*, *Minor*, *Build* and *Revision*. The build writes them into the file's version resource, and [**App.Major**](../Packages/AppGlobalClassObject/_App/Major), [**App.Minor**](../Packages/AppGlobalClassObject/_App/Minor), [**App.Build**](../Packages/AppGlobalClassObject/_App/Build) and [**App.Revision**](../Packages/AppGlobalClassObject/_App/Revision) return them. When they are not set, the version is 1.0.0.0. In the `Settings` file they are `project.versionMajor`, `project.versionMinor`, `project.versionBuild` and `project.versionRevision`. ### Product Name -The `ProductName` text that the build writes into the file's version resource. When it is not set, the build writes the [Application Title](#application-title) there instead. At run time, [**App.ProductName**](../../Packages/AppGlobalClassObject/_App/ProductName) returns the product name. In the `Settings` file it is `project.versionProductName`. +The `ProductName` text that the build writes into the file's version resource. When it is not set, the build writes the [Application Title](#application-title) there instead. At run time, [**App.ProductName**](../Packages/AppGlobalClassObject/_App/ProductName) returns the product name. In the `Settings` file it is `project.versionProductName`. ### Company Name -The `CompanyName` text that the build writes into the file's version resource. [**App.CompanyName**](../../Packages/AppGlobalClassObject/_App/CompanyName) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionCompanyName`. +The `CompanyName` text that the build writes into the file's version resource. [**App.CompanyName**](../Packages/AppGlobalClassObject/_App/CompanyName) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionCompanyName`. ### File Description -The `FileDescription` text that the build writes into the file's version resource. [**App.FileDescription**](../../Packages/AppGlobalClassObject/_App/FileDescription) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionFileDescription`. +The `FileDescription` text that the build writes into the file's version resource. [**App.FileDescription**](../Packages/AppGlobalClassObject/_App/FileDescription) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionFileDescription`. ### Legal Copyright -The `LegalCopyright` text that the build writes into the file's version resource. [**App.LegalCopyright**](../../Packages/AppGlobalClassObject/_App/LegalCopyright) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionLegalCopyright`. +The `LegalCopyright` text that the build writes into the file's version resource. [**App.LegalCopyright**](../Packages/AppGlobalClassObject/_App/LegalCopyright) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionLegalCopyright`. ### Legal Trademarks -The `LegalTrademarks` text that the build writes into the file's version resource. [**App.LegalTrademarks**](../../Packages/AppGlobalClassObject/_App/LegalTrademarks) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionLegalTrademarks`. +The `LegalTrademarks` text that the build writes into the file's version resource. [**App.LegalTrademarks**](../Packages/AppGlobalClassObject/_App/LegalTrademarks) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionLegalTrademarks`. ### Comments -The `Comments` text that the build writes into the file's version resource. [**App.Comments**](../../Packages/AppGlobalClassObject/_App/Comments) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionComments`. +The `Comments` text that the build writes into the file's version resource. [**App.Comments**](../Packages/AppGlobalClassObject/_App/Comments) returns it at run time. A new project does not set it. In the `Settings` file it is `project.versionComments`. ### Auto-Increment @@ -158,7 +160,7 @@ How a built EXE initializes COM and OLE, which sets its threading model: **OleIn ## Is Console Application -When set to **Yes**, the built executable is marked as a console application rather than a GUI application. It is **No** by default; the **Standard EXE (Console App)** template sets it to **Yes**. In the `Settings` file it is `project.isConsoleApplication`. [Writing a command-line tool](../../../Features/Project-Configuration/Project-Types#writing-a-command-line-tool-output-exit-code-and-arguments) shows how such a program writes its output and sets its exit code. +When set to **Yes**, the built executable is marked as a console application rather than a GUI application. It is **No** by default; the **Standard EXE (Console App)** template sets it to **Yes**. In the `Settings` file it is `project.isConsoleApplication`. [Writing a command-line tool](../../Features/Project-Configuration/Project-Types#writing-a-command-line-tool-output-exit-code-and-arguments) shows how such a program writes its output and sets its exit code. ## Native Subsystem @@ -170,32 +172,32 @@ The name of a procedure to use as the entry point of the built file. That proced ## Runtime Binding of DLL Declares -When set to **Yes**, the project's [**Declare**](../../Core/Declare) statements are resolved at run time. When set to **No**, they are added to the import address table (IAT) of the built file. It is **Yes** by default. A **Declare** that comes from a type library always goes into the import address table, whatever this setting says. In the `Settings` file it is `project.dllRuntimeBinding`. +When set to **Yes**, the project's [**Declare**](../Core/Declare) statements are resolved at run time. When set to **No**, they are added to the import address table (IAT) of the built file. It is **Yes** by default. A **Declare** that comes from a type library always goes into the import address table, whatever this setting says. In the `Settings` file it is `project.dllRuntimeBinding`. ## Conditional Compilation Args -Conditional compilation constants for the whole project, as `name=value` pairs separated by colons: `foo=42:bar=-42`. Each value must be between -32768 and 32767. These are the project-wide constants that [**#If**](../../Core/Topic-Preprocessor) can test; **#Const** defines a constant for its own module only. A new project does not set it. In the `Settings` file it is `project.conditionalCompilationArguments`. +Conditional compilation constants for the whole project, as `name=value` pairs separated by colons: `foo=42:bar=-42`. Each value must be between -32768 and 32767. These are the project-wide constants that [**#If**](../Core/Topic-Preprocessor) can test; **#Const** defines a constant for its own module only. A new project does not set it. In the `Settings` file it is `project.conditionalCompilationArguments`. ## Option Explicit On -Whether [**Option Explicit**](../../Core/Option) is on in a file that has no **Option Explicit** statement of its own. It is **Yes** by default. In the `Settings` file it is `project.optionExplicit`. +Whether [**Option Explicit**](../Core/Option) is on in a file that has no **Option Explicit** statement of its own. It is **Yes** by default. In the `Settings` file it is `project.optionExplicit`. ## Auto Prettify Source Code -When set to **Yes**, the editor corrects the capitalization of words as they are typed, and corrects spacing to the usual VBE layout. It is **Yes** by default. In the `Settings` file it is `project.autoPrettify`. +When set to **Yes**, the editor corrects the capitalization of words, and corrects spacing to the usual VBE layout. A correction to the line you are typing in appears when the caret moves to another line. [Auto Prettify](Code/Editor#auto-prettify) describes the rule. It is **Yes** by default. In the `Settings` file it is `project.autoPrettify`. ## CodeLens - Show Run Procedure {: #show-run-procedure } -When set to **Yes**, the editor shows **▶ run** and the procedure's name in the CodeLens above each procedure in a standard module that takes no arguments. [CodeLens](../../../Features/Compiler-IDE/CodeLens) describes running a procedure that way. It is **Yes** by default. In the `Settings` file it is `project.codeLensRunProcedure`. +When set to **Yes**, the editor shows **▶ run** and the procedure's name in the CodeLens above each procedure in a standard module that takes no arguments. [CodeLens](Code/IntelliSense#codelens) describes running a procedure that way. It is **Yes** by default. In the `Settings` file it is `project.codeLensRunProcedure`. ## Runtime Windows Codepage -The Windows code page that [**Chr**](../../Modules/Strings/Chr), **Chr$**, [**String**](../../Modules/Strings/String), **String$**, [**StrConv**](../../Modules/Strings/StrConv) and [**Asc**](../../Modules/Strings/Asc) use at run time. The choices are the system code page at compile time, the system code page at run time, fourteen code pages named by number, from **CODEPAGE 1252 - WESTERN EUROPEAN (Latin-1)** to **CODEPAGE 949 - KOREAN (KS C 5601)**, and **OTHER**. The default is **SYSTEM CODEPAGE [AT COMPILE TIME]**. In the `Settings` file it is `project.ansiCodePageRuntime`. +The Windows code page that [**Chr**](../Modules/Strings/Chr), **Chr$**, [**String**](../Modules/Strings/String), **String$**, [**StrConv**](../Modules/Strings/StrConv) and [**Asc**](../Modules/Strings/Asc) use at run time. The choices are the system code page at compile time, the system code page at run time, fourteen code pages named by number, from **CODEPAGE 1252 - WESTERN EUROPEAN (Latin-1)** to **CODEPAGE 949 - KOREAN (KS C 5601)**, and **OTHER**. The default is **SYSTEM CODEPAGE [AT COMPILE TIME]**. In the `Settings` file it is `project.ansiCodePageRuntime`. ## Use Unicode Standard Library -When set to **Yes**, the compiler uses the Unicode versions of Windows API calls wherever it can: [**MsgBox**](../../Modules/Interaction/MsgBox) calls `MessageBoxW` rather than `MessageBoxA`, for example. It is **Yes** by default. In the `Settings` file it is `runtime.useUnicodeStandardLibrary`. +When set to **Yes**, the compiler uses the Unicode versions of Windows API calls wherever it can: [**MsgBox**](../Modules/Interaction/MsgBox) calls `MessageBoxW` rather than `MessageBoxA`, for example. It is **Yes** by default. In the `Settings` file it is `runtime.useUnicodeStandardLibrary`. ## Unicode Control Notifications @@ -204,11 +206,11 @@ When set to **Yes**, twinBASIC container windows --- forms, UserControls, Pictur ## Include Procedure Name Symbols in Built Executables {: #include-procedure-name-symbols } -When set to **Yes**, the compiler includes the names of procedures in built EXEs and DLLs, and the call stack information read at run time uses them. When set to **No**, [**ErrorStackFrame**](../../Packages/VBRUN/ErrorStackFrame/) gives `{unknown}` for those names in a built executable. It is **No** by default. The names make the file slightly larger and do not slow it down. In the `Settings` file it is `compiler.includeStackSymbols`. +When set to **Yes**, the compiler includes the names of procedures in built EXEs and DLLs, and the call stack information read at run time uses them. When set to **No**, [**ErrorStackFrame**](../Packages/VBRUN/ErrorStackFrame/) gives `{unknown}` for those names in a built executable. It is **No** by default. The names make the file slightly larger and do not slow it down. In the `Settings` file it is `compiler.includeStackSymbols`. ## Trace Flags -What the trace logger records, together with [Trace Output](#trace-output). [Debug Trace Logger](../../../Features/Compiler-IDE/Debugging#debug-trace-logger) describes the logger. Each flag has a box, and no box is ticked by default: +What the trace logger records, together with [Trace Output](#trace-output). [The trace logger](Debugging/Debug-Console#the-trace-logger) describes the logger. Each flag has a box, and no box is ticked by default: - **Trace Procedure Entry and Exit points** - **Trace Procedure Arguments** -- needs *Trace Procedure Entry and Exit points* as well @@ -216,7 +218,7 @@ What the trace logger records, together with [Trace Output](#trace-output). [Deb - **Trace IDispatch::GetIDsOfNames calls** - **Trace IDispatch::Invoke calls** - **Trace Window (HWND) Messages** -- **Trace Debug.TracePrint output** -- what [**Debug.TracePrint**](../../Modules/Debug#traceprint) writes +- **Trace Debug.TracePrint output** -- what [**Debug.TracePrint**](../Modules/Debug#traceprint) writes - **Buffered Trace Log File Writing** -- not for tracing a hard crash - **Trace IClassFactory::CreateInstance and IClassFactory2::CreateInstanceLic calls for exposed COM classes** - **Trace DllGetClassObject calls** -- COM and ActiveX creation of the project's exposed classes @@ -231,7 +233,7 @@ Where the trace log goes: the full path of the log file. A new project does not - `${SESSIONID}` -- a GUID that is unique to each session and each thread - `${DATE}` -- the date, as `YYYYMMDD` - `${TIME}` -- the time, as `HHNNSS` -- `${DEBUG}` -- sends the log to the IDE's [DEBUG CONSOLE](DebugConsole) only +- `${DEBUG}` -- sends the log to the IDE's [DEBUG CONSOLE](Debugging/Debug-Console) only In the `Settings` file it is `compiler.traceOutput`. @@ -240,15 +242,15 @@ In the `Settings` file it is `compiler.traceOutput`. ## Disable Overflow Checks -When set to **Yes**, the compiler leaves out every run-time check for integer overflow in arithmetic, which produces more efficient code. It is **No** by default. It is the same as [**IntegerOverflowChecks(False)**](../../Core/Attributes#integeroverflowchecks) on every procedure in the project. [**IntegerOverflowChecks(True)**](../../Core/Attributes#integeroverflowchecks) on a procedure, module or class turns the integer overflow checks back on there. Dividing the most negative **Integer**, **Long** or **LongLong** by -1 with [**\\**](../../Core/IntegerDivide) or [**Mod**](../../Core/Mod) raises error 6 whatever the setting. It affects the project only, not the packages it references. In the `Settings` file it is `compiler.disableOverflowChecks`. +When set to **Yes**, the compiler leaves out every run-time check for integer overflow in arithmetic, which produces more efficient code. It is **No** by default. It is the same as [**IntegerOverflowChecks(False)**](../Core/Attributes#integeroverflowchecks) on every procedure in the project. [**IntegerOverflowChecks(True)**](../Core/Attributes#integeroverflowchecks) on a procedure, module or class turns the integer overflow checks back on there. Dividing the most negative **Integer**, **Long** or **LongLong** by -1 with [**\\**](../Core/IntegerDivide) or [**Mod**](../Core/Mod) raises error 6 whatever the setting. It affects the project only, not the packages it references. In the `Settings` file it is `compiler.disableOverflowChecks`. ## Disable Array Bounds Checks -When set to **Yes**, the compiler leaves out every run-time check of array bounds when array elements are read and written, which produces more efficient code. It is **No** by default. It is the same as [**ArrayBoundsChecks(False)**](../../Core/Attributes#arrayboundschecks) on every procedure in the project. [**ArrayBoundsChecks(True)**](../../Core/Attributes#arrayboundschecks) on a procedure, module or class turns the array bounds checks back on there. It affects the project only, not the packages it references. In the `Settings` file it is `compiler.disableArrayBoundsChecks`. +When set to **Yes**, the compiler leaves out every run-time check of array bounds when array elements are read and written, which produces more efficient code. It is **No** by default. It is the same as [**ArrayBoundsChecks(False)**](../Core/Attributes#arrayboundschecks) on every procedure in the project. [**ArrayBoundsChecks(True)**](../Core/Attributes#arrayboundschecks) on a procedure, module or class turns the array bounds checks back on there. It affects the project only, not the packages it references. In the `Settings` file it is `compiler.disableArrayBoundsChecks`. ## Disable FPU Error Checks -When set to **Yes**, the compiler leaves out every run-time check for floating-point (FPU) errors in arithmetic, which produces more efficient code. It is **No** by default. It is the same as [**FloatingPointErrorChecks(False)**](../../Core/Attributes#floatingpointerrorchecks) on every procedure in the project. [**FloatingPointErrorChecks(True)**](../../Core/Attributes#floatingpointerrorchecks) on a procedure, module or class turns the floating-point error checks back on there. It affects the project only, not the packages it references. In the `Settings` file it is `compiler.disableFPUErrorChecks`. +When set to **Yes**, the compiler leaves out every run-time check for floating-point (FPU) errors in arithmetic, which produces more efficient code. It is **No** by default. It is the same as [**FloatingPointErrorChecks(False)**](../Core/Attributes#floatingpointerrorchecks) on every procedure in the project. [**FloatingPointErrorChecks(True)**](../Core/Attributes#floatingpointerrorchecks) on a procedure, module or class turns the floating-point error checks back on there. It affects the project only, not the packages it references. In the `Settings` file it is `compiler.disableFPUErrorChecks`. ## Sanitize Booleans @@ -256,7 +258,7 @@ When set to **Yes**, the compiler makes sure that a **Boolean** value from an ex ## Constant Function Folding -Experimental. When set to **Yes**, the compiler replaces some function calls with their results as it compiles, where the result is fully known at compile time. It affects only calls to the VBA standard library, and calls to standard-module functions marked with the [**ConstantFoldable**](../../Core/Attributes#constantfoldable) attribute. It is **No** by default. In the `Settings` file it is `optimizer.constantFunctionFolding`. +Experimental. When set to **Yes**, the compiler replaces some function calls with their results as it compiles, where the result is fully known at compile time. It affects only calls to the VBA standard library, and calls to standard-module functions marked with the [**ConstantFoldable**](../Core/Attributes#constantfoldable) attribute. It is **No** by default. In the `Settings` file it is `optimizer.constantFunctionFolding`. ## Large Address Aware (LAA) {: .la-aware } @@ -276,7 +278,7 @@ When set to **Yes**, the build marks the file as DEP-aware. On hardware that sup ## Export > [!WARNING] -> **File → Export Project** deletes everything in its target folder before it writes, without asking. Read [Export Project](Menu/File#export-project) before setting these. +> **File → Export Project** deletes everything in its target folder before it writes, without asking. Read [Export Project](Reference/Menu/File#export-project) before setting these. ### Export Path @@ -292,7 +294,7 @@ When set to **Yes**, every save of the project also runs **Export Project** into ### Export Verbose -When set to **Yes**, **Export Project** writes a line to the [Debug Console](DebugConsole) for each file and folder it deletes, `[EXPORT] DELETED: …`, and for each file it writes, `[EXPORT] DONE: …`. When set to **No**, the console shows only the line that starts the export, the `[EXPORT] COMPLETED` line that ends it, and any failure. It is **No** by default. In the `Settings` file it is `project.exportVerbose`. +When set to **Yes**, **Export Project** writes a line to the [Debug Console](Debugging/Debug-Console) for each file and folder it deletes, `[EXPORT] DELETED: …`, and for each file it writes, `[EXPORT] DONE: …`. When set to **No**, the console shows only the line that starts the export, the `[EXPORT] COMPLETED` line that ends it, and any failure. It is **No** by default. In the `Settings` file it is `project.exportVerbose`. ## Force DPI Awareness At Startup {: #dpi-awareness index="high DPI" } @@ -301,7 +303,7 @@ How the program sets its DPI awareness as it starts: **NONE**, **SYSTEM_DPI_AWAR ## Runtime Command Line Args -The text that [**Command$**](../../Modules/Interaction/Command) returns while the project runs in a debug session in the IDE. A new project does not set it. In the `Settings` file it is `debugger.runtimeCommandLineArguments`. +The text that [**Command$**](../Modules/Interaction/Command) returns while the project runs in a debug session in the IDE. A new project does not set it. In the `Settings` file it is `debugger.runtimeCommandLineArguments`. ## Immediate Memory Invalidation @@ -311,7 +313,7 @@ When set to **Yes**, the memory of a **String**, **Variant** or array is overwri When set to **Yes**, a run-time error stops the program at the failing line even while an `On Error Resume Next` or `On Error GoTo` statement is in effect, with the same error panel as an error that nothing handles. It is **No** by default, and then the handler gets the error. **Debug → Debugger Options → Break On All Errors** turns the same setting on and off. In the `Settings` file it is `debugger.breakOnAllErrors`. -[When a run-time error stops the program](Menu/Debug#when-a-run-time-error-stops-the-program) describes the panel. [Debugger Options](Menu/Debug#debugger-options) says what **Ignore (Resume Next)** does when this setting is on. +[When a run-time error stops the program](Debugging/Breakpoints-and-Stepping#when-a-run-time-error-stops-the-program) describes the panel. [Break On All Errors and Debuggable](Debugging/Breakpoints-and-Stepping#break-on-all-errors-and-debuggable) says what **Ignore (Resume Next)** does when this setting is on. ## Build Stack Reserve Size @@ -323,7 +325,7 @@ The version of Windows that the built file is marked for. The build writes it in ## Codegen Model -The kind of code that the compiler generates for a built file, especially when it does not use [LLVM](../../../LLVM/Getting-Started#llvm-in-twinbasic): **SMALL** makes the file as small as it can, whatever the speed; **FAST** generates larger code, aiming to run slightly faster; **BALANCED** is between the two. The default is **BALANCED**. Only the main project's setting counts: a package uses the setting of the project that references it. In the `Settings` file it is `project.codegenModel`. +The kind of code that the compiler generates for a built file, especially when it does not use [LLVM](../../LLVM/Getting-Started#llvm-in-twinbasic): **SMALL** makes the file as small as it can, whatever the speed; **FAST** generates larger code, aiming to run slightly faster; **BALANCED** is between the two. The default is **BALANCED**. Only the main project's setting counts: a package uses the setting of the project that references it. In the `Settings` file it is `project.codegenModel`. ## Strip PE File Relocation Symbols {: #strip-pe-symbols } @@ -346,7 +348,7 @@ Overrides the image base address in the PE header of a Win64 EXE, DLL or OCX. Th ## Debuggable -When set to **Yes**, breakpoints can be set in the project's procedures, and stepping into them works as usual. It is **Yes** by default. **No** is mainly useful in a package, to keep the projects that use the package from stepping into its code. The [**Debuggable**](../../Core/Attributes#debuggable) attribute changes it for a module, a class or a procedure. In the `Settings` file it is `project.debuggable`. +When set to **Yes**, breakpoints can be set in the project's procedures, and stepping into them works as usual. It is **Yes** by default. **No** is mainly useful in a package, to keep the projects that use the package from stepping into its code. The [**Debuggable**](../Core/Attributes#debuggable) attribute changes it for a module, a class or a procedure. In the `Settings` file it is `project.debuggable`. ## Feature Flags @@ -360,8 +362,8 @@ In the `Settings` file they are `project.featureFlagsUI` and `project.featureFla ## Compiler Options (BUILD) -Turns on [LLVM compilation](../../../LLVM/Getting-Started#llvm-in-twinbasic) and its optimizations for the executable a build produces. +Turns on [LLVM compilation](../../LLVM/Getting-Started#llvm-in-twinbasic) and its optimizations for the executable a build produces. ## Compiler Options (DEBUG) -Turns on [LLVM compilation](../../../LLVM/Getting-Started#llvm-in-twinbasic) and its optimizations when the project runs in the IDE. This is not recommended: the IDE cannot debug code compiled with LLVM. +Turns on [LLVM compilation](../../LLVM/Getting-Started#llvm-in-twinbasic) and its optimizations when the project runs in the IDE. This is not recommended: the IDE cannot debug code compiled with LLVM. diff --git a/docs/IDE/Reference/index.md b/docs/IDE/Reference/index.md new file mode 100644 index 000000000..063d2acbb --- /dev/null +++ b/docs/IDE/Reference/index.md @@ -0,0 +1,15 @@ +--- +title: IDE Reference +parent: IDE +nav_order: 99 +permalink: /tB/IDE/Reference +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. + +- [**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. diff --git a/docs/IDE/Splash Screen.md b/docs/IDE/Splash Screen.md index cf4d802fe..e33ced2c9 100644 --- a/docs/IDE/Splash Screen.md +++ b/docs/IDE/Splash Screen.md @@ -14,4 +14,4 @@ The Splash Screen appears when the IDE starts with no project to open, as it doe 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](Menu/Help), which also repeats the VIP Gold Supporters grid. +> 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/Status Bar.md b/docs/IDE/Status Bar.md index 4668d871d..10852ec3b 100644 --- a/docs/IDE/Status Bar.md +++ b/docs/IDE/Status Bar.md @@ -1,8 +1,11 @@ --- title: Status Bar -parent: IDE +parent: IDE Reference +grand_parent: IDE # nav_order: 2 -permalink: /tB/IDE/Project/StatusBar +permalink: /tB/IDE/Reference/StatusBar +redirect_from: + - /tB/IDE/Project/StatusBar --- # Status Bar diff --git a/docs/IDE/Toolbar.md b/docs/IDE/Toolbar.md index 46c0da3b1..c6b2d7598 100644 --- a/docs/IDE/Toolbar.md +++ b/docs/IDE/Toolbar.md @@ -1,8 +1,11 @@ --- title: Toolbar -parent: IDE +parent: IDE Reference +grand_parent: IDE # nav_order: 3 -permalink: /tB/IDE/Project/Toolbar +permalink: /tB/IDE/Reference/Toolbar +redirect_from: + - /tB/IDE/Project/Toolbar --- # Toolbar @@ -15,38 +18,38 @@ permalink: /tB/IDE/Project/Toolbar ![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) +- Save All Changes (Ctrl+S) +- Find In Project... (Ctrl+Shift+F) - Switch Between Form And Code - Undo - Redo -- Start / Continue (F5) -- Break Into Code (CTRL + BREAK) -- Stop -- Step Over (SHIFT + F8 / F10) -- Step Into (F8 / F11) -- Step Out (CTRL + SHIFT + F8 / SHIFT + F11) +- 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) - Restart the compiler - Clean (Deregister & delete build) - Build -- Comment Selection (CTRL + K) -- Uncomment Selection (CTRL + SHIFT + K) -- Indent Block (CTRL + [) -- Outdent Block (CTRL + ]) -- Align controls to the LEFT (ALT + ARROWLEFT) -- Align controls to the TOP (ALT + ARROWUP) -- Align controls to the RIGHT (ALT + ARROWRIGHT) -- Align controls to the DOWN (ALT + ARROWDOWN) -- Resize controls to the WIDEST (CTRL + SHIFT + ARROWRIGHT) -- Resize controls to the TALLEST (CTRL + SHIFT + ARROWDOWN) -- Resize controls to the NARROWEST (CTRL + SHIFT + ARROWLEFT) -- Resize controls to the SHORTEST (CTRL + SHIFT + ARROWUP) +- 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 +- Launch the form in isolation to test the functionality -- [Run Or Preview](../Debugging/Running#run-or-preview) - Change IDE Theme - Global Search @@ -57,12 +60,12 @@ The build configuration box chooses whether the project is compiled as 32-bit or - **win32** builds a 32-bit executable or DLL. - **win64** builds a 64-bit one. [64-bit Office](../../../Features/Project-Configuration/Project-Types#calling-a-standard-dll-from-vba-or-excel), for example, can load only a 64-bit DLL. -CTRL + F1 switches to **win64**, and CTRL + F2 switches to **win32**. The keys are the default bindings of the IDE commands `tbBuild_SwitchToWin64` and `tbBuild_SwitchToWin32`, and [Manage Keyboard Shortcuts](Menu/Window#manage-keyboard-shortcuts) can change them. +Ctrl+F1 switches to **win64**, and Ctrl+F2 switches to **win32**. The keys are the default bindings of the IDE commands `tbBuild_SwitchToWin64` and `tbBuild_SwitchToWin32`, and [Manage Keyboard Shortcuts](Menu/Window#manage-keyboard-shortcuts) can change them. The IDE remembers the choice for each project, and sets the box back to it when the project is opened again. It keeps the choice in its own settings, under the path of the `.twinproj` file, not in the project. A project with no remembered choice uses whatever the box shows, and a newly started IDE shows **win32**. The box also sets the `Win64` compiler constant --- 1 in **win64**, 0 in **win32** --- and so decides which branch of an `#If Win64` block the editor treats as active and which it greys out. [Compiler Constants](../../../Reference/Compiler-Constants#appearance) shows the same code in both modes. -The default [Build Output Path](Settings#build-output-path) puts `win32` or `win64` in the file name, so the two builds do not overwrite each other. +The default [Build Output Path](../Project-Settings#build-output-path) puts `win32` or `win64` in the file name, so the two builds do not overwrite each other. The box has a third entry, **safeMode**. It opens the project's files without starting the compiler services, and the IDE describes it as a way to investigate, fix or recover code before saving it and restarting in normal mode. The IDE also switches to **safeMode** by itself when the compiler keeps crashing, and says *Compiler crash loop detected. Restarting in SAFE mode.* diff --git a/docs/IDE/Toolbox.md b/docs/IDE/Toolbox.md index fe76bdb7d..09c25f9bf 100644 --- a/docs/IDE/Toolbox.md +++ b/docs/IDE/Toolbox.md @@ -25,4 +25,4 @@ Click on the _Available COM References_ tab. ![The same dialog on its Available COM References tab, where a search box sits above an alphabetical list of unticked type libraries registered on the machine, ABBYY FineReader 16 SharePoint Integration Component, AccessibilityCplAdmin, Acrobat and Acrobat Access among them on this machine, against Library Symbol, Version and Publisher columns.](Images/ProjectSettings_AvailableCOMReferences.png){:width="1102" height="774"} -See [Project Settings](Settings) for more info. +See [Project Settings](../Project-Settings) for more info. diff --git a/docs/IDE/Variables.md b/docs/IDE/Variables.md deleted file mode 100644 index 8784c3c50..000000000 --- a/docs/IDE/Variables.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Variables -parent: IDE -# nav_order: 2 -permalink: /tB/IDE/Project/Variables -index: - - variables panel - - locals window ---- - -# Variables - -![The Variables pane, empty apart from its title bar, with no variables listed.](Images/Variables.png){:width="240" height="248"} - -The Variables pane lists the variables in scope at the current execution point during debugging, showing each variable's name, type, and current value. Structured types---classes and user-defined types---can be expanded to inspect their individual members. It takes the place of the Locals window in VB6. - -The pane lists nothing outside a paused debugging session, which is the state shown above. diff --git a/docs/IDE/Watches.md b/docs/IDE/Watches.md deleted file mode 100644 index f238933f5..000000000 --- a/docs/IDE/Watches.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Watches -parent: IDE -# nav_order: -permalink: /tB/IDE/Project/Watches -index: watch window ---- - -# Watches - -When a project isn't open this will be empty. - -![The WATCHES panel with no project open: a title bar holding only the drag grip and a close button, above an empty body.](Images/Watches.png){:width="300" height="76"} - -Once you open a project it will other buttons available. - -![The same panel with a project open, two more buttons now in its title bar: a plus for adding a watch, and a stack of lines with a small x for clearing the list.](Images/Watches_1.png){:width="300" height="76"} - -If you wish to add a new watch click ![a thin grey plus sign](Images/Add.png){:width="16" height="16"} and type in your "Expression to watch" - -![The panel after the plus is clicked, a white text box across the top of its body holding the grey placeholder text Expression to watch.](Images/Watches_2.png){:width="300" height="76"} - -You can remove any with the Clear button ![four stacked lines with a small x at their lower right](Images/Clear.png){:width="16" height="16"} diff --git a/docs/IDE/_Images/Call Stack.af b/docs/IDE/_Images/Call Stack.af deleted file mode 100644 index 6215a635d..000000000 Binary files a/docs/IDE/_Images/Call Stack.af and /dev/null differ diff --git a/docs/IDE/_Images/Debug Console Auto Scroll.af b/docs/IDE/_Images/Debug Console Auto Scroll.af deleted file mode 100644 index d7a32bf45..000000000 Binary files a/docs/IDE/_Images/Debug Console Auto Scroll.af and /dev/null differ diff --git a/docs/IDE/_Images/Debug Console Clear.af b/docs/IDE/_Images/Debug Console Clear.af deleted file mode 100644 index c3dcb0712..000000000 Binary files a/docs/IDE/_Images/Debug Console Clear.af and /dev/null differ diff --git a/docs/IDE/_Images/Debug Console Input.af b/docs/IDE/_Images/Debug Console Input.af deleted file mode 100644 index 51615c754..000000000 Binary files a/docs/IDE/_Images/Debug Console Input.af and /dev/null differ diff --git a/docs/IDE/_Images/Debug Console Options.af b/docs/IDE/_Images/Debug Console Options.af deleted file mode 100644 index d4a5e809d..000000000 Binary files a/docs/IDE/_Images/Debug Console Options.af and /dev/null differ diff --git a/docs/IDE/_Images/Debug Console.af b/docs/IDE/_Images/Debug Console.af deleted file mode 100644 index c7aec8b44..000000000 Binary files a/docs/IDE/_Images/Debug Console.af and /dev/null differ diff --git a/docs/IDE/_Images/Diagnostics Toggles.af b/docs/IDE/_Images/Diagnostics Toggles.af deleted file mode 100644 index b14e65719..000000000 Binary files a/docs/IDE/_Images/Diagnostics Toggles.af and /dev/null differ diff --git a/docs/IDE/_Images/Diagnostics Totals.af b/docs/IDE/_Images/Diagnostics Totals.af deleted file mode 100644 index 065d14604..000000000 Binary files a/docs/IDE/_Images/Diagnostics Totals.af and /dev/null differ diff --git a/docs/IDE/_Images/Diagnostics.af b/docs/IDE/_Images/Diagnostics.af deleted file mode 100644 index 5b6a18269..000000000 Binary files a/docs/IDE/_Images/Diagnostics.af and /dev/null differ diff --git a/docs/IDE/_Images/Editor.af b/docs/IDE/_Images/Editor.af deleted file mode 100644 index 162ee8e54..000000000 Binary files a/docs/IDE/_Images/Editor.af and /dev/null differ diff --git a/docs/IDE/index.md b/docs/IDE/index.md index d3c81154a..36d6c120a 100644 --- a/docs/IDE/index.md +++ b/docs/IDE/index.md @@ -8,8 +8,10 @@ permalink: /tB/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 IDE consists of several fixed panes and tool windows. The [**Project Explorer**](IDE/Project/Explorer) shows the file tree of the open project; the [**Editor**](IDE/Project/Editor) is the main code and designer surface; the [**Properties**](IDE/Project/Properties) pane shows and edits properties for the selected item; the [**Toolbox**](IDE/Project/Toolbox) lists the controls available to drop onto a form. +The IDE consists of several fixed panes and tool windows. The [**Project Explorer**](IDE/Project/Explorer) shows the file tree of the open project; the [**Editor**](IDE/Code/Editor) is the main code and designer surface; the [**Properties**](IDE/Project/Properties) pane shows and edits properties for the selected item; the [**Toolbox**](IDE/Project/Toolbox) lists the controls available to drop onto a form. -The debug tool windows --- [**Call Stack**](IDE/Project/CallStack), [**Watches**](IDE/Project/Watches), [**Variables**](IDE/Project/Variables), [**Debug Console**](IDE/Project/DebugConsole), [**Diagnostics**](IDE/Project/Diagnostics), [**Outline**](IDE/Project/Outline), and [**Memory**](IDE/Project/Memory) --- open during a debug session. +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 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/). The [**tbForm**](IDE/Project/Editor/Form) and [**tbReport**](IDE/Project/Editor/Report) designers open when a form or report file is selected in the Project Explorer. Third-party and community [**addins**](IDE/AddIns/) extend the IDE with additional commands and tool windows. diff --git a/docs/LLVM/Getting-Started.md b/docs/LLVM/Getting-Started.md index 1533e21bd..d7fd1600b 100644 --- a/docs/LLVM/Getting-Started.md +++ b/docs/LLVM/Getting-Started.md @@ -20,7 +20,7 @@ During compilation, LLVM translates your code into an *intermediate representati ## LLVM in twinBASIC -You turn LLVM on in [Project Settings](../tB/IDE/Project/Settings). It is off by default while it is experimental. Project Settings has these options for it: +You turn LLVM on in [Project Settings](../tB/IDE/Project-Settings). It is off by default while it is experimental. Project Settings has these options for it: ![The Project Settings dialog scrolled to its two compiler option sections, Compiler Options (BUILD) and Compiler Options (DEBUG). Each has the same unticked boxes in two columns: Enable LLVM Compilation, LLVM: Generate optimized code, LLVM: Optimize for smaller filesize, and one LLVM: Target CPUs with box for each of AES, AVX, AVX2, BMI2, FMA, FXSR, LZCNT, POPCNT, RDSEED, SHA, SSE, SSE2, SSE3, SSE4.1, SSE4.2, SSSE3, XSAVE, XSAVEC, XSAVEOPT and XSAVES. Under the BUILD section a note says that software built for a CPU feature will not run on a CPU without it. Under the DEBUG section a bold note says that LLVM compilation in the IDE debug environment is not recommended, because LLVM-compiled code cannot be debugged.](Images/llvmdoc1.png){:width="1102" height="774"} @@ -41,7 +41,7 @@ The remaining options, from **LLVM: Target CPUs with AES** to **LLVM: Target CPU When LLVM is on, a progress dialog appears when you build or run your program showing the completion percentage of the build process. For large applications, and for applications with very large individual procedures, compiling with LLVM can take much longer than the standard compiler. A very large application can take several minutes or more to compile, depending on your hardware. -twinBASIC keeps a cache of compiled code, so later builds are usually much faster: code that has not changed is not compiled again. To clear the cache and compile everything on the next build, choose **Flush the LLVM compiler cache** on the [**Tools** menu](../tB/IDE/Project/Menu/Tools). +twinBASIC keeps a cache of compiled code, so later builds are usually much faster: code that has not changed is not compiled again. To clear the cache and compile everything on the next build, choose **Flush the LLVM compiler cache** on the [**Tools** menu](../tB/IDE/Reference/Menu/Tools). ## Availability @@ -75,7 +75,7 @@ All other language features should work, in both 32-bit and 64-bit builds. Pleas ## General LLVM options -Besides the project settings, three settings in [IDE Options](../tB/IDE/Project/Menu/Tools) control how LLVM works when it is on: +Besides the project settings, three settings in [IDE Options](../tB/IDE/Customising/IDE-Options) control how LLVM works when it is on. Changing any of the three ends the LLVM cache process and restarts the compiler at once, with no question asked. ![Part of the twinBASIC IDE Options dialog, showing three LLVM settings: LLVM Compiler: Maximum number of threads, set to its default of 1; LLVM Compiler: Complex procedure reporting threshold (milliseconds), set to 10000; and LLVM Compiler: Keep cache process alive after exiting the IDE, ticked.](Images/llvmdoc2.png){:width="628" height="187"} @@ -83,7 +83,7 @@ Besides the project settings, three settings in [IDE Options](../tB/IDE/Project/ : The number of threads the LLVM compiler can create. More threads can make LLVM compilation faster, but use more memory. If LLVM compilation starts to crash, this value is probably too high, and lowering it should stop the crashes. The default is 1, but newer computers can probably handle 10 or more. **LLVM Compiler: Complex procedure reporting threshold (milliseconds)** -: A compile time. Every procedure that takes at least this long to compile is listed in the [Debug Console](../tB/IDE/Project/DebugConsole), which shows the procedures you may want to split up to reduce compile time. The default is 10000 milliseconds, which is 10 seconds. +: A compile time. Every procedure that takes at least this long to compile is listed in the [Debug Console](../tB/IDE/Debugging/Debug-Console), which shows the procedures you may want to split up to reduce compile time. The default is 10000 milliseconds, which is 10 seconds. **LLVM Compiler: Keep cache process alive after exiting the IDE** : Keeps the LLVM cache running after you exit the IDE, so that LLVM does not have to do a long full compile again. It is on by default. diff --git a/docs/Miscellaneous/FAQs.md b/docs/Miscellaneous/FAQs.md index d512b5aac..105e52039 100644 --- a/docs/Miscellaneous/FAQs.md +++ b/docs/Miscellaneous/FAQs.md @@ -134,9 +134,7 @@ You can also create a post in the #bugs channel of the [twinBASIC Discord server
Is the twinBASIC IDE available in other languages? -Yes. The whole front-end UI can be localized, with translations supplied by members of the community. The IDE ships with language packs for Catalan, Chinese (Simplified), Chinese (Traditional), French, German, Greek, Hindi, Hungarian, Indonesian (Bahasa), Italian, Japanese, Malayalam, Polish, Portuguese (Brazil), Russian, Spanish, Swedish and Turkish, besides the base British English. Choose one under [**Window** > **Language**](tB/IDE/Project/Menu/Window#language). - -Each pack is a UTF-8 text file in the `locale` folder of the IDE, named by its IETF language tag (`pl-pl.lang`, `fr-fr.lang`); the folder's `README.txt` says how to write a new one. A string a pack does not translate is taken from the base pack, `en-gb.lang`. Translators share new and updated packs in [#langpacks on the tB Discord server](https://discord.com/channels/927638153546829845/1329533568376115282). +Yes. The whole front-end UI can be localized, with translations supplied by members of the community. The IDE ships with language packs for 18 languages besides the base British English. Choose one under **Window → Language**. [The language of the IDE](tB/IDE/Customising/#the-language-of-the-ide) lists the packs as the menu shows them, says what stays in English, and says how to write a pack: a pack is a UTF-8 text file in a `locale` folder, and the IDE reads the packs when it starts. Translators share new and updated packs in [#langpacks on the tB Discord server](https://discord.com/channels/927638153546829845/1329533568376115282). Internal text such as the hover information does not yet support localization, but this is planned for the future. @@ -216,11 +214,11 @@ In addition to the directory you extract the IDE to, twinBASIC stores files and - `%APPDATA%\twinBASIC` - (storage of themes, linked packages, and other files that you want to keep when deleting previous installs) + (storage of themes, language packs, linked packages, add-ins and other files that you want to keep when deleting previous installs) - and in the registry under `HKEY_CURRENT_USER\Software\VB and VBA Program Settings\twinBASIC_IDE` - (current IDE configuration info; recent project list, panel layouts, license info, selected theme, keyboard shortcuts, etc) + (current IDE configuration info; recent project list, panel layouts, license info, selected theme, keyboard shortcuts, etc. [Customising the IDE](tB/IDE/Customising/#where-the-ide-keeps-its-settings) says which value holds what)
@@ -249,7 +247,7 @@ You can import individual files, from VB projects or any type, through the Impor ![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="468" height="397"} -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 .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.
diff --git a/docs/Reference/Attributes.md b/docs/Reference/Attributes.md index b27a4aeac..dddf1f8d4 100644 --- a/docs/Reference/Attributes.md +++ b/docs/Reference/Attributes.md @@ -63,7 +63,7 @@ Syntax: **[ArrayBoundsChecks** [ **( True** \| **False )** ] **]** Applicable to: [**Class**](Class), [**Module**](Module), [procedure](../Gloss#procedure) -Disables or enables array element access bounds checking within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **ArrayBoundsChecks(False)** turns the check off. **ArrayBoundsChecks(True)** turns it back on where the project setting [**Disable Array Bounds Checks**](../IDE/Project/Settings#disable-array-bounds-checks) has turned it off for the whole project. +Disables or enables array element access bounds checking within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **ArrayBoundsChecks(False)** turns the check off. **ArrayBoundsChecks(True)** turns it back on where the project setting [**Disable Array Bounds Checks**](../IDE/Project-Settings#disable-array-bounds-checks) has turned it off for the whole project. ## BindOnlyIfNoArguments (optional Bool) {: #bindonlyifnoarguments } @@ -226,7 +226,7 @@ Syntax: **[CompilerOptions( "** options **" )]** Applicable to: [procedure definitions](../Gloss#procedure) -Sets the LLVM compiler options for one procedure, in place of the **Compiler Options** in [Project Settings](../IDE/Project/Settings). *options* is a comma-separated list of flags; an empty string turns LLVM off for the procedure. Typical use would be `[CompilerOptions("+llvm,+optimize,+optimizesize")]` to compile the procedure using the built-in LLVM instead of the default compiler, with chosen optimizations. [Getting Started with LLVM](../../LLVM/Getting-Started) describes LLVM compilation in full. Compiler options available: +Sets the LLVM compiler options for one procedure, in place of the **Compiler Options** in [Project Settings](../IDE/Project-Settings). *options* is a comma-separated list of flags; an empty string turns LLVM off for the procedure. Typical use would be `[CompilerOptions("+llvm,+optimize,+optimizesize")]` to compile the procedure using the built-in LLVM instead of the default compiler, with chosen optimizations. [Getting Started with LLVM](../../LLVM/Getting-Started) describes LLVM compilation in full. Compiler options available: - **+llvm** - uses LLVM to compile this procedure. This feature is experimental at the moment. The LLVM compiler back-end is built into twinBASIC. It is not necessary to have LLVM separately installed, and any such installation is ignored by twinBASIC. - **+optimize** - enables optimization during compilation of this procedure @@ -582,7 +582,7 @@ Syntax: **[FloatingPointErrorChecks** [ **( True** \| **False)** ] **]** Applicable to: [**Class**](Class), [**Module**](Module), [procedure](../Gloss#procedure) -Disables or enables floating point error checks within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **FloatingPointErrorChecks(False)** turns the checks off. **FloatingPointErrorChecks(True)** turns them back on where the project setting [**Disable FPU Error Checks**](../IDE/Project/Settings#disable-fpu-error-checks) has turned them off for the whole project. +Disables or enables floating point error checks within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **FloatingPointErrorChecks(False)** turns the checks off. **FloatingPointErrorChecks(True)** turns them back on where the project setting [**Disable FPU Error Checks**](../IDE/Project-Settings#disable-fpu-error-checks) has turned them off for the whole project. ## FormDesignerId (String) {: #formdesignerid } @@ -701,7 +701,7 @@ Syntax: **[IntegerOverflowChecks** [ **( True** \| **False )** ] **]** Applicable to: [**Class**](Class), [**Module**](Module), [procedure](../Gloss#procedure) -Disables or enables integer overflow checks within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **IntegerOverflowChecks(False)** turns the checks off. **IntegerOverflowChecks(True)** turns them back on where the project setting [**Disable Overflow Checks**](../IDE/Project/Settings#disable-overflow-checks) has turned them off for the whole project. +Disables or enables integer overflow checks within the scope of a class, module, or a single procedure/method. Used on performance-critical routines. **IntegerOverflowChecks(False)** turns the checks off. **IntegerOverflowChecks(True)** turns them back on where the project setting [**Disable Overflow Checks**](../IDE/Project-Settings#disable-overflow-checks) has turned them off for the whole project. Dividing the most negative **Integer**, **Long** or **LongLong** by -1 with [**\\**](IntegerDivide) or [**Mod**](Mod) raises error 6 whatever this attribute or the project setting says. diff --git a/docs/Reference/Built-In/tbIDE/index.md b/docs/Reference/Built-In/tbIDE/index.md index 5087870fd..cc22847a1 100644 --- a/docs/Reference/Built-In/tbIDE/index.md +++ b/docs/Reference/Built-In/tbIDE/index.md @@ -77,7 +77,7 @@ The build path above writes the DLL into the IDE's own `addins` folder, and the and then names the process that holds the file, the IDE's own compiler, `twinBASIC_win32_noDEP.exe`. Error code 32 is Windows' *file in use*. To change an addin and build it again, build it somewhere else and copy it in while the IDE is closed: -1. Set the addin project's [Build Output Path](../../IDE/Project/Settings#build-output-path) to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`, the path the other project templates use. +1. Set the addin project's [Build Output Path](../../IDE/Project-Settings#build-output-path) to `${SourcePath}\Build\${ProjectName}_${Architecture}.${FileExtension}`, the path the other project templates use. 2. Build. The DLL goes into a `Build` folder beside the `.twinproj`, as `MyAddIn_win32.dll`. 3. Close the IDE, copy the new DLL over the old one in the installation's `addins\win32` folder, keeping the old one's name, and start the IDE again. It loads the new build. diff --git a/docs/Reference/Categories.md b/docs/Reference/Categories.md index ca08d3e97..e14ce072e 100644 --- a/docs/Reference/Categories.md +++ b/docs/Reference/Categories.md @@ -464,7 +464,7 @@ Procedures: Methods of the intrinsic [Debug](../tB/Modules/Debug) object: -* [Debug.Print](../tB/Modules/Debug#print) - write to the [Debug Console](../tB/IDE/Project/DebugConsole) +* [Debug.Print](../tB/Modules/Debug#print) - write to the [Debug Console](../tB/IDE/Debugging/Debug-Console) * [Debug.TracePrint](../tB/Modules/Debug#traceprint) - write to the active trace output, which also works in a compiled executable * [Debug.Cls](../tB/Modules/Debug#cls) - clear the Debug Console * [Debug.Assert](../tB/Modules/Debug#assert) - halt when a condition is **False** diff --git a/docs/Reference/Core/Option.md b/docs/Reference/Core/Option.md index 72423c8bc..2d5aad880 100644 --- a/docs/Reference/Core/Option.md +++ b/docs/Reference/Core/Option.md @@ -88,7 +88,7 @@ This option makes it mandatory to require variable declarations. There is no com When **Option Explicit** appears in a module, all variables must be explicitly declared by using the [**Dim**](Dim), [**Private**](Private), [**Public**](Public), [**ReDim**](ReDim), or [**Static**](Static) statements. Attempting to use an undeclared variable name raises an error at [compile time](../Gloss#compile-time). -Without the **Option Explicit** statement, and when the [**Option Explicit On**](../IDE/Project/Settings#option-explicit-on) project setting is changed to its non-default value of *No*, all undeclared variables are of **Variant** type unless the default type is otherwise specified with a [**Def**_type_](Deftype) statement. +Without the **Option Explicit** statement, and when the [**Option Explicit On**](../IDE/Project-Settings#option-explicit-on) project setting is changed to its non-default value of *No*, all undeclared variables are of **Variant** type unless the default type is otherwise specified with a [**Def**_type_](Deftype) statement. > [!NOTE] > The **Option Explicit On** project setting is *Yes* by default in new projects. diff --git a/docs/Reference/Core/Print.md b/docs/Reference/Core/Print.md index 6468ff0f8..e68861a5d 100644 --- a/docs/Reference/Core/Print.md +++ b/docs/Reference/Core/Print.md @@ -96,7 +96,7 @@ Close #1 ' Close file. ## Writing to the Debug Console -[**Debug.Print**](../Modules/Debug#print) writes to the IDE's [Debug Console](../IDE/Project/DebugConsole). The console is monospaced, so a print zone there is 14 characters wide and zones begin at columns 0, 14, 28 and so on. [**Debug.TracePrint**](../Modules/Debug#traceprint) takes the same *outputlist* but writes to the trace log instead. +[**Debug.Print**](../Modules/Debug#print) writes to the IDE's [Debug Console](../IDE/Debugging/Debug-Console). The console is monospaced, so a print zone there is 14 characters wide and zones begin at columns 0, 14, 28 and so on. [**Debug.TracePrint**](../Modules/Debug#traceprint) takes the same *outputlist* but writes to the trace log instead. ## Writing to a drawing surface diff --git a/docs/Reference/Default/VBA/Debug.md b/docs/Reference/Default/VBA/Debug.md index ec895d452..69555c2e0 100644 --- a/docs/Reference/Default/VBA/Debug.md +++ b/docs/Reference/Default/VBA/Debug.md @@ -6,7 +6,7 @@ permalink: /tB/Modules/Debug # Debug {: .no_toc } -The intrinsic debugging object. Writes to the IDE's [Debug Console](../IDE/Project/DebugConsole), clears it, asserts a condition, and writes to the trace log. +The intrinsic debugging object. Writes to the IDE's [Debug Console](../IDE/Debugging/Debug-Console), clears it, asserts a condition, and writes to the trace log. **Debug** is a built-in class with a predeclared instance, so there is nothing to declare or construct --- the name **Debug** is the object. It has four methods and no properties. @@ -20,7 +20,7 @@ The intrinsic debugging object. Writes to the IDE's [Debug Console](../IDE/Proje ## Debug.Print {: #print } -Writes text to the [Debug Console](../IDE/Project/DebugConsole). +Writes text to the [Debug Console](../IDE/Debugging/Debug-Console). Syntax: **Debug.Print** [ *outputlist* ] @@ -29,6 +29,8 @@ Syntax: **Debug.Print** [ *outputlist* ] The console is monospaced, so a zone here is 14 characters wide and zones begin at columns 0, 14, 28 and so on. An expression that reaches the end of its zone pushes the next one into the zone after. +In a built executable, **Debug.Print** writes nothing. To log from an executable, use [**Debug.TracePrint**](#traceprint). + ```tb check_build Debug.Print ' a blank line Debug.Print "value: "; 42 ' value: 42 @@ -60,7 +62,7 @@ End Sub ## Debug.Cls {: #cls } -Clears the [Debug Console](../IDE/Project/DebugConsole). +Clears the [Debug Console](../IDE/Debugging/Debug-Console). Syntax: **Debug.Cls** @@ -84,7 +86,7 @@ Syntax: **Debug.Assert** *booleanexpression* *booleanexpression* : An expression evaluating to **True** or **False**. Execution continues when it is **True** and breaks when it is **False**. -In a procedure compiled with [LLVM](../../LLVM/Getting-Started), **Debug.Assert** is ignored: it does not break, and *booleanexpression* is not evaluated. +In a built executable, and in a procedure compiled with [LLVM](../../LLVM/Getting-Started), **Debug.Assert** is ignored: it does not break, and *booleanexpression* is not evaluated. Use it to state something the surrounding code relies on, so a violated assumption stops at the line that states it rather than further away. @@ -106,7 +108,7 @@ End Function ## See Also -- [Debug Console](../IDE/Project/DebugConsole) -- the IDE pane **Print** writes to, and its buttons +- [Debug Console](../IDE/Debugging/Debug-Console) -- the IDE pane **Print** writes to, and its buttons - [Debugging Features](../../Features/Compiler-IDE/Debugging) -- the trace logger **TracePrint** feeds, and its project settings - [Assert Package](../Packages/Assert/) -- assertions for unit tests, with reported results - [ErrObject](ErrObject/) -- the other intrinsic object in this package diff --git a/docs/Reference/Default/VBA/FileSystem/Input.md b/docs/Reference/Default/VBA/FileSystem/Input.md index 282cba88b..12b12d219 100644 --- a/docs/Reference/Default/VBA/FileSystem/Input.md +++ b/docs/Reference/Default/VBA/FileSystem/Input.md @@ -32,7 +32,7 @@ For files opened for **Binary** access, an attempt to read through the file usin ### Example -This example uses the **Input** function to read one character at a time from a file and print it to the [Debug Console](../../IDE/Project/DebugConsole). *TESTFILE* is assumed to be a text file with a few lines of sample data. +This example uses the **Input** function to read one character at a time from a file and print it to the [Debug Console](../../IDE/Debugging/Debug-Console). *TESTFILE* is assumed to be a text file with a few lines of sample data. ```tb check_build Dim MyChar As Variant diff --git a/docs/Reference/Glossary.md b/docs/Reference/Glossary.md index de18ea837..5ea8b6dcd 100644 --- a/docs/Reference/Glossary.md +++ b/docs/Reference/Glossary.md @@ -465,7 +465,7 @@ Note that the named arguments don't have to appear in the normal positional orde ## namespace {: index="namespaces" } -(twinBASIC) A name that groups the names declared under it, so that code reaches them through it, followed by a dot. A [package](#package) groups its components under the namespace given when it is [published](../Features/Packages/Creating-TWINPACK), and a project that references the package reaches them through that name; see [Packages](../Features/Packages/). A project's own namespace is its [**Project Name**](IDE/Project/Settings#project-name). **Import Library** ... **As** *namespace* names a [statically linked library](../Features/Advanced/Static-Linking#generic-syntax), which **Declare** statements then use in place of a DLL name. A class with the **[AppObject]** [attribute](../Features/Language/Interfaces-CoClasses) is part of the global namespace. +(twinBASIC) A name that groups the names declared under it, so that code reaches them through it, followed by a dot. A [package](#package) groups its components under the namespace given when it is [published](../Features/Packages/Creating-TWINPACK), and a project that references the package reaches them through that name; see [Packages](../Features/Packages/). A project's own namespace is its [**Project Name**](IDE/Project-Settings#project-name). **Import Library** ... **As** *namespace* names a [statically linked library](../Features/Advanced/Static-Linking#generic-syntax), which **Declare** statements then use in place of a DLL name. A class with the **[AppObject]** [attribute](../Features/Language/Interfaces-CoClasses) is part of the global namespace. ## Null diff --git a/docs/Reference/twinBASIC-Additions.md b/docs/Reference/twinBASIC-Additions.md index 2fca09ad5..5e210f1b3 100644 --- a/docs/Reference/twinBASIC-Additions.md +++ b/docs/Reference/twinBASIC-Additions.md @@ -177,7 +177,7 @@ See [Features → Enhanced API Declarations](../Features/Advanced/API-Declaratio ## IDE additions -- **CodeLens** --- inline action bars above procedures ("Run", "Debug", "Test") without leaving the editor. See [Features → CodeLens](../Features/Compiler-IDE/CodeLens). +- **CodeLens** --- inline action bars above procedures ("Run", "Debug", "Test") without leaving the editor. See [CodeLens](../tB/IDE/Code/IntelliSense#codelens). - **Package server** (TWINSERV) --- install packages from a central registry without leaving the IDE. See [Features → Importing a package](../Features/Packages/Importing-TWINSERV). - **Type inference** --- `Dim x = 1` infers **Long**; `For Each item In collection` infers the element type when the collection is typed. See [Features → Type Inference](../Features/Language/Type-Inference). - **Conditional compilation constants** (`#Const`, `#If`) --- a superset of the VBA set; see [Compiler Constants](Compiler-Constants). diff --git a/docs/Tutorials/Testing-with-Assert.md b/docs/Tutorials/Testing-with-Assert.md index 46ad39843..008dd9f21 100644 --- a/docs/Tutorials/Testing-with-Assert.md +++ b/docs/Tutorials/Testing-with-Assert.md @@ -102,7 +102,7 @@ There are two ways to run a test Sub: 1. **CodeLens** --- above each `Sub` line, the editor shows a **▶ run** link. Click it to run that one Sub. A test that passes returns without printing anything; one that fails stops on the failing assertion, as described below. -2. **F6 from inside the Sub** --- place the cursor inside the Sub and press **F6**. twinBASIC runs that procedure and stops when it returns or when an assertion fails. **F5** does not do this: it starts the whole project, as **Run → Start** does. +2. **F6 from inside the Sub** --- place the cursor inside the Sub and press **F6**. twinBASIC runs that procedure and stops when it returns or when an assertion fails. **F5** does not do this: it starts the whole project, as **Run → Start** does. With the cursor in the code of a form, **F6** previews the form instead of running a procedure; see [Run Or Preview](../tB/IDE/Debugging/Running#run-or-preview). To run all tests in a batch, add a runner Sub to `TestStringUtils` that calls each test in sequence: @@ -124,7 +124,7 @@ Place the cursor inside `RunAllTests` and press **F6**, or click the **▶ run** All PadLeft tests passed. ``` -If an assertion fails, the run stops on that line with the run-time error **Assertion FAILED** (-353703420, `&HEAEAEA04`), before the final `Debug.Print`. The IDE shows the failing line, and the call stack names the test it is in. The error panel under the line has four buttons: **Try Again (Resume)**, **Ignore (Resume Next)**, **Stop** and **Search Online**. [When a run-time error stops the program](../tB/IDE/Project/Menu/Debug#when-a-run-time-error-stops-the-program) describes them, and how to look at the test's variables. Nothing about the failure is written to the Debug Console, and the expected and actual values are not shown. +If an assertion fails, the run stops on that line with the run-time error **Assertion FAILED** (-353703420, `&HEAEAEA04`), before the final `Debug.Print`. The IDE shows the failing line, and the call stack names the test it is in. The error panel under the line has four buttons: **Try Again (Resume)**, **Ignore (Resume Next)**, **Stop** and **Search Online**. [When a run-time error stops the program](../tB/IDE/Debugging/Breakpoints-and-Stepping#when-a-run-time-error-stops-the-program) describes them, and how to look at the test's variables. Nothing about the failure is written to the Debug Console, and the expected and actual values are not shown. > [!IMPORTANT] > **Stop** and **Ignore (Resume Next)** do not end the run at a failed assertion. Both let the test go on past the failed check as if it had passed, so `RunAllTests` runs the remaining tests and then prints `All PadLeft tests passed.` To end the run, click the failing test's `End Sub` line, press **Ctrl+F9** (**Debug → Set Next Statement**), then choose **Run → End**. diff --git a/docs/_book.yml b/docs/_book.yml index 43c1ad54f..2ad3cd56e 100644 --- a/docs/_book.yml +++ b/docs/_book.yml @@ -268,31 +268,24 @@ parts: no_heading_shift: true outline_closed: true - title: Project Settings - page: /tB/IDE/Project/Settings + page: /tB/IDE/Project-Settings no_outline_entry: true no_heading_shift: true outline_closed: true - - title: File Menu - page: /tB/IDE/Project/Menu/File - no_outline_entry: true - no_heading_shift: true - outline_closed: true - - title: Toolbar - page: /tB/IDE/Project/Toolbar - no_outline_entry: true - no_heading_shift: true + - title: Writing code + landing_page: /tB/IDE/Code/ + page: /tB/IDE/Code/ + landing_is_target: true outline_closed: true - - title: Debug Menu - page: /tB/IDE/Project/Menu/Debug - no_outline_entry: true - no_heading_shift: true + - title: Running and debugging + landing_page: /tB/IDE/Debugging/ + page: /tB/IDE/Debugging/ + landing_is_target: true outline_closed: true - - title: Debugging Panes - pages: - - /tB/IDE/Project/DebugConsole - - /tB/IDE/Project/CallStack - - /tB/IDE/Project/Variables - - /tB/IDE/Project/Memory + - title: Customising the IDE + landing_page: /tB/IDE/Customising/ + page: /tB/IDE/Customising/ + landing_is_target: true outline_closed: true - title: Add Ins page: /tB/IDE/AddIns/ @@ -305,6 +298,11 @@ parts: no_outline_entry: true no_heading_shift: true outline_closed: true + - title: IDE Reference + pages: + - /tB/IDE/Reference/Menu/File + - /tB/IDE/Reference/Toolbar + outline_closed: true - title: The Core Language subtitle: Statements, operators, and built-in keywords @@ -603,35 +601,30 @@ left_out: - reason: IDE pages that are still screenshots and the labels they show, or headings with nothing under them no_descent: true pages: - - /tB/IDE/Project/Diagnostics - - /tB/IDE/Project/FindReplace - - /tB/IDE/Project/Editor - /tB/IDE/Project/Editor/Form - /tB/IDE/Project/Explorer - /tB/IDE/AddIns/Community - - /tB/IDE/Project/Menu - - /tB/IDE/Project/Menu/Edit - - /tB/IDE/Project/Menu/View - - /tB/IDE/Project/Menu/Project - - /tB/IDE/Project/Menu/Format - - /tB/IDE/Project/Menu/Run - - /tB/IDE/Project/Menu/AddIns - - /tB/IDE/Project/Menu/Help - - /tB/IDE/Project/Menu/Tools # one real line, under a visible TODO note + - /tB/IDE/Reference/Menu + - /tB/IDE/Reference/Menu/Project + - /tB/IDE/Reference/Menu/Format + - /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/Project/StatusBar + - /tB/IDE/Reference/StatusBar + - /tB/IDE/Reference - /tB/IDE/Project/Toolbox - /tB/IDE/Project/Editor/Report - /tB/IDE/Project/Properties - /tB/IDE/Project/Webpage - /tB/IDE/Project/Splash - - /tB/IDE/Project/Watches - - /tB/IDE/Project/Outline - - /tB/IDE/Project/History - - /tB/IDE/Project/OpenEditors - /tB/IDE/Project/PackagePublishing - /tB/IDE/AddIns/GlobalSearch - - /tB/IDE/Project/Menu/Window # ~800 lines of shortcut JSON that would print unfolded + - /tB/IDE/Reference/Menu/Edit + - /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/docs/index.md b/docs/index.md index cbce02ba3..4c14a5416 100644 --- a/docs/index.md +++ b/docs/index.md @@ -80,7 +80,7 @@ Step-by-step guides, the first four written for someone new to twinBASIC. [Tutor ## The twinBASIC IDE -The [**IDE section**](tB/IDE) documents the editor, project explorer, debugging panes (call stack, watches, diagnostics, debug console), the [**tbForm**](tB/IDE/Project/Editor/Form) and [**tbReport**](tB/IDE/Project/Editor/Report) designers, and the per-feature side panes. To install third-party addins, see [**Add Ins**](tB/IDE/AddIns/); to author your own, the [**tbIDE package**](tB/Packages/tbIDE/) is the addin SDK. +The [**IDE section**](tB/IDE) documents the [code editor and the panels beside it](tB/IDE/Code/) (diagnostics, find, outline), the project explorer, [running and debugging](tB/IDE/Debugging/) (breakpoints, call stack, watches, debug console), the [**tbForm**](tB/IDE/Project/Editor/Form) and [**tbReport**](tB/IDE/Project/Editor/Report) designers, and the per-feature side panes. To install third-party addins, see [**Add Ins**](tB/IDE/AddIns/); to author your own, the [**tbIDE package**](tB/Packages/tbIDE/) is the addin SDK. ## Community and external resources diff --git a/eval/search-experiments/intent/search_prose_queries.json b/eval/search-experiments/intent/search_prose_queries.json index 90b7ccdad..4960124e0 100644 --- a/eval/search-experiments/intent/search_prose_queries.json +++ b/eval/search-experiments/intent/search_prose_queries.json @@ -11,7 +11,7 @@ { "q": "regular expressions", "expected": ["/tB/IDE/Project/FindReplace"] }, { "q": "error handling", "expected": ["/Reference/Categories"] }, { "q": "threading and atomics", "expected": ["/Reference/Categories"] }, - { "q": "array bounds checks", "expected": ["/tB/IDE/Project/Settings"] }, + { "q": "array bounds checks", "expected": ["/tB/IDE/Project-Settings"] }, { "q": "File I/O", "expected": ["/Features/Standard-Library/File-IO"] }, { "q": "Fusion", "expected": ["/Features/Fusion"] }, { "q": "AddressOf operator", "expected": ["/tB/Core/AddressOf"] }, diff --git a/eval/search_prose_queries.json b/eval/search_prose_queries.json index aea55b585..3197d292a 100644 --- a/eval/search_prose_queries.json +++ b/eval/search_prose_queries.json @@ -11,7 +11,7 @@ { "q": "regular expressions", "expected": ["/tB/IDE/Project/FindReplace"] }, { "q": "error handling", "expected": ["/Reference/Categories"] }, { "q": "threading and atomics", "expected": ["/Reference/Categories"] }, - { "q": "array bounds checks", "expected": ["/tB/IDE/Project/Settings"] }, + { "q": "array bounds checks", "expected": ["/tB/IDE/Project-Settings"] }, { "q": "File I/O", "expected": ["/Features/Standard-Library/File-IO"] }, { "q": "Fusion", "expected": ["/Features/Fusion"] }, { "q": "AddressOf operator", "expected": ["/tB/Core/AddressOf"] }, @@ -39,7 +39,7 @@ { "q": "inline initialization", "expected": ["/Features/Language/Inline-Initialization"] }, { "q": "field initialization", "expected": ["/Features/Language/Inline-Initialization"] }, { "q": "windowless controls", "expected": ["/Features/GUI-Components/Windowless"] }, - { "q": "break mode", "expected": ["/tB/IDE/Project/Menu/Debug"] }, + { "q": "break mode", "expected": ["/tB/IDE/Reference/Menu/Debug"] }, { "q": "accelerator key", "expected": ["/tB/Packages/VB/Label"], "behind": ["/tB/Packages/VB/CommandButton"] }, { "q": "access key", "expected": ["/tB/Packages/VB/Label"], "behind": ["/tB/Packages/VB/CommandButton"] }, { "q": "type-declaration character", "expected": ["/Reference/Data-Types"] }, @@ -63,9 +63,9 @@ { "q": "inherited property", "expected": ["/Features/Language/Inheritance"] }, { "q": "z-order", "expected": ["/tB/Packages/VB/Form"], "behind": ["/tB/Packages/VB/MDIForm", "/tB/Packages/VB/Report"] }, { "q": "windows api tutorial", "expected": ["/Tutorials/Windows-API"] }, - { "q": "standard exe", "expected": ["/tB/IDE/Project/New"], "behind": ["/tB/IDE/Project/Settings", "/Features/Project-Configuration/Project-Types"] }, - { "q": "create an ActiveX DLL", "expected": ["/tB/IDE/Project/New"], "behind": ["/tB/IDE/Project/Settings", "/Features/Project-Configuration/Project-Types"] }, - { "q": "create ActiveX DLL", "expected": ["/tB/IDE/Project/New"], "behind": ["/tB/IDE/Project/Settings", "/Features/Project-Configuration/Project-Types"] }, + { "q": "standard exe", "expected": ["/tB/IDE/Project/New"], "behind": ["/tB/IDE/Project-Settings", "/Features/Project-Configuration/Project-Types"] }, + { "q": "create an ActiveX DLL", "expected": ["/tB/IDE/Project/New"], "behind": ["/tB/IDE/Project-Settings", "/Features/Project-Configuration/Project-Types"] }, + { "q": "create ActiveX DLL", "expected": ["/tB/IDE/Project/New"], "behind": ["/tB/IDE/Project-Settings", "/Features/Project-Configuration/Project-Types"] }, {"q": "ByRef", "expected": ["/tB/Core/Sub#passing-arguments-byref-and-byval"]}, {"q": "ByVal", "expected": ["/tB/Core/Sub#passing-arguments-byref-and-byval"]}, {"q": "ByVal vs ByRef", "expected": ["/tB/Core/Sub#passing-arguments-byref-and-byval"]}, @@ -83,7 +83,7 @@ {"q": "multiple return values", "expected": ["/tB/Core/Function#returning-more-than-one-value"]}, {"q": "default property", "expected": ["/tB/Core/Attributes", "/tB/Gloss#default-member"]}, {"q": "registry access", "expected": ["/Reference/Categories"]}, - {"q": "add reference", "expected": ["/tB/IDE/Project/Settings"], "behind": ["/tB/IDE/Project/Menu/Project"]}, + {"q": "add reference", "expected": ["/tB/IDE/Project-Settings"], "behind": ["/tB/IDE/Reference/Menu/Project"]}, {"q": "new project dialog", "expected": ["/tB/IDE/Project/New"]}, {"q": "find and replace", "expected": ["/tB/IDE/Project/FindReplace"]}, {"q": "search and replace", "expected": ["/tB/IDE/Project/FindReplace"]}, @@ -104,7 +104,7 @@ {"q": "optional arguments", "expected": ["/tB/Core/Sub"]}, {"q": "default parameter values", "expected": ["/tB/Core/Sub"]}, {"q": "default argument values", "expected": ["/tB/Core/Sub"]}, - {"q": "high DPI", "expected": ["/tB/IDE/Project/Settings"]}, - {"q": "DPI awareness", "expected": ["/tB/IDE/Project/Settings"]}, - {"q": "dark mode", "expected": ["/Features/Compiler-IDE/IDE-Features"], "behind": ["/tB/IDE/Project/Menu/Window"]} + {"q": "high DPI", "expected": ["/tB/IDE/Project-Settings"]}, + {"q": "DPI awareness", "expected": ["/tB/IDE/Project-Settings"]}, + {"q": "dark mode", "expected": ["/Features/Compiler-IDE/IDE-Features"], "behind": ["/tB/IDE/Reference/Menu/Window"]} ] diff --git a/scripts/lib/axe-scan.mjs b/scripts/lib/axe-scan.mjs index 9447c4533..24c8b073c 100644 --- a/scripts/lib/axe-scan.mjs +++ b/scripts/lib/axe-scan.mjs @@ -77,7 +77,7 @@ export const SAMPLE_PAGES = [ "/404.html", "/Documentation/Development/Pipeline-Stages.html", "/Features/index.html", - "/tB/IDE/Project/Menu/Window.html", + "/tB/IDE/Reference/Menu/Window.html", "/Videos/tB.html", "/Reference/Procedures-and-Functions.html", // The only page in the site carrying both an IMPORTANT and a TIP @@ -206,7 +206,7 @@ export const PAGE_STATES = { // that nothing looked at before. Neither substitutes for the other. export const STATE_AUDITS = [ { - filePath: "/tB/IDE/Project/Menu/Window.html", + filePath: "/tB/IDE/Reference/Menu/Window.html", state: "section-links-open", }, { diff --git a/scripts/lib/cli-cases.mjs b/scripts/lib/cli-cases.mjs index 87cb0807e..5390c7479 100644 --- a/scripts/lib/cli-cases.mjs +++ b/scripts/lib/cli-cases.mjs @@ -403,6 +403,7 @@ const HELP_TOOLS = { "scripts/probe_build_twice.mjs": null, "scripts/probe_ide_image_labels.mjs": null, "scripts/probe_shared_temp.mjs": null, + "scripts/probe_theme_semicolon.mjs": null, "scripts/survey_tooling.mjs": null, "scripts/sweep_a11y.mjs": null, "scripts/sweep_attributes.mjs": null, @@ -484,6 +485,7 @@ const REFUSALS = { "scripts/probe_build_twice.mjs": ["ide"], "scripts/probe_ide_image_labels.mjs": ["ide"], "scripts/probe_shared_temp.mjs": ["ide"], + "scripts/probe_theme_semicolon.mjs": ["ide"], "scripts/survey_tooling.mjs": ["root"], "scripts/sweep_a11y.mjs": ["out"], "scripts/sweep_attributes.mjs": ["out"], diff --git a/scripts/lib/shot-annotate.mjs b/scripts/lib/shot-annotate.mjs index 30598369c..62cab1922 100644 --- a/scripts/lib/shot-annotate.mjs +++ b/scripts/lib/shot-annotate.mjs @@ -57,7 +57,7 @@ // window draws and no capture of the page holds, drawn as a replica: its // options from the element, the selected one marked, in the owner's one style // (a white list, a 1 px grey border, the select's own font, the selected option -// white on #6E6E6E). `select` is a `css` anchor of the select or of an element +// white on #6E6E6E; an option the page sets in bold, bold). `select` is a `css` anchor of the select or of an element // inside or around it; `at` the list's top-left corner, by default the select's // bottom-left, where the OS list opens; `rows` how many options show, the rest // behind a scroll bar, the selected one in the middle. Drawn in the order of the @@ -433,19 +433,23 @@ function draw(prims, S, resolve = null) { const first = scrolls ? Math.max(0, Math.min(options.length - shownRows, current - Math.floor(shownRows / 2))) : 0; const g = el("g", { class: "tbShotList", "data-name": p.name ?? String(lists) }); lists++; - const write = (s, x, y, fill) => { + // an option the page sets in bold (the editor's combo boxes mark an event that has a + // handler so) is written in bold + const bold = (o) => Number.parseInt(getComputedStyle(o).fontWeight, 10) >= 600; + const write = (s, x, y, fill, heavy = false) => { const t = el( "text", { x, y, fill, "font-family": style.fontFamily, "font-size": size, "dominant-baseline": "central" }, g, ); + if (heavy) t.setAttribute("font-weight", "bold"); t.textContent = s; return t; }; // the width: the select's, or the longest option's let longest = 0; for (const o of options) { - const t = write(o.text, 0, 0, "none"); + const t = write(o.text, 0, 0, "none", bold(o)); longest = Math.max(longest, t.getComputedTextLength()); t.remove(); } @@ -467,7 +471,7 @@ function draw(prims, S, resolve = null) { row.setAttribute("class", n === current ? "tbShotListRow tbShotListCurrent" : "tbShotListRow"); row.setAttribute("data-text", options[n].text); if (n === current) row.setAttribute("fill", S.listCurrent); - write(options[n].text, x + 1 + S.listPadX, ry + rowH / 2, n === current ? "#fff" : "#000"); + write(options[n].text, x + 1 + S.listPadX, ry + rowH / 2, n === current ? "#fff" : "#000", bold(options[n])); } if (scrolls) { const tx = x + w - 1 - S.listScrollbar; diff --git a/scripts/probe_theme_semicolon.mjs b/scripts/probe_theme_semicolon.mjs new file mode 100644 index 000000000..f42e6bc65 --- /dev/null +++ b/scripts/probe_theme_semicolon.mjs @@ -0,0 +1,170 @@ +#!/usr/bin/env node +// Find the properties the IDE silently drops when it loads the themes it ships. +// +// node scripts/probe_theme_semicolon.mjs [options] +// +// The defect is the entry of BUGS-TO-REPORT.md whose reproducer is +// bugs/theme-missing-semicolon/: in BETA 1005, line 63 of Dark.theme and of +// Light.theme reads `DiagnosticsGroupFontSize: 90%` with no `;`, and the +// IDE's theme loader loses that property and the next one. +// +// The loader is applyThemeData in \ide\main.js. It takes the file's +// text with a "\r\n" added, removes /* ... */ comments, cuts it at every ";" +// followed by a space, a tab or a line end, removes the line breaks from each +// piece, cuts each piece at every ": " and ":\t", and keeps the piece only if +// that gives exactly two parts (the name, with leading spaces and tabs +// removed, and the value). A piece that gives any other number is dropped +// without a message. This probe applies the same rule to every .theme file in +// the install's themes folder, and reports each piece that holds text and is +// dropped, with the line it starts on in the file as it is on disk. +// +// It reads the files and nothing else: it starts no IDE and no program, and +// writes nothing. Exit codes: see USAGE. +import { readdirSync, readFileSync, statSync } from "node:fs"; +import path from "node:path"; +import { die, exitOnCrash, parseCli, printHelpAndExit, withUsageError } from "../lib/cli.mjs"; +import { buildNumber, findIde } from "./lib/tb-install.mjs"; + +exitOnCrash(); + +const USAGE = `usage: node scripts/probe_theme_semicolon.mjs [--ide ] [-h, --help] + +Reads every .theme file in the themes folder of the twinBASIC install and splits +it into properties as the IDE's theme loader does (applyThemeData in ide\\main.js): +comments removed, cut at each ";" followed by a space, a tab or a line end, each +piece cut at each ": " and ":", and kept only if that gives exactly two +parts. A piece that does not is dropped by the IDE without a message, and the +usual cause is a property that lacks its ";", which merges it with the next one +and loses both. Nothing is written and no program is started. + +The probe prints one line for each dropped piece, \` line : dropped +\`, with the file relative to the install, then a summary +line, \` properties are lost in of shipped themes (BETA )\`, +or \`no shipped theme loses a property (BETA )\` when none does. A piece +with no ": " in it is shown whole; a merged piece is shown as the names it lost. + + --ide twinBASIC.exe (default: $TB_IDE, else the newest + twinBASIC_IDE_BETA_* on the Desktop); its themes are the + .theme files in the themes folder beside it + -h, --help print this text and exit + +Exit codes: + 0 every piece of every shipped theme loads: the defect is not there + 1 at least one piece is dropped: the defect is there + 2 the probe could not do its job: a refused command line, no IDE, no themes + folder or no .theme file in it, or a crash`; + +const usageError = { format: (err) => `${err.message}\n${USAGE}` }; + +const { values } = withUsageError( + () => + parseCli(process.argv.slice(2), { + options: { + ide: { type: "string" }, + help: { type: "boolean", short: "h", default: false }, + }, + stopAt: ["help"], + }), + usageError, +); +if (values.help) printHelpAndExit(USAGE); + +/** + * The pieces applyThemeData cuts `text` into, each with the offset in `text` + * where it starts and the number of parts it gives. Comments are removed as the + * loader removes them (not replaced by spaces), and every character that stays + * keeps the offset it had in `text`, so that a piece can be placed in the file. + */ +function loaderPieces(text) { + const kept = []; + const at = []; + const withEnd = `${text}\r\n`; + const comment = /\/\*[\s\S]*?\*\//g; + let from = 0; + for (let m = comment.exec(withEnd); ; m = comment.exec(withEnd)) { + const to = m ? m.index : withEnd.length; + for (let i = from; i < to; i++) { + kept.push(withEnd[i]); + at.push(i); + } + if (!m) break; + from = m.index + m[0].length; + } + const stripped = kept.join(""); + + const pieces = []; + const cut = /; |;\t|;\n|;\r\n/g; + let start = 0; + for (let m = cut.exec(stripped); ; m = cut.exec(stripped)) { + const end = m ? m.index : stripped.length; + const raw = stripped.slice(start, end); + const flat = raw.replace(/[\r\n]/g, ""); + pieces.push({ offset: at[start], raw, flat, parts: flat.split(/: |:\t/).length }); + if (!m) break; + start = m.index + m[0].length; + } + return pieces; +} + +/** The line number, from 1, of the first character of `piece` that is not white space. */ +function lineOf(text, piece) { + let i = piece.offset; + while (i < text.length && /\s/.test(text[i])) i++; + return text.slice(0, i).split("\n").length; +} + +/** The names of the properties that start a line of `piece`, as the files lay them out. */ +function namesIn(piece) { + return [...piece.raw.matchAll(/(?:^|\n)[ \t]*([^\s:;]+)(?=:[ \t])/g)].map((m) => m[1]); +} + +/** What the loader lost with `piece`: the names of the properties run together in it, else the piece whole. */ +function lost(piece) { + const names = namesIn(piece); + if (names.length < 2) return `"${piece.flat.trim()}"`; + return `${names.join(" and ")} (run together with no ";" between them)`; +} + +/** How many properties `piece` costs: the ones run together in it, at least one. */ +function lostCount(piece) { + return Math.max(1, namesIn(piece).length); +} + +const ide = findIde(values.ide); +if (!ide || !statSync(ide, { throwIfNoEntry: false })?.isFile()) { + die( + 2, + (ide ? `no twinBASIC IDE at ${ide}: ` : "no twinBASIC IDE found: ") + + "pass --ide , set TB_IDE, or unpack a twinBASIC_IDE_BETA_ folder on your Desktop", + ); +} +const install = path.dirname(ide); +const folder = path.join(install, "themes"); +if (!statSync(folder, { throwIfNoEntry: false })?.isDirectory()) die(2, `no themes folder beside ${ide}: ${folder}`); +const files = readdirSync(folder) + .filter((name) => /\.theme$/i.test(name)) + .sort(); +if (!files.length) die(2, `no .theme file in ${folder}`); + +let dropped = 0; +let affected = 0; +for (const name of files) { + const text = readFileSync(path.join(folder, name), "utf8"); + const shown = `themes/${name}`; + let here = 0; + for (const piece of loaderPieces(text)) { + if (piece.parts === 2 || !piece.flat.trim()) continue; + here += lostCount(piece); + console.log(`${shown} line ${lineOf(text, piece)}: dropped ${lost(piece)}`); + } + dropped += here; + if (here) affected++; +} + +const build = `BETA ${buildNumber(ide) ?? "?"}`; +console.log( + dropped + ? `${dropped} ${dropped === 1 ? "property is" : "properties are"} lost in ${affected} of ${files.length} shipped themes (${build})` + : `no shipped theme loses a property (${build})`, +); +process.exit(dropped ? 1 : 0); diff --git a/scripts/shoot_docs.mjs b/scripts/shoot_docs.mjs index 06b546e5d..1daf35f42 100644 --- a/scripts/shoot_docs.mjs +++ b/scripts/shoot_docs.mjs @@ -58,6 +58,12 @@ // 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 +// 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 +// View menus with the code focused +// debugging the same with test/shots/debugging staged as well: the "Running and +// debugging" chapter's pictures of a paused program, run from the IDE // 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) @@ -67,6 +73,8 @@ // in no theme; the ListView's is of the project run from the IDE (F5) // codelens test/shots/codelens: a procedure run by its CodeLens bar, the editor above // the DEBUG CONSOLE +// memory test/shots/memory: Sub Main run to its Stop, and the MEMORY 1 pane showing +// a String's characters // package the sample built as a TWINPACK package, and fusion test/shots/fusion, whose // Fusion host the compiler builds as it loads: the DEBUG CONSOLE's lines, // the paths they name on a folder of the tool's own (PACKAGE_ROOT) @@ -237,7 +245,11 @@ forms and settings-webview2), and the CustomControls tutorial's pictures of a custom control's code and PROPERTIES (setup customcontrols), and the editor alone: the build select's greying, inline hints, a hover, a form's JSON (setups code, customcontrols and sample9), the whole window with its features named (setup -featuremap), a procedure run by its CodeLens bar (setup codelens), the DEBUG +featuremap), the editor's tabs, combo boxes, menus, IntelliSense, finding and DIAGNOSTICS +in the same project (setup code-chapter), the same project paused, stopped by an error +and running, with its panels, hovers, console and the Debug and Run menus (setup debugging), +a procedure run by its CodeLens bar (setup codelens), the MEMORY 1 +pane at a Stop, showing a String (setup memory), the DEBUG CONSOLE after a package build and a Fusion host build (setups package and fusion), Project Settings' packages from TWINSERV, signed out, downloaded, embedded and linked (setups package-server, settings-linked and package-check; they need the network), @@ -1274,6 +1286,41 @@ async function startFeatureMap(run) { return run.lane.open(src); } +// ---- code-chapter: FeatureTour, the Inventory project of the feature map, with +// test/shots/codechapter's files staged onto it (in the work folder only: the feature map does +// not move), for the pictures of the "Writing code" chapter, all of one project. Helpers is a +// module with an enumeration, a hexadecimal constant of the wrong width and a variable ReDim +// declares; Ev and Ev2 are classes with events, one file; Watcher a class with WithEvents fields +// of both, handling one event of the first. The project's warnings are set as a person sets them +// in Project Settings, so that DIAGNOSTICS has a hint and an information message as well as the +// feature map's warnings: TB0016 (ReDim declares a variable) a hint, TB0012 (a hexadecimal +// literal of the wrong width) information. The project compiles clean; the one error its +// pictures show is put in by the shot, in the page only. +const CODE_CHAPTER = path.join(REPO_ROOT, "test", "shots", "codechapter"); +const CODE_CHAPTER_WARNINGS = { errors: [], hints: ["TB0016"], ignored: [], info: ["TB0012"], warnings: [] }; + +// ---- debugging: the same project with test/shots/debugging staged onto it as well, for the +// "Running and debugging" chapter: a module, Checks, with the chapter's FillTable (a Subscript +// error on its fourth pass), a Sub that starts a short ping with Shell, and a Sub that waits a +// minute at most. It is a setup of its own, so that the Writing code chapter's pictures (whose +// combo boxes list the project's modules) do not change. +const DEBUGGING = path.join(REPO_ROOT, "test", "shots", "debugging"); + +async function startCodeChapter(run, { extra = null } = {}) { + run.step = "open"; + const what = [CODE_CHAPTER, ...(extra ? [extra] : [])].map((d) => path.relative(REPO_ROOT, d)).join(" and "); + say(run.name, `opening ${path.relative(REPO_ROOT, FEATUREMAP)} with ${what}`); + const src = path.join(run.work, extra ? "debugging-src" : "codechapter-src"); + cpSync(FEATUREMAP, src, { recursive: true }); + cpSync(CODE_CHAPTER, src, { recursive: true }); + if (extra) cpSync(extra, src, { recursive: true }); + const file = path.join(src, "Settings"); + const settings = JSON.parse(readFileSync(file, "utf8")); + settings["project.warnings"] = CODE_CHAPTER_WARNINGS; + writeFileSync(file, `${JSON.stringify(settings, null, "\t")}\n`); + return run.lane.open(src); +} + // ---- community: the demo with the compiler started on no licence key, the status bar's // COMMUNITY EDITION badge as the IDE draws it for a key that is not there (the Community // Edition), and its LIMITED services badge while a compiler starts.The IDE reads the key from its settings into @@ -1332,6 +1379,8 @@ const SETUPS = { code: { ports: 1, start: startCode, prepare: prepareSample }, sample9: { ports: 1, start: startSample9, prepare: prepareSample }, featuremap: { ports: 1, start: startFeatureMap, prepare: prepareSample }, + "code-chapter": { ports: 1, start: startCodeChapter, prepare: prepareSample }, + debugging: { ports: 1, start: (run) => startCodeChapter(run, { extra: DEBUGGING }), prepare: prepareSample }, project: { ports: 1, start: startProject, prepare: prepareProject }, community: { ports: 1, start: startProject, prepare: prepareCommunity }, splash: { ports: 1, start: startProject, prepare: prepareProject }, @@ -1348,6 +1397,7 @@ const SETUPS = { "no-project": { ports: 1, start: startNoProject, prepare: prepareNoProject }, programs: { ports: 1, start: startPrograms, prepare: preparePrograms }, codelens: { ports: 1, start: startCodeLens, prepare: prepareSample }, + memory: { ports: 1, start: startMemory, prepare: prepareSample }, package: { ports: 1, start: startPackage, prepare: prepareSample }, fusion: { ports: 1, start: startFusion, prepare: prepareSample }, "package-server": { ports: 1, start: startPackageServer, prepare: preparePackageServer }, @@ -1365,15 +1415,16 @@ const SETUPS = { // border box, so the clip is grown by it, which is also what rounds their // 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. -function menuShot(name, top, item = null, { subOnly = false, setup = "no-project", before = null } = {}) { +// `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. +function menuShot(name, top, item = null, { subOnly = false, setup = "no-project", before = null, after = null } = {}) { return { out: `IDE/Menu/Images/${name}.png`, setup, async take({ c }) { await resetUi(c); - if (before) await before(c); try { + if (before) await before(c); const drop = await openMenu(c, top, { close: !before }); const title = await rectOf(c, `#${TOP[top]}`); const sub = item ? await hoverItem(c, item) : null; @@ -1391,6 +1442,7 @@ function menuShot(name, top, item = null, { subOnly = false, setup = "no-project return await capture(c, name, snapOut(area), { keep }); } finally { await closeMenus(c); + if (after) await after(c); } }, }; @@ -3125,6 +3177,20 @@ const nameUserAsUserIn = (c, css) => })()`); const nameUserAsUser = (c) => nameUserAsUserIn(c, null); +// The lane stages the project with a build path in its temporary folder, and the dialog shows +// it: the template's own path instead, typed as a user would (input and change), so that the +// dialog holds it and does not put the real path back when it draws the row again, as it does +// now and then over the value nameUserAsUser sets; Cancel keeps nothing. capture refuses the +// picture if the real path shows all the same, in or out of the clip. For every shot of the +// dialog in which the row may be drawn. +const templateBuildPath = (c) => + c.evaluate(`(() => { + const i = document.querySelector(${JSON.stringify(`${settingPart("project.buildPath", "CONTENT")} input`)}); + i.value = "\${SourcePath}\\\\Build\\\\\${ProjectName}_\${Architecture}.\${FileExtension}"; + i.dispatchEvent(new Event("input", { bubbles: true })); + i.dispatchEvent(new Event("change", { bubbles: true })); +})()`); + // The dialog of Project > Project Settings (or Project > References, which filters its // list to the library references), picture `out` of it, Cancel to close it. `prepare(c)` // scrolls or switches it; `clipOf(box, c)` takes a part of the dialog. @@ -3165,17 +3231,7 @@ const settingsShots = [ }), settingsShot("Features/Images/569150839-9ffc87ac-250d-40a4-bb47-669b607ad76f.png", { async prepare(c) { - // The lane stages the project with a build path in its temporary folder, and the - // dialog shows it: the template's own path instead, typed as a user would (input and - // change), so that the dialog holds it and does not put the real path back when it - // draws the row again, as it does now and then; Cancel keeps nothing. capture refuses - // the picture if the real path shows all the same. - await c.evaluate(`(() => { - const i = document.querySelector(${JSON.stringify(`${settingPart("project.buildPath", "CONTENT")} input`)}); - i.value = "\${SourcePath}\\\\Build\\\\\${ProjectName}_\${Architecture}.\${FileExtension}"; - i.dispatchEvent(new Event("input", { bubbles: true })); - i.dispatchEvent(new Event("change", { bubbles: true })); -})()`); + await templateBuildPath(c); await scrollToSetting(c, "project.buildPath"); }, clipOf: (m) => ({ x: m.x, y: m.y, width: m.width, height: 460 }), @@ -3235,6 +3291,8 @@ const settingsShots = [ // the Compiler Warnings section, its heading to the fifteenth warning, TB0002's list drawn // open below its select and the two warnings Option Explicit is about ringed async prepare(c) { + // the build path row is outside the clip, but on the page the user-name check reads + await templateBuildPath(c); await c.evaluate(`(() => { const w = document.querySelector(".warningOptions"); w.style.height = "360px"; @@ -5316,174 +5374,2198 @@ async function takeFeatureMap({ c }) { } } -// The route a person takes: Standard EXE is selected on the New tab, and Open -// asks for its options. Cancel closes the dialog and creates nothing. Last -// of the setup, since the IDE has begun to start a project. -const newProjectOptionsShot = { - out: "Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png", - setup: "no-project", - take: ({ c }) => - inNewProject(c, async () => { - await clickInModal(c, "Open", ".msgBoxButton"); - if (!(await waitModal(c, "New Project Options", { timeout: 60000 }))) { - throw new Error("Open on Standard EXE did not show the New Project Options dialog"); - } - await dialogStill(c); - // the Project Name is selected: leave 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); -})()`); - try { - return await dialogShot(c, "New_Project_Options"); - } finally { - await closeModal(c, "Cancel"); - await sleep(1000); - } - }), +// ---- the "Writing code" chapter (setup code-chapter): FeatureTour with test/shots/codechapter + +const CHAPTER_FILE = (name) => `/FeatureTour/Sources/${name}`; +const CHAPTER_IMAGE = (name) => `IDE/Code/Images/${name}.png`; +// The editor's three navigation combo boxes, native selects named by their class, left to +// right, and the bar that holds them. +const COMBOS = ["codePanelDropDownComponent", "codePanelDropDownScope", "codePanelDropDownProcedure"]; +const COMBO = (n) => ({ css: `select.${COMBOS[n]}` }); +const COMBO_BAR = ".codePanelDropDownContainer"; +// 16 pixels past the right end of box 3, where a picture of the boxes and the code stops: the +// minimap beyond it is drawn a little differently according to what the editor showed before. +const comboEnd = async (c) => { + const r = await rectOf(c, "select.codePanelDropDownProcedure"); + return r.x + r.width + 16; }; +// What each combo box shows, closed. +const comboTexts = (c) => + c.evaluate( + `${JSON.stringify(COMBOS)}.map((k) => { const s = document.querySelector("select." + k); return s && s.selectedIndex >= 0 ? s.options[s.selectedIndex].text : null; })`, + ); -// About's last line reads the compiler's licence, which the IDE has only once a -// project has been loaded and its compiler has answered: so it is taken in the -// project setup. -const aboutShot = { - out: "IDE/Menu/Images/Menu_Help_About.png", - setup: "project", - async take({ c }) { - const ready = await waitFor( - c, - () => c.evaluate(`licenceIsSet === true && !/NOT READY/.test(compilerLicence.innerText)`), - { timeout: 60000, interval: 250 }, - ); - if (!ready) throw new Error("the compiler never answered the licence check, so About would say NOT READY"); - return inDialog(c, { command: "tbHelp_ShowAboutWindow", title: "About", close: "Close" }, () => - dialogShot(c, "Menu_Help_About"), - ); - }, -}; +// The line and column (1-based) of the `nth` line of the open file holding `text`, `offset` +// characters into it; throws when there is none. +async function codePlace(c, text, { nth = 1, offset = 0 } = {}) { + const at = await c.evaluate(`(() => { + const ls = editor.getModel().getLinesContent(); + let k = 0; + for (let i = 0; i < ls.length; i++) { + const j = ls[i].indexOf(${JSON.stringify(text)}); + if (j >= 0 && ++k === ${nth}) return { line: i + 1, column: j + 1 + ${offset} }; + } + return null; +})()`); + if (!at) throw new Error(`the open file has no line holding ${JSON.stringify(text)}`); + return at; +} -// A badge's tooltip is its plain HTML `title`, which WebView2 draws as a native Windows tooltip -// on a real mouse hover and in no other way: no capture holds it, and no mouse the tool can -// move reaches a private desktop (WIP.Screenshots.md, Special IDE states). So it is drawn in the page, as -// a replica of Windows 10's tooltip holding the badge's own title, read from the IDE: the -// owner's decision (2026-10-10). The look is calibrated against the pictures taken by hand in -// January: Segoe UI 9 pt (12 px) in #242424 on white, a 1 px #242424 border, 16 px lines, 7 px -// of padding at the sides, 3 above and 4 below, the box a whole number of pixels wide. It does -// not follow the IDE's theme, so the Light pass takes the dark pass's picture and keeps no file -// of its own. It is placed as Windows places a tooltip by the cursor, which is taken to be at -// the badge's middle: its left edge at the cursor, 21 px below it, or above the badge when it -// would not fit below (the status bar is at the bottom of the window), and inside the window. -const TOOLTIP_ID = "tbShotTooltip"; -const TOOLTIP_CSS = - "all:initial;position:fixed;z-index:2147483647;pointer-events:none;box-sizing:border-box;" + - "border:1px solid #242424;background:#fff;color:#242424;white-space:pre-line;" + - "font:12px/16px 'Segoe UI',sans-serif;padding:3px 7px 4px 7px;display:block"; +// The caret on `text` in the open file (see codePlace), the focus in the code, and the view +// scrolled so that `top` is the first line shown (1 by default). +async function caretOn(c, text, { nth = 1, offset = 0, top = 1 } = {}) { + const at = await codePlace(c, text, { nth, offset }); + await setCursor(c, at.line, at.column); + await c.evaluate(`editor.setScrollTop(editor.getTopForLineNumber(${top}))`); + await frames(c); + return at; +} -// Puts the replica of the tooltip of the element `sel` into the page; returns its text and -// its rectangle in CSS pixels. Throws when the element has no title. -async function tooltipOn(c, sel) { +// The box of the editor's code area (the Monaco editor: margin, code, minimap). +const editorBox = (c) => + c.evaluate(`(() => { + const r = editor.getDomNode().getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; +})()`); + +// The box of lines `from` to `to` of the open file as shown, the editor's width. +async function linesBox(c, from, to) { const r = await c.evaluate(`(() => { - const badge = document.querySelector(${JSON.stringify(sel)}); - if (!badge) return { error: "no such element" }; - const text = badge.title; - if (!text) return { error: "the element has no title" }; - document.getElementById(${JSON.stringify(TOOLTIP_ID)})?.remove(); - const e = document.createElement("div"); - e.id = ${JSON.stringify(TOOLTIP_ID)}; - e.style.cssText = ${JSON.stringify(TOOLTIP_CSS)}; - e.textContent = text; - document.body.appendChild(e); - e.style.left = "0px"; - e.style.top = "0px"; - e.style.width = Math.ceil(e.getBoundingClientRect().width) + "px"; - const t = e.getBoundingClientRect(); - const b = badge.getBoundingClientRect(); - const cursor = { x: b.x + b.width / 2, y: b.y + b.height / 2 }; - const left = Math.max(0, Math.min(window.innerWidth - t.width, Math.round(cursor.x))); - const below = Math.round(cursor.y) + 21; - const top = below + t.height <= window.innerHeight ? below : Math.round(b.y) - t.height - 2; - e.style.left = left + "px"; - e.style.top = top + "px"; - const f = e.getBoundingClientRect(); - return { text, rect: { x: f.x, y: f.y, width: f.width, height: f.height } }; - })()`); - if (r.error) throw new Error(`the tooltip of ${sel}: ${r.error}`); + const e = editor.getDomNode().getBoundingClientRect(); + const top = editor.getTopForLineNumber(${from}) - editor.getScrollTop(); + const bottom = editor.getTopForLineNumber(${to}) - editor.getScrollTop() + editor.getOption(monaco.editor.EditorOption.lineHeight); + return { x: e.x, y: e.y + top, width: e.width, height: bottom - top }; +})()`); return r; } -const tooltipOff = (c) => c.evaluate(`document.getElementById(${JSON.stringify(TOOLTIP_ID)})?.remove()`); +// Inventory open with the caret inside Add, so that the three combo boxes show the class, (general) +// and the procedure; the bar of combo boxes and the first lines of the class under it, each box +// numbered as the Editor page's table numbers it. +const codeCombosShot = { + out: CHAPTER_IMAGE("Combos"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Inventory.twin"]); + await caretOn(c, "ReDim Preserve items(count)", { offset: 0 }); + const want = ["Inventory", "(general)", "Add"]; + const shown = await waitFor( + c, + async () => { + const t = await comboTexts(c); + return t.every((s, i) => s?.startsWith(want[i])) && t; + }, + { timeout: 10000, interval: 100 }, + ); + if (!shown) throw new Error(`the combo boxes show ${JSON.stringify(await comboTexts(c))}, not ${want.join(", ")}`); + const bar = await rectOf(c, COMBO_BAR); + if (!bar) throw new Error("the editor shows no combo boxes"); + const code = await linesBox(c, 1, 11); + const area = { x: bar.x, y: bar.y, width: (await comboEnd(c)) - bar.x, height: code.y + code.height - bar.y }; + // each badge inside its box, short of the box's arrow + const annotate = [0, 1, 2].map((n) => ({ + type: "badge", + n: n + 1, + on: { of: COMBO(n), at: "right", dx: -44 }, + side: "above", + gap: -11, + })); + return annotatedClip(c, "Combos", area, annotate); + }, +}; -// The picture of the replica alone, as the January ones are. -async function tooltipShotTake(c, name, sel) { - const { text, rect } = await tooltipOn(c, sel); +// The editor alone in the window, every panel docked beside it out of the layout, in a page +// `width` wide; the default layout put back after `fn`. +async function editorAlone(c, width, fn, height = IDE_SIZE.height) { + await resetUi(c); + await closeFindWidget(c); + for (const id of DOCKED_BESIDE_EDITOR) await undock(c, id); try { - say(c.shot.name, ` the tooltip text: ${JSON.stringify(text)}`); - return await capture(c, name, snapOut(rect), { away: () => parkMouse(c) }); + return await atSize(c, width, height, async () => { + await settledBox(c, editorBox); + return fn(); + }); } finally { - await tooltipOff(c); + await defaultLayout(c); } } -// The status bar's services badge once the compiler's services have all answered. -const servicesBadgeReady = async (c) => { - await resetUi(c); - const ready = await waitFor( - c, - () => - c.evaluate( - `(() => { const e = document.getElementById("compilerStatusOuter"); return !!e && e.classList.contains("servicesOK") && e.innerText.includes("OPERATIONAL"); })()`, - ), - { timeout: 60000, interval: 250 }, +// Runs `fn` on the open file and then undoes every edit made since, in the page, so that the +// file is as it was opened and its tab shows no unsaved change; whatever box is open closed +// first. +async function editedThenUndone(c, fn) { + const version = await c.evaluate("editor.getModel().getAlternativeVersionId()"); + let out; + let failure = null; + try { + out = await fn(); + } catch (e) { + failure = e; + } + await pressKey(c, "Escape"); + await pressKey(c, "Escape"); + const back = await c.evaluate(`(() => { + const m = editor.getModel(); + for (let i = 0; i < 100 && m.getAlternativeVersionId() !== ${version} && m.canUndo(); i++) editor.trigger("shoot_docs", "undo"); + return m.getAlternativeVersionId() === ${version}; +})()`); + if (failure) throw failure; + if (!back) throw new Error("the edits made for the picture could not be undone"); + return out; +} + +// A new line put in after the `nth` line holding `after` in the open file, indented eight +// spaces, holding `text`; returns its number. +async function newLineAfter(c, after, text, { nth = 1 } = {}) { + const { line } = await codePlace(c, after, { nth }); + await c.evaluate( + `editor.executeEdits("shoot_docs", [{ range: new monaco.Range(${line + 1}, 1, ${line + 1}, 1), text: ${JSON.stringify(` ${text}\n`)} }])`, ); - if (!ready) throw new Error("the services badge never said OPERATIONAL"); -}; + return line + 1; +} -const servicesShot = { - out: "IDE/Images/Services_Operational.png", - setup: "project", - async take({ c }) { - await servicesBadgeReady(c); - return capture(c, "Services_Operational", snapOut(await rectOf(c, "#compilerStatusOuter")), { - away: () => parkMouse(c), - }); - }, -}; +// The signature-help box, as the page holds it: whether it is on and shown, its box, its +// signatures, and whether F1 has expanded it. +const SIGNATURE = `(() => { + const b = debugConsoleEntryIntellisenseBoxSignature; + const r = b.getBoundingClientRect(); + return { on: !!context.activeSignatureHelp.value, shown: r.width > 0 && r.height > 0, + box: { x: r.x, y: r.y, width: r.width, height: r.height }, + sigs: b.querySelectorAll(".sigHelpHeaderText").length, expanded: sigHelpIsExpanded }; +})()`; -// The tooltip of that badge, the four services each OPERATIONAL (a replica, see TOOLTIP_CSS). -const servicesTooltipShot = { - out: "IDE/Images/Services_Operational_Tooltip.png", - setup: "project", +// Signature help as it opens when `(` is typed after `call`, on a new line put in after the +// `nth` line holding `after` in Inventory, with the editor alone in a page wide enough for the +// whole box; with `expand`, after F1 too. The picture is the box and the code from the line +// above it down to the typed line; the help under the typed line (its datatype) is left out. +// The compiler underlines the call, which has no arguments yet, so the picture waits for that +// underline: it is there or not according to when the picture is taken, else. +function signatureShot(name, { after, nth = 1, call, sigs, expand = false }) { + return { + out: CHAPTER_IMAGE(name), + setup: "code-chapter", + take: ({ c }) => + editorAlone(c, 1600, async () => { + await openFile(c, CHAPTER_FILE("Inventory.twin")); + return editedThenUndone(c, async () => { + const line = await newLineAfter(c, after, call, { nth }); + await setCursor(c, line, 9 + call.length); + await frames(c); + await pressKey(c, "("); + const open = await waitFor( + c, + async () => { + const s = await c.evaluate(SIGNATURE); + return s.on && s.shown && s.sigs === sigs && s; + }, + { timeout: 10000, interval: 100 }, + ); + if (!open) throw new Error(`typing ( after ${call} showed no signature help with ${sigs} signatures`); + const marked = await waitFor( + c, + () => + c.evaluate( + `monaco.editor.getModelMarkers({ resource: editor.getModel().uri }).some((m) => m.startLineNumber === ${line})`, + ), + { timeout: 15000, interval: 100 }, + ); + if (!marked) throw new Error(`the compiler never marked the call on line ${line}`); + // F1 expands or collapses the box, and the page keeps the state for the next box it shows + const f1 = async (want) => { + if ((await c.evaluate(SIGNATURE)).expanded === want) return; + await pressKey(c, "F1"); + const now = await waitFor(c, async () => (await c.evaluate(SIGNATURE)).expanded === want, { + timeout: 5000, + interval: 100, + }); + if (!now) throw new Error(`F1 did not ${want ? "expand" : "collapse"} the signature help`); + }; + await f1(expand); + const { box } = await settledBox(c, () => c.evaluate(SIGNATURE)); + const lineBox = await linesBox(c, line - 1, line); + const ed = await editorBox(c); + // from the top of the line 6 pixels above the box, so that its edge has room + const crossed = await c.evaluate( + `editor.getTargetAtClientPoint(${ed.x + 8}, ${box.y - 6})?.position?.lineNumber ?? null`, + ); + if (!crossed) throw new Error("no line of code lies under the signature help's top edge"); + const top = (await linesBox(c, crossed, crossed)).y; + const area = { + x: ed.x, + y: top, + width: box.x + box.width + 12 - ed.x, + height: lineBox.y + lineBox.height - top, + }; + try { + return await capture(c, name, snapOut(area, 1600, IDE_SIZE.height), { away: () => parkMouse(c) }); + } finally { + await f1(false); + } + }); + }), + }; +} + +// The hover over a call to a procedure of the project: Total, in MainModule's Main, a function +// of the Inventory class. The picture is the popup and the line under it, from the editor's left. +const codeHoverShot = { + out: CHAPTER_IMAGE("Hover"), + setup: "code-chapter", async take({ c }) { - await servicesBadgeReady(c); - return tooltipShotTake(c, "Services_Operational_Tooltip", "#compilerStatusOuter"); + await chapterTabs(c, ["MainModule.twin"]); + const at = await codePlace(c, "stock.Total(", { offset: "stock.".length + 2 }); + await c.evaluate("editor.setScrollTop(0)"); + let shown = false; + for (let attempt = 0; attempt < 3 && !shown; attempt++) { + await restMouse(c, at.line, at.column); + shown = await waitFor(c, async () => (await hoverText(c))?.includes("Inventory") && true, { timeout: 6000 }); + } + if (!shown) throw new Error(`the hover on Total showed nothing naming Inventory: ${await hoverText(c)}`); + try { + const popup = await settledBox(c, () => + c.evaluate(`(() => { + const r = [...document.querySelectorAll(".monaco-hover")].find((e) => !e.classList.contains("hidden") && e.getBoundingClientRect().height > 0).getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; +})()`), + ); + const lineBox = await linesBox(c, at.line, at.line); + const ed = await editorBox(c); + const top = Math.min(popup.y, lineBox.y) - 8; + const bottom = Math.max(popup.y + popup.height, lineBox.y + lineBox.height) + 8; + const area = { x: ed.x, y: top, width: popup.x + popup.width + 12 - ed.x, height: bottom - top }; + // the mouse stays on Total: the popup goes when it leaves + return await capture(c, "Hover", snapOut(area)); + } finally { + await mouseAway(c); + } }, }; -// The services badge with no compiler connected, as the IDE starts with no project. -const unavailableBadgeReady = async (c) => { - await resetUi(c); - const unavailable = await c.evaluate( - `!compilerStatusOuter.classList.contains("servicesOK") && !compilerStatusOuter.classList.contains("servicesPARTIAL") && /UNAVAILABLE/.test(compilerStatus.innerText)`, - ); - if (!unavailable) throw new Error("the services badge does not say UNAVAILABLE"); +// Inventory's Total with the inline code hints on (Always show IDE Inline Code Hints, set in the +// page only, as for flags-attribute): each End line, and Next, followed by the hint the compiler +// sends for it. The picture is the function, from the editor's left to past the longest hint. +const codeHintsShot = { + out: CHAPTER_IMAGE("Hints"), + setup: "code-chapter", + take: ({ c }) => editorAlone(c, IDE_SIZE.width, () => takeCodeHints(c)), }; +async function takeCodeHints(c) { + await openFile(c, CHAPTER_FILE("Inventory.twin")); + const first = (await codePlace(c, "Public Function Total")).line; + const last = (await codePlace(c, "Total = subtotal")).line + 1; + await caretOn(c, "Public Function Total", { top: first - 1 }); + const hints = (on) => c.evaluate(`liveIDEOptions.${HINTS_OPTION} = ${on}; changedIdeOptions();`); + const ends = `[...document.querySelectorAll(".inlineDecoration")].map((e) => e.getBoundingClientRect()).filter((r) => r.width)`; + try { + await hints(true); + const drawn = await waitFor( + c, + () => + c.evaluate( + `[...document.querySelectorAll(".inlineDecoration")].some((e) => getComputedStyle(e, "::after").content.includes("Total") && getComputedStyle(e, "::after").display !== "none")`, + ), + { timeout: 10000, interval: 100 }, + ); + if (!drawn) throw new Error("the editor drew no inline hint after End Function"); + const box = await linesBox(c, first, last); + // past the longest hint and the longest line + const right = await settledBox(c, () => + c.evaluate(`(() => { + const m = editor.getModel(); + const left = editor.getDomNode().getBoundingClientRect().x; + const lines = []; + for (let n = ${first}; n <= ${last}; n++) lines.push(left + editor.getScrolledVisiblePosition({ lineNumber: n, column: m.getLineMaxColumn(n) }).left); + return Math.max(...lines, ...${ends}.filter((r) => r.y >= ${box.y} && r.y < ${box.y + box.height}).map((r) => r.right)); +})()`), + ); + const ed = await editorBox(c); + const area = { x: ed.x, y: box.y - 4, width: right + 24 - ed.x, height: box.height + 4 }; + return await capture(c, "Hints", snapOut(area), { away: () => parkMouse(c) }); + } finally { + await hints(false); + } +} -// Its tooltip, each of the four services Not initialized (they read Disconnected only for 0.1 s -// between one compiler and the next), is the badge's own title, drawn as a replica. -const unavailableTooltipShot = { - out: "IDE/Images/Services_Unavailable_Tooltip.png", - setup: "no-project", - async take({ c }) { - await unavailableBadgeReady(c); - return tooltipShotTake(c, "Services_Unavailable_Tooltip", "#compilerStatusOuter"); - }, -}; +// A right-click at a point of the page, as a person makes it: the mouse moved there first. +async function rightClickAt(c, x, y) { + await mouseMove(c, x, y); + await c.send("Input.dispatchMouseEvent", { type: "mousePressed", x, y, button: "right", buttons: 2, clickCount: 1 }); + await c.send("Input.dispatchMouseEvent", { type: "mouseReleased", x, y, button: "right", buttons: 0, clickCount: 1 }); +} -const unavailableShot = { - out: "IDE/Images/Services_Unavailable.png", - setup: "no-project", +// The chapter's files open as tabs, in this order, and no other tab. +// Monaco's find widget closed, as Esc in it closes it (the editor is one for every file, so a +// widget left open shows over the next file's code), and its marks with it. +async function closeFindWidget(c) { + const open = `!!editor.getDomNode().querySelector(".find-widget.visible")`; + if (!(await c.evaluate(open))) return; + await c.evaluate(`editor.getContribution("editor.contrib.findController").closeFindWidget()`); + if (!(await waitFor(c, async () => !(await c.evaluate(open)), { timeout: 3000, interval: 50 }))) { + throw new Error("the find widget would not close"); + } + await frames(c); +} + +// With `layout`, the default layout first: a picture of DIAGNOSTICS leaves it floated and hidden. +async function chapterTabs(c, names, { layout = true } = {}) { + await resetUi(c); + await closeFindWidget(c); + if (layout) await defaultLayout(c); + await closeTabs(c, [], { discard: true }); + for (const n of names) await openFile(c, CHAPTER_FILE(n)); + const shown = (await editorTabs(c)).map((t) => t.name); + if (shown.join() !== names.join()) + throw new Error(`the editor's tabs are ${shown.join(", ")}, not ${names.join(", ")}`); +} + +const TAB = (name) => ({ css: "#codePanelTabs > .tabItem", text: name }); + +// The tab strip with four tabs in four states, each named by a label under it with an arrow up +// to it: Types as it was opened; Watcher with a change not saved (a space put in at the end of +// its first line, in the page, and undone after), which puts a dot in place of the close button; +// Inventory tinted by its warnings; and Ev opened by one click in the Project Explorer with +// File Explorer: Single-Click Preview on (set in the page only), a preview tab in italics. +const codeTabsStatesShot = { + out: CHAPTER_IMAGE("Tabs_States"), + setup: "code-chapter", async take({ c }) { - await unavailableBadgeReady(c); + await chapterTabs(c, ["Types.twin", "Inventory.twin"]); + // The page tints a tab when the compiler sends the file's diagnostics while the tab is open, + // not when a tab opens on a file the compiler has reported already: so Inventory is changed + // and the change undone, as a person editing it would, and the compiler reports it again. + const tinted = () => + c.evaluate( + `[...document.querySelectorAll("#codePanelTabs > .tabItem")].some((e) => e.querySelector(".tabItemText").textContent.trim() === "Inventory.twin" && e.classList.contains("warning"))`, + ); + await editedThenUndone(c, async () => { + await c.evaluate( + `editor.executeEdits("shoot_docs", [{ range: new monaco.Range(1, editor.getModel().getLineMaxColumn(1), 1, editor.getModel().getLineMaxColumn(1)), text: " " }])`, + ); + if (!(await waitFor(c, tinted, { timeout: 15000, interval: 100 }))) { + throw new Error("the compiler's diagnostics for Inventory did not tint its tab"); + } + }); + await openFile(c, CHAPTER_FILE("Watcher.twin")); + const was = await c.evaluate("liveIDEOptions.previewDocs"); + try { + await c.evaluate("liveIDEOptions.previewDocs = true; changedIdeOptions();"); + // the change goes with the tab, closed with Discard Changes after the picture + await c.evaluate( + `editor.executeEdits("shoot_docs", [{ range: new monaco.Range(1, editor.getModel().getLineMaxColumn(1), 1, editor.getModel().getLineMaxColumn(1)), text: " " }])`, + ); + await peOpen(c, "Sources", true); + await peSelect(c, "Ev.twin"); + const states = await waitFor( + c, + () => + c.evaluate(`(() => { + const tabs = [...document.querySelectorAll("#codePanelTabs > .tabItem")]; + const t = (n) => tabs.find((e) => e.querySelector(".tabItemText").textContent.trim() === n); + const ok = !!t("Ev.twin")?.querySelector(".tabItemPreview") && + t("Inventory.twin")?.classList.contains("warning") && + !!t("Watcher.twin")?.querySelector(".circleIconSmall") && + !t("Types.twin")?.classList.contains("warning") && !t("Types.twin")?.classList.contains("error"); + return ok ? tabs.map((e) => e.className) : null; +})()`), + { timeout: 10000, interval: 100 }, + ); + if (!states) { + const now = await c.evaluate( + `[...document.querySelectorAll("#codePanelTabs > .tabItem")].map((e) => e.className + " / " + e.innerHTML.replace(/\\s+/g, " ").slice(0, 300))`, + ); + throw new Error(`the four tabs never showed their four states: ${JSON.stringify(now)}`); + } + await mouseAway(c); + const strip = await rectOf(c, "#codePanelTabs"); + const tabs = [ + ["Types.twin", "Normal"], + ["Inventory.twin", "Has warnings"], + ["Watcher.twin", "Unsaved changes"], + ["Ev.twin", "Preview tab"], + ]; + // the labels in two rows, so that two side by side never touch + const annotate = tabs.flatMap(([name, text], i) => { + const gap = i % 2 ? 78 : 36; + return [ + { type: "label", text, on: TAB(name), side: "below", gap }, + { type: "arrow", from: { of: TAB(name), at: "bottom", dy: gap - 2 }, to: TAB(name) }, + ]; + }); + const last = (await editorTabs(c)).at(-1).tab; + const area = { x: strip.x, y: strip.y, width: last.x + last.width + 40 - strip.x, height: strip.height }; + // the strip and the labels on the strip's own ground, the code under it left out + const ground = await c.evaluate(`(() => { + for (let e = document.getElementById("codePanelTabs"); e; e = e.parentElement) { + const k = getComputedStyle(e).backgroundColor; + if (!/^rgba\\(.*, 0\\)$/.test(k) && k !== "transparent") return k; + } + return null; +})()`); + if (!ground) throw new Error("the tab strip has no background colour"); + return await annotatedClip(c, "Tabs_States", area, annotate, { + keep: ["#codePanelTabs", `#${LAYER_ID}`], + solid: ground, + }); + } finally { + await c.evaluate(`liveIDEOptions.previewDocs = ${JSON.stringify(was)}; changedIdeOptions();`); + await closeTabs(c, [], { discard: true }); + } + }, +}; + +// The right-click menu of a tab, open over the editor: the tab strip and the menu. +const codeTabMenuShot = { + out: CHAPTER_IMAGE("Tab_Menu"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Types.twin", "Inventory.twin", "Helpers.twin"]); + // the active tab, at its lower right corner, so that the menu opens under it and covers no + // tab's name + const tab = (await editorTabs(c)).find((t) => t.name === "Helpers.twin").tab; + try { + await rightClickAt(c, tab.x + tab.width - 6, tab.y + tab.height - 3); + if (!(await menuOpened(c))) throw new Error("a right-click on a tab opened no menu"); + const menu = await rectOf(c, "#contextMenu"); + const strip = await rectOf(c, "#codePanelTabs"); + // the strip's tabs and the menu, from the strip's left to past the menu, short of the minimap + // (see comboEnd) + const area = grow(union(strip, menu), 8); + area.x = strip.x; + area.width = menu.x + menu.width + 40 - strip.x; + return await capture(c, "Tab_Menu", snapOut(area), { away: () => parkMouse(c) }); + } finally { + await closeMenus(c); + await closeTabs(c, [], { discard: true }); + } + }, +}; + +// The editor's right-click menu, opened over Twice's Return line in Helpers, with its Code Editor +// Options submenu open: from the line above the one clicked, the code under the menu and the +// submenu, with the editor alone in the window so that both open over code. The submenu opens +// level with its item, near the menu's foot, and hangs below it: the page is tall enough that it +// still ends over the code. +const CONTEXT_MENU_HEIGHT = 1040; +const codeContextMenuShot = { + out: CHAPTER_IMAGE("Context_Menu"), + setup: "code-chapter", + take: ({ c }) => editorAlone(c, IDE_SIZE.width, () => takeContextMenu(c), CONTEXT_MENU_HEIGHT), +}; +async function takeContextMenu(c) { + await chapterTabs(c, ["Helpers.twin"]); + // the clicked line second in the view, so that the menu and its submenu open over code + const { line: returnLine } = await codePlace(c, "Return n * 2"); + const at = await caretOn(c, "Return n * 2", { offset: 4, top: returnLine - 1 }); + try { + const line = await linesBox(c, at.line, at.line); + const p = await c.evaluate(`(() => { + const e = editor.getDomNode().getBoundingClientRect(); + const q = editor.getScrolledVisiblePosition({ lineNumber: ${at.line}, column: ${at.column} }); + return { x: e.x + q.left, y: e.y + q.top + q.height / 2 }; +})()`); + await rightClickAt(c, p.x, p.y); + if (!(await menuOpened(c))) throw new Error("a right-click in the code opened no menu"); + const sub = await hoverItem(c, "Code Editor Options"); + const menu = await rectOf(c, "#contextMenu"); + const above = { ...line, y: line.y - line.height, width: menu.x - line.x }; + const area = grow(union(union(menu, sub), above), 8); + area.x = line.x; + area.y = above.y; + area.height = Math.max(menu.y + menu.height, sub.y + sub.height) + 8 - above.y; + return await capture(c, "Context_Menu", snapOut(area, IDE_SIZE.width, CONTEXT_MENU_HEIGHT)); + } finally { + await closeMenus(c); + } +} + +// Monaco's command palette, opened by the command of the right-click menu's Monaco Command +// Palette, its input empty: the palette and the top of the editor round it. +const codePaletteShot = { + out: CHAPTER_IMAGE("Command_Palette"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Helpers.twin"]); + await caretOn(c, "Return n * 2", { offset: 4 }); + const widget = `[...document.querySelectorAll(".quick-input-widget")].find((e) => e.getBoundingClientRect().height > 0 && getComputedStyle(e).display !== "none")`; + try { + await command(c, "tbEditor_ShowMonacoCommandPalette"); + const box = await waitFor( + c, + () => + c.evaluate(`(() => { + const w = ${widget}; + if (!w || !w.querySelectorAll(".monaco-list-row").length) return null; + const r = w.getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; +})()`), + { timeout: 5000, interval: 100 }, + ); + if (!box) throw new Error("the command palette did not open with its list"); + const settled = await settledBox(c, () => + c.evaluate( + `(() => { const r = ${widget}.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; })()`, + ), + ); + const ed = await editorBox(c); + // from the editor's left to past the palette, short of the minimap (see comboEnd) + const area = { + x: ed.x, + y: ed.y, + width: settled.x + settled.width + 24 - ed.x, + height: settled.y + settled.height + 16 - ed.y, + }; + return await capture(c, "Command_Palette", snapOut(area), { away: () => parkMouse(c) }); + } finally { + await pressKey(c, "Escape"); + await waitFor(c, async () => !(await c.evaluate(`!!(${widget})`)), { timeout: 3000, interval: 50 }); + } + }, +}; + +// Watcher open with the caret in src_Changed, so that box 2 shows src and box 3 the event the +// handler is for; box 3's list, which the page fills on a real press and an OS window draws, drawn +// open as a replica below it: Changed [src_Changed] in bold above Reset. The bar and the class's +// code under it. +const codeComboListsShot = { + out: CHAPTER_IMAGE("Combo_Lists"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Watcher.twin"]); + await caretOn(c, "Count = Count + Twice", { offset: 0 }); + const want = ["Watcher", "src", "Changed [src_Changed]"]; + const shown = await waitFor( + c, + async () => { + const t = await comboTexts(c); + return t.join("|") === want.join("|") && t; + }, + { timeout: 10000, interval: 100 }, + ); + if (!shown) throw new Error(`the combo boxes show ${JSON.stringify(await comboTexts(c))}, not ${want.join(", ")}`); + const box = await rectOf(c, "select.codePanelDropDownProcedure"); + const count = () => + c.evaluate( + `[...document.querySelector("select.codePanelDropDownProcedure").options].filter((o) => !o.hidden && o.text).length`, + ); + try { + // a real press fills the list, and opens the OS's, which Escape closes + await clickAt(c, box.x + box.width / 2, box.y + box.height / 2); + const filled = await waitFor(c, async () => (await count()) >= 2, { timeout: 5000, interval: 100 }); + await pressKey(c, "Escape"); + if (!filled) throw new Error("a press on box 3 did not fill its list"); + const bar = await rectOf(c, COMBO_BAR); + const code = await linesBox(c, 1, 15); + const area = { x: bar.x, y: bar.y, width: (await comboEnd(c)) - bar.x, height: code.y + code.height - bar.y }; + return await listClip(c, "Combo_Lists", area, [{ type: "list", select: COMBO(2) }], { margin: 0 }); + } finally { + await caretOn(c, "Count = Count + Twice", { offset: 0 }); + } + }, +}; + +// The Edit and View menus with the code editor focused (the caret in Helpers), as cut-outs. +const focusCode = async (c) => { + await chapterTabs(c, ["Helpers.twin"]); + await caretOn(c, "Return n * 2", { offset: 4 }); + if (!(await c.evaluate("editor.hasTextFocus()"))) throw new Error("the code editor does not have the focus"); +}; + +// ---- the EDITOR panel, its parts named + +// The EDITOR panel of the default layout on its own (a cut-out on the window's ground), Helpers +// open with Types and Inventory as tabs beside it and the caret in Twice, the fold arrows shown +// always (set in the page only, as the feature map sets them), and its parts named by labels in +// the space the panels round it leave: the header's ... button from above, the Tabs List button, +// the combo boxes and the minimap from the right, the tab strip and the margin from the left. +const EDITOR_PANEL_MARK = "data-tb-shot-editor"; +const codeEditorPanelShot = { + out: CHAPTER_IMAGE("Editor_Panel"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Types.twin", "Inventory.twin", "Helpers.twin"]); + await caretOn(c, "Return n * 2", { offset: 4 }); + const options = { hideCodeEditorFolding: false, codeFoldingAlwaysVisible: true }; + const keys = Object.keys(options); + const was = JSON.parse( + await c.evaluate( + `JSON.stringify(Object.fromEntries(${JSON.stringify(keys)}.map((k) => [k, liveIDEOptions[k] === undefined ? null : liveIDEOptions[k]])))`, + ), + ); + try { + await c.evaluate(`Object.assign(liveIDEOptions, ${JSON.stringify(options)}); changedIdeOptions();`); + // the panel is marked for the cut-out, an attribute that draws nothing + const panel = await c.evaluate(`(() => { + const h = [...document.querySelectorAll(".sectionHeaderInner")].find((e) => e.textContent === "EDITOR 1"); + const p = h.closest(".toolWindowContainer"); + p.setAttribute(${JSON.stringify(EDITOR_PANEL_MARK)}, ""); + const r = p.getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; +})()`); + const shown = await waitFor(c, () => c.evaluate(`document.querySelectorAll(".monaco-editor .cldr").length > 0`), { + timeout: 5000, + interval: 100, + }); + if (!shown) throw new Error("the editor shows no fold arrows"); + const OPTIONS = { css: `[${EDITOR_PANEL_MARK}] .ellipsesIcon2` }; + const TABS_LIST = { css: TABS_BUTTON }; + const MINIMAP = { css: `[${EDITOR_PANEL_MARK}] .monaco-editor .minimap` }; + const GUTTER = { css: `[${EDITOR_PANEL_MARK}] .monaco-editor .margin` }; + const at = await resolveAnchors(c, { + options: OPTIONS, + tabsList: TABS_LIST, + box3: COMBO(2), + minimap: MINIMAP, + gutter: GUTTER, + tab: TAB("Types.twin"), + }); + const right = panel.x + panel.width + 44; + const left = panel.x - 44; + const point = (x, y) => ({ rect: { x, y, width: 0, height: 0 } }); + const mid = (r) => r.y + r.height / 2; + const marginY = mid(at.gutter) - 60; + const annotate = [ + { type: "label", text: "Options", on: { of: OPTIONS, at: "top", dy: -44 }, side: "above", gap: 0 }, + { type: "arrow", from: { of: OPTIONS, at: "top", dy: -42 }, to: OPTIONS }, + { type: "label", text: "Tabs List", on: point(right, mid(at.tabsList)), side: "right", gap: 0 }, + { type: "arrow", from: point(right - 4, mid(at.tabsList)), to: TABS_LIST }, + { type: "label", text: "Combo boxes", on: point(right, mid(at.box3)), side: "right", gap: 0 }, + { type: "arrow", from: point(right - 4, mid(at.box3)), to: COMBO(2) }, + { type: "label", text: "Minimap", on: point(right, at.minimap.y + 60), side: "right", gap: 0 }, + { + type: "arrow", + from: point(right - 4, at.minimap.y + 60), + to: point(at.minimap.x + at.minimap.width - 30, at.minimap.y + 60), + }, + { type: "label", text: "Tab strip", on: point(left, mid(at.tab)), side: "left", gap: 0 }, + { type: "arrow", from: point(left + 4, mid(at.tab)), to: TAB("Types.twin") }, + { type: "label", text: "Margin", on: point(left, marginY), side: "left", gap: 0 }, + { type: "arrow", from: point(left + 4, marginY), to: point(at.gutter.x + 24, marginY) }, + ]; + const ground = await c.evaluate("getComputedStyle(document.body).backgroundColor"); + return await annotatedClip(c, "Editor_Panel", panel, annotate, { + keep: [`[${EDITOR_PANEL_MARK}]`, `#${LAYER_ID}`], + solid: ground, + }); + } finally { + await c.evaluate(`(() => { + document.querySelector("[${EDITOR_PANEL_MARK}]")?.removeAttribute(${JSON.stringify(EDITOR_PANEL_MARK)}); + const was = ${JSON.stringify(was)}; + for (const k of Object.keys(was)) { + if (was[k] === null) delete liveIDEOptions[k]; + else liveIDEOptions[k] = was[k]; + } + changedIdeOptions(); +})()`); + } + }, +}; + +// ---- Paste As Comment: before and after + +// The page's clipboard calls answered from a text of the tool's own, so that the IDE never reads +// or writes the host's clipboard; put back by REAL_CLIPBOARD. +const FAKE_CLIPBOARD = (text) => `(() => { + if (!window.tbRealRequest) { + window.tbRealRequest = debugSocket.request; + window.tbRealReadText = navigator.clipboard.readText; + window.tbRealWriteText = navigator.clipboard.writeText; + } + const real = window.tbRealRequest.bind(debugSocket); + const clip = { text: ${JSON.stringify(text)} }; + debugSocket.request = function (method, params, cb, ...rest) { + if (method === "copyClipboardIDE") { clip.text = params.text; cb && cb({ body: {} }); return; } + if (method === "readClipboardIDE") { cb && cb({ body: { text: clip.text, extraData: undefined } }); return; } + return real(method, params, cb, ...rest); + }; + navigator.clipboard.readText = () => Promise.resolve(clip.text); + navigator.clipboard.writeText = () => Promise.resolve(); +})()`; +const REAL_CLIPBOARD = `(() => { + if (!window.tbRealRequest) return; + debugSocket.request = window.tbRealRequest; + navigator.clipboard.readText = window.tbRealReadText; + navigator.clipboard.writeText = window.tbRealWriteText; + delete window.tbRealRequest; +})()`; +// Three lines of text, the second blank, as another program would put them on the clipboard. +const PASTED = "Debug.Print Twice(6)\r\n\r\nDebug.Print Twice(7)"; + +// Helpers' Third with a blank line put in under its Debug.Print and the caret on it, and the same +// Sub after Paste As Comment of PASTED there, side by side with an arrow from one to the other. +const codePasteAsCommentShot = { + out: CHAPTER_IMAGE("Paste_As_Comment"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Helpers.twin"]); + const { reference, prefer } = c.shot; + Object.assign(c.shot, { reference: null, prefer: null }); + let made; + try { + made = await editedThenUndone(c, async () => { + const line = await newLineAfter(c, "Debug.Print Twice(5)", ""); + await setCursor(c, line, 9); + const SUB = { code: "Public Sub Third()" }; + const before = await codeLines(c, "Paste_As_Comment-before", "Public Sub Third()", "End Sub", { + anchors: { before: SUB }, + }); + await c.evaluate(FAKE_CLIPBOARD(PASTED)); + try { + await setCursor(c, line, 9); + await command(c, "tbEditor_ClipboardPasteAsComment"); + const pasted = await waitFor( + c, + () => + c.evaluate( + `editor.getModel().getLinesContent().filter((l) => l.includes("' Debug.Print Twice(")).length === 2`, + ), + { timeout: 5000, interval: 100 }, + ); + if (!pasted) throw new Error("Paste As Comment put in no commented lines"); + } finally { + await c.evaluate(REAL_CLIPBOARD); + } + await pressKey(c, "Escape"); + const after = await codeLines(c, "Paste_As_Comment-after", "Public Sub Third()", "End Sub", { + anchors: { after: SUB }, + }); + return [before, { ...after, column: 1 }]; + }); + } finally { + Object.assign(c.shot, { reference, prefer }); + } + try { + const { box } = await composite(c, made, [ + { + type: "arrow", + from: { of: { ref: "before" }, at: "right", dx: 10 }, + to: { of: { ref: "after" }, at: "left", dx: -4 }, + }, + ]); + return await capture(c, "Paste_As_Comment", snapOut(box), { away: () => parkMouse(c) }); + } finally { + await uncomposite(c); + } + }, +}; + +// ---- Finding + +const FIND = panelSel("FIND REPLACE"); + +// Find In Project from the end of Helpers, Find What typed as a person types it, and Find Next +// pressed once: the match after Helpers' last Twice is in Watcher, which the panel opens in a +// preview tab with the match selected. The panel floating where it opens, and the editor round it. +const codeFindProjectShot = { + out: CHAPTER_IMAGE("Find_Project"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Helpers.twin"]); + await caretOn(c, "End Module"); + const state = () => + c.evaluate(`(() => { const t = findReplacePanel._this; return { + what: t.findWhatInput.value, project: t.findScopeProjectButton.checked, packages: !t.findInPackagesInput.disabled, + options: [t.findWholeWordGroupInput, t.findMatchCaseGroupInput, t.findUsePatternMatchingInput, t.findUseRegExpInput].some((e) => e.checked) }; })()`); + const click = async (expr) => { + const r = await c.evaluate( + `(() => { const e = ${expr}; const q = e && e.getBoundingClientRect(); return q && q.width ? { x: q.x + q.width / 2, y: q.y + q.height / 2 } : null; })()`, + ); + if (!r) throw new Error(`nothing to click: ${expr}`); + await clickAt(c, r.x, r.y); + }; + try { + await pressKey(c, "f", { ctrl: true, shift: true }); + if (!(await waitFor(c, () => rectOf(c, FIND), { timeout: 5000, interval: 100 }))) { + throw new Error("Ctrl+Shift+F did not show the Find / Replace panel"); + } + if (!(await state()).project) await click("findReplacePanel._this.findScopeProjectButton"); + await click("findReplacePanel._this.findWhatInput"); + await pressKey(c, "a", { ctrl: true }); + await typeText(c, "Twice"); + const s = await state(); + if (s.what !== "Twice" || !s.project || !s.packages || s.options) { + throw new Error(`the Find / Replace panel is not as the picture needs it: ${JSON.stringify(s)}`); + } + // The search starts at the top of the current file: Find Next is pressed until the match it + // selects is in Watcher, a file after Helpers in the project's order. + const at = () => + c.evaluate(`(() => { + const tab = document.querySelector("#codePanelTabs > .tabItemSelected .tabItemText")?.textContent.trim(); + const p = editor.getPosition(); + return { tab, line: p.lineNumber, column: p.column, selected: editor.getModel().getValueInRange(editor.getSelection()) }; +})()`); + let found = false; + for (let press = 0; press < 6 && !found; press++) { + const was = JSON.stringify(await at()); + await click( + `[...document.querySelectorAll(${JSON.stringify(`${FIND} *`)})].find((b) => b.children.length === 0 && b.textContent.trim() === "Find Next")`, + ); + const now = await waitFor( + c, + async () => { + const a = await at(); + return JSON.stringify(a) !== was && a.selected === "Twice" && a; + }, + { timeout: 10000, interval: 100 }, + ); + if (!now) throw new Error(`Find Next selected no further match: ${was}`); + found = now.tab === "Watcher.twin"; + } + if (!found) throw new Error("Find Next never selected Twice in Watcher"); + await frames(c); + const ed = await editorPanelRect(c); + // the floating panel moved inside the editor panel, as a person drags it there, so that the + // picture holds nothing of the panels beside the editor + await c.evaluate(`document.querySelector(${JSON.stringify(FIND)}).style.left = "${ed.x + 24}px"`); + await frames(c); + const panel = await rectOf(c, FIND); + // from the editor panel's left to past the panel, short of the minimap (see comboEnd) + const area = { + x: ed.x, + y: ed.y, + width: panel.x + panel.width + 24 - ed.x, + height: (await linesBox(c, 15, 15)).y + 4 - ed.y, + }; + return await capture(c, "Find_Project", snapOut(area), { away: () => parkMouse(c) }); + } finally { + await pressKey(c, "Escape"); + await closePanelIfShown(c, "FIND REPLACE"); + await closeTabs(c, [], { discard: true }); + } + }, +}; + +// Quick Replace (Alt+H) in Helpers: Monaco's find widget at the top right of the editor, its +// replace row shown, Twice typed as the text to find, the count of matches, and the matches marked +// in the code. +const codeQuickFindShot = { + out: CHAPTER_IMAGE("Quick_Find"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Helpers.twin"]); + await caretOn(c, "Public Function Twice", { offset: "Public Function ".length + 2 }); + const widget = `editor.getDomNode().querySelector(".find-widget")`; + try { + await pressKey(c, "h", { alt: true }); + const open = await waitFor( + c, + () => + c.evaluate(`(() => { const w = ${widget}; return !!w && w.classList.contains("visible") && w.classList.contains("replaceToggled") && + w.contains(document.activeElement); })()`), + { timeout: 5000, interval: 100 }, + ); + if (!open) throw new Error("Alt+H did not show the find widget with its replace row"); + // the word typed into the box to find, over whatever an earlier search left there + await pressKey(c, "a", { ctrl: true }); + await typeText(c, "Twice"); + const counted = await waitFor( + c, + () => + c.evaluate(`(() => { const w = ${widget}; return document.activeElement.value === "Twice" && + /\\d+ of \\d+/.test(w.querySelector(".matchesCount")?.textContent ?? "") && w.querySelector(".matchesCount").textContent; })()`), + { timeout: 5000, interval: 100 }, + ); + if (!counted) throw new Error("typing Twice into the find widget counted no matches"); + const box = await settledBox(c, () => + c.evaluate( + `(() => { const r = ${widget}.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; })()`, + ), + ); + const ed = await editorBox(c); + const last = await codePlace(c, "Debug.Print Twice(5)"); + const lines = await linesBox(c, 1, last.line); + // to just past the widget, short of the minimap (see comboEnd) + const area = { + x: ed.x, + y: ed.y, + width: box.x + box.width + 8 - ed.x, + height: Math.max(box.y + box.height, lines.y + lines.height) + 4 - ed.y, + }; + return await capture(c, "Quick_Find", snapOut(area), { away: () => parkMouse(c) }); + } finally { + await closeFindWidget(c); + } + }, +}; + +// ---- Navigating + +// Helpers with a bookmark on Twice's first line and one on Third's, made with Ctrl+B, and the +// glyph of the lower one named from below, the arrow straight up the glyph margin. The bookmarks +// are cleared after with the right-click menu's Clear Bookmarks (Project) command. +const codeBookmarksShot = { + out: CHAPTER_IMAGE("Bookmarks"), + setup: "code-chapter", + async take({ c }) { + await chapterTabs(c, ["Helpers.twin"]); + const first = await codePlace(c, "Public Function Twice"); + try { + for (const text of ["Public Function Twice", "Public Sub Third"]) { + await caretOn(c, text, { offset: 4, top: first.line - 1 }); + await pressKey(c, "b", { ctrl: true }); + } + await caretOn(c, "End Module", { top: first.line - 1 }); + const glyphs = await waitFor( + c, + () => + c.evaluate(`(() => { + const g = [...document.querySelectorAll(".bookmarkActiveGlyph")].map((e) => e.getBoundingClientRect()).filter((r) => r.width); + return g.length === 2 ? g.sort((a, b) => a.y - b.y).map((r) => ({ x: r.x, y: r.y, width: r.width, height: r.height })) : null; +})()`), + { timeout: 5000, interval: 100 }, + ); + if (!glyphs) throw new Error("Ctrl+B on two lines did not draw two bookmark glyphs"); + const end = await codePlace(c, "End Module"); + const code = await linesBox(c, first.line - 1, end.line); + const lower = glyphs[1]; + const x = lower.x + lower.width / 2; + const labelY = code.y + code.height + 40; + // the label's left end under the glyph, the arrow up from its top edge: nothing of the label + // is left of the editor + const annotate = [ + { + type: "label", + text: "Bookmark", + on: { rect: { x: lower.x - 4, y: labelY, width: 0, height: 0 } }, + side: "right", + gap: 0, + }, + { type: "arrow", from: { rect: { x, y: labelY - 16, width: 0, height: 0 } }, to: { rect: lower } }, + ]; + const area = { x: code.x, y: code.y, width: 560, height: code.height }; + return await annotatedClip(c, "Bookmarks", area, annotate); + } finally { + await command(c, "tbEditor_ClearBookmarksProject"); + await waitFor(c, () => c.evaluate(`document.querySelectorAll(".bookmarkActiveGlyph").length === 0`), { + timeout: 3000, + interval: 100, + }); + } + }, +}; + +// The OUTLINE panel, floating, for Helpers, with its ... menu open (a real click on the button). +const codeOutlineMenuShot = annotatedPanelShot( + "Outline_Menu", + "OUTLINE", + { width: 300, height: 180 }, + { + setup: "code-chapter", + out: CHAPTER_IMAGE("Outline_Menu"), + before: (c) => openFile(c, CHAPTER_FILE("Helpers.twin")), + async open(c) { + const b = await rectOf(c, `${panelSel("OUTLINE")} .ellipsesIcon2`); + if (!b) throw new Error("OUTLINE has no ... button"); + await clickAt(c, b.x + b.width / 2, b.y + b.height / 2); + if (!(await menuOpened(c))) throw new Error("the ... button of OUTLINE opened no menu"); + }, + }, +); + +// ---- Diagnostics + +// An error put into Helpers in the page (a line printing a name nothing declares, in Third), with +// Hints and Information switched on in DIAGNOSTICS by their commands, as the count buttons do; the +// error discarded and the switches put back after. +const DIAG_ERROR = "Debug.Print Undefined1"; +// Waits for the project's counts of errors, warnings, hints and information messages, as the page +// keeps them for the Project Explorer's root (which DIAGNOSTICS shows when it is open), to be +// `want` for a second and a half: DIAGNOSTICS may be closed, its counters with it. +async function waitProjectCounts(c, want) { + const counts = () => + c.evaluate( + `["Error", "Warning", "Hint", "Info"].map((k) => fs.tree.rootFolder["cachedDiagnostics_child" + k + "Count"] ?? 0)`, + ); + let since = 0; + const ok = await waitFor( + c, + async () => { + if ((await counts()).join() !== want.join()) { + since = 0; + return false; + } + since ||= Date.now(); + return Date.now() - since > 1500; + }, + { timeout: 30000, interval: 300 }, + ); + if (!ok) + throw new Error(`the project's diagnostics never settled at ${want.join(",")}: ${(await counts()).join(",")}`); +} +// With `layout`, the default layout first (before DIAGNOSTICS floats, never after). +async function diagnosticsWithError(c, { layout = true } = {}) { + await chapterTabs(c, ["Helpers.twin"], { layout }); + await newLineAfter(c, "Debug.Print Twice(5)", DIAG_ERROR); + await waitProjectCounts(c, [1, 4, 1, 1]); + const shown = await c.evaluate("JSON.stringify(problemsPanelOptions)"); + c.shot.problemsShown = shown; + if (!(await c.evaluate("problemsPanelOptions.showHints"))) await command(c, "tbProblems_ToggleShowHints"); + if (!(await c.evaluate("problemsPanelOptions.showInfos"))) await command(c, "tbProblems_ToggleShowInfos"); +} +async function diagnosticsBack(c) { + const was = c.shot.problemsShown ? JSON.parse(c.shot.problemsShown) : null; + c.shot.problemsShown = null; + if (was) { + if ((await c.evaluate("problemsPanelOptions.showHints")) !== was.showHints) + await command(c, "tbProblems_ToggleShowHints"); + if ((await c.evaluate("problemsPanelOptions.showInfos")) !== was.showInfos) + await command(c, "tbProblems_ToggleShowInfos"); + } + await closeTabs(c, [], { discard: true }); + await waitProjectCounts(c, [0, 4, 1, 1]); +} +// The box of the floating DIAGNOSTICS row holding `code`, once the panel lists a row of each kind. +const diagRow = (c, code) => + waitFor( + c, + () => + c.evaluate(`(() => { + const rows = [...document.querySelectorAll(${JSON.stringify(`${DIAG} .problemNodeItem`)})]; + if (!["TB5079", "TB0003", "TB0016", "TB0012"].every((k) => rows.some((e) => e.textContent.includes(k)))) return null; + const r = rows.find((e) => e.textContent.includes(${JSON.stringify(code)}))?.getBoundingClientRect(); + return r ? { x: r.x, y: r.y, width: r.width, height: r.height } : null; +})()`), + { timeout: 5000, interval: 100 }, + ); + +// DIAGNOSTICS floating, its rows of all four kinds in three files, the error's row selected with a +// real click, which shows Jump to issue and Help under it. +const codeDiagnosticsRowsShot = panelShot("Diagnostics_Rows", "PROBLEMS", [560, 360], { + setup: "code-chapter", + out: CHAPTER_IMAGE("Diagnostics_Rows"), + before: diagnosticsWithError, + async prepare(c) { + const r = await diagRow(c, "TB5079"); + if (!r) throw new Error("DIAGNOSTICS has no row for the error"); + await clickAt(c, r.x + 60, r.y + r.height / 2); + if (!(await waitFor(c, () => rectOf(c, `${DIAG} .problemButtonJump`), { timeout: 3000, interval: 50 }))) { + throw new Error("a click on the error's row showed no Jump to issue button"); + } + await frames(c); + }, + restore: diagnosticsBack, +}); + +// The same panel with its right-click menu open over the error's row. +const codeDiagnosticsMenuShot = annotatedPanelShot( + "Diagnostics_Menu", + "PROBLEMS", + { width: 560, height: 360 }, + { + setup: "code-chapter", + out: CHAPTER_IMAGE("Diagnostics_Menu"), + // the panel floats before this runs: the layout is left as it is + before: (c) => diagnosticsWithError(c, { layout: false }), + async open(c) { + const r = await diagRow(c, "TB5079"); + if (!r) throw new Error("DIAGNOSTICS has no row for the error"); + await rightClickAt(c, r.x + 60, r.y + r.height / 2); + if (!(await menuOpened(c))) throw new Error("a right-click on a DIAGNOSTICS row opened no menu"); + }, + after: diagnosticsBack, + }, +); + +const codeChapterShots = [ + codeEditorPanelShot, + codeTabsStatesShot, + codeTabMenuShot, + codeComboListsShot, + codeContextMenuShot, + codePaletteShot, + // the Edit and View menus are shown as they are while a person types code + menuShot("Menu_Edit", "Edit", null, { setup: "code-chapter", before: focusCode }), + menuShot("Menu_View", "View", null, { setup: "code-chapter", before: focusCode }), + codeCombosShot, + signatureShot("SignatureHelp", { after: "Dim i As Long", nth: 2, call: "i = InStr", sigs: 2 }), + signatureShot("SignatureHelp_Expanded", { after: "count = 0", call: "MsgBox", sigs: 1, expand: true }), + codeHoverShot, + codeHintsShot, + codePasteAsCommentShot, + codeFindProjectShot, + codeQuickFindShot, + codeBookmarksShot, + codeOutlineMenuShot, + codeDiagnosticsRowsShot, + codeDiagnosticsMenuShot, +]; + +// ---- the "Running and debugging" chapter (setup debugging): the Writing code chapter's project, +// FeatureTour, with test/shots/debugging's Checks module as well. Main calls Inventory.Total, +// whose loop line takes the breakpoint; the program prints one line on the loop's first pass. +// Every picture starts from nothing running, no breakpoint, no watch, an empty DEBUG CONSOLE and +// the default layout (debugReset), and leaves it so. The DEBUG CONSOLE's line times and the time +// a line took change from run to run: they are set in the page once the real ones are shown, as +// the CodeLens pictures' are. + +const DEBUG_IMAGE = (name) => `IDE/Debugging/Images/${name}.png`; +const LOOP_LINE = "subtotal = subtotal + items(i).Quantity * price"; +const isPaused = (c) => c.evaluate("!!context.atBreakpoint?.value"); + +// The value a panel's tree shows for the row at `names` (from the top), or null. +const treeValue = (c, panel, names) => + c.evaluate(`(() => { + let f = ${panel}.tree.rootFolder; + for (const n of ${JSON.stringify(names)}) f = f && Object.values(f.entries || {}).find((e) => e.name === n); + return f ? ((f.customRenderData || {}).value ?? null) : null; +})()`); + +// The text of the WATCHES panel: each row's expression, value and type on lines of their own. +const watchesText = (c) => + c.evaluate(String.raw`watchPanel.tree.sectionHeader.parentNode.innerText.replace(/\n{2,}/g, "\n")`); + +// An object's address changes from run to run: each one the page shows is set to OBJECT_ADDRESS, +// in the page, once the real ones are shown. +const OBJECT_ADDRESS = "441954992"; +const fixObjectAddresses = (c) => + c.evaluate(String.raw`(() => { + const walk = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); + for (let n = walk.nextNode(); n; n = walk.nextNode()) { + if (n.nodeValue.includes("{object: ")) n.nodeValue = n.nodeValue.replace(/\{object: \d+\}/g, "{object: ${OBJECT_ADDRESS}}"); + } +})()`); + +// The DEBUG CONSOLE emptied with its own button. +async function clearConsole(c) { + if (await c.evaluate("debugConsoleContent.dataNodes.length === 0")) return; + const clear = await rectOf(c, '[title="Clear Debug Console"]'); + if (!clear) throw new Error("the DEBUG CONSOLE has no Clear button"); + await clickAt(c, clear.x + clear.width / 2, clear.y + clear.height / 2); + if (!(await waitFor(c, () => c.evaluate("debugConsoleContent.dataNodes.length === 0"), { timeout: 5000 }))) { + throw new Error("the DEBUG CONSOLE was not cleared"); + } + await parkMouse(c); +} + +// Nothing running, no breakpoint, no watch, no tab, the default layout and an empty console. +async function debugReset(c) { + await resetUi(c); + await removePointer(c); + for (let i = 0; i < 3 && (await codeExecuting(c)); i++) { + await command(c, "tbDebug_Stop"); + await waitFor(c, async () => !(await codeExecuting(c)), { timeout: 15000, interval: 100 }); + } + if (await codeExecuting(c)) throw new Error("the program would not end"); + await command(c, "tbDebug_BreakpointsClear"); + await command(c, "tbWatches_Clear"); + await closeTabs(c, [], { discard: true }); + await defaultLayout(c); + await clearConsole(c); + await frames(c); +} + +// A breakpoint on the line of the open file holding `text`, set with F9 as a person sets it. +async function breakpointOn(c, text) { + const { line } = await codePlace(c, text); + await setCursor(c, line, 9); + await pressKey(c, "F9"); + const set = await waitFor( + c, + () => + c.evaluate( + `editor.getModel().getAllDecorations().some((d) => d.range.startLineNumber === ${line} && /breakpointActiveGlyph/.test(d.options.glyphMarginClassName || ""))`, + ), + { timeout: 5000, interval: 50 }, + ); + if (!set) throw new Error(`F9 set no breakpoint on ${JSON.stringify(text)}`); + return line; +} + +// A watch added with Debug > Add Watch..., its expression typed and entered. +async function addWatch(c, expression) { + await command(c, "tbWatches_Add"); + const box = await waitFor(c, () => c.evaluate(`document.activeElement?.placeholder === "Expression to watch"`), { + timeout: 5000, + interval: 50, + }); + if (!box) throw new Error("Add Watch... opened no box"); + await typeText(c, expression); + await pressKey(c, "Enter"); + const added = await waitFor( + c, + () => + c.evaluate( + `Object.values(watchPanel.tree.rootFolder.entries || {}).some((e) => e.name === ${JSON.stringify(expression)})`, + ), + { timeout: 5000, interval: 50 }, + ); + if (!added) throw new Error(`the watch ${expression} was not added`); +} + +// Waits for the program to be paused with `test` true; throws naming `what` otherwise. +async function waitStop(c, test, what) { + const ok = await waitFor(c, async () => (await isPaused(c)) && (await test()), { timeout: 60000, interval: 150 }); + if (!ok) throw new Error(`the program did not stop ${what}`); + await frames(c); +} + +// Main run with a breakpoint on Total's loop line (and on `also`, if given), stopped there on +// the loop's first pass (`pass` 1, i = 0) or its second (`pass` 2, i = 1), when the first has +// printed its line. Inventory is open; `before` runs before Start. +async function pausedInTotal(c, { pass = 1, also = null, before = null } = {}) { + await openFile(c, CHAPTER_FILE("Inventory.twin")); + await breakpointOn(c, LOOP_LINE); + if (also) await breakpointOn(c, also); + if (before) await before(c); + await clearConsole(c); + await startRun(c); + const iIs = (v) => async () => (await treeValue(c, "variablesPanel", ["Locals", "i"])) === v; + await waitStop(c, iIs("0"), "at the breakpoint in Total"); + if (pass === 2) { + await command(c, "tbDebug_StartOrContinue"); + // the console's text, whether or not the layout shows it + const printed = async () => + (await c.evaluate(`document.querySelector(".debugConsoleOuter")?.textContent ?? ""`)).includes(": Widget") && + (await iIs("1")()); + await waitStop(c, printed, "on the loop's second pass"); + } + // the panels fill in turn: wait for the call stack's two frames + const frames2 = await waitFor( + c, + () => + c.evaluate( + `Object.values(liveStackTraceInfo).some((t) => Object.values(t || {}).filter((f) => f && f.name).length >= 2)`, + ), + { timeout: 10000, interval: 100 }, + ); + if (!frames2) throw new Error("the Call Stack never showed Total and Main"); +} + +// A layout set in the page and saved nowhere. +async function useLayout(c, layout) { + if (!(await c.evaluate(`restorePanelLayout(${JSON.stringify(JSON.stringify(layout))})`))) { + throw new Error("the page refused the layout"); + } + await frames(c); +} +const LAYOUT_ROWS = (bottom, size) => ({ + type: "horizontal", + variableSize: true, + size: "0%", + content: [ + { id: "TOOLBAR", variableSize: false, size: "fit-content" }, + { id: "EDITOR", variableSize: true, size: "0%" }, + { type: "vertical", variableSize: false, size, content: bottom }, + ], +}); +// the editor with its panels below it, each `[id, size]` and the last taking the rest +const panelsRow = (panels) => + panels.map(([id, size], i) => + i === panels.length - 1 ? { id, variableSize: true, size: "0%" } : { id, variableSize: false, size }, + ); +const PAUSED_LAYOUT = LAYOUT_ROWS( + panelsRow([["DEBUG CONSOLE", "30%"], ["CALL STACK", "22%"], ["VARIABLES", "24%"], ["WATCHES"]]), + "34%", +); +const STOP_LAYOUT = LAYOUT_ROWS(panelsRow([["CALL STACK", "42%"], ["VARIABLES"]]), "23%"); +// the TOOLBOX, empty beside code, wide enough for labels on the editor's left +const GLYPHS_LAYOUT = { + type: "horizontal", + variableSize: true, + size: "0%", + content: [ + { id: "TOOLBAR", variableSize: false, size: "fit-content" }, + { + type: "vertical", + variableSize: true, + size: "0%", + content: [ + { id: "TOOLBOX", variableSize: false, size: "22%" }, + { id: "EDITOR", variableSize: true, size: "0%" }, + ], + }, + ], +}; + +// The box of the docked panel headed `title` (EDITOR 1, CALL STACK...). +const panelBox = (c, title) => + c.evaluate(`(() => { + const h = [...document.querySelectorAll(".sectionHeaderInner")].find((e) => e.textContent === ${JSON.stringify(title)}); + const r = h?.closest(".toolWindowContainer")?.getBoundingClientRect(); + return r && r.width ? { x: r.x, y: r.y, width: r.width, height: r.height } : null; +})()`); + +// The whole window paused in Total on the loop's second pass, in a layout with the DEBUG +// CONSOLE, CALL STACK, VARIABLES and WATCHES under the editor; two watches. +const debugPausedPanelsShot = { + out: DEBUG_IMAGE("Paused_Panels"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await useLayout(c, PAUSED_LAYOUT); + await pausedInTotal(c, { + pass: 2, + before: async (c) => { + await addWatch(c, "subtotal"); + await addWatch(c, "items(i).Quantity"); + }, + }); + const watched = await waitFor(c, async () => (await watchesText(c)).includes("items(i).Quantity:\n8\n"), { + timeout: 10000, + interval: 100, + }); + if (!watched) { + const shown = await watchesText(c); + throw new Error(`the watches never showed their values: ${JSON.stringify(shown)}`); + } + await c.evaluate( + `editor.setScrollTop(editor.getTopForLineNumber(${(await codePlace(c, "Public Function Total")).line}))`, + ); + await fixConsoleTimes(c); + await fixObjectAddresses(c); + await frames(c); + return await capture(c, "Paused_Panels", snapOut({ x: 0, y: 0, ...IDE_SIZE }), { away: () => parkMouse(c) }); + } finally { + await debugReset(c); + } + }, +}; + +// FillTable run with F6 and stopped by its Subscript error: the editor with the error panel +// under the failing line, and CALL STACK and VARIABLES under the editor. +const debugErrorPanelShot = { + out: DEBUG_IMAGE("Error_Panel"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + return await atSize(c, 1100, 700, async () => { + await useLayout(c, STOP_LAYOUT); + await openFile(c, CHAPTER_FILE("Checks.twin")); + await caretOn(c, "For i = 1 To 6", { offset: 4 }); + await command(c, "tbDebug_RunOrPreview"); + await waitStop( + c, + async () => + (await c.evaluate(`document.querySelectorAll(".errorWidgetButton").length`)) === 4 && + (await treeValue(c, "variablesPanel", ["Locals", "idx"])) === "7", + "at the Subscript error", + ); + await c.evaluate( + `editor.setScrollTop(editor.getTopForLineNumber(${(await codePlace(c, "Public Sub FillTable")).line}))`, + ); + await frames(c); + const ed = await panelBox(c, "EDITOR 1"); + const vars = await panelBox(c, "VARIABLES"); + if (!ed || !vars) throw new Error("the editor or VARIABLES is not in the layout"); + const area = { x: 0, y: ed.y, width: 1100, height: vars.y + vars.height - ed.y }; + return capture(c, "Error_Panel", snapOut(area, 1100, 700), { away: () => parkMouse(c) }); + }); + } finally { + await debugReset(c); + } + }, +}; + +// Inventory at Total with a breakpoint on the loop line and the mouse resting in the margin of +// the line above, which shows the faint dot there; a pointer drawn at the mouse, and the two +// dots named by labels in the empty TOOLBOX on the editor's left. +const debugBreakpointGlyphsShot = { + out: DEBUG_IMAGE("Breakpoint_Glyphs"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await useLayout(c, GLYPHS_LAYOUT); + await openFile(c, CHAPTER_FILE("Inventory.twin")); + const bp = await breakpointOn(c, LOOP_LINE); + const first = (await codePlace(c, "Public Function Total")).line; + await caretOn(c, "Public Sub Add", { top: first }); + const above = bp - 1; + const at = await c.evaluate(`(() => { + const e = editor.getDomNode().getBoundingClientRect(); + const l = editor.getLayoutInfo(); + const lh = editor.getOption(monaco.editor.EditorOption.lineHeight); + const y = e.y + editor.getTopForLineNumber(${above}) - editor.getScrollTop() + lh / 2; + return { x: e.x + l.glyphMarginLeft + l.glyphMarginWidth - 3, y: y + 3 }; +})()`); + await mouseMove(c, at.x - 6, at.y); + await mouseMove(c, at.x, at.y); + const ghost = await waitFor(c, () => rectOf(c, ".breakpointInactiveGlyph"), { timeout: 5000, interval: 50 }); + if (!ghost) throw new Error("the margin showed no faint dot under the mouse"); + await drawPointer(c, at); + const code = await linesBox(c, first + 3, bp + 4); + const ed = await editorBox(c); + const left = ed.x - 64; + const dot = await rectOf(c, ".breakpointActiveGlyph"); + const mid = (r) => r.y + r.height / 2; + const annotate = [ + { type: "label", text: "Breakpoint", on: POINT(left, mid(dot) + 16), side: "left", gap: 0 }, + { type: "arrow", from: POINT(left + 4, mid(dot) + 16), to: { css: ".breakpointActiveGlyph" } }, + { type: "label", text: "Faint dot", on: POINT(left, mid(ghost) - 16), side: "left", gap: 0 }, + { type: "arrow", from: POINT(left + 4, mid(ghost) - 16), to: { css: ".breakpointInactiveGlyph" } }, + ]; + const area = { x: ed.x, y: code.y, width: 560, height: code.height }; + return await annotatedClip(c, "Breakpoint_Glyphs", area, annotate, { away: null }); + } finally { + await removePointer(c); + await mouseMove(c, 2, IDE_SIZE.height - 10); + await debugReset(c); + } + }, +}; + +// The first colour of the element `sel` and its ancestors that is not transparent: the ground +// a cut-out of it is put on. +const groundOf = (c, sel) => + c.evaluate(`(() => { + for (let e = document.querySelector(${JSON.stringify(sel)}); e; e = e.parentElement) { + const k = getComputedStyle(e).backgroundColor; + if (!/^rgba\\(.*, 0\\)$/.test(k) && k !== "transparent") return k; + } + return null; +})()`); + +// The open file scrolled so that the line holding `text` is the first shown. +async function scrollToLine(c, text) { + await c.evaluate(`editor.setScrollTop(editor.getTopForLineNumber(${(await codePlace(c, text)).line}))`); + await frames(c); +} + +// Main run with a breakpoint on its last line, stopped there once Locals holds `first`. +async function pausedInMain(c) { + await openFile(c, CHAPTER_FILE("MainModule.twin")); + await breakpointOn(c, "Debug.Print first.Quantity"); + await clearConsole(c); + await startRun(c); + await waitStop(c, async () => (await treeValue(c, "variablesPanel", ["Locals", "first"])) !== null, "in Main"); +} + +// The node of a panel's tree at `names` (from the top), as an expression for the page. +const treeNode = (panel, names) => + `(() => { let f = ${panel}.tree.rootFolder; for (const n of ${JSON.stringify(names)}) f = f && Object.values(f.entries || {}).find((e) => e.name === n); return f || null; })()`; + +// A folder of a panel's tree expanded (or collapsed, with `open` false) by a click on its +// plus (or minus) box, as a person does it, once its rows show. A click flips the folder at +// once, and the panel draws it again when its parts arrive, from the expanded state it keeps +// for each path: the flip does not always hold, so the click is made again until it does. +async function toggleRow(c, panel, names, open = true) { + const node = treeNode(panel, names); + const there = () => + c.evaluate( + `(() => { const f = ${node}; return !!f && f.isCollapsed === ${!open} && (${!open} || Object.keys(f.entries || {}).length > 0); })()`, + ); + for (let attempt = 0; attempt < 4; attempt++) { + if ((await there()) && (await sleep(500), await there())) { + await parkMouse(c); + await frames(c); + return; + } + const at = await c.evaluate(`(() => { + const d = ${node}?.innerDiv?.querySelector(".treeNodeIcon"); + const r = d?.getBoundingClientRect(); + return r && r.width ? { x: r.x + r.width / 2, y: r.y + r.height / 2 } : null; +})()`); + if (!at) throw new Error(`the ${panel} has no row ${names.join(" > ")}`); + await clickAt(c, at.x, at.y); + await waitFor(c, there, { timeout: 3000, interval: 50 }); + } + throw new Error(`four clicks did not ${open ? "expand" : "collapse"} ${names.join(" > ")}`); +} + +// A line typed into the DEBUG CONSOLE's input row and entered, once the console shows the +// time it took. The text is put in the row whole: a space typed while the row's completion +// list is open accepts the list's first entry. +async function consoleRun(c, line) { + const taken = async () => ((await consoleText(c)).match(/\(time taken: /g) || []).length; + const before = await taken(); + await c.evaluate(`(() => { const i = debugConsoleEntry; i.focus(); i.value = ${JSON.stringify(line)}; })()`); + await pressKey(c, "Enter"); + if (!(await waitFor(c, async () => (await taken()) > before, { timeout: 15000, interval: 100 }))) { + throw new Error(`the console never finished ${JSON.stringify(line)}`); + } + await frames(c); +} + +// Inventory paused at its loop line on the loop's first pass, with a second breakpoint on the +// function's last line: the editor, and CALL STACK and VARIABLES under it. +const debugPausedLineShot = { + out: DEBUG_IMAGE("Paused_Line"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + return await atSize(c, 1100, 700, async () => { + await useLayout(c, STOP_LAYOUT); + await pausedInTotal(c, { also: "Total = subtotal" }); + await scrollToLine(c, "Public Function Total"); + await fixObjectAddresses(c); + await frames(c); + const ed = await panelBox(c, "EDITOR 1"); + const vars = await panelBox(c, "VARIABLES"); + if (!ed || !vars) throw new Error("the editor or VARIABLES is not in the layout"); + const area = { x: 0, y: ed.y, width: 1100, height: vars.y + vars.height - ed.y }; + return capture(c, "Paused_Line", snapOut(area, 1100, 700), { away: () => parkMouse(c) }); + }); + } finally { + await debugReset(c); + } + }, +}; + +// The toolbar's run and step buttons while Inventory is paused, cut out on the toolbar's own +// ground (its other controls left out), each button that works named by a label below, the +// labels in a column at the right, the rightmost button's highest, so that no arrow crosses +// another. +const RUN_BUTTONS = ["playIcon", "pauseIcon", "stopIcon", "debugStepOverIcon", "debugStepIntoIcon", "debugStepOutIcon"]; +const debugToolbarPausedShot = { + out: DEBUG_IMAGE("Toolbar_Paused"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await pausedInTotal(c); + const bar = await toolbarRect(c); + const ground = await groundOf(c, "#playIcon"); + if (!ground) throw new Error("the toolbar has no background colour"); + const ids = ["debugStepOutIcon", "debugStepIntoIcon", "debugStepOverIcon", "stopIcon", "playIcon"]; + const names = ["Step Out", "Step Into", "Step Over", "Stop", "Continue"]; + const boxes = []; + for (const id of ids) { + const r = await rectOf(c, `#${id}`); + if (!r) throw new Error(`the toolbar has no #${id}`); + boxes.push(r); + } + const live = await c.evaluate( + `${JSON.stringify(ids)}.every((id) => !document.getElementById(id).classList.contains("disabledCommand")) && document.getElementById("pauseIcon").classList.contains("disabledCommand")`, + ); + if (!live) throw new Error("the toolbar's buttons are not as they are at a stop"); + const right = boxes[0].x + boxes[0].width + 28; + const below = bar.y + bar.height; + const annotate = ids.flatMap((id, i) => { + const y = below + 22 + 26 * i; + return [ + { type: "label", text: names[i], on: POINT(right, y), side: "right", gap: 0 }, + { type: "arrow", from: POINT(right - 4, y), to: { css: `#${id}` }, elbow: true }, + ]; + }); + const left = boxes[4].x - 12; + const area = { x: left, y: bar.y, width: right - left, height: bar.height }; + return await annotatedClip(c, "Toolbar_Paused", area, annotate, { + keep: [...RUN_BUTTONS.map((id) => `#${id}`), `#${LAYER_ID}`], + solid: ground, + }); + } finally { + await debugReset(c); + } + }, +}; + +// The status bar's box for a program that the program started (Checks' PingLater, a ping with +// no window, typed into the console with nothing running), its menu open above it: the status +// bar from a little left of the box to a little right of it, cut out on the window's ground. The box +// names the process id, which changes from run to run: it is set in the page to PROCESS_ID. +// The ping is ended with the menu's Terminate, or by its id if the box has gone. +const PROCESS_BOX = "#runningProcessesContainer > .runningProcessOuter"; +const PROCESS_ID = "10432"; +const debugProcessBoxShot = { + out: DEBUG_IMAGE("Process_Box"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + let pid = null; + try { + await consoleRun(c, "PingLater"); + if (!(await waitFor(c, () => rectOf(c, PROCESS_BOX), { timeout: 15000, interval: 100 }))) { + throw new Error("PingLater put no PROCESS box in the status bar"); + } + pid = Number((await consoleText(c)).match(/\[SHELL\] process (\d+) started/)?.[1]) || null; + await c.evaluate( + `document.querySelector(${JSON.stringify(`${PROCESS_BOX} .runningProcess`)}).textContent = "PROCESS ${PROCESS_ID}: RUNNING"`, + ); + await frames(c); + const box = await rectOf(c, PROCESS_BOX); + await clickAt(c, box.x + box.width / 2, box.y + box.height / 2); + if (!(await menuOpened(c))) throw new Error("a click on the PROCESS box opened no menu"); + const menu = await rectOf(c, "#contextMenu"); + const bar = await rectOf(c, "#rootStatusBar"); + const ground = await c.evaluate("getComputedStyle(document.body).backgroundColor"); + const left = Math.min(box.x, menu.x) - 24; + const top = menu.y - 12; + const right = Math.max(box.x + box.width, menu.x + menu.width) + 160; + const area = { x: left, y: top, width: right - left, height: bar.y + bar.height - top }; + return await capture(c, "Process_Box", snapOut(area), { + keep: ["#rootStatusBar", "#contextMenu"], + solid: ground, + away: () => parkMouse(c), + }); + } finally { + await endPing(c, pid); + await debugReset(c); + } + }, +}; + +// The ping PingLater started ended: with Terminate on its box's menu while the box is there, +// else by its process id. +async function endPing(c, pid) { + await closeMenus(c); + const box = await rectOf(c, PROCESS_BOX); + if (box) { + await clickAt(c, box.x + box.width / 2, box.y + box.height / 2); + if (await menuOpened(c)) { + const t = (await menuItems(c)).find((i) => i.text === "Terminate"); + if (t) await clickAt(c, t.x + t.width / 2, t.y + t.height / 2); + } + await waitFor(c, async () => !(await rectOf(c, PROCESS_BOX)), { timeout: 15000, interval: 100 }); + await closeMenus(c); + } + if (pid) killTree(pid); +} + +// VARIABLES floated on its own, `width` by `height`. +const floatVariables = (c, width = 420, height = 214) => floatPanel(c, "VARIABLES", { width, height }); + +// Main paused on its last line, VARIABLES floated with the user-defined type `first` expanded. +const debugVariablesExpandedShot = { + out: DEBUG_IMAGE("Variables_Expanded"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await pausedInMain(c); + await toggleRow(c, "variablesPanel", ["Locals", "first"]); + const box = await floatVariables(c); + await fixObjectAddresses(c); + await frames(c); + return await capture(c, "Variables_Expanded", snapOut(box), { away: () => parkMouse(c) }); + } finally { + if (await c.evaluate(`${treeNode("variablesPanel", ["Locals", "first"])}?.isCollapsed === false`)) { + await toggleRow(c, "variablesPanel", ["Locals", "first"], false); + } + await unfloatPanel(c, "VARIABLES"); + await debugReset(c); + } + }, +}; + +// Total paused on the loop's second pass: VARIABLES floated, `subtotal`'s value double-clicked +// and its text box open (`edit`); or VARIABLES docked at the editor's right, the mouse resting +// on `subtotal`'s row and its tooltip shown, the top of the panel and the tooltip cut out on +// the panel's ground (a floating panel is drawn over the tooltip). The tooltip names the +// value's address, which changes from run to run: it is set in the page to POINTER_TEXT. +const POINTER_TEXT = "(&H0019F3A4)"; +const TIP_MARK = "data-tb-shot-tip"; +const PANEL_MARK = "data-tb-shot-panel"; +const TIP_LAYOUT = { + type: "horizontal", + variableSize: true, + size: "0%", + content: [ + { id: "TOOLBAR", variableSize: false, size: "fit-content" }, + { + type: "vertical", + variableSize: true, + size: "0%", + content: [ + { id: "VARIABLES", variableSize: false, size: "33%" }, + { id: "EDITOR", variableSize: true, size: "0%" }, + ], + }, + ], +}; +function variablesRowShot(name, { edit }) { + return { + out: DEBUG_IMAGE(name), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await pausedInTotal(c, { pass: 2 }); + if (!edit) await useLayout(c, TIP_LAYOUT); + await fixObjectAddresses(c); + let box; + if (edit) box = await floatVariables(c, 400, 236); + else { + const panel = await panelBox(c, "VARIABLES"); + if (!panel) throw new Error("VARIABLES is not in the layout"); + box = { ...panel, height: 250 }; + await c.evaluate( + `[...document.querySelectorAll(".sectionHeaderInner")].find((e) => e.textContent === "VARIABLES").closest(".toolWindowContainer").setAttribute(${JSON.stringify(PANEL_MARK)}, "")`, + ); + } + const row = `${treeNode("variablesPanel", ["Locals", "subtotal"])}?.innerDiv`; + const at = await c.evaluate(`(() => { + const v = ${row}?.querySelector(".variableValue")?.getBoundingClientRect(); + const n = ${row}?.querySelector(".variableName")?.getBoundingClientRect(); + return v && n ? { value: { x: v.x + v.width / 2, y: v.y + v.height / 2 }, name: { x: n.x + n.width / 2, y: n.y + n.height / 2 } } : null; +})()`); + if (!at) throw new Error("VARIABLES has no row for subtotal"); + if (edit) { + // the first click of the two draws the row again, which can lose the double-click: + // it is made again until the box opens + let open = false; + for (let attempt = 0; attempt < 3 && !open; attempt++) { + await clickAt(c, at.value.x, at.value.y); + await clickAt(c, at.value.x, at.value.y, { clickCount: 2 }); + open = await waitFor( + c, + () => + c.evaluate( + `document.activeElement?.tagName === "INPUT" && !!document.activeElement.closest(${JSON.stringify(panelSel("VARIABLES"))})`, + ), + { timeout: 3000, interval: 50 }, + ); + } + if (!open) throw new Error("a double-click on subtotal's value opened no text box"); + await fixObjectAddresses(c); + await frames(c); + return await capture(c, name, snapOut(box), { away: () => parkMouse(c) }); + } + await mouseMove(c, at.name.x - 4, at.name.y); + await mouseMove(c, at.name.x, at.name.y); + // The tooltip is laid out, hidden, as soon as the mouse rests, and placed again when it + // is shown (class visibleTooltip) about a second later, then fades in: its box once it + // is shown and its fade has ended, else null. + const tipBox = () => + c.evaluate(`(() => { + const t = document.getElementById("hoverTooltip"); + if (!t?.classList.contains("visibleTooltip") || t.getAnimations({ subtree: true }).length) return null; + t.setAttribute(${JSON.stringify(TIP_MARK)}, ""); + const walk = document.createTreeWalker(t, NodeFilter.SHOW_TEXT); + for (let n = walk.nextNode(); n; n = walk.nextNode()) n.nodeValue = n.nodeValue.replace(/\\(&H[0-9A-F]+\\)/, ${JSON.stringify(POINTER_TEXT)}); + const r = t.getBoundingClientRect(); + return r.width ? { x: r.x, y: r.y, width: r.width, height: r.height } : null; +})()`); + if (!(await waitFor(c, tipBox, { timeout: 8000, interval: 100 }))) { + throw new Error("resting on subtotal's row showed no tooltip"); + } + const shown = await settledBox(c, tipBox); + const ground = await groundOf(c, `[${PANEL_MARK}] .sectionBody`); + const png = await capture(c, name, snapOut(grow(union(box, shown), 8)), { + keep: [`[${PANEL_MARK}]`, `[${TIP_MARK}]`], + solid: ground, + }); + const still = await tipBox(); + if (JSON.stringify(still) !== JSON.stringify(shown)) { + throw new Error( + `the tooltip moved during the capture: ${JSON.stringify(shown)}, then ${JSON.stringify(still)}`, + ); + } + return png; + } finally { + await pressKey(c, "Escape"); + await c.evaluate(`(() => { + document.querySelector("[${TIP_MARK}]")?.removeAttribute(${JSON.stringify(TIP_MARK)}); + document.querySelector("[${PANEL_MARK}]")?.removeAttribute(${JSON.stringify(PANEL_MARK)}); +})()`); + if (edit) await unfloatPanel(c, "VARIABLES"); + await debugReset(c); + } + }, + }; +} + +// WATCHES floated with four watches, one an expression the compiler refuses: while Total is +// paused on the loop's second pass, or (`ended`) once the program has been ended there. +const WATCHES = ["items(i).Name", "items(i).Quantity", "subtotal", "subtotal *"]; +function watchesShot(name, { ended }) { + return { + out: DEBUG_IMAGE(name), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await pausedInTotal(c, { + pass: 2, + before: async (c) => { + for (const w of WATCHES) await addWatch(c, w); + }, + }); + const evaluated = await waitFor( + c, + async () => { + const t = await watchesText(c); + return t.includes("(compile error") && t.includes('"Gadget"') && t; + }, + { timeout: 10000, interval: 100 }, + ); + if (!evaluated) throw new Error(`the watches were never evaluated: ${JSON.stringify(await watchesText(c))}`); + if (ended) { + await stopRun(c); + const gone = await waitFor( + c, + async () => ((await watchesText(c)).match(/not available/g) || []).length === WATCHES.length, + { timeout: 10000, interval: 100 }, + ); + if (!gone) + throw new Error(`the watches did not all read not available: ${JSON.stringify(await watchesText(c))}`); + } + const box = await floatPanel(c, "WATCHES", { width: 520, height: 128 }); + return await capture(c, name, snapOut(box), { away: () => parkMouse(c) }); + } finally { + await unfloatPanel(c, "WATCHES"); + await debugReset(c); + } + }, + }; +} + +// Total paused on the loop's first pass, Main's row of CALL STACK clicked: MainModule's line +// highlighted with no arrow, VARIABLES showing Main's variables. +const debugCallStackFramesShot = { + out: DEBUG_IMAGE("CallStack_Frames"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + return await atSize(c, 1100, 700, async () => { + await useLayout(c, STOP_LAYOUT); + await pausedInTotal(c); + const at = await c.evaluate(`(() => { + for (const t of Object.values(callStackPanel.tree.rootFolder.entries)) { + for (const f of Object.values(t.entries || {})) { + const d = f.innerDiv; + if (d && /^Main\\n/.test(d.innerText)) { const r = d.getBoundingClientRect(); return { x: r.x + r.width / 2, y: r.y + r.height / 2 }; } + } + } + return null; +})()`); + if (!at) throw new Error("CALL STACK has no row for Main"); + await clickAt(c, at.x, at.y); + const moved = await waitFor( + c, + async () => + (await c.evaluate(`editor.getModel().getLinesContent().some((l) => l.includes("Public Sub Main"))`)) && + (await treeValue(c, "variablesPanel", ["Locals", "first"])) !== null, + { timeout: 10000, interval: 100 }, + ); + if (!moved) throw new Error("a click on Main in CALL STACK did not show Main"); + await c.evaluate("editor.setScrollTop(0)"); + await parkMouse(c); + await fixObjectAddresses(c); + await frames(c); + const ed = await panelBox(c, "EDITOR 1"); + const vars = await panelBox(c, "VARIABLES"); + if (!ed || !vars) throw new Error("the editor or VARIABLES is not in the layout"); + const area = { x: 0, y: ed.y, width: 1100, height: vars.y + vars.height - ed.y }; + return capture(c, "CallStack_Frames", snapOut(area, 1100, 700), { away: () => parkMouse(c) }); + }); + } finally { + await debugReset(c); + } + }, +}; + +// Total paused on the loop's second pass, the mouse resting on the loop line: on `subtotal`, +// or (`selection`) inside `items(i).Quantity * price`, selected. The picture is the popup and +// the line, from the editor's left. +function debugHoverShot(name, { selection }) { + return { + out: DEBUG_IMAGE(name), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await pausedInTotal(c, { pass: 2 }); + await scrollToLine(c, "Public Function Total"); + const at = await codePlace(c, LOOP_LINE); + let col = at.column + 3; + let want = "subtotal"; + if (selection) { + const text = "items(i).Quantity * price"; + const from = at.column + LOOP_LINE.indexOf(text); + await c.evaluate( + `editor.setSelection(new monaco.Selection(${at.line}, ${from}, ${at.line}, ${from + text.length}))`, + ); + col = from + 10; + want = text; + } + let shown = false; + for (let attempt = 0; attempt < 3 && !shown; attempt++) { + await restMouse(c, at.line, col); + shown = await waitFor(c, async () => (await hoverText(c))?.includes(`${want}:`) && true, { timeout: 6000 }); + } + if (!shown) throw new Error(`the hover showed no value of ${want}: ${await hoverText(c)}`); + const popup = await settledBox(c, () => + c.evaluate(`(() => { + const r = [...document.querySelectorAll(".monaco-hover")].find((e) => !e.classList.contains("hidden") && e.getBoundingClientRect().height > 0).getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; +})()`), + ); + const lineBox = await linesBox(c, at.line, at.line); + const ed = await editorBox(c); + const top = Math.min(popup.y, lineBox.y) - 8; + const bottom = Math.max(popup.y + popup.height, lineBox.y + lineBox.height) + 8; + const right = Math.max(popup.x + popup.width, (await pointOf(c, at.line, at.column + LOOP_LINE.length)).x); + const area = { x: ed.x, y: top, width: right + 12 - ed.x, height: bottom - top }; + // the mouse stays on the line: the popup goes when it leaves + return await capture(c, name, snapOut(area)); + } finally { + await mouseAway(c); + await debugReset(c); + } + }, + }; +} + +// The DEBUG CONSOLE floated on its own, `width` (560) by `height`. +const floatConsole = (c, height, width = 560) => floatPanel(c, "DEBUG CONSOLE", { width, height }); + +// Total paused on the loop's second pass, two lines entered in the DEBUG CONSOLE: the value of +// subtotal printed, then a new value given to it. +const debugConsoleRunShot = { + out: DEBUG_IMAGE("Console_Run"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + await pausedInTotal(c, { pass: 2 }); + await consoleRun(c, "? subtotal"); + await consoleRun(c, "subtotal = 5"); + const box = await floatConsole(c, 236); + await fixConsoleTimes(c, "0.0001023s"); + await frames(c); + return await capture(c, "Console_Run", snapOut(box), { away: () => parkMouse(c) }); + } finally { + await unfloatPanel(c, "DEBUG CONSOLE"); + await debugReset(c); + } + }, +}; + +// The DEBUG CONSOLE floated after Main has run to its end, its menu opened with a right-click +// right of its first line, the menu inside the panel and clear of the lines. (A cut-out of the console would show +// the line it measures its text with, which it hides.) +const debugConsoleMenuShot = { + out: DEBUG_IMAGE("Console_Menu"), + setup: "debugging", + async take({ c }) { + await debugReset(c); + try { + // the run can be over before the IDE is seen running: its last line is the time it took + await command(c, "tbDebug_StartOrContinue"); + const ended = await waitFor( + c, + async () => (await consoleText(c)).includes("(time taken: ") && !(await codeExecuting(c)), + { timeout: 60000, interval: 100 }, + ); + if (!ended) throw new Error("Main did not run to its end"); + const box = await floatConsole(c, 180, 640); + await fixConsoleTimes(c, "0.0021175s"); + await frames(c); + await rightClickAt(c, box.x + 410, box.y + 34); + if (!(await menuOpened(c))) throw new Error("a right-click in the DEBUG CONSOLE opened no menu"); + const menu = await rectOf(c, "#contextMenu"); + if (union(box, menu).width !== box.width || union(box, menu).height !== box.height) { + throw new Error("the menu is not inside the DEBUG CONSOLE"); + } + return await capture(c, "Console_Menu", snapOut(box), { away: () => parkMouse(c) }); + } finally { + await closeMenus(c); + await unfloatPanel(c, "DEBUG CONSOLE"); + await debugReset(c); + } + }, +}; + +// The Debug and Run menus as they are while a program is paused and while one runs: Total +// paused, the caret in the code; Checks' WaitAWhile run with F6, a loop of a minute at most. +async function pausedForMenu(c) { + await debugReset(c); + await pausedInTotal(c); + const top = (await codePlace(c, "Public Function Total")).line; + await caretOn(c, LOOP_LINE, { offset: 4, top }); + if (!(await c.evaluate("editor.hasTextFocus()"))) throw new Error("the code editor does not have the focus"); +} +async function runningForMenu(c) { + await debugReset(c); + await openFile(c, CHAPTER_FILE("Checks.twin")); + await caretOn(c, "started = Timer", { offset: 4 }); + await command(c, "tbDebug_RunOrPreview"); + if (!(await waitFor(c, () => codeExecuting(c), { timeout: 30000, interval: 100 }))) { + throw new Error("F6 never started WaitAWhile"); + } + await frames(c); +} + +const debuggingShots = [ + debugPausedPanelsShot, + debugErrorPanelShot, + debugBreakpointGlyphsShot, + debugPausedLineShot, + debugToolbarPausedShot, + debugProcessBoxShot, + debugVariablesExpandedShot, + variablesRowShot("Variables_Edit", { edit: true }), + variablesRowShot("Variables_Tooltip", { edit: false }), + watchesShot("Watches_Values", { ended: false }), + watchesShot("Watches_NotAvailable", { ended: true }), + debugCallStackFramesShot, + debugHoverShot("Hover_Paused", { selection: false }), + debugHoverShot("Hover_Selection", { selection: true }), + debugConsoleRunShot, + debugConsoleMenuShot, +]; + +// The route a person takes: Standard EXE is selected on the New tab, and Open +// asks for its options. Cancel closes the dialog and creates nothing. Last +// of the setup, since the IDE has begun to start a project. +const newProjectOptionsShot = { + out: "Miscellaneous/Images/05306a72-4ff6-427d-8970-969ef0c582e6.png", + setup: "no-project", + take: ({ c }) => + inNewProject(c, async () => { + await clickInModal(c, "Open", ".msgBoxButton"); + if (!(await waitModal(c, "New Project Options", { timeout: 60000 }))) { + throw new Error("Open on Standard EXE did not show the New Project Options dialog"); + } + await dialogStill(c); + // the Project Name is selected: leave 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); +})()`); + try { + return await dialogShot(c, "New_Project_Options"); + } finally { + await closeModal(c, "Cancel"); + await sleep(1000); + } + }), +}; + +// About's last line reads the compiler's licence, which the IDE has only once a +// project has been loaded and its compiler has answered: so it is taken in the +// project setup. +const aboutShot = { + out: "IDE/Menu/Images/Menu_Help_About.png", + setup: "project", + async take({ c }) { + const ready = await waitFor( + c, + () => c.evaluate(`licenceIsSet === true && !/NOT READY/.test(compilerLicence.innerText)`), + { timeout: 60000, interval: 250 }, + ); + if (!ready) throw new Error("the compiler never answered the licence check, so About would say NOT READY"); + return inDialog(c, { command: "tbHelp_ShowAboutWindow", title: "About", close: "Close" }, () => + dialogShot(c, "Menu_Help_About"), + ); + }, +}; + +// A badge's tooltip is its plain HTML `title`, which WebView2 draws as a native Windows tooltip +// on a real mouse hover and in no other way: no capture holds it, and no mouse the tool can +// move reaches a private desktop (WIP.Screenshots.md, Special IDE states). So it is drawn in the page, as +// a replica of Windows 10's tooltip holding the badge's own title, read from the IDE: the +// owner's decision (2026-10-10). The look is calibrated against the pictures taken by hand in +// January: Segoe UI 9 pt (12 px) in #242424 on white, a 1 px #242424 border, 16 px lines, 7 px +// of padding at the sides, 3 above and 4 below, the box a whole number of pixels wide. It does +// not follow the IDE's theme, so the Light pass takes the dark pass's picture and keeps no file +// of its own. It is placed as Windows places a tooltip by the cursor, which is taken to be at +// the badge's middle: its left edge at the cursor, 21 px below it, or above the badge when it +// would not fit below (the status bar is at the bottom of the window), and inside the window. +const TOOLTIP_ID = "tbShotTooltip"; +const TOOLTIP_CSS = + "all:initial;position:fixed;z-index:2147483647;pointer-events:none;box-sizing:border-box;" + + "border:1px solid #242424;background:#fff;color:#242424;white-space:pre-line;" + + "font:12px/16px 'Segoe UI',sans-serif;padding:3px 7px 4px 7px;display:block"; + +// Puts the replica of the tooltip of the element `sel` into the page; returns its text and +// its rectangle in CSS pixels. Throws when the element has no title. +async function tooltipOn(c, sel) { + const r = await c.evaluate(`(() => { + const badge = document.querySelector(${JSON.stringify(sel)}); + if (!badge) return { error: "no such element" }; + const text = badge.title; + if (!text) return { error: "the element has no title" }; + document.getElementById(${JSON.stringify(TOOLTIP_ID)})?.remove(); + const e = document.createElement("div"); + e.id = ${JSON.stringify(TOOLTIP_ID)}; + e.style.cssText = ${JSON.stringify(TOOLTIP_CSS)}; + e.textContent = text; + document.body.appendChild(e); + e.style.left = "0px"; + e.style.top = "0px"; + e.style.width = Math.ceil(e.getBoundingClientRect().width) + "px"; + const t = e.getBoundingClientRect(); + const b = badge.getBoundingClientRect(); + const cursor = { x: b.x + b.width / 2, y: b.y + b.height / 2 }; + const left = Math.max(0, Math.min(window.innerWidth - t.width, Math.round(cursor.x))); + const below = Math.round(cursor.y) + 21; + const top = below + t.height <= window.innerHeight ? below : Math.round(b.y) - t.height - 2; + e.style.left = left + "px"; + e.style.top = top + "px"; + const f = e.getBoundingClientRect(); + return { text, rect: { x: f.x, y: f.y, width: f.width, height: f.height } }; + })()`); + if (r.error) throw new Error(`the tooltip of ${sel}: ${r.error}`); + return r; +} + +const tooltipOff = (c) => c.evaluate(`document.getElementById(${JSON.stringify(TOOLTIP_ID)})?.remove()`); + +// The picture of the replica alone, as the January ones are. +async function tooltipShotTake(c, name, sel) { + const { text, rect } = await tooltipOn(c, sel); + try { + say(c.shot.name, ` the tooltip text: ${JSON.stringify(text)}`); + return await capture(c, name, snapOut(rect), { away: () => parkMouse(c) }); + } finally { + await tooltipOff(c); + } +} + +// The status bar's services badge once the compiler's services have all answered. +const servicesBadgeReady = async (c) => { + await resetUi(c); + const ready = await waitFor( + c, + () => + c.evaluate( + `(() => { const e = document.getElementById("compilerStatusOuter"); return !!e && e.classList.contains("servicesOK") && e.innerText.includes("OPERATIONAL"); })()`, + ), + { timeout: 60000, interval: 250 }, + ); + if (!ready) throw new Error("the services badge never said OPERATIONAL"); +}; + +const servicesShot = { + out: "IDE/Images/Services_Operational.png", + setup: "project", + async take({ c }) { + await servicesBadgeReady(c); + return capture(c, "Services_Operational", snapOut(await rectOf(c, "#compilerStatusOuter")), { + away: () => parkMouse(c), + }); + }, +}; + +// The tooltip of that badge, the four services each OPERATIONAL (a replica, see TOOLTIP_CSS). +const servicesTooltipShot = { + out: "IDE/Images/Services_Operational_Tooltip.png", + setup: "project", + async take({ c }) { + await servicesBadgeReady(c); + return tooltipShotTake(c, "Services_Operational_Tooltip", "#compilerStatusOuter"); + }, +}; + +// The services badge with no compiler connected, as the IDE starts with no project. +const unavailableBadgeReady = async (c) => { + await resetUi(c); + const unavailable = await c.evaluate( + `!compilerStatusOuter.classList.contains("servicesOK") && !compilerStatusOuter.classList.contains("servicesPARTIAL") && /UNAVAILABLE/.test(compilerStatus.innerText)`, + ); + if (!unavailable) throw new Error("the services badge does not say UNAVAILABLE"); +}; + +// Its tooltip, each of the four services Not initialized (they read Disconnected only for 0.1 s +// between one compiler and the next), is the badge's own title, drawn as a replica. +const unavailableTooltipShot = { + out: "IDE/Images/Services_Unavailable_Tooltip.png", + setup: "no-project", + async take({ c }) { + await unavailableBadgeReady(c); + return tooltipShotTake(c, "Services_Unavailable_Tooltip", "#compilerStatusOuter"); + }, +}; + +const unavailableShot = { + out: "IDE/Images/Services_Unavailable.png", + setup: "no-project", + async take({ c }) { + await unavailableBadgeReady(c); say(c.shot.name, ` the badge's title: ${JSON.stringify(await c.evaluate("compilerStatusOuter.title"))}`); return capture(c, "Services_Unavailable", snapOut(await rectOf(c, "#compilerStatusOuter")), { away: () => parkMouse(c), @@ -5993,6 +8075,83 @@ const codeLensShots = [ ), ]; +// ---- memory: test/shots/memory, MemoryDemo, whose Sub Main stops with s holding fifteen +// characters (setup memory): the MEMORY 1 pane at the stop, `s` typed into its box and +// entered, which the IDE turns into StrPtr(s). The pane is sized to two rows, which the +// characters and their terminating null fill exactly. The string's address changes from run +// to run: the address column is set in the page once the rows are read, as the CodeLens +// pictures' console times are. The run is started before the pane floats and ended after. + +const MEMORY_PROJECT = path.join(REPO_ROOT, "test", "shots", "memory"); +const MEMORY_ADDRESSES = ["&H0F423AC8", "&H0F423AD8"]; +const MEMORY_TEXT = "H.e.l.l.o. .t.w.i.n.B.A.S.I.C..."; + +async function startMemory(run) { + run.step = "open"; + say(run.name, `opening ${path.relative(REPO_ROOT, MEMORY_PROJECT)}`); + return run.lane.open(MEMORY_PROJECT); +} + +// The pane's rows as the page shows them: their count, and the text column of each. +const memoryRows = (c) => + c.evaluate(`(() => { + const rows = [...hexEditorPanel.outerDiv.querySelectorAll(".hexedr tbody tr")]; + return { count: rows.length, text: rows.map((r) => [...r.querySelectorAll("td.acell")].map((d) => d.textContent).join("")).join("") }; +})()`); + +const memoryShot = panelShot("Memory_String", "MEMORY 1", [696, 120], { + setup: "memory", + async before(c) { + await startRun(c); + if (!(await waitFor(c, () => c.evaluate("!!context.atBreakpoint?.value"), { timeout: 60000, interval: 100 }))) { + throw new Error("the program did not stop at its Stop statement"); + } + }, + async prepare(c) { + // the pane's height for two rows: the rows follow the pane's size + const sel = panelSel("MEMORY 1"); + for (let i = 0; i < 20; i++) { + const { count } = await memoryRows(c); + if (count === 2) break; + await c.evaluate(`(() => { + const p = document.querySelector(${JSON.stringify(sel)}); + p.style.height = (p.getBoundingClientRect().height + (${count} < 2 ? 4 : -4)) + "px"; +})()`); + await frames(c); + } + if ((await memoryRows(c)).count !== 2) throw new Error("the MEMORY 1 pane would not show two rows"); + await c.evaluate( + `(() => { const i = hexEditorPanel.outerDiv.querySelector(".addr-input"); i.focus(); i.select(); })()`, + ); + await typeText(c, "s"); + await pressKey(c, "Enter"); + // The rows are drawn again once the debugger answers, and the cell at the address then + // takes the focus: the box can already read StrPtr(s) from the theme before. + const read = await waitFor( + c, + async () => + (await c.evaluate(`hexEditorPanel.outerDiv.querySelector(".addr-input").value`)) === "StrPtr(s)" && + (await c.evaluate(`!!document.activeElement?.closest?.(".hexedr td.cell")`)) && + (await memoryRows(c)).text === MEMORY_TEXT, + { timeout: 15000, interval: 100 }, + ); + if (!read) throw new Error(`the MEMORY 1 pane shows ${JSON.stringify(await memoryRows(c))}`); + await c.evaluate(`(() => { + const a = ${JSON.stringify(MEMORY_ADDRESSES)}; + hexEditorPanel.outerDiv.querySelectorAll(".hexedr tbody th.addr").forEach((t, i) => { t.textContent = a[i]; }); + document.activeElement?.blur(); +})()`); + await frames(c); + const shown = await c.evaluate( + `[...hexEditorPanel.outerDiv.querySelectorAll(".hexedr tbody th.addr")].map((t) => t.textContent)`, + ); + if (JSON.stringify(shown) !== JSON.stringify(MEMORY_ADDRESSES)) { + throw new Error(`the MEMORY 1 pane drew its addresses again: ${JSON.stringify(shown)}`); + } + }, + restore: (c) => stopRun(c), +}); + // ---- 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 @@ -7630,12 +9789,11 @@ const SHOTS = [ splashShot, menuBarShot, menuShot("Menu_File", "File"), - menuShot("Menu_Edit", "Edit"), - menuShot("Menu_View", "View"), menuShot("Menu_Project", "Project"), menuShot("Menu_Format", "Format"), - menuShot("Menu_Debug", "Debug"), - menuShot("Menu_Run", "Run"), + // the Debug menu while a program is paused, the Run menu while one runs + menuShot("Menu_Debug", "Debug", null, { setup: "debugging", before: pausedForMenu, after: debugReset }), + menuShot("Menu_Run", "Run", null, { setup: "debugging", before: runningForMenu, after: debugReset }), menuShot("Menu_Tools", "Tools"), menuShot("Menu_Add-Ins", "Add-Ins"), menuShot("Menu_Window", "Window"), @@ -7662,9 +9820,12 @@ const SHOTS = [ ...codeShots, ...sample9Shots, featureMapShot, + ...codeChapterShots, + ...debuggingShots, newProjectOptionsShot, ...programShots, ...codeLensShots, + memoryShot, packageBuildShot, fusionShot, ...packageServerShots, @@ -7686,6 +9847,14 @@ const PARTS = { { suffix: "-2", from: "IDE/Images/OpenEditors_1.png" }, { suffix: "-3", from: "IDE/Images/PackagePublishing_1.png" }, ], + "code-chapter": [ + { suffix: "-2", from: "IDE/Code/Images/Combos.png" }, + { suffix: "-3", from: "IDE/Code/Images/Paste_As_Comment.png" }, + ], + debugging: [ + { suffix: "-2", from: "IDE/Debugging/Images/Variables_Expanded.png" }, + { suffix: "-3", from: "IDE/Debugging/Images/CallStack_Frames.png" }, + ], customcontrols: [ { suffix: "-2", from: "Tutorials/CustomControls/Images/ccMyFieldClass.png" }, { suffix: "-3", from: "Tutorials/CustomControls/Images/ccCustomControlAttribute.png" }, @@ -7719,11 +9888,18 @@ const JOB_SECONDS = { code: 33, sample9: 11, featuremap: 39, + "code-chapter": 95, + "code-chapter-2": 85, + "code-chapter-3": 100, + debugging: 74, + "debugging-2": 64, + "debugging-3": 71, "no-project": 68, "no-project-2": 59, "no-project-3": 70, programs: 45, codelens: 35, + memory: 30, package: 25, fusion: 20, "package-server": 150, diff --git a/test/ide/code-editor.test.mjs b/test/ide/code-editor.test.mjs new file mode 100644 index 000000000..7f4174c95 --- /dev/null +++ b/test/ide/code-editor.test.mjs @@ -0,0 +1,479 @@ +// The code editor and the panels used while writing code, tested against seven entries in +// BUGS-TO-REPORT.md: +// +// "Format Selection from a continuation line fails with *IDE startup failure*, and closes an IDE +// that was opened with its project" (bugs/format-selection-continuation) +// "The Diagnostics panel's Copy and Copy All put HTML in the text, and label an info or a hint +// {WARNING}" (bugs/diagnostics-copy-html) +// "The Outline panel lists a file's modules and classes but none of their members" +// (bugs/outline-no-members) +// "Go To Definition, Ctrl+Up and Ctrl+Down put the caret on the line after a declaration" +// (bugs/definition-line-after) +// "Escape does not remove the extra cursors" (bugs/escape-keeps-cursors) +// "An editor tab opened after the compiler has reported its file is not tinted for the file's +// warnings or errors" (bugs/tab-tint-opened-later) +// "Shift+F10 opens the code editor's context menu at the top-left corner of the IDE window, not +// at the caret" (bugs/shift-f10-menu-corner) +// +// The project in probes/code-editor has two files: Editing.twin, whose Subs Continued and Plain +// are left unindented for Format Selection, and Helpers.twin, with the function Twice and two +// diagnostics, the info TB0012 and the warning TB0018, which its Settings set to those levels. +// +// The first test closes the IDE (the defect), and starts the lane's next IDE on the project. The +// lane opens the project on the IDE's command line, as a .twinproj opened from Explorer is; an IDE +// started from its icon prints the same failure in the DEBUG CONSOLE instead and stays open, which +// is not tested here. +// +// THE IDE SAVES ITS OPTIONS IN THE USER'S OWN REGISTRY (HKCU\Software\VB and VBA Program +// Settings\twinBASIC_IDE\IDESettings). So, as soon as the project is open, the page's +// hostAppObject is replaced by a proxy whose SaveIDESetting, ChangeWindowSetting and recent-list +// calls do nothing, as customising.test.mjs does. THE USER'S CLIPBOARD IS NEVER TOUCHED: the +// Diagnostics panel copies with navigator.clipboard.writeText, which in the IDE's WebView2 writes +// the Windows clipboard, so that and the host's copyClipboardIDE request are replaced by recorders +// first. Nothing here reaches SaveSetting, so lanes.mjs names no settings for this lane. +// +// The tests share the lane's IDE in order. Each test states what BETA 1005 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/Code/ 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 { readFileSync } from "node:fs"; +import path from "node:path"; +import { before, test } from "node:test"; +import { fileURLToPath } from "node:url"; +import { portTaken, sleep } from "../../scripts/lib/tb-ide.mjs"; +import { + answerMessageBox, + clickAt, + editorState, + messageBoxes, + openFile, + pressKey, + select, + setCursor, + 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", "code-editor"); +const SOURCES = "/CodeEditorProbe/Sources/"; +const EDITING = `${SOURCES}Editing.twin`; +const HELPERS = `${SOURCES}Helpers.twin`; +const read = (name) => readFileSync(path.join(PROJECT, "Sources", name), "utf8").split(/\r?\n/); +const EDITING_SRC = read("Editing.twin"); +const HELPERS_SRC = read("Helpers.twin"); +const lineIn = (src, text, from = 1) => { + const n = src.findIndex((l, i) => i >= from - 1 && l.includes(text)) + 1; + assert.ok(n > 0, `no line holding ${JSON.stringify(text)}`); + return n; +}; +const MAIN = lineIn(EDITING_SRC, "Public Sub Main()"); +const MAIN_DIM = lineIn(EDITING_SRC, "Dim total As Long"); +const MAIN_CALL = lineIn(EDITING_SRC, "total = Helpers.Twice(3)"); +const MAIN_PRINT = lineIn(EDITING_SRC, "Debug.Print total"); +const CONTINUED = lineIn(EDITING_SRC, "Public Sub Continued()"); +const CONTINUATION = lineIn(EDITING_SRC, "b As Long"); +const PLAIN = lineIn(EDITING_SRC, "Public Sub Plain()"); +const PLAIN_SELECT = lineIn(EDITING_SRC, "Select Case c"); +const PLAIN_END_SELECT = lineIn(EDITING_SRC, "End Select", PLAIN); +const TWICE = lineIn(HELPERS_SRC, "Public Function Twice("); +const BIG_VALUE = lineIn(HELPERS_SRC, "Public Const BigValue"); +const NARROWING = lineIn(HELPERS_SRC, "narrow = Twice(2)"); + +// Blocks every call that writes the user's registry, and records every copy instead of letting it +// reach the Windows clipboard (see the top of this file). +const GUARD_JS = `(() => { + if (window.__guarded) return; + window.__guarded = true; + const blocked = new Set(["SaveIDESetting", "ChangeWindowSetting", "AddRecentsList", "RemoveRecentsList"]); + hostAppObject = new Proxy(hostAppObject, { + get(target, key) { + if (blocked.has(key)) return async () => undefined; + return Reflect.get(target, key); + }, + }); + window.__copies = []; + navigator.clipboard.writeText = (t) => { window.__copies.push(String(t)); return Promise.resolve(); }; + navigator.clipboard.write = () => { window.__copies.push("(clipboard.write)"); return Promise.resolve(); }; + const request = debugSocket.request.bind(debugSocket); + debugSocket.request = function (method, params, cb, ...rest) { + if (method === "copyClipboardIDE") { window.__copies.push(String(params && params.text)); if (cb) cb({ body: {} }); return; } + return request(method, params, cb, ...rest); + }; +})()`; + +const lines = (c, from, to) => + c.evaluate(`Array.from({ length: ${to - from + 1} }, (_, i) => editor.getModel().getLineContent(${from} + i))`); +const selections = (c) => + c.evaluate(`editor.getSelections().map((s) => s.positionLineNumber + ":" + s.positionColumn)`); + +// The screen point of a line and column of the code editor. +const pointAt = (c, line, column) => + c.evaluate(`(() => { + editor.revealPositionInCenterIfOutsideViewport({ lineNumber: ${line}, column: ${column} }); + const p = editor.getScrolledVisiblePosition({ lineNumber: ${line}, column: ${column} }); + const r = editor.getDomNode().getBoundingClientRect(); + return p ? { x: r.x + p.left + 2, y: r.y + p.top + p.height / 2 } : null; + })()`); + +scenario("the code editor and its panels", (lane) => { + let c; + + // A right-click with real mouse events. + async function rightClick(x, y) { + for (const type of ["mouseMoved", "mousePressed", "mouseReleased"]) { + await c.send("Input.dispatchMouseEvent", { + type, + x, + y, + button: type === "mouseMoved" ? "none" : "right", + buttons: type === "mousePressed" ? 2 : 0, + clickCount: 1, + }); + } + await sleep(500); + } + + // Clicks the item of the open context menu that reads `label`. + async function pickMenuItem(label) { + const at = await c.evaluate(`(() => { + const it = [...document.querySelectorAll("#contextMenu > *")].find((e) => e.innerText.replace(/\\s+/g, " ").trim() === ${JSON.stringify(label)} && e.getBoundingClientRect().width); + if (!it) return null; + const q = it.getBoundingClientRect(); + return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; + })()`); + assert.ok(at, `the context menu has no item ${label}`); + await clickAt(c, at.x, at.y); + await sleep(500); + } + + // The rows of the Diagnostics panel: their text and where to click them. + const problemRows = () => + c.evaluate(`[...(problemsPanel.tree.sectionHeader.parentNode).querySelectorAll(".problemNodeItem")] + .map((e) => { const r = e.getBoundingClientRect(); return { text: e.innerText.replace(/\\s+/g, " ").trim(), x: r.x + 60, y: r.y + r.height / 2, shown: !!(r.width && r.height) }; }) + .filter((r) => r.shown)`); + + // Starts the lane's IDE on the project, with its page guarded, and opens Editing.twin. + async function start() { + // An ended IDE's WebView2 can hold the DevTools port for a while. + for (let i = 0; i < 150 && (await portTaken(lane.port)); i++) await sleep(200); + c = await lane.open(PROJECT); + await c.evaluate(GUARD_JS); + await openFile(c, EDITING, { line: MAIN, column: 1 }); + } + + const running = (pid) => { + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } + }; + + // Selects the continuation line of Continued and the line after it, chooses Edit > Format + // Selection, and returns the alert that opens, or null. + async function formatFromContinuation() { + c.dialogs.length = 0; + await select(c, { startLine: CONTINUATION, startColumn: 1, endLine: CONTINUATION + 1, endColumn: 999 }); + await c.evaluate(`executeIdeCommand("tbEditor_FormatSelection")`); + return waitFor(c, () => c.dialogs.find((d) => d.type === "alert"), { timeout: 5000 }); + } + + const checkAlert = (alert) => { + assert.ok(alert, "Format Selection opened no alert"); + assert.match(alert.message, /^IDE startup failure \(BETA twinBASIC IDE BETA \d+\):/); + assert.match(alert.message, /Assignment to constant variable\./); + assert.match(alert.message, /provideDocumentRangeFormattingEdits/); + }; + + before(start); + + test("Format Selection on a selection that starts on a continuation line opens an IDE startup failure alert, and the IDE then closes", async (t) => { + const pid = lane.run.pid; + const alert = await formatFromContinuation(); + t.diagnostic(JSON.stringify(alert?.message.split("\n").slice(0, 4).join(" / "))); + checkAlert(alert); + const ended = await waitFor(null, () => !running(pid), { timeout: 15 * 1000, interval: 250 }); + assert.ok(ended, "the IDE was still running 15 s after the alert"); + // The IDE has gone: the lane's next IDE is started on the same project. + c.dialogs.length = 0; + await lane.closeProject(); + await start(); + }); + + // start() opens Editing.twin only, so Helpers.twin, which has the warning TB0018, has no tab yet. + const HELPERS_NODE = `fs.tree.resolvePath(${JSON.stringify(`twinbasic:${HELPERS}`)})`; + const helpersTint = () => + c.evaluate(`(() => { + const n = ${HELPERS_NODE}; + const tab = n.openEditorNode && n.openEditorNode.tabElement; + return { row: n.treeItemClass || "", warnings: n.cachedDiagnostics_warningCount || 0, tab: tab ? tab.className : null }; + })()`); + + test("a tab opened on a file the compiler has already reported stays untinted, while its Project Explorer row is tinted", async (t) => { + const reported = await waitFor( + c, + async () => { + const s = await helpersTint(); + return s.row === "warning" ? s : null; + }, + { timeout: 30 * 1000 }, + ); + t.diagnostic(`before the tab opens: ${JSON.stringify(reported ?? (await helpersTint()))}`); + assert.ok(reported, "the Project Explorer row of Helpers.twin is not tinted for its warning"); + assert.equal(reported.tab, null, "Helpers.twin has a tab already"); + await openFile(c, HELPERS, { line: 1, column: 1 }); + await sleep(3000); + const now = await helpersTint(); + t.diagnostic(`3 s after the tab opened: ${JSON.stringify(now)}`); + assert.ok(now.tab, "Helpers.twin has no tab"); + assert.ok(now.tab.includes("tabItem"), `not a tab: ${now.tab}`); + assert.doesNotMatch(now.tab, /\b(warning|error)\b/, "the tab is tinted"); + assert.equal(now.row, "warning", "the Project Explorer row is no longer tinted"); + }); + + test("control: after an edit to the file and its undo, the compiler reports it again and its tab is tinted", async (t) => { + await openFile(c, HELPERS, { line: 1, column: 1 }); + const original = await c.evaluate("editor.getModel().getValue()"); + const version = await c.evaluate("editor.getModel().getAlternativeVersionId()"); + await c.evaluate( + `editor.executeEdits("code-editor.test", [{ range: new monaco.Range(1, editor.getModel().getLineMaxColumn(1), 1, editor.getModel().getLineMaxColumn(1)), text: " " }])`, + ); + const tinted = await waitFor( + c, + async () => { + const s = await helpersTint(); + return /\bwarning\b/.test(s.tab ?? "") ? s : null; + }, + { timeout: 15 * 1000, interval: 100 }, + ); + const undone = await c.evaluate(`(() => { + const m = editor.getModel(); + for (let i = 0; i < 20 && m.getAlternativeVersionId() !== ${version} && m.canUndo(); i++) editor.trigger("code-editor.test", "undo"); + return m.getAlternativeVersionId() === ${version}; + })()`); + t.diagnostic(`after the edit: ${JSON.stringify(tinted ?? (await helpersTint()))}`); + assert.ok(undone, "the edit could not be undone"); + assert.equal(await c.evaluate("editor.getModel().getValue()"), original); + assert.ok(tinted, "the tab of Helpers.twin was not tinted after the edit"); + }); + + // The open context menu's place, or null when none is open. + const menuRect = () => + c.evaluate(`(() => { + const m = document.getElementById("contextMenu"); + const r = m && m.getBoundingClientRect(); + return r && r.width && m.children.length ? { x: r.x, y: r.y, items: [...m.children].map((e) => e.innerText.replace(/\\s+/g, " ").trim()).filter(Boolean) } : null; + })()`); + async function closeMenu() { + await pressKey(c, "Escape"); + assert.ok(await waitFor(c, async () => !(await menuRect())), "the context menu stayed open after Escape"); + } + + test("control: a right-click in the code opens the editor's menu at the pointer", async (t) => { + await openFile(c, EDITING, { line: MAIN_PRINT, column: 9 }); + const at = await pointAt(c, MAIN_PRINT, 9); + assert.ok(at, "the line is not on screen"); + await rightClick(at.x, at.y); + const menu = await menuRect(); + t.diagnostic(`clicked at ${Math.round(at.x)},${Math.round(at.y)}; the menu: ${JSON.stringify(menu)}`); + assert.ok(menu, "no menu opened"); + assert.match(menu.items[0], /^Go To Definition\b/); + assert.ok(Math.abs(menu.x - at.x) < 30 && Math.abs(menu.y - at.y) < 30, "the menu is not at the pointer"); + await closeMenu(); + }); + + test("Shift+F10 opens the editor's menu at the top-left corner of the IDE window, not at the caret", async (t) => { + await setCursor(c, MAIN_PRINT, 9); + const caret = await pointAt(c, MAIN_PRINT, 9); + assert.equal(await menuRect(), null, "a menu is open already"); + await pressKey(c, "F10", { shift: true }); + const menu = await waitFor(c, menuRect, { timeout: 5000 }); + t.diagnostic(`the caret at ${Math.round(caret.x)},${Math.round(caret.y)}; the menu: ${JSON.stringify(menu)}`); + assert.ok(menu, "Shift+F10 opened no menu"); + assert.match(menu.items[0], /^Go To Definition\b/); + assert.ok(caret.x > 100 && caret.y > 100, "the caret is too near the corner to tell"); + assert.deepEqual([Math.round(menu.x), Math.round(menu.y)], [0, 0]); + await closeMenu(); + }); + + test("Escape leaves the two cursors that Ctrl+Alt+Down added", async (t) => { + await openFile(c, EDITING, { line: MAIN_DIM, column: 9 }); + await setCursor(c, MAIN_DIM, 9); + await pressKey(c, "ArrowDown", { ctrl: true, alt: true }); + await pressKey(c, "ArrowDown", { ctrl: true, alt: true }); + await sleep(300); + const added = await selections(c); + t.diagnostic(`after Ctrl+Alt+Down twice: ${JSON.stringify(added)}`); + assert.equal(added.length, 3, "Ctrl+Alt+Down twice did not give three cursors"); + await pressKey(c, "Escape"); + await sleep(300); + assert.deepEqual(await selections(c), added, "the first Escape changed the cursors"); + await pressKey(c, "Escape"); + await sleep(300); + assert.deepEqual(await selections(c), added, "the second Escape changed the cursors"); + }); + + test("control: a click in the code goes back to one cursor", async () => { + assert.ok((await selections(c)).length > 1, "there are not several cursors to start from"); + const at = await pointAt(c, MAIN_PRINT, 9); + assert.ok(at, "the line is not on screen"); + await clickAt(c, at.x, at.y); + await sleep(300); + assert.deepEqual(await selections(c), [`${MAIN_PRINT}:9`]); + }); + + test("control: Go To Definition on a local variable puts the caret on its Dim line", async () => { + const col = EDITING_SRC[MAIN_PRINT - 1].indexOf("total") + 2; + await setCursor(c, MAIN_PRINT, col); + await pressKey(c, "F12"); + const s = await waitFor(c, async () => { + const e = await editorState(c); + return e.line !== MAIN_PRINT ? e : null; + }); + assert.ok(s, "F12 did not move the caret"); + assert.equal(s.file, EDITING); + assert.equal(s.line, MAIN_DIM); + }); + + test("Go To Definition on a function in another module puts the caret on the line after its declaration", async (t) => { + await openFile(c, EDITING, { line: MAIN_CALL, column: 1 }); + const col = EDITING_SRC[MAIN_CALL - 1].indexOf("Twice") + 2; + await setCursor(c, MAIN_CALL, col); + await pressKey(c, "F12"); + const s = await waitFor(c, async () => { + const e = await editorState(c); + return e.file === HELPERS ? e : null; + }); + assert.ok(s, "F12 did not open Helpers.twin"); + await sleep(800); + const e = await editorState(c); + t.diagnostic( + `Twice is declared on line ${TWICE}; the caret is at ${e.line}:${e.column}, ${JSON.stringify(e.lineText)}`, + ); + assert.equal(e.line, TWICE + 1); + }); + + test("Ctrl+Down puts the caret on the line after the next procedure's declaration, and Ctrl+Up on the line after its own procedure's", async (t) => { + await openFile(c, EDITING, { line: MAIN_DIM, column: 1 }); + await setCursor(c, MAIN_DIM, 1); + await pressKey(c, "ArrowDown", { ctrl: true }); + await sleep(500); + const down = await editorState(c); + t.diagnostic(`Continued is declared on line ${CONTINUED}; Ctrl+Down put the caret at ${down.line}:${down.column}`); + assert.equal(down.line, CONTINUED + 1); + await setCursor(c, PLAIN + 2, 1); + await pressKey(c, "ArrowUp", { ctrl: true }); + await sleep(500); + const up = await editorState(c); + t.diagnostic( + `Plain is declared on line ${PLAIN}; Ctrl+Up from line ${PLAIN + 2} put the caret at ${up.line}:${up.column}`, + ); + assert.equal(up.line, PLAIN + 1); + }); + + test("the Diagnostics panel's Copy puts the code in a , and Copy All labels the info {WARNING}", async (t) => { + await c.evaluate(`executeIdeCommand("tbProblems_ShowPanel"); executeIdeCommand("tbProblems_TurnShowInfosOn")`); + const rows = await waitFor( + c, + async () => { + const r = await problemRows(); + return r.some((x) => x.text.startsWith("TB0018")) && r.some((x) => x.text.startsWith("TB0012")) ? r : null; + }, + { timeout: 30 * 1000 }, + ); + assert.ok(rows, `the Diagnostics panel does not show TB0018 and TB0012: ${JSON.stringify(await problemRows())}`); + const warning = rows.find((x) => x.text.startsWith("TB0018")); + + await c.evaluate("window.__copies.length = 0"); + await rightClick(warning.x, warning.y); + await pickMenuItem("Copy"); + const copy = await c.evaluate("window.__copies.splice(0)"); + t.diagnostic(`Copy: ${JSON.stringify(copy)}`); + assert.equal(copy.length, 1, "Copy copied nothing, or more than once"); + assert.match( + copy[0], + new RegExp( + `^\\{WARNING\\} ${HELPERS.replace(/\//g, "\\/")} \\[${NARROWING},\\d+\\]: TB0018implicit narrowing`, + ), + ); + + await rightClick(warning.x, warning.y); + await pickMenuItem("Copy All"); + const all = await c.evaluate("window.__copies.splice(0)"); + t.diagnostic(`Copy All: ${JSON.stringify(all)}`); + assert.equal(all.length, 1, "Copy All copied nothing, or more than once"); + assert.match(all[0], /^twinBASIC \(twinBASIC IDE BETA \d+\) compilation error report:\r\n/); + assert.match( + all[0], + new RegExp(` \\{WARNING\\} ${HELPERS.replace(/\//g, "\\/")} \\[${BIG_VALUE},\\d+\\]: TB0012`), + ); + assert.match(all[0], / TB0018<\/span>/); + }); + + test("the Outline lists the module Editing and none of its procedures", async (t) => { + await c.evaluate(`executeIdeCommand("tbOutline_ShowPanel")`); + await openFile(c, HELPERS); + await openFile(c, EDITING); + const symbols = await waitFor( + c, + async () => { + const s = await c.evaluate( + `outlinePanel && outlinePanel.cachedSymbols ? JSON.stringify(outlinePanel.cachedSymbols.map((s) => ({ name: s.name, children: (s.children || []).map((k) => k.name) }))) : null`, + ); + return s && s.includes('"Editing"') ? JSON.parse(s) : null; + }, + { timeout: 15 * 1000 }, + ); + await sleep(500); + const text = await c.evaluate(`outlinePanel.tree.sectionHeader.parentNode.innerText.replace(/\\s+/g, " ").trim()`); + t.diagnostic(`symbols: ${JSON.stringify(symbols)}; panel: ${JSON.stringify(text)}`); + assert.ok(symbols, "the Outline never had the symbols of Editing.twin"); + assert.deepEqual(symbols, [{ name: "Editing", children: [] }]); + for (const name of ["Main", "Continued", "Plain"]) { + assert.ok(!text.includes(name), `the Outline shows ${name}: ${JSON.stringify(text)}`); + } + }); + + test("control: Format Selection re-indents a selection that starts on a Select Case line", async (t) => { + c.dialogs.length = 0; + await select(c, { startLine: PLAIN_SELECT, startColumn: 1, endLine: PLAIN_END_SELECT, endColumn: 999 }); + await c.evaluate(`executeIdeCommand("tbEditor_FormatSelection")`); + const want = [" Select Case c", " Case 1", " c = 2", " End Select"]; + const got = await waitFor(c, async () => { + const l = await lines(c, PLAIN_SELECT, PLAIN_END_SELECT); + return l.join("|") === want.join("|") ? l : null; + }); + t.diagnostic(JSON.stringify(await lines(c, PLAIN_SELECT, PLAIN_END_SELECT))); + assert.ok(got, "Format Selection did not re-indent the four lines"); + assert.deepEqual(c.dialogs.splice(0), [], "Format Selection opened an alert"); + }); + + // The control above has left the project with unsaved changes. + test("with unsaved changes, the same Format Selection changes nothing, and the IDE asks to save them before it closes", async (t) => { + const pid = lane.run.pid; + const original = await lines(c, CONTINUATION - 1, CONTINUATION + 1); + const alert = await formatFromContinuation(); + checkAlert(alert); + const box = await waitFor(c, async () => (await messageBoxes(c)).pop()); + t.diagnostic(`the box: ${JSON.stringify(box)}`); + assert.ok(box, "no message box opened after the alert"); + assert.equal(box.text.trim(), "You've made changes to this project. Save now?"); + assert.deepEqual(box.buttons, ["Save", "Discard Changes", "Cancel"]); + await answerMessageBox(c, "Cancel"); + await sleep(1500); + assert.ok(running(pid), "Cancel did not keep the IDE running"); + assert.deepEqual(await messageBoxes(c), [], "the box stayed open after Cancel"); + assert.deepEqual( + await lines(c, CONTINUATION - 1, CONTINUATION + 1), + original, + "Format Selection changed the lines", + ); + c.dialogs.length = 0; + }); +}); diff --git a/test/ide/code-find.test.mjs b/test/ide/code-find.test.mjs new file mode 100644 index 000000000..c15b88939 --- /dev/null +++ b/test/ide/code-find.test.mjs @@ -0,0 +1,304 @@ +// The Find / Replace panel, tested against five entries in BUGS-TO-REPORT.md: +// +// "Find with Whole Word Only never finds a match at the very start of the file" +// (bugs/find-whole-word-file-start) +// "Find with Regular Expressions finds nothing when the pattern holds a capital letter" +// (bugs/find-regex-capital-letter) +// "A regular-expression match that spans a line break is shown as an empty selection, and Find +// Next does not move past it" (bugs/find-regex-line-break) +// "The Find / Replace panel's *Invalid RegExp:* alert leaves out the reason" +// (bugs/find-regex-alert-reason) +// "After a search in Selected Text, the scope stays on a greyed Selected Text, and Find Next stops +// at once" (bugs/find-scope-stuck-selected-text) +// +// The project in probes/code-find holds one file, Finder.twin, whose first word, Module, is at +// line 1, column 1. Each test starts the search from a place it sets, with the options it needs +// set by clicking their boxes, and records what each press of Find Next gives: the selection, an +// IDE message box (answered No when it has a No, else OK) or a javascript alert. +// +// THE IDE SAVES ITS OPTIONS IN THE USER'S OWN REGISTRY (HKCU\Software\VB and VBA Program +// Settings\twinBASIC_IDE\IDESettings), the Find What history and the panel's place among them. So, +// as soon as the project is open, the page's hostAppObject is replaced by a proxy whose +// SaveIDESetting, ChangeWindowSetting and recent-list calls do nothing, as customising.test.mjs +// does. Nothing here reaches SaveSetting, so lanes.mjs names no settings for this lane. +// +// The tests share the lane's IDE in order. Each test states what BETA 1005 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/Code/ 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 { readFileSync } from "node:fs"; +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 { + answerMessageBox, + clickAt, + editorState, + messageBoxes, + openFile, + pressKey, + select, + 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", "code-find"); +const FILE = "/CodeFindProbe/Sources/Finder.twin"; +const SOURCE = readFileSync(path.join(PROJECT, "Sources", "Finder.twin"), "utf8").split(/\r?\n/); +const lineOf = (text) => { + const n = SOURCE.findIndex((l) => l.includes(text)) + 1; + assert.ok(n > 0, `Finder.twin has no line holding ${JSON.stringify(text)}`); + return n; +}; +const END_MODULE = lineOf("End Module"); +const FIRST_END_SUB = lineOf("End Sub"); +const MAIN_DIM = lineOf("Dim total As Long"); +const MAIN_PRINT = lineOf('Debug.Print "total is "'); +const SECOND_PRINT = lineOf('Debug.Print "second"'); + +const SEARCHED = "The specified region has been searched."; +const END_OF_SCOPE = "End of search scope has been reached; do you want to continue from the beginning?"; +const isBox = (x, text) => typeof x === "string" && x.startsWith("BOX ") && x.includes(text); + +// Blocks every call that writes the user's registry (see the top of this file). +const GUARD_JS = `(() => { + if (window.__guarded) return; + window.__guarded = true; + const blocked = new Set(["SaveIDESetting", "ChangeWindowSetting", "AddRecentsList", "RemoveRecentsList"]); + hostAppObject = new Proxy(hostAppObject, { + get(target, key) { + if (blocked.has(key)) return async () => undefined; + return Reflect.get(target, key); + }, + }); +})()`; + +const F = "findReplacePanel._this"; +const ROOT = `${F}.sectionHeader.parentNode`; +const BOXES = { + whole: "findWholeWordGroupInput", + mcase: "findMatchCaseGroupInput", + pattern: "findUsePatternMatchingInput", + regex: "findUseRegExpInput", + file: "findScopeFileButton", + selected: "findScopeSelectedTextButton", +}; + +// Each option and scope as "x" (ticked) or "-", with "(off)" when it is greyed. +const PANEL_JS = `(() => { + const t = ${F}; + const st = (e) => (e.checked ? "x" : "-") + (e.disabled ? "(off)" : ""); + return { find: t.findWhatInput.value, ${Object.entries(BOXES) + .map(([k, v]) => `${k}: st(t.${v})`) + .join(", ")} }; +})()`; + +// The centre of an element of the page, or null when it has no size. +const centreOf = (expr) => `(() => { + const e = ${expr}; + if (!e) return null; + const q = e.getBoundingClientRect(); + return q.width && q.height ? { x: q.x + q.width / 2, y: q.y + q.height / 2 } : null; +})()`; + +// A button of the panel by its caption. +const button = (caption) => + `[...${ROOT}.querySelectorAll("input[type=button], button, div, span")].find((b) => ((b.value || "") === ${JSON.stringify(caption)} || (b.children.length === 0 && b.textContent.trim() === ${JSON.stringify(caption)})) && b.getBoundingClientRect().width)`; + +scenario("the Find / Replace panel", (lane) => { + let c; + + const panel = () => c.evaluate(PANEL_JS); + + async function clickElement(expr, what) { + const at = await c.evaluate(centreOf(expr)); + assert.ok(at, `${what} is not on screen`); + await clickAt(c, at.x, at.y); + await sleep(300); + } + + // Ticks or clears an option of the panel, by clicking it, until it reads as wanted: ticking Match + // Case clears Regular Expressions and the reverse, so each is set by what it reads now. + async function setOption(key, want) { + if ((await panel())[key].startsWith("x") !== want) await clickElement(`${F}.${BOXES[key]}`, `the box ${key}`); + assert.equal((await panel())[key].startsWith("x"), want, `the box ${key} did not change`); + } + + async function setOptions(want) { + for (const k of ["whole", "mcase", "pattern", "regex"]) await setOption(k, !!want[k]); + } + + // Types into Find What, replacing what it holds. + async function setFind(text) { + await clickElement(`${F}.findWhatInput`, "Find What"); + await pressKey(c, "a", { ctrl: true }); + await typeText(c, text); + assert.equal((await panel()).find, text, "Find What does not hold what was typed"); + } + + // Presses Find Next `n` times. Each press gives ":-: ", "BOX + // " for a message box, which is then answered, or "ALERT " for a javascript alert. + async function findNext(n) { + const out = []; + for (let i = 0; i < n; i++) { + await clickElement(button("Find Next"), "the Find Next button"); + await sleep(800); + const alerts = c.dialogs.splice(0); + for (const d of alerts) out.push(`ALERT ${d.message}`); + const boxes = await messageBoxes(c); + if (boxes.length) { + const top = boxes[boxes.length - 1]; + out.push(`BOX ${top.text.replace(/\s+/g, " ").trim()}`); + await answerMessageBox(c, top.buttons.includes("No") ? "No" : top.buttons[0]); + await sleep(400); + continue; + } + if (alerts.length) continue; + const s = await editorState(c); + out.push( + `${s.selection.startLine}:${s.selection.startColumn}-${s.selection.endLine}:${s.selection.endColumn} ${s.selectedText}`, + ); + } + return out; + } + + // A point in the code editor on `line`, right of its text, that no floating panel covers. + async function pointOnLine(line) { + const at = await c.evaluate(`(() => { + editor.revealLineInCenter(${line}); + const p = editor.getScrolledVisiblePosition({ lineNumber: ${line}, column: 1 }); + const r = editor.getDomNode().getBoundingClientRect(); + const lines = editor.getDomNode().querySelector(".view-lines"); + if (!p) return null; + const y = r.y + p.top + p.height / 2; + for (let x = r.right - 40; x > r.x + 200; x -= 20) { + const e = document.elementFromPoint(x, y); + if (e && lines.contains(e)) return { x, y }; + } + return null; + })()`); + assert.ok(at, `no point on line ${line} of the code editor is clear of the panel`); + return at; + } + + before(async () => { + c = await lane.open(PROJECT); + await c.evaluate(GUARD_JS); + await openFile(c, FILE, { line: MAIN_DIM, column: 13 }); + await setCursor(c, MAIN_DIM, 13); + await pressKey(c, "f", { ctrl: true }); + assert.ok( + await waitFor(c, () => + c.evaluate(`typeof findReplacePanel === "object" && !!${F} && !!(${centreOf(`${F}.findWhatInput`)})`), + ), + "Ctrl+F did not show the Find / Replace panel", + ); + await sleep(500); + const p = await panel(); + assert.equal(p.file, "x", `Ctrl+F did not tick Current File: ${JSON.stringify(p)}`); + }); + + test("control: without Whole Word Only, Find Next finds Module at line 1, column 1 after End Module", async (t) => { + await setOptions({}); + await setFind("Module"); + await setCursor(c, 2, 1); + const r = await findNext(2); + t.diagnostic(JSON.stringify(r)); + assert.deepEqual(r, [`${END_MODULE}:5-${END_MODULE}:11 Module`, "1:1-1:7 Module"]); + }); + + test("with Whole Word Only, Find Next never finds Module at line 1, column 1", async (t) => { + await setOptions({ whole: true }); + await setFind("Module"); + await setCursor(c, 2, 1); + const r = await findNext(3); + t.diagnostic(JSON.stringify(r)); + assert.equal(r[0], `${END_MODULE}:5-${END_MODULE}:11 Module`, "the first press did not find End Module"); + assert.ok(isBox(r[1], SEARCHED), "the second press did not end the search"); + assert.ok(!r.some((x) => x.startsWith("1:1-")), `a press found line 1, column 1: ${JSON.stringify(r)}`); + }); + + test("control: a regular expression in lower case, or with the i flag, finds End Sub", async (t) => { + await setOptions({ regex: true }); + for (const re of ["/end sub/", "/End Sub/i"]) { + await setFind(re); + await setCursor(c, 1, 1); + const r = await findNext(1); + t.diagnostic(`${re}: ${JSON.stringify(r)}`); + assert.deepEqual(r, [`${FIRST_END_SUB}:5-${FIRST_END_SUB}:12 End Sub`], `${re} did not find End Sub`); + } + }); + + test("a regular expression with a capital letter, /End Sub/, finds nothing", async (t) => { + await setOptions({ regex: true }); + const p = await panel(); + assert.equal(p.mcase, "-", "Match Case is ticked with Regular Expressions"); + await setFind("/End Sub/"); + await setCursor(c, 1, 1); + const r = await findNext(1); + t.diagnostic(JSON.stringify(r)); + assert.equal(r.length, 1); + assert.ok(isBox(r[0], SEARCHED), `/End Sub/ found something: ${JSON.stringify(r)}`); + }); + + test("a match of /^\\s+end sub$/, which spans a line break, is an empty selection, and Find Next stays on it", async (t) => { + await setOptions({ regex: true }); + await setFind("/^\\s+end sub$/"); + await setCursor(c, 1, 1); + const r = await findNext(2); + t.diagnostic(JSON.stringify(r)); + const before = FIRST_END_SUB - 1; + const end = SOURCE[before - 1].length + 1; + const empty = `${before}:${end}-${before}:${end} `; + assert.deepEqual(r, [empty, empty]); + }); + + test("the alert for an invalid regular expression, /(/, reads only Invalid RegExp:", async (t) => { + await setOptions({ regex: true }); + await setFind("/(/"); + await setCursor(c, 1, 1); + const r = await findNext(1); + t.diagnostic(JSON.stringify(r)); + assert.deepEqual(r, ["ALERT Invalid RegExp:"]); + }); + + test("after a search in Selected Text and a click in the code, Selected Text stays ticked but greyed, and Find Next stops at once", async (t) => { + await setOptions({}); + await select(c, { startLine: MAIN_DIM, startColumn: 1, endLine: MAIN_PRINT, endColumn: 999 }); + await pressKey(c, "f", { ctrl: true }); + await sleep(500); + await setFind("total"); + let p = await panel(); + t.diagnostic(`with the selection: ${JSON.stringify(p)}`); + assert.equal(p.selected, "x", `a selection over three lines did not tick Selected Text: ${JSON.stringify(p)}`); + const first = await findNext(1); + t.diagnostic(`in the selection: ${JSON.stringify(first)}`); + assert.match( + first[0], + new RegExp(`^${MAIN_DIM}:\\d+-${MAIN_DIM}:\\d+ total$`), + "Find Next did not find total in the selection", + ); + + const at = await pointOnLine(SECOND_PRINT); + await clickAt(c, at.x, at.y); + await sleep(600); + const s = await editorState(c); + assert.equal(s.line, SECOND_PRINT, "the click did not put the caret on the line clicked"); + p = await panel(); + t.diagnostic(`after the click: ${JSON.stringify(p)}`); + assert.equal(p.selected, "x(off)", `Selected Text is not ticked and greyed: ${JSON.stringify(p)}`); + assert.equal(p.file, "-", `Current File is ticked: ${JSON.stringify(p)}`); + const r = await findNext(1); + t.diagnostic(`Find Next: ${JSON.stringify(r)}`); + assert.equal(r.length, 1); + assert.ok(isBox(r[0], END_OF_SCOPE), `Find Next did not stop at once: ${JSON.stringify(r)}`); + }); +}); diff --git a/test/ide/customising.test.mjs b/test/ide/customising.test.mjs new file mode 100644 index 000000000..242d96a7a --- /dev/null +++ b/test/ide/customising.test.mjs @@ -0,0 +1,322 @@ +// Customising the IDE: custom themes and keyboard-shortcut groups, tested against three entries in +// BUGS-TO-REPORT.md: +// +// "A `Dark.theme` or `Light.theme` in `%APPDATA%\twinBASIC\themes` stops every custom theme from +// inheriting, and Reload from disk from applying the theme" (bugs/theme-override-breaks-inheritance) +// "Two themes that inherit from each other freeze the IDE, and choosing one fails with a stack +// overflow" (bugs/theme-inherit-loop) +// "A keyboard-shortcut group whose name contains `|` switches other groups on and off" +// (bugs/keyboard-group-bar-name) +// +// No project is opened: each IDE starts as from its icon, and the New / Open dialog is cancelled. +// The custom theme files go only into the lane's own APPDATA (\appdata\twinBASIC\themes), which +// the IDE reads as its common themes folder. +// +// THE IDE SAVES ITS OPTIONS IN THE USER'S OWN REGISTRY, HKCU\Software\VB and VBA Program +// Settings\twinBASIC_IDE\IDESettings, whatever its APPDATA is: choosing a theme writes colorTheme, and +// the keyboard dialog and menu write CUSTOM_KEYBOARD_SHORTCUT_GROUPS and +// ACTIVE_KEYBOARD_SHORTCUT_GROUPS. So, as soon as each IDE's page has loaded and before anything is +// chosen, the page's hostAppObject is replaced by a proxy whose SaveIDESetting, ChangeWindowSetting +// and recent-list calls do nothing (as shoot_docs.mjs's PAGE_DEFAULTS does). Nothing here reaches +// SaveSetting, so lanes.mjs names no settings for this lane. +// +// The tests share the lane's IDE in order: the first two run in an IDE started with one custom theme, +// AtStart (inherits light); the third starts a new IDE with a Dark.theme beside it; the fourth uses +// whichever IDE is open. Each test states what BETA 1005 does. If one fails after an IDE update, the +// IDE has changed: update the entry in BUGS-TO-REPORT.md, or its report in bugs/filed/, the WARNING on +// docs/IDE/Customising/Themes.md or Keyboard-Shortcuts.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 { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { test } from "node:test"; +import { portTaken, sleep } from "../../scripts/lib/tb-ide.mjs"; +import { clickAt, pressKey, typeText, waitFor } from "../../scripts/lib/tb-operate.mjs"; +import { scenario } from "../addin/scenario.mjs"; + +const RED = "rgb(255, 0, 0)"; +const GREEN = "rgb(0, 128, 0)"; +const LOOP_A = "rgb(1, 1, 1)"; +const theme = (inherits, menu) => `inherits: ${inherits};\nMenuBackColor: ${menu};\n`; + +// What the page knows of its themes and the colours applied now. +const THEME_STATE_JS = `(() => { + const v = (n) => getComputedStyle(document.documentElement).getPropertyValue(n).trim(); + return { + colorTheme: liveIDEOptions.colorTheme, + darkMode: document.body.classList.contains("darkMode"), + menu: v("--themeMenuBackColor"), + loaded: customThemes.length, + expected: customThemesCount, + errors: window.__errors.slice(), + }; +})()`; + +// Blocks every call that writes the user's registry, records the page's uncaught errors, and keeps a +// heartbeat that measures the longest time the page did not run. +const GUARD_JS = `(() => { + if (window.__guarded) return; + window.__guarded = true; + const blocked = new Set(["SaveIDESetting", "ChangeWindowSetting", "AddRecentsList", "RemoveRecentsList"]); + hostAppObject = new Proxy(hostAppObject, { + get(target, key) { + if (blocked.has(key)) return async () => undefined; + return Reflect.get(target, key); + }, + }); + window.__errors = []; + window.addEventListener("error", (e) => window.__errors.push(String(e.message))); + window.__maxGap = 0; + let last = performance.now(); + setInterval(() => { + const now = performance.now(); + window.__maxGap = Math.max(window.__maxGap, now - last); + last = now; + }, 50); +})()`; + +scenario("customising the IDE", (lane) => { + let c = null; + const common = path.join(lane.appdata, "twinBASIC", "themes"); + const writeTheme = (name, text) => { + mkdirSync(common, { recursive: true }); + writeFileSync(path.join(common, `${name}.theme`), text, "utf8"); + }; + + // A new IDE with no project, its New / Open dialog cancelled and its page guarded. + async function startIde() { + if (lane.run) await lane.closeProject(); + // An ended IDE's WebView2 can hold the DevTools port for a while. + for (let i = 0; i < 150 && (await portTaken(lane.port)); i++) await sleep(200); + c = await lane.openNoProject(); + const ready = await waitFor( + c, + () => + c.evaluate( + `(() => { try { return typeof liveIDEOptions === "object" && [...document.querySelectorAll(".modalTitleBar")].some((b) => b.innerText.includes("New / Open")); } catch { return false; } })()`, + ), + { timeout: 90 * 1000, interval: 250 }, + ); + assert.ok(ready, "the IDE showed no New / Open dialog"); + await sleep(1500); + await c.evaluate(GUARD_JS); + await clickModalButton("Cancel"); + assert.deepEqual(await modalTitles(), [], "the New / Open dialog did not close"); + return c; + } + + const modalTitles = () => + c.evaluate( + `[...document.querySelectorAll(".modalDialogContainer")].filter((m) => m.getBoundingClientRect().width).map((m) => (m.querySelector(".modalTitleBar")?.innerText || "").trim())`, + ); + + // Clicks the button of the top modal dialog that reads `caption`. + async function clickModalButton(caption) { + const at = await c.evaluate(`(() => { + const ms = [...document.querySelectorAll(".modalDialogContainer")].filter((m) => m.getBoundingClientRect().width); + const m = ms[ms.length - 1]; + if (!m) return null; + const b = [...m.querySelectorAll("button, .dialogButton, .msgBoxButton, [class*=Button]")] + .find((e) => e.textContent.trim() === ${JSON.stringify(caption)} && e.getBoundingClientRect().width); + if (!b) return null; + const q = b.getBoundingClientRect(); + return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; + })()`); + assert.ok(at, `no button "${caption}" in the top dialog`); + await clickAt(c, at.x, at.y); + await sleep(600); + } + + // The Window > Theme list, as its items read: a ticked one starts "[x] ". + async function themeMenu() { + await c.evaluate(`executeIdeCommand("tbThemes_ShowList")`); + await sleep(600); + const items = await c.evaluate( + `[...document.querySelectorAll("#contextMenu > *")].map((e) => (e.querySelector(".codicon-check") ? "[x] " : "") + e.innerText.trim()).filter((t) => t)`, + ); + await c.evaluate(`hideContextMenu()`); + await sleep(200); + return items; + } + + // Clicks the item of the Window > Theme list that is `label` or starts "`label` (". + async function chooseTheme(label) { + await c.evaluate(`executeIdeCommand("tbThemes_ShowList")`); + await sleep(600); + const at = await c.evaluate(`(() => { + const l = ${JSON.stringify(label)}; + const it = [...document.querySelectorAll("#contextMenu > *")].find((e) => { const t = e.innerText.trim(); return t === l || t.startsWith(l + " ("); }); + if (!it) return null; + const q = it.getBoundingClientRect(); + return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; + })()`); + assert.ok(at, `the theme list has no item ${label}`); + await clickAt(c, at.x, at.y); + await sleep(800); + await c.evaluate(`try { hideContextMenu() } catch {}`); + } + + const themeState = () => c.evaluate(THEME_STATE_JS); + + // Waits until the page has read AtStart.theme again with `colour` in it. + const waitForAtStartText = (colour) => + waitFor( + c, + () => c.evaluate(`customThemes.some((t) => t.name === "AtStart" && t.text.includes(${JSON.stringify(colour)}))`), + { timeout: 20 * 1000 }, + ); + + test("without a Dark.theme of its own, a custom theme inherits Light and Reload from disk applies an edit", async () => { + writeTheme("AtStart", theme("light", RED)); + await startIde(); + const menu = await themeMenu(); + assert.ok(menu.includes("AtStart (Light)"), `AtStart is not listed as AtStart (Light): ${JSON.stringify(menu)}`); + assert.ok(menu.includes("Classic (Light)"), `Classic is not listed as Classic (Light): ${JSON.stringify(menu)}`); + await chooseTheme("AtStart"); + let s = await themeState(); + assert.equal(s.darkMode, false, `AtStart did not give the light base: ${JSON.stringify(s)}`); + assert.equal(s.menu, RED, `AtStart's menu colour is not applied: ${JSON.stringify(s)}`); + writeTheme("AtStart", theme("light", GREEN)); + await chooseTheme("Reload from disk"); + assert.ok(await waitForAtStartText(GREEN), "Reload from disk did not read AtStart.theme again"); + s = await themeState(); + for (let i = 0; i < 25 && s.menu !== GREEN; i++) { + await sleep(200); + s = await themeState(); + } + assert.equal(s.menu, GREEN, `Reload from disk did not apply the edit: ${JSON.stringify(s)}`); + }); + + test("two themes that inherit each other freeze the page, and choosing one fails with a stack overflow", async (t) => { + if (!lane.run) await startIde(); + writeTheme("LoopA", theme("LoopB", LOOP_A)); + writeTheme("LoopB", theme("LoopA", "rgb(2, 2, 2)")); + await c.evaluate(`window.__maxGap = 0; window.__errors.length = 0`); + await chooseTheme("Reload from disk"); + const read = await waitFor( + c, + () => c.evaluate(`customThemes.length === customThemesCount && customThemes.some((t) => t.name === "LoopB")`), + { timeout: 60 * 1000, interval: 500 }, + ); + assert.ok(read, "Reload from disk never read LoopA.theme and LoopB.theme"); + await sleep(500); + const gap = Math.round(await c.evaluate("window.__maxGap")); + t.diagnostic(`the page did not run for ${gap} ms`); + assert.ok(gap >= 2000, `the page never stopped for 2 s: its longest pause was ${gap} ms`); + const menu = await themeMenu(); + assert.ok(menu.includes("LoopB (undefined)"), `LoopB is not listed as LoopB (undefined): ${JSON.stringify(menu)}`); + const loopA = menu.find((m) => m.replace("[x] ", "").startsWith("LoopA (")); + assert.ok(loopA, `LoopA is not listed: ${JSON.stringify(menu)}`); + await c.evaluate(`window.__errors.length = 0`); + await chooseTheme("LoopA"); + const s = await themeState(); + t.diagnostic(`after choosing LoopA: ${JSON.stringify(s)}`); + assert.equal(s.colorTheme, "LoopA", `choosing LoopA did not select it: ${JSON.stringify(s)}`); + assert.notEqual(s.menu, LOOP_A, `LoopA's menu colour was applied: ${JSON.stringify(s)}`); + assert.ok( + s.errors.some((e) => /Maximum call stack size exceeded/.test(e)), + `choosing LoopA raised no stack overflow: ${JSON.stringify(s.errors)}`, + ); + }); + + test("a Dark.theme in the APPDATA themes folder unlinks every custom theme, and Reload from disk stops applying", async (t) => { + rmSync(path.join(common, "LoopA.theme"), { force: true }); + rmSync(path.join(common, "LoopB.theme"), { force: true }); + writeTheme("AtStart", theme("light", RED)); + writeTheme("Dark", theme("light", "rgb(13, 14, 15)")); + await startIde(); + const menu = await themeMenu(); + t.diagnostic(`the theme list: ${JSON.stringify(menu)}`); + assert.ok( + menu.includes("AtStart (undefined)"), + `AtStart is not listed as AtStart (undefined): ${JSON.stringify(menu)}`, + ); + assert.ok( + menu.includes("Classic (undefined)"), + `Classic is not listed as Classic (undefined): ${JSON.stringify(menu)}`, + ); + await chooseTheme("AtStart"); + let s = await themeState(); + assert.equal(s.darkMode, true, `AtStart, which inherits light, did not give the dark base: ${JSON.stringify(s)}`); + assert.equal(s.menu, RED, `AtStart's own menu colour is not applied: ${JSON.stringify(s)}`); + assert.equal( + s.loaded, + s.expected - 1, + `the page did not count one theme more than it loaded: ${JSON.stringify(s)}`, + ); + writeTheme("AtStart", theme("light", GREEN)); + await chooseTheme("Reload from disk"); + assert.ok(await waitForAtStartText(GREEN), "Reload from disk did not read AtStart.theme again"); + await sleep(2000); + s = await themeState(); + assert.equal(s.menu, RED, `Reload from disk applied the edit: ${JSON.stringify(s)}`); + }); + + test("a group named A|B switches B on with it, and toggling B twice switches both off", async (t) => { + if (!lane.run) await startIde(); + // The user's own groups, which the IDE read from the registry, are set aside in the page. + await c.evaluate( + `customKeyboardShortcutGroups = {}; activeKeyboardShortcutGroupNames = ""; syncActiveKeyboardShortcuts()`, + ); + + // Manage Keyboard Shortcuts, Add New..., the name, Enter, Save Changes. + async function addGroup(name) { + await c.evaluate(`executeIdeCommand("tbKeyboardShortcuts_ShowManageKeyboardShortcuts")`); + await sleep(800); + assert.deepEqual( + await modalTitles(), + ["Manage Keyboard Shortcuts"], + "the Manage Keyboard Shortcuts dialog did not open", + ); + await clickModalButton("Add New..."); + const box = await c.evaluate(`(() => { + const i = document.querySelector(".configEditorEntryNew"); + if (!i) return null; + i.focus(); + return document.activeElement === i; + })()`); + assert.ok(box, "Add New... showed no name box"); + await typeText(c, name); + await pressKey(c, "Enter"); + await sleep(500); + await clickModalButton("Save Changes"); + assert.deepEqual(await modalTitles(), [], "Save Changes did not close the dialog"); + } + + const keyState = () => + c.evaluate(`({ + groups: Object.keys(customKeyboardShortcutGroups), + active: activeKeyboardShortcutGroupNames, + ticked: getCustomKeyboardShortcutMenuOptions().filter((o) => o.name).map((o) => (o.codicon === "check" ? "[x] " : "") + o.name.replace(/^.* \\d+\\. /, "")), + })`); + + await addGroup("A|B"); + await addGroup("B"); + let s = await keyState(); + t.diagnostic(`both added: ${JSON.stringify(s)}`); + assert.deepEqual(s.groups, ["A|B", "B"], `the groups are not A|B and B: ${JSON.stringify(s)}`); + assert.equal(s.active, "|A|B||B|", `both groups are not on: ${JSON.stringify(s)}`); + assert.deepEqual(s.ticked, ["[x] A|B", "[x] B"], `both groups are not ticked: ${JSON.stringify(s)}`); + + // B is the second custom group: Window > Keyboard Shortcuts > Custom 2. B runs this command. + await c.evaluate(`executeIdeCommand("tbKeyboardShortcuts_ToggleActiveKeyboardShortcutGroupCustom2")`); + await sleep(500); + s = await keyState(); + t.diagnostic(`B clicked once: ${JSON.stringify(s)}`); + assert.equal(s.active, "|A|B|", `the first click on B did not leave |A|B|: ${JSON.stringify(s)}`); + assert.deepEqual( + s.ticked, + ["[x] A|B", "[x] B"], + `B is not still ticked after the first click: ${JSON.stringify(s)}`, + ); + + await c.evaluate(`executeIdeCommand("tbKeyboardShortcuts_ToggleActiveKeyboardShortcutGroupCustom2")`); + await sleep(500); + s = await keyState(); + t.diagnostic(`B clicked twice: ${JSON.stringify(s)}`); + assert.equal(s.active, "|A", `the second click on B did not leave |A: ${JSON.stringify(s)}`); + assert.deepEqual(s.ticked, ["A|B", "B"], `A|B and B are not both off: ${JSON.stringify(s)}`); + }); +}); diff --git a/test/ide/debugging.test.mjs b/test/ide/debugging.test.mjs new file mode 100644 index 000000000..53de3e703 --- /dev/null +++ b/test/ide/debugging.test.mjs @@ -0,0 +1,518 @@ +// Running and debugging, tested against eight entries in BUGS-TO-REPORT.md: +// +// "F6 does not find the procedure when the caret is right of the column where the procedure's +// End line ends, or on that End line" (bugs/f6-procedure-not-found) +// "After a stop at a breakpoint, Step Out does not stop in the procedure's caller" +// (bugs/step-out-after-breakpoint) +// "The Variables panel's edit box cuts a String at an apostrophe, and leaving it sets the +// variable to the part before" (bugs/variables-edit-apostrophe) +// "After an edit in the Variables panel, a Double or Date row shows a wrong value until the next +// refresh" (bugs/variables-edit-stale-display) +// "Hovering over a Sub call while paused runs the Sub" (bugs/hover-runs-sub) +// "Search Online does not encode the error text: an & in it cuts the search short" +// (bugs/search-online-unencoded) +// "An edit at the run-time error panel prints Aborting, and the rest of the program runs" +// (bugs/edit-at-error-panel-runs-on) +// "A compile error at F5 is reported as a codegen error to report, at the caller's line" +// (bugs/compile-error-reported-as-codegen) +// +// The project in probes/debugging has a Sub Main that calls Direct, then Locals, whose locals are +// of several types, then Fails, which raises an error whose description holds an &, and then +// prints "main end"; and a Function Answer that nothing calls, for F6. The tests find its lines +// by their @ marks. +// +// THE IDE SAVES ITS OPTIONS AND PANEL LAYOUT IN THE USER'S OWN REGISTRY (HKCU\Software\VB and VBA +// Program Settings\twinBASIC_IDE\IDESettings). So, as soon as the project is open, the page's +// hostAppObject is replaced by a proxy whose SaveIDESetting, ChangeWindowSetting and recent-list +// calls do nothing, as memory.test.mjs does. NO TEST OPENS A BROWSER: the error panel's Search +// Online calls root.Shell with a "cmd.exe /c start" command line, so root.Shell is replaced by a +// recorder at the same time, and before() fails, before any run, if the recorder is not in place. +// Nothing here reaches SaveSetting, so lanes.mjs names no settings for this lane. +// +// The tests share the lane's IDE in order. The edits the last tests make (a letter typed at the end +// of the @EDIT comment, a line that does not compile in Direct) are not undone. Each test states +// what BETA 1005 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/Debugging/ 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 { readFileSync } from "node:fs"; +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 { consoleMark, linesSince } from "../../scripts/lib/tb-ide-console.mjs"; +import { click, clickAt, 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", "debugging"); +const FILE = "/DebuggingProbe/Sources/Startup.twin"; +const SOURCE = readFileSync(path.join(PROJECT, "Sources", "Startup.twin"), "utf8").split(/\r?\n/); +// The 1-based line of a mark, and the 1-based column of a word on it (inside the word). +const at = (mark) => { + const n = SOURCE.findIndex((l) => new RegExp(`@${mark}\\b`).test(l)) + 1; + assert.ok(n > 0, `no line marked @${mark}`); + return n; +}; +const columnOf = (line, word) => SOURCE[line - 1].indexOf(word) + 2; +const MAIN = SOURCE.findIndex((l) => l.includes("Public Sub Main()")) + 1; +const CALL_DIRECT = at("CALLDIRECT"); +const M2 = at("M2"); +const DIRECT = at("DIRECT"); +// Direct's End Sub, which carries no comment: where it ends matters to F6. +const DIRECT_END = SOURCE.findIndex((l, i) => i >= DIRECT && l.trim() === "End Sub") + 1; +const ANSWER = at("ANSWER"); +const LOCALS = SOURCE.findIndex((l) => l.includes("Public Sub Locals()")) + 1; +const BUMP = at("BUMP"); +const LOCALS_END = at("LOCALSEND"); +const RAISE = at("RAISE"); +const EDIT = at("EDIT"); + +const ABORTING = "[EDIT & CONTINUE] not yet supported. Aborting."; +const NOT_FOUND = "RunCodeUnderCursor: failed to identify the procedure at the cursor position."; + +// Blocks every call that writes the user's registry, and records every command line the page +// passes to root.Shell instead of running it (see the top of this file). +const GUARD_JS = `(() => { + if (!window.__guarded) { + window.__guarded = true; + const blocked = new Set(["SaveIDESetting", "ChangeWindowSetting", "AddRecentsList", "RemoveRecentsList"]); + hostAppObject = new Proxy(hostAppObject, { + get(target, key) { + if (blocked.has(key)) return async () => undefined; + return Reflect.get(target, key); + }, + }); + window.__shell = []; + const recorder = function (command) { window.__shell.push(String(command)); return Promise.resolve(0); }; + recorder.__tbRecorder = true; + root.Shell = recorder; + } + return !!(root.Shell && root.Shell.__tbRecorder); +})()`; + +// Whether it is paused and running, the line it is paused on, and the error panel's text. +const STATE_JS = `(() => { + const w = document.querySelector(".errorWidget"); + return { + paused: !!context.atBreakpoint.value, + running: !!context.codeExecuting.value, + line: activeDebuggingLine, + panel: w ? w.innerText : null, + }; +})()`; + +// A row of the Variables panel's Locals: its value as shown, and where to click on it. +const ROW_JS = (name) => `(() => { + const locals = Object.values(variablesPanel.tree.rootFolder.entries).find((e) => e.name === "Locals"); + const row = locals && Object.values(locals.entries || {}).find((e) => e.name === ${JSON.stringify(name)}); + if (!row || !row.innerDiv) return null; + row.innerDiv.scrollIntoView({ block: "center" }); + const q = row.innerDiv.getBoundingClientRect(); + return { value: row.customRenderData.value, x: q.x + q.width * 0.6, y: q.y + Math.min(q.height, 20) / 2 }; +})()`; +const EDIT_BOX_JS = `(() => { const e = document.getElementById("renameInput"); return e ? e.value : null; })()`; +const HOVER_JS = `(() => { + const h = [...document.querySelectorAll(".monaco-hover")].find((e) => !e.classList.contains("hidden") && e.getBoundingClientRect().height > 4); + return h ? h.innerText.replace(/\\xa0/g, " ").replace(/\\n{2,}/g, "\\n").trim() : null; +})()`; +const GLYPHS_JS = `editor.getModel().getAllDecorations().filter((d) => /breakpointActiveGlyph/.test(d.options.glyphMarginClassName || "")).map((d) => d.range.startLineNumber)`; + +scenario("running and debugging", (lane) => { + let c; + const state = () => c.evaluate(STATE_JS); + + // Wait for the run to stop or to end. The IDE's counters dip to 0 while a stop's events are + // handled, so an end is confirmed by a second look. + async function settled({ timeout = 60 * 1000 } = {}) { + return waitFor( + c, + async () => { + const s = await state(); + if (s.paused) return "paused"; + if (s.running) return false; + await sleep(1500); + const again = await state(); + return again.paused ? "paused" : again.running ? false : "ended"; + }, + { timeout, interval: 150 }, + ); + } + // Wait for the run to end, confirmed by a second look; false if it is still going. + async function ended({ timeout = 30 * 1000 } = {}) { + return !!(await waitFor( + c, + async () => { + if ((await state()).running) return false; + await sleep(1500); + return !(await state()).running; + }, + { timeout, interval: 150 }, + )); + } + async function run() { + const mark = await consoleMark(c); + await c.evaluate(`executeIdeCommand("tbDebug_StartOrContinue")`); + return mark; + } + async function stopIfRunning() { + if ((await state()).running) { + await c.evaluate(`executeIdeCommand("tbDebug_Stop")`); + assert.ok(await waitFor(c, async () => !(await state()).running, { timeout: 30 * 1000 }), "the run did not stop"); + await sleep(1000); + } + } + // F9 on each line, with the caret there, after Ctrl+Shift+F9 has cleared any others. + async function breakpointsOn(...lines) { + await openFile(c, FILE, { line: lines[0] ?? 1, column: 1 }); + if ((await c.evaluate(GLYPHS_JS)).length) { + await pressKey(c, "F9", { ctrl: true, shift: true }); + assert.ok(await waitFor(c, async () => !(await c.evaluate(GLYPHS_JS)).length), "a breakpoint is left"); + } + for (const line of lines) { + await setCursor(c, line, 9); + await pressKey(c, "F9"); + assert.ok(await waitFor(c, async () => (await c.evaluate(GLYPHS_JS)).includes(line)), `no breakpoint on ${line}`); + await sleep(500); + } + } + // A line typed into the DEBUG CONSOLE with Enter; what it printed, the line itself first. + async function consoleLine(text) { + const mark = await consoleMark(c); + await c.evaluate(`(() => { const i = debugConsoleEntry; i.focus(); i.value = ${JSON.stringify(text)}; })()`); + await pressKey(c, "Enter"); + await sleep(1500); + return (await linesSince(c, mark)).filter((l) => !l.startsWith("(time taken")); + } + const row = (name) => c.evaluate(ROW_JS(name)); + async function doubleClickRow(name) { + const r = await row(name); + assert.ok(r, `the Variables panel has no row ${name}`); + await clickAt(c, r.x, r.y); + await clickAt(c, r.x, r.y, { clickCount: 2 }); + assert.ok(await waitFor(c, async () => (await c.evaluate(EDIT_BOX_JS)) !== null), `no edit box for ${name}`); + } + // Paused at the breakpoint on the @LOCALSEND line, in Locals. + async function pausedInLocals() { + const s = await state(); + if (s.paused && s.line === LOCALS_END && !s.panel) return; + await stopIfRunning(); + await breakpointsOn(LOCALS_END); + await run(); + assert.equal(await settled(), "paused", "the run did not stop at the breakpoint"); + assert.equal((await state()).line, LOCALS_END); + await sleep(1000); + } + // Paused at the error panel on the @RAISE line, with no breakpoint on the way. + async function atErrorPanel() { + await stopIfRunning(); + await breakpointsOn(); + const mark = await run(); + assert.equal(await settled(), "paused", "the run did not stop at the error"); + const s = await state(); + assert.equal(s.line, RAISE); + assert.ok(s.panel, "no error panel"); + await sleep(1000); + return mark; + } + // Types a letter at the end of the @EDIT comment line, as a person editing the code does. + async function typeInComment() { + await openFile(c, FILE, { line: EDIT, column: 1 }); + await setCursor(c, EDIT, (await c.evaluate(`editor.getModel().getLineMaxColumn(${EDIT})`)) || 1); + await typeText(c, "x"); + } + + before(async () => { + c = await lane.open(PROJECT); + assert.equal(await c.evaluate(GUARD_JS), true, "root.Shell could not be replaced: no test may click Search Online"); + }); + + // ------------------------------------------------------------------ F6 (bugs/f6-procedure-not-found) + + async function f6(line, column) { + await stopIfRunning(); + await breakpointsOn(); + await openFile(c, FILE, { line, column }); + await setCursor(c, line, column); + const mark = await consoleMark(c); + await pressKey(c, "F6"); + await sleep(2500); + await settled(); + return linesSince(c, mark); + } + + // Direct's End Sub ends at column 12 (after "End Sub" at 5), Answer's End Function at 17. + const END_SUB_AFTER = SOURCE[DIRECT_END - 1].length + 1; + const ANSWER_END = SOURCE.findIndex((l, i) => i >= ANSWER && l.trim() === "End Function"); + const END_FUNCTION_AFTER = SOURCE[ANSWER_END].length + 1; + + test("control: F6 on a line of a procedure, left of the column where its End line ends, runs the procedure", async () => { + let lines = await f6(DIRECT, END_SUB_AFTER - 1); + assert.deepEqual(lines.slice(0, 2), ["Executing 'DebuggingProbe.Startup.Direct'...", "in direct"]); + lines = await f6(ANSWER, END_FUNCTION_AFTER - 1); + assert.deepEqual(lines.slice(0, 1), ["Executing 'DebuggingProbe.Startup.Answer'..."]); + }); + + test("F6 on a line of a procedure, at or right of the column where its End line ends, does not find the procedure", async () => { + assert.deepEqual(await f6(DIRECT, END_SUB_AFTER), [NOT_FOUND]); + assert.deepEqual(await f6(ANSWER, END_FUNCTION_AFTER), [NOT_FOUND]); + }); + + test("F6 on an End Sub line does not find the procedure, at any column", async () => { + assert.deepEqual(await f6(DIRECT_END, 1), [NOT_FOUND]); + assert.deepEqual(await f6(DIRECT_END, 5), [NOT_FOUND]); + }); + + // ------------------------------------------------------------------ Step Out (bugs/step-out-after-breakpoint) + + test("control: after F8 into a procedure from a breakpoint in its caller, Step Out stops in the caller", async () => { + await stopIfRunning(); + await breakpointsOn(CALL_DIRECT); + await run(); + assert.equal(await settled(), "paused"); + assert.equal((await state()).line, CALL_DIRECT); + await c.evaluate(`executeIdeCommand("tbDebug_StepInto")`); + await sleep(2000); + assert.equal(await settled(), "paused"); + const into = (await state()).line; + assert.ok(into >= DIRECT - 1 && into <= DIRECT_END, `F8 stopped on ${into}, not in Direct`); + await c.evaluate(`executeIdeCommand("tbDebug_StepOut")`); + await sleep(2000); + assert.equal(await settled(), "paused"); + assert.equal((await state()).line, M2); + await stopIfRunning(); + }); + + test("after a stop at a breakpoint in a procedure, Step Out does not stop in its caller: the run goes on", async (t) => { + await stopIfRunning(); + await breakpointsOn(DIRECT); + const mark = await run(); + assert.equal(await settled(), "paused"); + assert.equal((await state()).line, DIRECT); + await c.evaluate(`executeIdeCommand("tbDebug_StepOut")`); + await sleep(2000); + assert.equal(await settled(), "paused"); + const s = await state(); + const lines = await linesSince(c, mark); + t.diagnostic(`stopped on ${s.line}; printed ${JSON.stringify(lines)}`); + // It runs on, past the rest of Main's calls, to the error in Fails. + assert.equal(s.line, RAISE); + assert.ok(s.panel, "no error panel"); + assert.ok(lines.includes("m2") && lines.includes("m3"), "Main's lines after the call did not run"); + await stopIfRunning(); + }); + + test("after a stop at a breakpoint in a procedure, F8 off its End Sub does not stop in its caller, but in the next procedure the caller calls", async (t) => { + await stopIfRunning(); + await breakpointsOn(DIRECT); + const mark = await run(); + assert.equal(await settled(), "paused"); + assert.equal((await state()).line, DIRECT); + await c.evaluate(`executeIdeCommand("tbDebug_StepInto")`); + await sleep(2000); + assert.equal(await settled(), "paused"); + assert.equal((await state()).line, DIRECT_END, "F8 did not stop on End Sub"); + await c.evaluate(`executeIdeCommand("tbDebug_StepInto")`); + await sleep(2000); + assert.equal(await settled(), "paused"); + const s = await state(); + const lines = await linesSince(c, mark); + t.diagnostic(`stopped on ${s.line}; printed ${JSON.stringify(lines)}`); + assert.equal(s.line, LOCALS); + assert.ok(lines.includes("m2"), "Main's line after the call did not run"); + await stopIfRunning(); + }); + + // ------------------------------------------------------------------ Variables (bugs/variables-edit-apostrophe) + + test("control: Escape in the Variables edit box leaves a String with an apostrophe as it was", async () => { + await pausedInLocals(); + assert.deepEqual(await consoleLine(`s = "it's"`), [`s = "it's"`]); + assert.equal((await row("s")).value, `"it's"`); + await doubleClickRow("s"); + await pressKey(c, "Escape"); + await sleep(1500); + assert.equal((await row("s")).value, `"it's"`); + assert.deepEqual(await consoleLine("? s"), ["? s", "it's"]); + }); + + test("the Variables edit box holds a String only up to an apostrophe, and Enter sets the variable to the empty string", async () => { + await pausedInLocals(); + await consoleLine(`s = "it's"`); + await doubleClickRow("s"); + assert.equal(await c.evaluate(EDIT_BOX_JS), `"it`); + await pressKey(c, "Enter"); + await sleep(1500); + assert.equal((await row("s")).value, `""`); + assert.deepEqual(await consoleLine("? s"), ["? s", ""]); + }); + + test("a click away from the Variables edit box, with nothing typed, sets a String with an apostrophe to the empty string", async () => { + await pausedInLocals(); + await consoleLine(`s = "it's"`); + await doubleClickRow("s"); + const other = await row("n"); + await clickAt(c, other.x, other.y); + await sleep(1500); + assert.equal(await c.evaluate(EDIT_BOX_JS), null, "the edit box is still open"); + assert.equal((await row("s")).value, `""`); + assert.deepEqual(await consoleLine("? s"), ["? s", ""]); + }); + + // ------------------------------------------------------------------ Variables (bugs/variables-edit-stale-display) + + // Double-click the row, type the value over the box's text, Enter; the row two seconds later, + // what ? prints, and the row after that console line. + async function editValue(name, text) { + await pausedInLocals(); + await doubleClickRow(name); + await c.evaluate(`document.getElementById("renameInput").select()`); + await typeText(c, text); + await pressKey(c, "Enter"); + await sleep(2000); + const shown = (await row(name)).value; + const printed = await consoleLine(`? ${name}`); + await sleep(500); + return { shown, printed: printed.slice(1), after: (await row(name)).value }; + } + + test("control: a Long edited in the Variables panel shows its new value at once", async () => { + assert.deepEqual(await editValue("n", "9"), { shown: "9", printed: ["9"], after: "9" }); + }); + + test("a Double edited in the Variables panel shows 0 until the next refresh, while the variable holds the value", async () => { + assert.deepEqual(await editValue("d", "7.25"), { shown: "0", printed: ["7.25"], after: "7.25" }); + }); + + test("a Date edited in the Variables panel shows #12:00:00 AM# until the next refresh, while the variable holds the value", async () => { + assert.deepEqual(await editValue("dt", "#1/2/2020#"), { + shown: "#12:00:00 AM#", + printed: ["1/2/2020"], + after: "#1/2/2020#", + }); + }); + + // ------------------------------------------------------------------ hover (bugs/hover-runs-sub) + + // Moves the pointer away, then onto a word of the code, and returns the hover's text. + async function hover(line, column) { + await c.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: 5, y: 5, button: "none", buttons: 0 }); + await sleep(700); + const p = await c.evaluate(`(() => { + editor.revealLineInCenterIfOutsideViewport(${line}); + const v = editor.getScrolledVisiblePosition({ lineNumber: ${line}, column: ${column} }); + const r = editor.getDomNode().getBoundingClientRect(); + return v ? { x: r.x + v.left + 2, y: r.y + v.top + v.height / 2 } : null; + })()`); + assert.ok(p, `line ${line} is not on screen`); + await c.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: p.x, y: p.y, button: "none", buttons: 0 }); + const text = await waitFor(c, () => c.evaluate(HOVER_JS), { timeout: 5000, interval: 100 }); + await sleep(800); + await c.send("Input.dispatchMouseEvent", { type: "mouseMoved", x: 5, y: 5, button: "none", buttons: 0 }); + await sleep(500); + return text; + } + + test("control: hovering over a variable while paused shows its value", async () => { + await pausedInLocals(); + await openFile(c, FILE, { line: LOCALS_END, column: 1 }); + assert.equal(await hover(LOCALS_END, columnOf(LOCALS_END, "Value")), "t.Value: 6"); + assert.deepEqual(await consoleLine("? t.Value"), ["? t.Value", "6"]); + }); + + test("hovering over a Sub call while paused runs the Sub", async () => { + await pausedInLocals(); + await openFile(c, FILE, { line: BUMP, column: 1 }); + const before = (await consoleLine("? t.Value"))[1]; + assert.equal(await hover(BUMP, columnOf(BUMP, "Bump")), "t.Bump: 0"); + const after = (await consoleLine("? t.Value"))[1]; + assert.equal(Number(after), Number(before) + 1, `t.Value went from ${before} to ${after}`); + }); + + // ------------------------------------------------------------------ an edit while paused (bugs/edit-at-error-panel-runs-on) + + test("control: an edit at a breakpoint aborts the run: the rest of Main does not run", async (t) => { + await pausedInLocals(); + const mark = await consoleMark(c); + await typeInComment(); + assert.ok(await ended(), "the run did not end"); + const lines = await linesSince(c, mark); + t.diagnostic(JSON.stringify(lines)); + assert.ok(lines.includes(ABORTING), "no Aborting line"); + assert.ok(lines.includes("aborted"), "no aborted line"); + assert.ok(!lines.includes("m3") && !lines.includes("main end"), "Main went on"); + }); + + // ------------------------------------------------------------------ Search Online (bugs/search-online-unencoded) + + test("Search Online passes the error's text into the search URL unencoded, so an & in it ends the query", async () => { + await atErrorPanel(); + // Checked again just before the click: the click must never reach the real root.Shell. + assert.equal(await c.evaluate(`!!(root.Shell && root.Shell.__tbRecorder)`), true, "root.Shell is not the recorder"); + await c.evaluate(`window.__shell.length = 0`); + await click(c, { css: ".errorWidgetButton", text: "Search Online" }); + await sleep(1000); + assert.deepEqual(await c.evaluate(`window.__shell`), [ + 'cmd.exe /c start "link" "https://www.google.com/search?q=twinBASIC VB6 Run-time error 5 (00000005) Fails with A & B"', + ]); + await stopIfRunning(); + }); + + // ------------------------------------------------------------------ an edit at the error panel + + test("an edit at the error panel prints Aborting, and then the rest of Main runs", async (t) => { + await atErrorPanel(); + const mark = await consoleMark(c); + await typeInComment(); + assert.ok(await ended(), "the run did not end"); + const lines = await linesSince(c, mark); + t.diagnostic(JSON.stringify(lines)); + assert.equal(lines[0], ABORTING); + assert.ok(lines.includes("main end"), "the rest of Main did not run"); + assert.ok(!lines.includes("after raise"), "the rest of Fails ran"); + assert.ok(!lines.includes("aborted")); + }); + + // ------------------------------------------------------------------ a compile error at F5 (bugs/compile-error-reported-as-codegen) + + test("a compile error in a procedure Main calls is reported at F5 as a codegen error to report, at Main's call", async (t) => { + await stopIfRunning(); + await breakpointsOn(); + await openFile(c, FILE, { line: DIRECT_END, column: 1 }); + await c.evaluate( + `editor.executeEdits("tests", [{ range: new monaco.Range(${DIRECT_END}, 1, ${DIRECT_END}, 1), text: " x = = 1\\n" }])`, + ); + // The compiler reports the line before F5. + const marked = await waitFor( + c, + () => + c.evaluate( + `monaco.editor.getModelMarkers({ resource: editor.getModel().uri }).filter((m) => m.severity === monaco.MarkerSeverity.Error).length`, + ), + { timeout: 30 * 1000 }, + ); + t.diagnostic(`errors marked: ${marked}`); + const mark = await run(); + assert.equal(await settled(), "paused"); + const s = await state(); + const lines = await linesSince(c, mark); + t.diagnostic(JSON.stringify({ line: s.line, panel: s.panel, lines })); + assert.equal(s.line, MAIN); + assert.ok( + s.panel?.includes( + `COMPILATION (CODEGEN) ERROR AT LINE #${CALL_DIRECT}. PLEASE REPORT THIS ERROR TO https://git.io/JMh6S`, + ), + `the panel reads ${JSON.stringify(s.panel)}`, + ); + assert.ok( + lines.includes(`[LINKER] compilation (codegen) error detected in 'Startup.Main' at line #${CALL_DIRECT}`), + "no LINKER line", + ); + assert.ok(!lines.includes("m1"), "Main ran"); + await stopIfRunning(); + }); +}); diff --git a/test/ide/lanes.mjs b/test/ide/lanes.mjs index 91eb3dd6a..5e06bde49 100644 --- a/test/ide/lanes.mjs +++ b/test/ide/lanes.mjs @@ -29,4 +29,9 @@ export default [ { file: "recent-projects-copies.test.mjs" }, { file: "typing-after-open.test.mjs" }, { file: "restart-file-order.test.mjs" }, + { file: "customising.test.mjs" }, + { file: "code-find.test.mjs" }, + { file: "code-editor.test.mjs" }, + { file: "memory.test.mjs" }, + { file: "debugging.test.mjs" }, ]; diff --git a/test/ide/memory.test.mjs b/test/ide/memory.test.mjs new file mode 100644 index 000000000..bd0f152e2 --- /dev/null +++ b/test/ide/memory.test.mjs @@ -0,0 +1,220 @@ +// The MEMORY 1 pane, tested against four entries in BUGS-TO-REPORT.md: +// +// "In the Memory pane, the name of a Double, Boolean or user-defined type variable throws an +// uncaught BigInt error, and the box is not marked invalid" (bugs/memory-bigint-throw) +// "In a 64-bit program, the Memory pane's Address column drops the high 32 bits" +// (bugs/memory-address-64bit) +// "The Memory pane shows memory it cannot read as still loading, with ?? in every cell" +// (bugs/memory-unreadable-loading) +// "A negative number in the Memory pane's box gives rows whose addresses jump" +// (bugs/memory-negative-address) +// +// The project in probes/memory prints the address of n and stops at a Stop statement, with +// variables of several types in scope. The pane is opened from the View menu, and each +// expression is typed into its box with Enter, as a person does. The last test switches the +// project to win64 (lane.setBuildTarget; the runner's registry tidy deletes what the IDE +// remembers of it) and runs it again. +// +// THE IDE SAVES ITS PANEL LAYOUT IN THE USER'S OWN REGISTRY (HKCU\Software\VB and VBA Program +// Settings\twinBASIC_IDE\IDESettings), and opening the pane changes the layout. So, as soon as +// the project is open, the page's hostAppObject is replaced by a proxy whose SaveIDESetting, +// ChangeWindowSetting and recent-list calls do nothing, as code-editor.test.mjs does. Nothing +// here reaches SaveSetting, so lanes.mjs names no settings for this lane. +// +// The tests share the lane's IDE in order. Each test states what BETA 1005 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 docs/IDE/Memory.md that names 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 path from "node:path"; +import { before, test } from "node:test"; +import { fileURLToPath } from "node:url"; +import { sleep } from "../../scripts/lib/tb-ide.mjs"; +import { consoleMark, linesSince } from "../../scripts/lib/tb-ide-console.mjs"; +import { clickAt, pressKey, 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", "memory"); + +// Blocks every call that writes the user's registry (see the top of this file). +const GUARD_JS = `(() => { + if (window.__guarded) return; + window.__guarded = true; + const blocked = new Set(["SaveIDESetting", "ChangeWindowSetting", "AddRecentsList", "RemoveRecentsList"]); + hostAppObject = new Proxy(hostAppObject, { + get(target, key) { + if (blocked.has(key)) return async () => undefined; + return Reflect.get(target, key); + }, + }); +})()`; + +const STATE_JS = `({ paused: !!context.atBreakpoint.value, running: !!context.codeExecuting.value })`; + +// What the pane shows: the box and its classes, and the first rows, each cell's text with its +// state (loading, the placeholder of a cell still being read; invalidMem, unreadable memory). +const PANE_JS = (rows) => `(() => { + const h = hexEditorPanel.outerDiv.querySelector(".hexedr"); + const box = h.querySelector(".addr-input"); + const trs = [...h.querySelectorAll("tbody tr")].slice(0, ${rows}); + const focused = document.activeElement && document.activeElement.closest ? document.activeElement.closest("td") : null; + return { + box: box.value, + invalid: box.classList.contains("invalidExpression"), + focusedAddr: focused && focused.dataset.addr ? BigInt(focused.dataset.addr).toString(16).toUpperCase() : null, + rows: trs.map((tr) => ({ + addr: tr.querySelector("th.addr").textContent, + cells: [...tr.querySelectorAll("td.cell")].map((td) => td.textContent), + loading: tr.querySelectorAll("td.cell.loading").length, + invalidMem: tr.querySelectorAll("td.cell.invalidMem").length, + })), + }; +})()`; + +scenario("the MEMORY 1 pane", (lane) => { + let c; + let mark; + let address; // VarPtr(n), as the program printed it, in hexadecimal + const pane = (rows = 2) => c.evaluate(PANE_JS(rows)); + const state = () => c.evaluate(STATE_JS); + + // F5 to the Stop, and the address the program printed before it. + async function toStop() { + mark = await consoleMark(c); + await c.evaluate(`executeIdeCommand("tbDebug_StartOrContinue")`); + assert.ok( + await waitFor(c, async () => (await state()).paused, { timeout: 60 * 1000 }), + "the run did not stop at the Stop statement", + ); + const line = await waitFor(c, async () => (await linesSince(c, mark)).find((l) => l.includes("VarPtr(n)="))); + assert.ok(line, "the program did not print VarPtr(n)"); + address = line.slice(line.indexOf("=") + 1).trim(); + assert.match(address, /^[0-9A-F]+$/); + } + + async function endRun() { + await c.evaluate(`executeIdeCommand("tbDebug_Stop")`); + assert.ok(await waitFor(c, async () => !(await state()).running, { timeout: 30 * 1000 }), "the run did not end"); + } + + // Types an expression into the pane's box and presses Enter. + async function enter(text) { + await c.evaluate( + `(() => { const i = hexEditorPanel.outerDiv.querySelector(".addr-input"); i.focus(); i.select(); })()`, + ); + await typeText(c, text); + await pressKey(c, "Enter"); + await sleep(1500); + } + + // The pane is showing VarPtr(n): its first row at the address, n's bytes in its first cells. + const showsN = async () => { + const p = await pane(1); + return p.rows[0]?.addr === `&H${address.padStart(8, "0")}` && + p.rows[0].cells.slice(0, 4).join(" ") === "D2 04 00 00" + ? p + : null; + }; + + before(async () => { + c = await lane.open(PROJECT); + await c.evaluate(GUARD_JS); + // View > MEMORY 1 + const view = await c.evaluate( + `(() => { const q = rootMenuView.getBoundingClientRect(); return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; })()`, + ); + await clickAt(c, view.x, view.y); + await sleep(500); + const item = await c.evaluate(`(() => { + const it = [...document.querySelectorAll("#contextMenu > *")].find((e) => e.innerText.replace(/\\s+/g, " ").trim().startsWith("MEMORY 1") && e.getBoundingClientRect().width); + if (!it) return null; + const q = it.getBoundingClientRect(); + return { x: q.x + q.width / 2, y: q.y + q.height / 2 }; + })()`); + assert.ok(item, "the View menu has no item MEMORY 1"); + await clickAt(c, item.x, item.y); + assert.ok( + await waitFor(c, () => c.evaluate(`!!hexEditorPanel.outerDiv.querySelector(".hexedr .addr-input")`)), + "the MEMORY 1 pane did not open", + ); + await toStop(); + }); + + test("control: VarPtr(n) shows n's bytes at the address the program printed", async (t) => { + await enter("VarPtr(n)"); + const p = await waitFor(c, showsN, { timeout: 5000 }); + t.diagnostic(`VarPtr(n) = ${address}; the pane: ${JSON.stringify(await pane(1))}`); + assert.ok(p, "the pane does not show n's bytes at VarPtr(n)"); + assert.equal(p.invalid, false); + }); + + test("control: a name the debugger cannot evaluate turns the box red and leaves the rows", async () => { + await enter("unknownName"); + const p = await pane(1); + assert.equal(p.invalid, true, "the box is not red"); + assert.ok(await showsN(), "the rows changed"); + }); + + for (const [name, value] of [ + ["d", "1.5"], + ["flag", "True"], + ["udt", "{user defined type, 8 bytes}"], + ]) { + test(`the name ${name} throws "Cannot convert ${value} to a BigInt", and leaves the box unmarked and the rows as they were`, async (t) => { + const before = (await linesSince(c, mark)).length; + await enter(name); + const error = `IDE internal error: Uncaught SyntaxError: Cannot convert ${value} to a BigInt`; + const printed = await waitFor(c, async () => + (await linesSince(c, mark)).slice(before).find((l) => l.includes(error)), + ); + t.diagnostic(`DEBUG CONSOLE: ${JSON.stringify(printed)}`); + assert.ok(printed, `the DEBUG CONSOLE did not print ${error}`); + const p = await pane(1); + assert.equal(p.box, name); + assert.equal(p.invalid, false, "the box is red"); + assert.ok(await showsN(), "the rows changed"); + }); + } + + test("0 shows ?? in every value cell, as cells still being read, and never as unreadable memory", async (t) => { + await enter("0"); + await sleep(5000); + const p = await pane(3); + t.diagnostic(JSON.stringify(p.rows.map((r) => `${r.addr} ${r.cells.join(" ")}`))); + assert.equal(p.rows[0].addr, "&H00000000"); + for (const r of p.rows) { + assert.deepEqual(r.cells, Array(16).fill("??"), `row ${r.addr}`); + assert.equal(r.loading, 16, `row ${r.addr} has cells not marked loading`); + assert.equal(r.invalidMem, 0, `row ${r.addr} has cells marked unreadable`); + } + }); + + test("neg, which is -8, gives a first row at &HFFFFFFF8 and a second at &H00000008", async (t) => { + await enter("neg"); + const p = await pane(3); + t.diagnostic(JSON.stringify(p.rows.map((r) => r.addr))); + assert.deepEqual( + p.rows.map((r) => r.addr), + ["&HFFFFFFF8", "&H00000008", "&H00000018"], + ); + }); + + test("in a 64-bit run, VarPtr(n) shows n's bytes, and the Address column only the low 32 bits of its address", async (t) => { + await endRun(); + await lane.setBuildTarget("win64"); + await toStop(); + t.diagnostic(`VarPtr(n) = ${address}`); + assert.ok(BigInt(`0x${address}`) > 0xffffffffn, `VarPtr(n) is below 4 GB in this run: ${address}`); + await enter("VarPtr(n)"); + const p = await pane(1); + t.diagnostic(JSON.stringify(p)); + assert.deepEqual(p.rows[0].cells.slice(0, 4), ["D2", "04", "00", "00"], "the pane does not show n's bytes"); + assert.equal(p.focusedAddr, address, "the cell with the focus is not at VarPtr(n)"); + assert.equal(p.rows[0].addr, `&H${address.slice(-8)}`); + await endRun(); + }); +}); diff --git a/test/ide/probes/code-editor/Settings b/test/ide/probes/code-editor/Settings new file mode 100644 index 000000000..7264042d8 --- /dev/null +++ b/test/ide/probes/code-editor/Settings @@ -0,0 +1,65 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "CodeEditorProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "test/ide/code-editor.test.mjs opens it in a lane's copy of the install and tries Format Selection, the Diagnostics panel's Copy, the Outline, Go To Definition, Ctrl+Down and Escape with several cursors.", + "project.exportPathIsV2": true, + "project.id": "{7B247500-0000-4000-9000-7B24750000B3}", + "project.name": "CodeEditorProbe", + "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": [ + "TB0012" + ], + "warnings": [ + "TB0018" + ] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/ide/probes/code-editor/Sources/Editing.twin b/test/ide/probes/code-editor/Sources/Editing.twin new file mode 100644 index 000000000..8a1b9aaa3 --- /dev/null +++ b/test/ide/probes/code-editor/Sources/Editing.twin @@ -0,0 +1,26 @@ +Module Editing + ' test/ide/code-editor.test.mjs finds the lines it needs by their text. Continued + ' and Plain are left unindented for Format Selection. + Public Sub Main() + Dim total As Long + total = Helpers.Twice(3) + Debug.Print total + End Sub + + Public Sub Continued() +Dim a As Long, _ +b As Long +Select Case a +Case 1 +a = 2 +End Select + End Sub + + Public Sub Plain() +Dim c As Long +Select Case c +Case 1 +c = 2 +End Select + End Sub +End Module diff --git a/test/ide/probes/code-editor/Sources/Helpers.twin b/test/ide/probes/code-editor/Sources/Helpers.twin new file mode 100644 index 000000000..b5191bb40 --- /dev/null +++ b/test/ide/probes/code-editor/Sources/Helpers.twin @@ -0,0 +1,13 @@ +Module Helpers + ' BigValue gives the info TB0012, and Narrowing the warning TB0018 (Settings sets both). + Public Const BigValue As Long = &H8000 + + Public Function Twice(ByVal n As Long) As Long + Return n * 2 + End Function + + Public Sub Narrowing() + Dim narrow As Integer + narrow = Twice(2) + End Sub +End Module diff --git a/test/ide/probes/code-find/Settings b/test/ide/probes/code-find/Settings new file mode 100644 index 000000000..ba258b305 --- /dev/null +++ b/test/ide/probes/code-find/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "CodeFindProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "test/ide/code-find.test.mjs opens it in a lane's copy of the install and searches Finder.twin with the Find / Replace panel.", + "project.exportPathIsV2": true, + "project.id": "{7B247500-0000-4000-9000-7B24750000B2}", + "project.name": "CodeFindProbe", + "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/code-find/Sources/Finder.twin b/test/ide/probes/code-find/Sources/Finder.twin new file mode 100644 index 000000000..01a017a76 --- /dev/null +++ b/test/ide/probes/code-find/Sources/Finder.twin @@ -0,0 +1,14 @@ +Module Finder + ' test/ide/code-find.test.mjs searches this file with the Find / Replace panel. + ' Its first word must stay at line 1, column 1, and no comment may hold it. + Public Sub Main() + Dim total As Long + total = 2 + Debug.Print "total is " & total + Debug.Print Len("abc") + total + End Sub + + Public Sub Second() + Debug.Print "second" + End Sub +End Module diff --git a/test/ide/probes/debugging/Settings b/test/ide/probes/debugging/Settings new file mode 100644 index 000000000..790dc6d8e --- /dev/null +++ b/test/ide/probes/debugging/Settings @@ -0,0 +1,61 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "DebuggingProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "test/ide/debugging.test.mjs opens it in a lane's copy of the install, runs it in the debugger with breakpoints, steps, edits values in the Variables panel, hovers over its code, and edits it while it is paused.", + "project.exportPathIsV2": true, + "project.id": "{7B247500-0000-4000-9000-7B24750000B5}", + "project.name": "DebuggingProbe", + "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/debugging/Sources/Startup.twin b/test/ide/probes/debugging/Sources/Startup.twin new file mode 100644 index 000000000..d3cebe762 --- /dev/null +++ b/test/ide/probes/debugging/Sources/Startup.twin @@ -0,0 +1,41 @@ +' test/ide/debugging.test.mjs runs this program in the debugger. It finds the lines it uses +' by their @ marks, so a mark moves with its line. +Module Startup + + Public Sub Main() + Debug.Print "m1" + Direct ' @CALLDIRECT + Debug.Print "m2" ' @M2 + Locals + Debug.Print "m3" + Fails + Debug.Print "main end" + End Sub + + Public Sub Direct() + Debug.Print "in direct" ' @DIRECT + End Sub + + Public Function Answer() As Long + Answer = 42 ' @ANSWER + End Function + + Public Sub Locals() + Dim n As Long = 7 + Dim s As String = "hello" + Dim d As Double = 2.5 + Dim dt As Date = #10/11/2026 12:30:00 PM# + Dim t As New Thing + t.Value = 5 + t.Bump ' @BUMP + Debug.Print "locals end " & t.Value ' @LOCALSEND + End Sub + + Public Sub Fails() + Debug.Print "fails start" + Err.Raise 5, , "Fails with A & B" ' @RAISE + Debug.Print "after raise" + End Sub + + ' The tests type into the end of this line: @EDIT +End Module diff --git a/test/ide/probes/debugging/Sources/Thing.twin b/test/ide/probes/debugging/Sources/Thing.twin new file mode 100644 index 000000000..c879c1bc8 --- /dev/null +++ b/test/ide/probes/debugging/Sources/Thing.twin @@ -0,0 +1,8 @@ +' A class with a Sub that changes its state, for the hover test. +Class Thing + Public Value As Long + + Public Sub Bump() + Value = Value + 1 + End Sub +End Class diff --git a/test/ide/probes/memory/Settings b/test/ide/probes/memory/Settings new file mode 100644 index 000000000..3fa2a1df2 --- /dev/null +++ b/test/ide/probes/memory/Settings @@ -0,0 +1,33 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "MemoryProbe", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "test/ide/memory.test.mjs opens it in a lane's copy of the install, runs it to its Stop and enters expressions in the MEMORY 1 pane.", + "project.exportPathIsV2": true, + "project.id": "{7B247500-0000-4000-9000-7B24750000B4}", + "project.name": "MemoryProbe", + "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 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/ide/probes/memory/Sources/Startup.twin b/test/ide/probes/memory/Sources/Startup.twin new file mode 100644 index 000000000..412f30ed5 --- /dev/null +++ b/test/ide/probes/memory/Sources/Startup.twin @@ -0,0 +1,30 @@ +' The project test/ide/memory.test.mjs runs. Main prints the address of n and stops; at the +' stop the test enters expressions in the MEMORY 1 pane: VarPtr(n), the names of variables of +' several types, and 0. +Module Startup + + Private Type Rec + A As Long + B As Integer + C As Integer + End Type + + Public Sub Main() + Dim n As Long + Dim d As Double + Dim flag As Boolean + Dim udt As Rec + Dim neg As Long + n = 1234 + d = 1.5 + flag = True + udt.A = &H11223344 + udt.B = 5 + udt.C = 6 + neg = -8 + Debug.Print "VarPtr(n)=" & Hex(VarPtr(n)) + Stop + Debug.Print "done" + End Sub + +End Module diff --git a/test/shots/codechapter/Sources/Ev.twin b/test/shots/codechapter/Sources/Ev.twin new file mode 100644 index 000000000..f57605821 --- /dev/null +++ b/test/shots/codechapter/Sources/Ev.twin @@ -0,0 +1,20 @@ +Class Ev + + Public Event Changed(ByVal n As Long) + Public Event Reset() + + Public Sub Fire(ByVal n As Long) + RaiseEvent Changed(n) + End Sub + +End Class + +Class Ev2 + + Public Event Ping() + + Public Sub Go() + RaiseEvent Ping() + End Sub + +End Class diff --git a/test/shots/codechapter/Sources/Helpers.twin b/test/shots/codechapter/Sources/Helpers.twin new file mode 100644 index 000000000..072b4cfb5 --- /dev/null +++ b/test/shots/codechapter/Sources/Helpers.twin @@ -0,0 +1,24 @@ +Module Helpers + + Public Enum Colour + Red = 1 + Green = 2 + Blue = 4 + End Enum + + Public Const BigValue As Long = &H8000 + + Public Function Twice(ByVal n As Long) As Long + Return n * 2 + End Function + + Public Sub WithWarning() + ReDim made(3) + Debug.Print UBound(made) + End Sub + + Public Sub Third() + Debug.Print Twice(5) + End Sub + +End Module diff --git a/test/shots/codechapter/Sources/Watcher.twin b/test/shots/codechapter/Sources/Watcher.twin new file mode 100644 index 000000000..6fdd9dffe --- /dev/null +++ b/test/shots/codechapter/Sources/Watcher.twin @@ -0,0 +1,15 @@ +Class Watcher + + Private WithEvents src As Ev + Private WithEvents other As Ev2 + Public Count As Long + + Public Sub Attach(ByVal e As Ev) + Set src = e + End Sub + + Private Sub src_Changed(ByVal n As Long) + Count = Count + Twice(n) + End Sub + +End Class diff --git a/test/shots/debugging/Sources/Checks.twin b/test/shots/debugging/Sources/Checks.twin new file mode 100644 index 000000000..0608884b3 --- /dev/null +++ b/test/shots/debugging/Sources/Checks.twin @@ -0,0 +1,33 @@ +Module Checks + + ' The "Running and debugging" chapter's error example: on the fourth pass idx is 7, past + ' the last index of a. + Public Sub FillTable() + Dim a(5) As Long + Dim i As Long + Dim idx As Long + Dim total As Long + For i = 1 To 6 + idx = Choose(i, 1, 3, 5, 7, 2, 4) + a(idx) = i * 10 + total = total + i + Debug.Print "i = " & i & ", idx = " & idx & ", total = " & total + Next i + Debug.Print "loop finished, i = " & i & ", total = " & total + End Sub + + ' A program that the program starts, short-lived and with no window: the PROCESS box. + Public Sub PingLater() + Shell "ping -n 20 127.0.0.1", vbHide + End Sub + + ' A run that waits a minute at most, for the Run menu while a program runs. + Public Sub WaitAWhile() + Dim started As Double + started = Timer + Do While Timer - started < 60 + DoEvents + Loop + End Sub + +End Module diff --git a/test/shots/memory/Settings b/test/shots/memory/Settings new file mode 100644 index 000000000..68639da01 --- /dev/null +++ b/test/shots/memory/Settings @@ -0,0 +1,33 @@ +{ + "configuration.inherits": "Defaults", + "project.appTitle": "MemoryDemo", + "project.buildPath": "${SourcePath}\\Build\\${ProjectName}_${Architecture}.${FileExtension}", + "project.buildType": "Standard EXE", + "project.description": "MemoryDemo: Sub Main stops with a String, for the Memory pane picture", + "project.exportPathIsV2": true, + "project.id": "{5C0F8A71-3B2E-4D69-8A14-7E91C2D4C7A2}", + "project.name": "MemoryDemo", + "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 + } + ], + "project.settingsVersion": 1, + "project.startupObject": "Sub Main", + "project.warnings": { + "errors": [], + "hints": [], + "ignored": [], + "info": [], + "warnings": [] + }, + "runtime.useUnicodeStandardLibrary": true +} diff --git a/test/shots/memory/Sources/Startup.twin b/test/shots/memory/Sources/Startup.twin new file mode 100644 index 000000000..fb1e29c3d --- /dev/null +++ b/test/shots/memory/Sources/Startup.twin @@ -0,0 +1,12 @@ +' The project shoot_docs.mjs runs for the Memory pane's picture (setup memory). Main +' stops with s holding fifteen characters: two bytes each and two for the terminating +' null fill two rows of the pane at Cols 16, so nothing else in memory is shown. +Module Startup + + Public Sub Main() + Dim s As String + s = "Hello twinBASIC" + Stop + End Sub + +End Module