@mudlet/mudlet-web 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -0
  2. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  3. package/dist-lib/branding.d.ts +1 -1
  4. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  5. package/dist-lib/hooks/useOutput.d.ts +3 -1
  6. package/dist-lib/import/defaultPackages.d.ts +35 -2
  7. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  8. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  9. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  10. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  11. package/dist-lib/import/packageExport.d.ts +71 -0
  12. package/dist-lib/import/packageInstaller.d.ts +4 -1
  13. package/dist-lib/index.js +45935 -40688
  14. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  15. package/dist-lib/map/MapStore.d.ts +32 -4
  16. package/dist-lib/map/mapImageExport.d.ts +1 -1
  17. package/dist-lib/map/mapZoom.d.ts +71 -0
  18. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  19. package/dist-lib/mud/MudSession.d.ts +113 -11
  20. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  21. package/dist-lib/mud/commandSplit.d.ts +16 -0
  22. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  23. package/dist-lib/mud/connection/MudClient.d.ts +120 -16
  24. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  25. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  26. package/dist-lib/mud/events.d.ts +19 -4
  27. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  28. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  29. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  30. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  31. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  32. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  33. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  34. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  35. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  36. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  37. package/dist-lib/mud/protocol/index.d.ts +1 -1
  38. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  39. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  40. package/dist-lib/mud/text/Console.d.ts +64 -0
  41. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  42. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  43. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  44. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  45. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  46. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  47. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  48. package/dist-lib/scripting/IScriptingRuntime.d.ts +16 -0
  49. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  50. package/dist-lib/scripting/ScriptingAPI.d.ts +323 -54
  51. package/dist-lib/scripting/ScriptingEngine.d.ts +180 -23
  52. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  53. package/dist-lib/scripting/lua/LuaRuntime.d.ts +151 -2
  54. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  55. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  56. package/dist-lib/storage/index.d.ts +1 -1
  57. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  58. package/dist-lib/storage/schema.d.ts +116 -0
  59. package/dist-lib/styles.css +1 -1
  60. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  61. package/dist-lib/ui/CommandBar.d.ts +7 -1
  62. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  63. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  64. package/dist-lib/ui/HelpModal.d.ts +11 -0
  65. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  66. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  67. package/dist-lib/ui/Toolbar.d.ts +2 -1
  68. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  69. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  70. package/dist-lib/ui/components/index.d.ts +1 -0
  71. package/dist-lib/ui/helpTopics.d.ts +19 -0
  72. package/dist-lib/ui/labels/LabelManager.d.ts +7 -0
  73. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  74. package/dist-lib/ui/markdown.d.ts +10 -1
  75. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  76. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  77. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  78. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  79. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  80. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  81. package/dist-lib/ui/search/matcher.d.ts +15 -0
  82. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  83. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  84. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  85. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  86. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  87. package/dist-lib/ui/windows/WindowManager.d.ts +132 -2
  88. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  89. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  90. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  91. package/dist-lib/ui/windows/types.d.ts +35 -0
  92. package/dist-lib/utils/describeThrown.d.ts +22 -0
  93. package/dist-lib/utils/fontLoader.d.ts +5 -0
  94. package/dist-lib/vite.js +22 -0
  95. package/package.json +10 -6
  96. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  97. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -1,3 +1,4 @@
1
+ import { ItemIdSequence } from '../ItemIdSequence';
1
2
  import type { TimerNode } from '../../storage/schema';
2
3
  export type { TimerNode };
3
4
  type TempFn = () => void;
