codeeditor — u_pbt_codeeditor #
← Component reference · Guide contents
Code editor with syntax highlighting: ten languages, line numbers, region folding, search and replace, diagnostic markers in the gutter, and file drop.
▶ See it live — Demo application, Code editor tile: the preview, the code behind it and this page, side by side.
At a glance #
| Userobject | u_pbt_codeeditor |
| Item class | — (component without items) |
| Used for | Entering or displaying code, a SQL query, a configuration file: anywhere a multilineedit is not readable enough |
| Opt-in options | ib_track_caret, ib_allow_drop, ib_folding |
Quick start #
// window open event
uo_editor.is_syntax = uo_editor.SYNTAX_SQL
uo_editor.is_text = "SELECT c.name, SUM(o.amount) AS total~r~n" &
+ "FROM customer c~r~n" &
+ "WHERE o.status = 'paid'"
// Local variables
string ls_sql
// Read back what the user has actually typed
ls_sql = uo_editor.of_get_text()
is_textgives the same thing: the user's typing lands there as soon as the input settles (ue_changedevent).
Supported languages #
is_syntax accepts one of these constants, or SYNTAX_NONE (empty string) for plain text with no highlighting.
| Language | Constant | Other accepted spellings |
|---|---|---|
| PowerScript | SYNTAX_POWERSCRIPT | pb, powerbuilder |
| SQL | SYNTAX_SQL | tsql, plsql |
| JavaScript | SYNTAX_JAVASCRIPT | js, jsx |
| JSON | SYNTAX_JSON | jsonc |
| C family | SYNTAX_C · SYNTAX_CPP · SYNTAX_CSHARP · SYNTAX_JAVA | c++, cxx, cs |
| HTML / XML | SYNTAX_HTML · SYNTAX_XML | htm, xhtml, svg |
| CSS | SYNTAX_CSS | scss, less |
| Python | SYNTAX_PYTHON | py |
| YAML | SYNTAX_YAML | yml |
| Markdown | SYNTAX_MARKDOWN | md, mkd |
Languages of the same family share their highlighting (SYNTAX_JAVA colors like SYNTAX_C, SYNTAX_XML like SYNTAX_HTML): the constant you pick documents your intent, the result on screen is the same. The value is case-insensitive, and an unknown name falls back to plain text without raising an error.
Properties #
Content and language #
| Property | Type | Default | Purpose |
|---|---|---|---|
is_text | string | "" | The code displayed in the editor. When read back, returns the live content, the user's typing included, with the line endings it was given: a CRLF text reads back in CRLF, a CR-only text in CR, folded or not; a text that mixes them reads back in CRLF. A new text shows from its first line, clears the markers of of_add_marker and is not modified (ib_modified) |
is_syntax | string | "" | Highlighting language: SYNTAX_* constants (see the table above). SYNTAX_NONE = plain text. Reads back as written: SYNTAX_CSHARP stays SYNTAX_CSHARP, even though C# shares the C grammar |
ib_readonly | boolean | false | Read-only editor: the user can consult without being able to modify |
ib_modified | boolean | false | true as soon as the text differs from the one declared saved: a new is_text is not modified, typing, of_insert_text or a replace make it modified, undoing back to the saved text sets it back to false. Set it back to false after saving; true forces it |
Display #
| Property | Type | Default | Purpose |
|---|---|---|---|
ib_line_numbers | boolean | true | Shows or hides the line number gutter, on the left; hidden, a narrow gutter keeps the fold markers and the diagnostics of of_add_marker |
ib_current_line | boolean | true | Highlights the line where the caret sits; in ib_wrap mode the band covers every row of the line |
ib_folding | boolean | false | Opt-in: allows the #region … #endregion regions (//#region in PowerScript, JavaScript or C) to be folded from the gutter: #region starts folded, #regionopen starts open; the markers stay even with ib_line_numbers = false. It is a reading mode: while it is on the editor takes no typing, because the input area then holds the folded text and not the source; turning it on over closed regions also forgets the Ctrl+Z history. A marker is reached with the keyboard (Tab); Enter or Space folds or unfolds it |
ib_wrap | boolean | false | Wraps long lines instead of scrolling sideways |
ii_tab_size | integer | 4 | Number of columns a tab character takes up, from 1 to 12 (any other value falls back to 4); Enter after an opening brace indents by this width |
is_font_family | string | "" | Editor font (empty = the theme's monospaced font) |
ii_font_size | integer | 0 | Font size in pixels (0 = the theme's size) |
Navigation and interaction #
| Property | Type | Default | Purpose |
|---|---|---|---|
ib_search_enabled | boolean | true | Enables the built-in search bar (Ctrl+F, Ctrl+H to replace — see “Keyboard”); while false, of_find returns -4 |
ib_find_match_case | boolean | false | Search option: finds only the text with the same case. The Aa toggle of the bar is the same switch; applies to of_find, of_replace and of_replace_all |
ib_find_whole_word | boolean | false | Search option: finds the text only as a whole word (the _ belongs to the word: ls_a is not found inside ls_ab). The ab toggle of the bar |
ib_find_regex | boolean | false | Search option: the text is a regular expression (JavaScript syntax); the replacement of of_replace may then use $1, $& and $<name>. An invalid expression finds nothing, frames the box in red and makes of_replace return -5. The .* toggle of the bar |
il_doc_line | long | 0 | Brings the given line to the middle of the view and marks it with an accent band (numbered from 1, like the gutter) — the line a documentation or a search result points at. The user's caret and selection do not move (il_caret_line moves the caret). A line hidden in a folded region opens that region; the band is painted in ib_wrap mode too. 0 clears the band; a line past the end marks nothing and reads back as written; any new is_text clears the band |
il_caret_line | long | 1 | The line of the caret, from 1, read live (folded: the gutter's). Writing it puts the caret at the start of that line and brings it on screen — the “go to line” of a compiler error; a line hidden in a folded region opens that region, a line past the end stops at the last one. With ib_track_caret, ue_caret_changed follows, as for a click — nothing when the caret stays where it is |
il_caret_column | long | 1 | The column of the caret, from 1, read live: the rank of the character in its line (a tab counts for one). Writing it moves the caret along its line; past the end of the line it stops at that end. With ib_track_caret, ue_caret_changed follows, as for a click |
ib_track_caret | boolean | false | Opt-in: raises ue_caret_changed every time the caret moves — a gesture of the user or an order of your code; a new document (is_text, a load) says nothing about the caret |
ib_allow_drop | boolean | false | Opt-in: accepts files dropped from Windows Explorer; the full paths arrive through ue_drop_files |
is_theme_style | string | "" | Visual style of the component (THEME_STYLE_* constants); empty = the application's, followed at every change |
is_theme_mode | string | "" | Light or dark variant (THEME_MODE_* constants); empty = the application's, followed at every change |
il_theme_accent | long | -1 | Accent colour of this component (-1 = the application accent, or the theme's) |
is_tooltip | string | "" | Simple tooltip shown when hovering the component |
is_super_tooltip_title | string | "" | Title of the rich tooltip (takes precedence over is_tooltip) |
is_super_tooltip_text | string | "" | Text of the rich tooltip (rich markup accepted) |
is_super_tooltip_image | string | "" | Image of the rich tooltip |
Methods #
| Method | Purpose |
|---|---|
of_get_text ( ) → string | Returns the live content — the same value as is_text, for code that prefers a method call |
of_find (string as_text) | Opens the search bar, fills it in and highlights every occurrence of the text, following the ib_find_* options (folded, in the visible lines only). The first occurrence from the caret is brought on screen; the caret itself only moves when the user closes the bar. An empty text empties the box and clears the highlights. Returns 0 once applied, -4 when ib_search_enabled is false, -2 if the component is not created |
of_insert_text (string as_text) | Inserts text at the caret, exactly as if the user had typed it: the current selection is replaced, the caret lands after the insertion and the insertion is brought on screen. This is the method behind an insert snippet button, where is_text would throw away the work in progress. During a search, the insertion goes to the user's caret, never over the match. When the focus is in the editor or in its search bar, the insertion goes through the browser's own edit path: Ctrl+Z still undoes it. Raises ue_changed, and with ib_track_caret ue_caret_changed follows, as for typing. Nothing is inserted while the editor is read-only (ib_readonly) or folded (ib_folding). Returns 0 once applied, -4 when read-only or folded, -5 on an empty text, -2 if the component is not created |
of_replace (string as_find, string as_replace) | Replaces the first occurrence of as_find from the caret (wrapping to the top) and brings it on screen; the ib_find_* options apply, and with ib_find_regex the replacement may use $1, $& and $<name>. One Ctrl+Z undoes it. With ib_track_caret, ue_caret_changed follows when the caret moves. Returns the number of occurrences replaced (1, or 0 when there is none), -4 when read-only or folded, -5 on an empty search or an invalid expression, -2 if the component is not created |
of_replace_all (string as_find, string as_replace) | Replaces every occurrence of as_find, as one undo step. Same options and same codes as of_replace; returns how many occurrences were replaced |
of_selected_text ( ) → string | Returns the text the user has selected, read live ("" without a selection), with the line endings of is_text |
of_select_range (long al_from_line, long al_from_col, long al_to_line, long al_to_col) | Selects from (line, column) to (line, column), all from 1, and brings the selection on screen; the caret sits at the end given last, and a column past the end of its line stops at that end. With ib_track_caret, ue_caret_changed follows, as for a drag. Returns 0 once applied, -5 on a line outside the document or a column below 1, -4 while the editor is folded, -2 if the component is not created |
of_line_count ( ) | Returns how many lines the document has, folded or not, read live — the upper bound of a “go to line” box |
of_add_marker (long al_line, string as_kind, string as_tooltip) | Marks a line of the gutter with a diagnostic — MARKER_ERROR, MARKER_WARNING or MARKER_INFO — and a tooltip (markup accepted): what a compiler says, where it says it. Several markers may share a line: the worst kind gives the icon, the tooltip lists them all. They belong to the document: a new is_text clears them. Returns 0 once applied, -5 on a line outside the document or an unknown kind, -2 if the component is not created |
of_remove_marker (long al_line) | Removes every marker of one line. Returns 0 once removed, -5 when the line carries none, -2 if the component is not created |
of_clear_markers ( ) | Removes every marker of the gutter. Returns 0 once applied, -2 if the component is not created |
of_marker_count ( ) | Returns how many markers the gutter carries, read live (two on one line count for two) |
of_reset ( ) | Returns every property to its default and empties the editor. Returns 0 once applied, -2 when the component is not created |
of_set_redraw (boolean) | Batches a burst of changes into a single render. Returns 0 once applied, -2 when the component is not created |
of_save_as_png (string) · of_save_as_jpg (string) | Exports the rendering as an image. Returns 0 once the image is written, -4 when writing fails, -2 when the component is not created |
Events #
| Event | Raised when |
|---|---|
ue_changed ( ) | The user has modified the content and the typing has settled — also raised after of_insert_text or a replace. The event carries nothing: filling it meant re-reading the whole document on every settled keystroke, for an application that most often only wants to know that it changed. The one that wants the code asks for it — is_text or of_get_text(); ib_modified says whether it differs from the saved text |
ue_caret_changed (long al_line, long al_col) | The caret has moved (key, click, drag, typing, or your code); line and column counted from 1, the line being the gutter's when the editor is folded, the column the rank of the character in its line (a tab counts for one). Your code raises it like the gesture (il_caret_line, il_caret_column, of_select_range, of_insert_text, of_replace); nothing when the caret stays where it is, and a new document (is_text, a load) says nothing about the caret — requires ib_track_caret = true |
ue_find_result (long al_count, long al_index) | A search has completed, or moves to another occurrence: al_count occurrences found, al_index = rank of the one currently highlighted (from 1). While typing, it is raised only when the count changes; closing the bar does not raise it; nor does an empty search |
ue_drop_files (string as_files[]) | Files have been dropped from Windows: full paths, one entry per file. Requires ib_allow_drop = true |
ue_drag_enter ( ) | A file drag enters the editor (ib_allow_drop) |
ue_drag_leave ( ) | The file drag leaves the editor |
ue_ready ( ) | The component has finished loading; everything sent beforehand has been replayed |
ue_runtime_missing ( ) | The WebView2 runtime is missing: the component stays empty |
ue_bg_color (long al_color) | The component has computed its theme background color; the userobject has already adopted it (backcolor) |
Keyboard #
| Key | Effect |
|---|---|
| Tab | Indents to the next stop (ii_tab_size); several lines selected: indents the whole block |
| Shift+Tab | Outdents the line or the block |
| Enter | New line at the same indentation, one level deeper after {, ( or [ |
| Ctrl+F | Opens the search bar, filled with the selection (ib_search_enabled) |
| Ctrl+H | Opens the bar with its replace row (typeable editor only) |
| F3 · Shift+F3 | Next · previous match, from the code or the bar |
| Enter · Shift+Enter (bar) | Next · previous match; in the replace box, Enter replaces the current match and Ctrl+Enter replaces them all |
| Escape | Closes the bar and puts the caret on the current match |
| Tab, then Enter or Space (gutter) | Reaches a fold marker, folds or unfolds it (ib_folding) |
The column reported by ue_caret_changed and il_caret_column is the rank of the character in its line: a tab counts for one, whatever its width on screen.
Examples #
A SQL query editor #
// Freeze the drawing while the editor is set up
uo_query.of_set_redraw(/*on*/ false)
// SQL colouring, then the query to show
uo_query.is_syntax = uo_query.SYNTAX_SQL
uo_query.is_text = "-- Top customers by revenue collected~r~n" &
+ "SELECT c.name, SUM(o.amount) AS total~r~n" &
+ "FROM customer c~r~n" &
+ " INNER JOIN orders o ON o.cust_id = c.id~r~n" &
+ "WHERE o.status = 'paid'~r~n" &
+ "GROUP BY c.name~r~n" &
+ "ORDER BY total DESC"
// One repaint, everything at once
uo_query.of_set_redraw(/*on*/ true)
// clicked event of cb_execute
string ls_sql
// Run the query as typed
ls_sql = uo_query.of_get_text() // what the user really typed
of_execute(ls_sql)
A read-only viewer #
Ideal for presenting generated code, a log, or an excerpt the user has to read without modifying it.
// PowerScript colouring
uo_preview.is_syntax = uo_preview.SYNTAX_POWERSCRIPT
// A read-only viewer: no caret, no gutter, no highlight
uo_preview.ib_readonly = true // consultation only, no editing caret
uo_preview.ib_line_numbers = false // hides the number gutter
uo_preview.ib_current_line = false // no current line highlight
uo_preview.ib_wrap = true // wraps rather than scrolls
uo_preview.ii_tab_size = 2 // tabs displayed over 2 columns
// The code to show
uo_preview.is_text = of_generate_code()
Tracking the caret position in a status bar #
// Report every caret move (ue_caret_changed)
uo_editor.ib_track_caret = true // explicit opt-in: otherwise no event at all
// ue_caret_changed event of uo_editor: (long al_line, long al_col)
uo_status.of_panel(/*key*/ "pos").is_text = "Line " + String(al_line) + ", col. " + String(al_col)
Without ib_track_caret, the caret reports nothing: this trigger fires very often and stays off until you ask for it.
Searching and jumping to a line #
// Opens the search bar and highlights every occurrence
uo_editor.of_find(/*text*/ "ll_total")
// ue_find_result event of uo_editor: (long al_count, long al_index)
if al_count = 0 then
uo_status.of_panel(/*key*/ "main").is_text = "No match"
else
uo_status.of_panel(/*key*/ "main").is_text = String(al_index) + " / " + String(al_count)
end if
// Go to the line a compiler flagged and mark it in the gutter
uo_editor.of_add_marker(/*line*/ ll_error_line, /*kind*/ uo_editor.MARKER_ERROR, /*tooltip*/ ls_error_text)
uo_editor.il_caret_line = ll_error_line // the caret goes there, the line comes on screen
uo_editor.of_focus_webview() // the user fixes it at once
Opening a file dropped from Explorer #
// Accept files dropped from the Explorer
uo_editor.ib_allow_drop = true
// ue_drop_files event of uo_editor: (string as_files[])
string ls_content
integer li_file
// as_files[1] carries the FULL path of the first dropped file
li_file = FileOpen(as_files[1], StreamMode!, Read!)
if li_file > 0 then
FileReadEx(li_file, ls_content)
FileClose(li_file)
// Colour by the file's extension, then show its content
uo_editor.is_syntax = of_syntax_for_extension(as_files[1])
uo_editor.is_text = ls_content
end if
Reacting to changes #
// ue_changed event of uo_editor: ( )
cb_save.enabled = uo_editor.ib_modified // undoing back to the saved text sets it back to false
// clicked event of cb_save
if of_save_script(uo_editor.is_text) = 1 then
uo_editor.ib_modified = false // the saved text becomes the reference
cb_save.enabled = false
end if
The event is only raised once the typing has settled: continuous input does not generate one event per keystroke.
Renaming a variable throughout the script #
// Local variables
long ll_count
// Whole words only: ll_total2 is another variable
uo_editor.ib_find_whole_word = true
ll_count = uo_editor.of_replace_all(/*find*/ "ll_total", /*replace*/ "ldc_amount") // one Ctrl+Z undoes them all
Best practices #
- Always set
is_syntaxbeforeis_text: the code is colored from the very first paint, with no visible recoloring. - For a read-only display, combining
ib_readonly+ib_line_numbers = false+ib_wrapgives you a plain viewer that no longer looks like an editor. - To retrieve user input, read
is_text(orof_get_text()) once the typing has settled — that is, inside yourue_changed, which tells you when. ib_foldingis only worth it on long, structured files; leave it off for short excerpts. Turn it off before allowing edits: a folded outline is read, not edited, andof_get_text()always returns the whole source, closed regions included.- Wrap the loading of a large file in
of_set_redraw(false)/of_set_redraw(true). - Call
of_reset()before loading a document of a different nature: otherwise the previous language, tab size or read-only mode stay in place. - After saving, set
ib_modifiedback tofalse: it turnstrueagain at the first keystroke, andfalseif the user undoes back to the saved text. - To show the errors of a compilation,
of_clear_markers()then oneof_add_markerper diagnostic, andil_caret_lineon the first one.
Inherited from the common base #
These members exist on every visual component — they are not specific to this one. They are detailed once, in the transverse chapters; this table only says where to read them.
| Members | Role | Detailed in |
|---|---|---|
of_reset | Put the component back to zero | 3.6 Resetting a component: of_reset() |
of_register_shortcut · of_clear_shortcuts | The component's keyboard chords | 3.5 Keyboard shortcuts |
of_is_created · of_is_ready · of_get_last_error | Whether it was born, whether it is ready, what failed | 3.7 Diagnostics |
of_save_as_png · of_save_as_jpg | Export the rendering as an image | 3.8 Exporting the rendering as an image |
of_set_redraw | Group changes into a single repaint | 3.10 Best practices |
of_preload_icons | Icons shown with no delay | Instant display: of_icon |
of_set_translation | Translate one of the component's labels | 5.2 Adapting a label: of_set_translation |
of_focus_webview | Give the component the focus | 6.4 Keyboard and focus |
of_print · of_print_to_pdf | Print, or write a PDF | 6.9 Printing |
of_set_property · of_get_property · of_component_name | Driving a property by its name | 3.1 The property engine |
Two helpers are not inherited: of_icon and of_escape_markup live on n_pbt_utils. Declare one — n_pbt_utils lnv_utils, nothing to create — and call them on it.