@@ -8,15 +9,23 @@ export declare class TimerEngine {
8
9
  * share a name; we keep id as the canonical handle and build a separate
9
10
  * name → id index for Mudlet's name-based lookups. */
10
11
  private readonly perm;
11
- /** Name → first matching id, used by remainingTime / kill-by-name. */
12
+ /** Name → first matching id, used by remainingTime / kill-by-name. Only
13
+ * holds *armed* timers, since that is what loadPerm builds handles for. */
12
14
  private readonly permNameToId;
13
- private nextId;
14
- /** Number of live session-scoped temp timers (Mudlet `getProfileStats` temp count). */
15
+ /** Every stored permanent timer name, armed or not. remainingTime needs it
16
+ * to tell "exists but inactive" (-1) from "no such timer" (-2): a disabled
17
+ * timer has no handle, so its absence from `perm` proves nothing. */
18
+ private readonly knownPermNames;
19
+ /** Shared with every other engine in the profile — see ItemIdSequence. */
20
+ private idSeq;
21
+ setIdSequence(seq: ItemIdSequence): void;
22
+ /** Number of live session-scoped temp timers (Mudlet `getProfileStats` temp
23
+ * count). Killed-but-unreaped timers are not live and don't count. */
15
24
  get tempCount(): number;
16
25
  addTemp(seconds: number, fn: TempFn, repeat?: boolean): number;
17
26
  /**
18
- * Fire every one-shot timer that has come due, without waiting for the
19
- * event loop to deliver its setTimeout.
27
+ * Fire every timer that has come due, without waiting for the event loop to
28
+ * deliver its setTimeout.
20
29
  *
21
30
  * This exists for `waitForEvent`, the busted-only helper Mudlet implements
22
31
  * by spinning a nested QEventLoop. A browser can't re-enter its event loop,
@@ -24,15 +33,33 @@ export declare class TimerEngine {
24
33
  * deadlock; pumping the due timers by hand is the equivalent of Qt draining
25
34
  * its timer queue inside that nested loop.
26
35
  *
27
- * Repeating timers are deliberately skipped: their setInterval is still
28
- * armed, and firing them here would double-fire each tick. A repeat timer
29
- * therefore misses ticks across a synchronous wait the same thing that
30
- * happens whenever the main thread is busy.
36
+ * Repeating timers are pumped too Mudlet's own specs wait on a repeat
37
+ * tick, so skipping them made every such wait time out. That works because
38
+ * a repeat is a self-rescheduling setTimeout (see addTemp): the pending
39
+ * tick is cancelled and re-armed rather than left to land a second time.
31
40
  *
32
41
  * Returns the number of timers fired.
33
42
  */
34
43
  pumpDue(now?: number): number;
44
+ /**
45
+ * Whether a temporary timer with this id is still present.
46
+ *
47
+ * Backs `exists(id, "timer")`. Temp timers aren't in the store and — unlike
48
+ * temp triggers and aliases — aren't tracked in LuaRuntime's tempIds either,
49
+ * so this map is the only thing that knows.
50
+ *
51
+ * There is no enable/disable for a temporary timer, so this used to answer
52
+ * `isActive` too. A killed one splits the two apart: it is still present
53
+ * here until the reap, but no longer running — see {@link tempIsActive}.
54
+ */
55
+ hasTemp(id: number): boolean;
56
+ /** Whether a temporary timer is present *and* still running — `isActive(id,
57
+ * "timer")`. False for one killed since the last reap. */
58
+ tempIsActive(id: number): boolean;
35
59
  killTimer(id: number): boolean;
60
+ /** Free every timer killed since the last call. Runs once per processed line
61
+ * batch, mirroring the deferred cleanup Mudlet's TTimerUnit does. */
62
+ reapKilled(): void;
36
63
  /**
37
64
  * Cached previous load. `nodes` is the TimerNode keyed by id, `desc` is the
38
65
  * shape that determines whether the running setTimeout/setInterval is still
@@ -50,10 +77,14 @@ export declare class TimerEngine {
50
77
  * Mudlet `remainingTime(idOrName)` — seconds until the next fire. For
51
78
  * non-repeating timers, returns the time left before the one and only
52
79
  * fire. For repeating timers, returns the time until the next tick.
53
- * Returns -1 if no live timer matches (Mudlet's miss sentinel).
80
+ * Mirrors Mudlet's two miss sentinels, which the Lua wrapper turns into
81
+ * different messages: **-1** the timer exists but isn't running (a perm
82
+ * timer starts inactive), **-2** nothing of that id or name exists at all.
54
83
  * - Numeric arg: looks up tempTimer ids only.
55
- * - String arg: looks up permanent timer names; falls back to the stored
56
- * TimerNode id when the string is a uuid that no name matched.
84
+ * - String arg: permanent timer names (or a raw uuid), then temp ids —
85
+ * a temporary timer's "name" is the number tempTimer handed back, so
86
+ * remainingTime(tostring(id)) has to resolve the same timer as
87
+ * remainingTime(id).
57
88
  */
58
89
  remainingTime(idOrName: number | string): number;
59
90
  private stopPerm;
@@ -35,9 +35,24 @@ export type TriggerMatch = {
35
35
  namedSpans?: Record<string, CaptureSpan>;
36
36
  matchStart?: number;
37
37
  };
38
+ /** The lineage a trigger created mid-pass belongs to, and how many generations
39
+ * deep it sits in it. Absent on a trigger that was not created while a line was
40
+ * being processed. See TriggerEngine.addedWhileProcessing. */
41
+ interface SameLineChain {
42
+ chainId: number;
43
+ generation: number;
44
+ }
45
+ /** What a caller can tell the engine about a temp trigger beyond its pattern. */
46
+ export interface TempOptions {
47
+ /** As the runaway report names it. Mudlet uses the trigger's id when the
48
+ * call carried no name of its own. */
49
+ name?: string;
50
+ onStopped?: () => void;
51
+ }
38
52
  export declare class TriggerEngine {
39
53
  private readonly temp;
40
- private nextId;
54
+ /** Key for this engine's own temp map — see PatternEngine.nextInternalId. */
55
+ private nextInternalId;
41
56
  private inProcessTemp;
42
57
  private permCompiled;
43
58
  private allById;
@@ -45,12 +60,32 @@ export declare class TriggerEngine {
45
60
  private permReg;
46
61
  private unified;
47
62
  private orderDirty;
63
+ private processingDepth;
64
+ private addedWhileProcessing;
65
+ /** Lineage of each permanent ROOT trigger enrolled for the current line. */
66
+ private permSameLine;
67
+ /** Lineage id → name of the trigger whose script started it, for the report. */
68
+ private sameLineChainStarters;
69
+ private lastSameLineChainId;
70
+ private currentSameLineChainId;
71
+ private currentSameLineGeneration;
72
+ /** The name of the trigger whose script is running, for the abort report. */
73
+ private currentExecutingTriggerName;
74
+ /** When the runaway report was last posted, so a runaway whose creator
75
+ * outlives its line cannot bury the game text. */
76
+ private lastRunawayReportAt;
48
77
  private cache;
49
78
  private lineCounter;
50
79
  private readonly chainOpenUntil;
51
80
  private hasChildren;
52
81
  private andStates;
82
+ /** Triggers still firing on their own after completing, and the match they
83
+ * re-report while they do. See the fire-length branch in matchPermEntryOnce. */
84
+ private keepFiring;
53
85
  private filterActiveText;
86
+ /** Every offering a filter makes to its children this line: one per
87
+ * capture group, or the whole match when it has none. See openChain. */
88
+ private filterCaptures;
54
89
  private filterActiveOffset;
55
90
  /** Resolves once PCRE wasm is initialized and patterns can be compiled. */
56
91
  static ready(): Promise<void>;
@@ -78,7 +113,7 @@ export declare class TriggerEngine {
78
113
  * Invalid regex patterns return a no-op disposer so callers don't need
79
114
  * to special-case compile failures.
80
115
  */
81
- addTemp(pattern: string, fn: TempFn, kind?: 'regex' | 'substring' | 'startOfLine' | 'exactMatch' | 'prompt'): () => void;
116
+ addTemp(pattern: string, fn: TempFn, kind?: 'regex' | 'substring' | 'startOfLine' | 'exactMatch' | 'prompt', opts?: TempOptions): () => void;
82
117
  /**
83
118
  * Mudlet `tempLineTrigger(from, howMany, fn)`. A position-based trigger with
84
119
  * no pattern: it fires `fn([lineText])` on `howMany` consecutive lines,
@@ -95,9 +130,15 @@ export declare class TriggerEngine {
95
130
  * negative result is cached so we don't retry on every loadPerm. */
96
131
  private compileItem;
97
132
  processTemp(line: string, isPrompt?: boolean): void;
133
+ /** Leave a processing pass, and once the outermost one is over, forget the
134
+ * lineages it tracked: a trigger that outlives the line it was created on
135
+ * is no longer part of one, so what IT creates later starts counting
136
+ * afresh. */
137
+ private endPass;
98
138
  /** Match + fire a single temp trigger against `line`. Self-expiring `line`
99
139
  * triggers delete themselves (and dirty the unified order) when spent. */
100
140
  private fireTempEntry;
141
+ private fireTempEntryInner;
101
142
  matchPerm(line: string, isPrompt?: boolean): TriggerMatch[];
102
143
  /**
103
144
  * Match one permanent compiled entry against `line`, appending any matches
@@ -106,6 +147,9 @@ export declare class TriggerEngine {
106
147
  * shared across all entries processed for that line.
107
148
  */
108
149
  private matchPermEntry;
150
+ /** The nearest filter ancestor of `item`, or null when it has none. */
151
+ private innermostFilterId;
152
+ private matchPermEntryOnce;
109
153
  /**
110
154
  * Mudlet-faithful single pass: walk the one ordered list of permanent and
111
155
  * temporary triggers (see the ordering notes on the fields) and, for each
@@ -122,6 +166,53 @@ export declare class TriggerEngine {
122
166
  * re-entrancy a handler can cause via `feedTriggers`.
123
167
  */
124
168
  process(line: string, isPrompt: boolean, exec: (match: TriggerMatch) => void): void;
169
+ /**
170
+ * Enrol a trigger armed while a line was being processed, so the pass can
171
+ * offer it that line, and put it in a lineage: the one belonging to the
172
+ * trigger whose script armed it, a generation on, or a new one when the
173
+ * script predates the line.
174
+ */
175
+ private registerSameLineCreation;
176
+ /**
177
+ * The same, for a permanent trigger a script created mid-pass. Only ROOT
178
+ * triggers carry a lineage: one sitting in a folder creates on the folder's
179
+ * behalf, and reading the child's own (always empty) lineage instead would
180
+ * start a fresh one every round — which never deepens and so never trips.
181
+ */
182
+ notePermCreated(node: TriggerNode, root: TriggerNode): void;
183
+ /** Join the lineage of the trigger whose script is running, a generation on;
184
+ * or start one when that script predates the line. */
185
+ private nextSameLineChain;
186
+ /** The lineage of something enrolled for this line, whichever kind it is. */
187
+ private sameLineOf;
188
+ /** The root ancestor's id — the node a lineage is recorded against. */
189
+ private rootIdOf;
190
+ /** Run a permanent trigger's script inside its lineage, so what it creates
191
+ * is enrolled behind it rather than starting a lineage of its own. */
192
+ private execPerm;
193
+ /**
194
+ * Run `fn` as the script of the trigger named `name`, so anything it arms
195
+ * joins that trigger's lineage rather than starting one of its own.
196
+ */
197
+ runAsTrigger<T>(name: string, sameLine: SameLineChain | undefined, fn: () => T): T;
198
+ /**
199
+ * End a lineage that keeps arming triggers which match the line being
200
+ * processed. Stopping the pass alone would not do: what it created is still
201
+ * live, so the next line would start with a budget's worth of them and each
202
+ * would spawn a budget's worth again. Only this lineage is disowned — a
203
+ * capture trigger an unrelated script armed on the same line belongs to a
204
+ * lineage of its own and is left alone.
205
+ */
206
+ private stopSameLineCreationLoop;
207
+ /** Tell the player, at most once every REPORT interval — a runaway whose
208
+ * creator outlives the line trips on every matching line, and an unthrottled
209
+ * report would bury the game text it is trying to explain. */
210
+ private reportRunaway;
211
+ /** Where the runaway report is printed. ScriptingEngine wires this to the
212
+ * main console; unset (tests, teardown) the report is dropped. */
213
+ setRunawayReporter(fn: ((text: string) => void) | null): void;
214
+ /** How the engine switches off a permanent trigger it stopped. */
215
+ setPermDisabler(fn: ((nodeId: string) => void) | null): void;
125
216
  /** Rebuild the merged, path-sorted processing list from the current
126
217
  * permanent entries and temporary triggers. Called lazily from process()
127
218
  * when either source changed. */
@@ -137,9 +228,27 @@ export declare class TriggerEngine {
137
228
  * `ScriptingAPI.currentLineMatchesColor`. Passing `null` disables every
138
229
  * colour trigger (e.g. during runtime teardown).
139
230
  */
140
- setColorMatcher(fn: ((fg: number, bg: number) => boolean) | null): void;
231
+ setColorMatcher(fn: ((fg: number, bg: number, window: {
232
+ start: number;
233
+ length: number;
234
+ } | null) => boolean) | null): void;
141
235
  destroy(): void;
236
+ /**
237
+ * Advance every state an AND trigger has in flight against this line, and
238
+ * report the ones that completed on it.
239
+ *
240
+ * States are a LIST, not one: a trigger whose first condition matches twice
241
+ * before its second ever does has two matches part-way through, and both
242
+ * complete when that second condition finally arrives. Keeping a single
243
+ * state let the newer one overwrite the older, so only one ever fired.
244
+ */
142
245
  private processAndTrigger;
246
+ /** Carry one in-flight state as far through the remaining conditions as this
247
+ * line takes it. Several can be satisfied by the same line. */
248
+ private advanceAndState;
249
+ /** The match a completed AND state reports: every line's captures, in order,
250
+ * as `multimatches`. */
251
+ private andMatch;
143
252
  /**
144
253
  * Record a chain-head match: open the chain for `fireLength` more lines and,
145
254
  * if the trigger is also a filter, stash the captured/matched text so
@@ -1,4 +1,14 @@
1
+ import type { TriggerPattern } from '../storage/schema';
1
2
  import type { InstallOutcome } from './ScriptingAPI';
3
+ /**
4
+ * The item families the tree-walking APIs accept, lowercased — Mudlet's
5
+ * "'alias', 'button', 'script', 'keybind', 'timer' or 'trigger'", plus the
6
+ * 'key' spelling the engine already treats as 'keybind'. Lives here rather than
7
+ * on the engine so the no-op host below answers the same list: a runtime with no
8
+ * engine wired has no items, but it still knows what an item type IS, and
9
+ * reporting a valid type as invalid would send a caller looking for a typo.
10
+ */
11
+ export declare const KNOWN_ITEM_TYPES: ReadonlySet<string>;
2
12
  /**
3
13
  * Everything {@link ScriptingAPI} needs from {@link ScriptingEngine}.
4
14
  *
@@ -15,6 +25,22 @@ import type { InstallOutcome } from './ScriptingAPI';
15
25
  *
16
26
  * Members are grouped the way the engine groups them, not alphabetically.
17
27
  */
28
+ /** What tempComplexRegexTrigger asks for: a session-scoped trigger carrying
29
+ * everything a permanent one can. See ScriptingEngine.createTempComplexTrigger. */
30
+ export interface TempComplexTriggerSpec {
31
+ name: string;
32
+ patterns: TriggerPattern[];
33
+ code: string;
34
+ multiline: boolean;
35
+ isFilter: boolean;
36
+ multipleMatches: boolean;
37
+ fireLength: number;
38
+ delta: number;
39
+ highlight?: {
40
+ fg?: string;
41
+ bg?: string;
42
+ };
43
+ }
18
44
  export interface EngineHost {
19
45
  /** Run a chunk of Lua on behalf of a clicked hyperlink. Errors are reported
20
46
  * through the API's error channel rather than thrown. */
@@ -34,9 +60,12 @@ export interface EngineHost {
34
60
  requestReconnect(): boolean;
35
61
  /** Run a synthetic batch of lines through the same pipeline as network
36
62
  * output, so `feedTriggers` shares ordering semantics with real data. */
63
+ /** `fromServer` distinguishes a batch that came off the socket from one a
64
+ * script synthesised (feedTriggers). Absent means the socket. */
37
65
  processFlushBatch(groups: {
38
66
  text: string;
39
67
  type: string;
68
+ fromServer?: boolean;
40
69
  }[]): void;
41
70
  /** Apply any coalesced trigger/alias reloads immediately instead of on the
42
71
  * scheduled microtask. The Mudlet `perm…`, `enableTrigger` and
@@ -54,6 +83,8 @@ export interface EngineHost {
54
83
  installModuleFromPath(path: string): InstallOutcome;
55
84
  uninstallModuleByName(name: string): boolean;
56
85
  syncModuleToFile(name: string): Promise<void>;
86
+ /** Write every module flagged to sync back out to its own file. */
87
+ saveSyncedModules(): void;
57
88
  reloadModuleFromFile(name: string): boolean;
58
89
  setModuleSync(name: string, sync: boolean): void;
59
90
  getModuleSync(name: string): boolean;
@@ -75,12 +106,22 @@ export interface EngineHost {
75
106
  /** Read raw bytes from the profile VFS, or null if missing/unreadable.
76
107
  * Backs synchronous binary consumers such as setMovie's GIF decoder. */
77
108
  readFileBytes(path: string): Uint8Array | null;
109
+ /** Write raw bytes into the VFS, creating parent directories. False with no
110
+ * VFS or when the write throws. Paths are absolute, so this also reaches
111
+ * outside the profile — the shared window layout lives beside the profiles
112
+ * folder, not inside one. */
113
+ writeFileBytes(path: string, bytes: Uint8Array): boolean;
114
+ /** Absolute VFS path of the directory that holds every profile folder —
115
+ * Mudlet's configuration directory, where files shared between profiles
116
+ * (the window layout) live. Null with no VFS. */
117
+ configDirectory(): string | null;
78
118
  toggleScriptByName(name: string, enabled: boolean): boolean;
79
119
  toggleTriggerByName(name: string, enabled: boolean): boolean;
80
120
  toggleTimerByName(name: string, enabled: boolean): boolean;
81
121
  toggleAliasByName(name: string, enabled: boolean): boolean;
82
122
  toggleKeyByName(name: string, enabled: boolean): boolean;
83
- toggleToolBarByName(name: string, show: boolean): boolean;
123
+ /** Null when the toolbar moved, otherwise why it did not. */
124
+ toggleToolBarByName(name: string, show: boolean): string | null;
84
125
  setTriggerStayOpenByName(name: string, lines: number): boolean;
85
126
  /** The saved keybinding carrying this numeric id, or null — getKeyCode
86
127
  * resolves a permanent key by the id permKey returned, not only by name. */
@@ -104,12 +145,19 @@ export interface EngineHost {
104
145
  node: string;
105
146
  isActive: boolean;
106
147
  }> | null;
107
- findItemsByName(name: string, type: string, exact: boolean, caseSensitive: boolean): number[];
148
+ findItemsByName(name: string, type: string, exact: boolean, caseSensitive: boolean): number[] | null;
149
+ /** Whether `type` names an item family at all. */
150
+ isKnownItemType(type: string): boolean;
151
+ /** Next id from the profile's single item-id sequence. Temporary items draw
152
+ * from it too, so a temp and a permanent item can never collide. */
153
+ allocateItemId(): number;
108
154
  isAncestorsActiveById(id: number, type: string): boolean | null;
109
155
  getProfileStats(): Record<string, unknown>;
110
156
  createPermScript(name: string, parent: string, code: string): number;
111
157
  createPermRegexTrigger(name: string, parent: string, regexes: string[], code: string): number;
112
158
  createPermSubstringTrigger(name: string, parent: string, patterns: string[], code: string): number;
159
+ createTempComplexTrigger(spec: TempComplexTriggerSpec): number;
160
+ removeTemporaryTriggerById(id: number): boolean;
113
161
  createPermBeginOfLineStringTrigger(name: string, parent: string, patterns: string[], code: string): number;
114
162
  createPermExactMatchTrigger(name: string, parent: string, patterns: string[], code: string): number;
115
163
  createPermPromptTrigger(name: string, parent: string, code: string): number;
@@ -120,14 +168,19 @@ export interface EngineHost {
120
168
  createPermKey(name: string, parent: string, modifier: number, key: string | number, code: string): number;
121
169
  createTempButton(toolbar: string, name: string, code: string, orientation: number): number;
122
170
  createTempButtonToolbar(name: string, orientation: number, location: number): number;
171
+ /** True when the state actually changed; false when it already was that. */
123
172
  setButtonStateByName(name: string, state: boolean): boolean;
124
173
  getButtonStateByName(name: string): boolean | null;
174
+ /** Which of Mudlet's button refusals applies to `name`. */
175
+ buttonKindByName(name: string): 'missing' | 'plain' | 'pushdown';
125
176
  setButtonStyleSheetByName(name: string, css: string): boolean;
126
177
  setScriptByName(name: string, code: string, pos: number): number;
127
178
  getScriptByName(name: string, pos: number): {
128
179
  code: string;
129
- count: number;
180
+ id: number;
130
181
  } | null;
182
+ /** Remove the script with this numeric id; true when one was removed. */
183
+ removeScriptById(id: number): boolean;
131
184
  /** Mudlet's killTimer/killAlias/killTrigger/killKey by permanent-item name. */
132
185
  killByName(kind: 'timer' | 'alias' | 'trigger' | 'key', name: string): boolean;
133
186
  /** Mudlet `resetProfile()` — reload the profile with a fresh Lua VM. */
@@ -49,7 +49,15 @@ export interface IScriptingRuntime {
49
49
  setMsspValue(name: string, value: string): void;
50
50
  runWithMatches(code: string, name: string, matches: (string | undefined)[], multimatches?: (string | undefined)[][], namedGroups?: Record<string, string>, captureSpans?: CaptureSpan[], namedSpans?: Record<string, CaptureSpan>, fullMatchSpan?: CaptureSpan): void;
51
51
  destroy(): void;
52
+ /** Bytes behind a path in the runtime's read-only bundled namespace, or null
53
+ * when it has none / the path is not one of them. Optional because it is a
54
+ * property of how a runtime ships its own library, not of running scripts:
55
+ * the Lua runtime serves `/lua/...` this way. */
56
+ readBuiltinBytes?(path: string): Uint8Array | null;
52
57
  setCurrentLine(line: string, isPrompt: boolean): void;
58
+ /** The line {@link setCurrentLine} last recorded. The engine reads it back
59
+ * to restore the outer line after a nested feedTriggers. */
60
+ getCurrentLine(): string;
53
61
  /**
54
62
  * Mirror the last command-bar input into the Lua `command` global, matching
55
63
  * Mudlet's AliasUnit::processDataStream. Read by scripts/keys such as the
@@ -84,6 +92,9 @@ export interface IScriptingRuntime {
84
92
  * (unlike permanent items) don't live in the persisted store.
85
93
  */
86
94
  tempItemExists(id: number, type: string): boolean;
95
+ /** A live temp item of `type` carrying this script-supplied name (Mudlet's
96
+ * tempComplexRegexTrigger is the one temp API that takes one), or null. */
97
+ tempItemIdByName(name: string, type: string): number | null;
87
98
  /** Publish one use of a server-defined MXP element as `mxp.<element>`. */
88
99
  setMxpElement(name: string, attrs: Record<string, string>): void;
89
100
  /** Whether a live temp item is enabled — backs `isActive(id, type)`. */
@@ -91,6 +102,11 @@ export interface IScriptingRuntime {
91
102
  /** enable/disable a live temp item by id — backs enableTrigger/disableTrigger
92
103
  * (and the alias pair) when handed the numeric id rather than a name. */
93
104
  setTempItemEnabled(id: number, enabled: boolean): boolean;
105
+ /** Free every temp item killed since the last call, or only those of `type`.
106
+ * A killed item stops firing immediately but stays findable until here,
107
+ * which is Mudlet's deferred per-unit cleanup: aliases are reaped when
108
+ * nested alias processing unwinds, triggers when the line batch finishes. */
109
+ reapKilledTempItems(type?: 'alias' | 'trigger'): void;
94
110
  /** Rebuild saved Lua globals (a Mudlet `<VariablePackage>` tree) into `_G`. */
95
111
  restoreVariables(vars: MudletVariable[]): void;
96
112
  /** Snapshot the save-listed globals out of `_G` into a variable tree. */
@@ -0,0 +1,118 @@
1
+ /**
2
+ * MXP `<FRAME>` window layout — a port of Mudlet 4.21's `TMxpFrameManager`
3
+ * (src/TMxpFrameManager.cpp).
4
+ *
5
+ * MXP lets a server carve the client window into named sub-windows and then
6
+ * redirect text into them with `<DEST>`. Mudlet lays those out itself rather
7
+ * than handing them to the OS window manager: internal frames are tiled against
8
+ * the edges of the main console, each one shrinking the console's usable area
9
+ * by adding to a set of "MXP borders", and frames declared inside an open
10
+ * `<DEST>` nest inside that destination frame instead. `TITLE` gives a frame a
11
+ * tab header, and `DOCK`+`ALIGN=CLIENT` (a CMUD extension, not MXP 1.0) adds a
12
+ * frame as another tab of an existing one.
13
+ *
14
+ * This class owns the same bookkeeping — the frame records, the accumulated
15
+ * borders, the parent/child hierarchy and the tab groups — and drives the UI
16
+ * through {@link MxpFrameHost} so the geometry is testable without a DOM.
17
+ * mudix maps a frame's console onto a mini-console (an overlay panel with
18
+ * script-controlled geometry); the tab strip is window chrome rendered by
19
+ * ScriptWindow, and child frames nest by portalling into the parent panel's
20
+ * viewport, so their coordinates are parent-relative.
21
+ */
22
+ import type { MxpTabPage } from '../ui/windows/types';
23
+ /** Height in px of a frame's tab strip. Must match `.mxp-frame-tabs` in ScriptWindow.css. */
24
+ export declare const MXP_TAB_BAR_HEIGHT = 24;
25
+ /** Pixel insets carved out of the main console by edge-aligned frames. */
26
+ export interface MxpBorders {
27
+ top: number;
28
+ right: number;
29
+ bottom: number;
30
+ left: number;
31
+ }
32
+ /** Rectangle, in main-viewport coordinates, that top-level frames tile inside. */
33
+ export interface MxpArea {
34
+ x: number;
35
+ y: number;
36
+ width: number;
37
+ height: number;
38
+ }
39
+ /** Everything the layout needs from the app. Implemented by ScriptingAPI. */
40
+ export interface MxpFrameHost {
41
+ /** The region top-level frames may tile in: the main window minus whatever
42
+ * `setBorder*` has reserved. A GUI package that docks itself to an edge
43
+ * (Geyser's `Adjustable.Container{attached=…}`) reserves its strip that
44
+ * way, and a server laying out frames must not tile over it. MXP's own
45
+ * borders are excluded — the manager tracks those itself. */
46
+ consoleArea(): MxpArea;
47
+ /** Character cell `[width, height]` in px for the main output font, backing `Nc` sizes. */
48
+ charCellSize(): [number, number];
49
+ /** Create (or reposition) a frame's console. `parent` nests it inside that
50
+ * frame's viewport, making x/y parent-relative. */
51
+ placeFrameConsole(name: string, x: number, y: number, width: number, height: number, parent?: string): void;
52
+ /** Open an `EXTERNAL` frame as a free-floating window with its own titlebar. */
53
+ openExternalFrame(name: string, title: string, width: number, height: number): void;
54
+ destroyFrameConsole(name: string): void;
55
+ showFrameConsole(name: string): void;
56
+ raiseFrameConsole(name: string): void;
57
+ setFrameScrolling(name: string, enabled: boolean): void;
58
+ /** Install/replace a frame's tab strip. An empty `pages` list removes it. */
59
+ setFrameTabs(name: string, pages: MxpTabPage[], active: string): void;
60
+ /** Publish the accumulated borders so the main console shrinks around the frames. */
61
+ setMxpBorders(borders: MxpBorders): void;
62
+ }
63
+ export declare class MxpFrameManager {
64
+ private readonly host;
65
+ private readonly frames;
66
+ private borders;
67
+ constructor(host: MxpFrameHost);
68
+ /**
69
+ * `<FRAME name …>`. `dest` is the `<DEST>` frame open at the time the tag
70
+ * was parsed, which nests this frame inside it. Returns false when the tag
71
+ * could not be honoured (bad name, frame budget exhausted) — Mudlet renders
72
+ * the raw tag text in that case.
73
+ */
74
+ createFrame(name: string, attrs: Record<string, string>, dest?: string): boolean;
75
+ /** `ACTION=close`. Closing an unknown frame succeeds — it is already closed,
76
+ * and reporting failure would leak the raw tag into the output. */
77
+ closeFrame(name: string): boolean;
78
+ /** `ACTION=focus` — raise an existing frame. False when there is none. */
79
+ focusFrame(name: string): boolean;
80
+ /** `ACTION=open` on a frame that already exists: show and raise, no re-layout. */
81
+ showFrame(name: string): boolean;
82
+ /** MXP frames do not survive a reconnect — Mudlet's `resetAllFrames`. */
83
+ resetAllFrames(): void;
84
+ has(name: string): boolean;
85
+ frameNames(): string[];
86
+ /** Current MXP borders. Exposed for tests and for the initial UI read. */
87
+ getBorders(): MxpBorders;
88
+ /**
89
+ * The common case: a frame tiled against an edge of the main console, or —
90
+ * when declared inside an open `<DEST>` — stacked inside that frame.
91
+ */
92
+ private layoutInternalFrame;
93
+ /** `EXTERNAL` — a window of its own. The browser has no OS child windows,
94
+ * so this becomes a free-floating panel with a real titlebar. */
95
+ private layoutExternalFrame;
96
+ /** `DOCK=other ALIGN=CLIENT` — join `other`'s tab strip as another page. */
97
+ private layoutTabFrame;
98
+ /** Usable area inside a frame, i.e. its rect minus its own tab strip. */
99
+ private contentSize;
100
+ /** Push a frame's strip to the UI. `active` switches the visible page;
101
+ * omitting it keeps whichever page is showing, so a tab arriving or closing
102
+ * never yanks the reader off the one they were watching. */
103
+ private publishTabs;
104
+ /** Bring `name` to the front of whichever tab strip holds it, if any. */
105
+ private selectTabFor;
106
+ /**
107
+ * `WIDTH`/`HEIGHT`/`LEFT`/`TOP` accept `40c` (character cells), `25%` of the
108
+ * container, or `350px`/`350` (pixels). Anything unparseable is 0, matching
109
+ * Mudlet — the caller's minimums then take over.
110
+ */
111
+ private calcSize;
112
+ /**
113
+ * Rebuild the borders from the frames that are left. Called after a close so
114
+ * the main console reclaims the space; surviving frames keep the geometry
115
+ * they were given (Mudlet does not re-tile them either).
116
+ */
117
+ private recalculateBorders;
118
+ }