@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
@@ -29,9 +29,16 @@ export interface MxpFrameCommand {
29
29
  name: string;
30
30
  /** Upper-cased attribute keys → raw string values. Flag attributes
31
31
  * (`INTERNAL`/`EXTERNAL`/`FLOATING`) are present with value `"true"`.
32
- * `ACTION` is `open` (default) / `close` / `redirect`; geometry lives in
33
- * `LEFT`/`TOP`/`WIDTH`/`HEIGHT`. */
32
+ * `ACTION` is `open` (default) / `close` / `focus`; geometry lives in
33
+ * `ALIGN`/`LEFT`/`TOP`/`WIDTH`/`HEIGHT`. */
34
34
  attrs: Record<string, string>;
35
+ /** Name of the `<DEST>` frame that was open when this tag was seen, if any.
36
+ * A `<FRAME>` nested inside a `<DEST>` is laid out *inside* that frame
37
+ * rather than against the main window — Mudlet's `mCurrentDestination`
38
+ * check in TMxpFrameManager::layoutInternalFrame. The parser batches frames
39
+ * and redirects into separate arrays, so the association has to travel with
40
+ * the command. */
41
+ dest?: string;
35
42
  }
36
43
  /** Text the parser redirected into a named frame via `<DEST>…</DEST>`. */
37
44
  export interface MxpRedirect {
@@ -41,6 +48,9 @@ export interface MxpRedirect {
41
48
  segments: BufferSegment[];
42
49
  /** Plain text of the redirected run. */
43
50
  plain: string;
51
+ /** Clickable regions inside this run, offset into `plain`. Separate from
52
+ * `MxpLineResult.links` because the two index different strings. */
53
+ links: MxpLink[];
44
54
  /** `EOL` attr (or the network line ended mid-DEST): the write is a complete
45
55
  * line. */
46
56
  eol: boolean;
@@ -84,6 +94,8 @@ export declare class MxpParser {
84
94
  private mxpColorStack;
85
95
  /** Partial tag/entity held from the end of the previous line. */
86
96
  private pendingTag;
97
+ /** Whether the line being parsed came off the socket — see {@link parseLine}. */
98
+ private fromServer;
87
99
  /** Active `<DEST>` target frame (persists across lines until `</DEST>`), or
88
100
  * null when output flows to the main window. While set, appended text and
89
101
  * flushed runs route to `destOut`/`destPlain` instead of `out`/`plain`. */
@@ -103,9 +115,10 @@ export declare class MxpParser {
103
115
  private redirects;
104
116
  /** `<SOUND>`/`<MUSIC>` commands accumulated this line. */
105
117
  private sounds;
106
- /** Redirected-text scratch — the current `<DEST>` run's segments/plain. */
118
+ /** Redirected-text scratch — the current `<DEST>` run's segments/plain/links. */
107
119
  private destOut;
108
120
  private destPlain;
121
+ private destLinks;
109
122
  constructor(opts: {
110
123
  send: (raw: string) => void;
111
124
  presets?: HyperlinkPresetRegistry;
@@ -113,6 +126,9 @@ export declare class MxpParser {
113
126
  * tag's attributes resolved the way Mudlet resolves them (see
114
127
  * {@link elementEventAttrs}). Backs the Lua `mxp` table. */
115
128
  onElementEvent?: (name: string, attrs: Record<string, string>) => void;
129
+ /** Columns the main window wraps at — how wide `<HR>` draws its rule.
130
+ * Mudlet's `TMxpClient::getWrapWidth`, whose own fallback is 80. */
131
+ wrapWidth?: () => number;
116
132
  });
117
133
  /**
118
134
  * Lock the parser to secure line mode (or release the lock). Mudlet does
@@ -127,8 +143,15 @@ export declare class MxpParser {
127
143
  reset(): void;
128
144
  /** Parse one raw line (post telnet-strip, post UTF-8 decode), which may carry
129
145
  * ANSI SGR, MXP tags, `ESC[#z` modes, and entities. `baseSnapshot` is the
130
- * carried pen from the previous line. */
131
- parseLine(rawLine: string, baseSnapshot?: FormatStateSnapshot): MxpLineResult;
146
+ * carried pen from the previous line.
147
+ *
148
+ * `fromServer` false means the line was synthesised locally (feedTriggers).
149
+ * Such a line's `ESC[#z` is consumed but does not switch mode, matching
150
+ * Mudlet's `isFromServer` gate on `mMxpProcessor.setMode` — otherwise a
151
+ * script feeding a captured game line would leave the parser in whatever
152
+ * mode that capture ended on, and the next real tag would be discarded as
153
+ * unsafe. */
154
+ parseLine(rawLine: string, baseSnapshot?: FormatStateSnapshot, fromServer?: boolean): MxpLineResult;
132
155
  private effectiveMode;
133
156
  private resetTransientMode;
134
157
  private appendText;
@@ -27,5 +27,22 @@ export declare class ReplayPlayer {
27
27
  /** Stop playback and drop any pending chunk. Idempotent; onDone is not
28
28
  * fired for an aborted replay. */
29
29
  abort(): void;
30
+ /**
31
+ * Deliver every chunk that has come due, without waiting for its setTimeout.
32
+ *
33
+ * The counterpart of TimerEngine.pumpDue, and there for the same caller: a
34
+ * script blocked in waitForEvent/pumpEvents is not running the event loop,
35
+ * so the pending timeout below can never land and a replay started from such
36
+ * a script would sit at chunk zero forever. Mudlet has no equivalent problem
37
+ * — its nested QEventLoop keeps driving the replay timer along with
38
+ * everything else — so this is what standing in for that loop costs.
39
+ *
40
+ * Returns the number of chunks delivered.
41
+ */
42
+ pumpDue(now?: number): number;
43
+ /** When the pending chunk is due, in epoch ms; null when none is scheduled. */
44
+ private dueAt;
45
+ /** Feed the chunk at `index` and schedule the one after it. */
46
+ private deliverPending;
30
47
  private scheduleNext;
31
48
  }
@@ -1,6 +1,9 @@
1
1
  import { FormatState } from './FormatState';
2
2
  import { AnsiAwareBuffer } from './FormatState';
3
3
  import type { FormatStateSnapshot } from './FormatState';
4
+ /** Longest run of characters one echo or insert may add to a line. Mudlet's
5
+ * `TBuffer::MAX_CHARACTERS_PER_ECHO`. */
6
+ export declare const MAX_CHARACTERS_PER_ECHO = 1000000;
4
7
  /**
5
8
  * Self-contained text output entity — equivalent of Mudlet's TConsole.
6
9
  * Owns format state, line history, and cursor position.
@@ -36,6 +39,13 @@ export declare class Console {
36
39
  get batchDeleteSize(): number;
37
40
  setBatchDeleteSize(n: number): void;
38
41
  echo(text: string): void;
42
+ /**
43
+ * Move every newline-terminated line out of the in-flight `partial` and
44
+ * into history, leaving whatever follows the last newline as the new
45
+ * partial. Shared by `echo` and by `insertText`, which can put a newline
46
+ * into the line being built just as an echo can.
47
+ */
48
+ private promotePartialLines;
39
49
  /**
40
50
  * Append a pre-built complete line buffer to history. Used by the network
41
51
  * trigger pipeline: the line has already been parsed and is being added as
@@ -55,6 +65,14 @@ export declare class Console {
55
65
  * `selectCurrentLine` sees the pasted line.
56
66
  */
57
67
  appendBuffer(buffer: AnsiAwareBuffer): void;
68
+ /**
69
+ * The one way a completed line enters history. An OSC 8 link set to reveal
70
+ * itself after a delay is written into the buffer as spaces here and gets
71
+ * its text back when the delay is up — Mudlet does the same, so that a
72
+ * script reading the line back sees what the player can see rather than the
73
+ * secret behind it.
74
+ */
75
+ private store;
58
76
  private evict;
59
77
  /** Drain newly completed lines to hand to the renderer. */
60
78
  takeLines(): AnsiAwareBuffer[];
@@ -82,12 +100,32 @@ export declare class Console {
82
100
  clearPartial(): void;
83
101
  private get cursor();
84
102
  private get followingEnd();
103
+ /**
104
+ * Whether the line under the cursor is the in-flight `partial` — an echo
105
+ * that hasn't seen its newline yet — rather than a finished history line.
106
+ *
107
+ * The distinction matters to every cursor-line operation, not just the
108
+ * readers: Mudlet's cursor sits on the line being built, so `insertText`,
109
+ * `replace` and friends have to reach it there. Reading it but not writing
110
+ * it made `prefix()`/`suffix()` on an unfinished line silent no-ops.
111
+ */
112
+ private get onPartialLine();
85
113
  getLine(): string;
86
114
  getBuffer(): AnsiAwareBuffer | null;
115
+ /** The buffer the cursor is on: the partial when it is following the end,
116
+ * otherwise the history line it was parked on. */
117
+ private currentBuffer;
87
118
  /** Per-line prompt flag on the current cursor line. Mirrors Mudlet's TBuffer
88
119
  * behaviour: `isPrompt()` follows the cursor, so moveCursor + isPrompt can
89
120
  * inspect any historical line's prompt status, not just the most recent. */
90
121
  cursorOnPrompt(): boolean;
122
+ /** Whether the cursor was left one slot beyond the last line — the state
123
+ * deleteLine() leaves behind when it removes the line the cursor was on and
124
+ * there is nothing after it to shift up. Distinct from the ordinary
125
+ * "following the end" cursor (index -1), which every read treats as the last
126
+ * line. Reads cursorIdx rather than `cursor`, which clamps and so can never
127
+ * report either state. */
128
+ cursorPastEnd(): boolean;
91
129
  deleteLine(): void;
92
130
  /**
93
131
  * Insert `text` at the cursor (Mudlet `insertText`). Embedded `\n` split the
@@ -124,6 +162,13 @@ export declare class Console {
124
162
  * `keepHorizontal` move never reports past the end of a shorter line.
125
163
  */
126
164
  getCursorColumn(): number;
165
+ /**
166
+ * The column as it was set, unclamped. Mudlet keeps `mUserCursor.x()` raw
167
+ * and only the operations that write through it decide what a column past
168
+ * the end of the line means — `paste` pads out to it, which the clamped
169
+ * reading above cannot express.
170
+ */
171
+ getCursorColumnRaw(): number;
127
172
  setCursorColumn(col: number): boolean;
128
173
  /** Mark cursor as positioned at the end of existing rendered content, so the
129
174
  * next leading `\n` is treated as cursor advance rather than a blank line.
@@ -179,5 +224,24 @@ export declare class Console {
179
224
  * every continuation, matching setWindowWrapIndent /
180
225
  * setWindowWrapHangingIndent.
181
226
  */
227
+ /**
228
+ * The wrap this console applies to a line as it is stored — the console's
229
+ * own `setWindowWrap` width, so a line longer than it becomes several
230
+ * buffer lines rather than one long one that only *looks* wrapped.
231
+ *
232
+ * Off (width 0) unless a script sets one, which is the state nearly every
233
+ * console is in: the renderer wraps to the panel's real pixel width, and
234
+ * splitting on a column nobody chose would only fight it. Mudlet always
235
+ * splits because its buffer IS its layout.
236
+ */
237
+ setWrapWidth(width: number, indent?: number, hangingIndent?: number): void;
238
+ private wrapWidth;
239
+ private wrapIndent;
240
+ private wrapHangingIndent;
241
+ /**
242
+ * Split one completed line to the console's wrap width, newest-last. With no
243
+ * width set — the usual case — this is the line itself, untouched.
244
+ */
245
+ private toStoredLines;
182
246
  wrapLine(lineNumber: number, wrapAt?: number, indent?: number, hangingIndent?: number): boolean;
183
247
  }
@@ -1,5 +1,5 @@
1
1
  import { type OscPaletteOp } from "./ansiEscapes";
2
- import { HyperlinkPresetRegistry, type HyperlinkConfig } from "./hyperlinkConfig";
2
+ import { HyperlinkPresetRegistry, type HyperlinkConfig, type UnderlineStyle } from "./hyperlinkConfig";
3
3
  /** Apply OSC 4/104 palette operations to the global colour tables. Palette
4
4
  * changes affect text parsed *after* this point — which is exactly document
5
5
  * order, since lines are fed to the parser in the order the server sent them. */
@@ -53,6 +53,9 @@ export interface HexColor {
53
53
  color: string;
54
54
  }
55
55
  export type FormatColor = IndexedColor | RgbColor | HexColor;
56
+ /** One SGR parameter: a number, or its sub-parameters when it carried any
57
+ * (`4:3` → `[4, 3]`). See {@link parseSgrCodes}. */
58
+ export type SgrParam = number | number[];
56
59
  export type DimEasing = 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out';
57
60
  export interface DimEffect {
58
61
  startOpacity: number;
@@ -66,6 +69,9 @@ export interface FormatStateSnapshot {
66
69
  bold?: boolean;
67
70
  italic?: boolean;
68
71
  underline?: boolean;
72
+ /** Which underline SGR asked for (`4:1`..`4:5`). Only meaningful while
73
+ * `underline` is set; absent means the plain solid one. */
74
+ underlineStyle?: UnderlineStyle;
69
75
  inverse?: boolean;
70
76
  strikethrough?: boolean;
71
77
  overline?: boolean;
@@ -84,11 +90,11 @@ declare function cloneState(state?: FormatStateSnapshot): FormatStateSnapshot |
84
90
  declare function statesEqual(a?: FormatStateSnapshot, b?: FormatStateSnapshot): boolean;
85
91
  export declare class FormatState {
86
92
  static DEFAULT: {};
87
- foreground?: FormatColor;
88
93
  background?: FormatColor;
89
94
  bold?: boolean;
90
95
  italic?: boolean;
91
96
  underline?: boolean;
97
+ underlineStyle?: UnderlineStyle;
92
98
  inverse?: boolean;
93
99
  strikethrough?: boolean;
94
100
  overline?: boolean;
@@ -96,13 +102,66 @@ export declare class FormatState {
96
102
  rapidBlink?: boolean;
97
103
  dim?: DimEffect;
98
104
  hyperlink?: FormatHyperlink;
105
+ private fgNormal?;
106
+ private fgLight?;
107
+ /** True while the foreground is the profile's own — as it starts, and as
108
+ * SGR 0 and SGR 39 leave it. Bold never brightens a default foreground. */
109
+ private fgIsDefault;
110
+ private _foreground?;
111
+ /** The foreground in force, both variants already resolved. */
112
+ get foreground(): FormatColor | undefined;
113
+ /** Writing the foreground from outside the SGR decoder — `setFgColor`, MXP
114
+ * colour attributes, link styling — names one colour and knows nothing of
115
+ * the bold pair, so it becomes both variants. Without that a later bold
116
+ * would re-resolve to whatever the escape stream had left behind and undo
117
+ * the write. */
118
+ set foreground(color: FormatColor | undefined);
99
119
  constructor(initial?: FormatStateSnapshot);
100
120
  private applySnapshot;
101
121
  reset(): void;
102
122
  toSnapshot(): FormatStateSnapshot;
103
- applySgr(params: number[]): void;
123
+ applySgr(params: SgrParam[]): void;
124
+ /**
125
+ * Apply one SGR parameter that carried sub-parameters (`4:3`, `38:5:196`).
126
+ *
127
+ * Only the underline styles need reading apart from the flat form: an
128
+ * extended colour means the same written either way, so it is handed back to
129
+ * the ordinary path with its sub-parameters flattened.
130
+ */
131
+ private applySgrGroup;
132
+ /**
133
+ * `38:…` / `48:…` — an extended colour written with sub-parameters.
134
+ *
135
+ * The colon form carries one element the semicolon form does not: a colour
136
+ * space identifier sits between the `2` and the red component, almost always
137
+ * empty (`38:2::255:0:0`). So the components are the 4th, 5th and 6th
138
+ * sub-parameters here where they are the 3rd, 4th and 5th parameters there.
139
+ * Missing components are zero, as in Mudlet's decodeSGR38.
140
+ */
141
+ private applyExtendedColorGroup;
142
+ /** Record a foreground and the colour bold should show instead of it, then
143
+ * resolve which of the two is in force. Pass the same colour twice when it
144
+ * has no brighter twin. */
145
+ private setForeground;
146
+ /** Pick the variant the current bold state calls for. Mudlet writes each
147
+ * cell as `(!mIsDefaultColor && mBold) ? light : normal` — a default
148
+ * foreground stays default however bold the text is. */
149
+ private resolveForeground;
104
150
  setHyperlink(link?: FormatHyperlink): void;
105
151
  }
152
+ /**
153
+ * Split an SGR sequence into its parameters. `;` separates parameters and `:`
154
+ * their sub-parameters (ECMA-48 / ITU T.416), so a parameter that carries
155
+ * sub-parameters comes back as an array and a plain one as a number.
156
+ *
157
+ * The distinction matters for exactly one reason, but it matters a lot: `4:3` is
158
+ * a *curly underline*, one parameter with a sub-parameter, while `4;3` is an
159
+ * underline followed by italics. Flattening both — which mudix did, to make
160
+ * `38:5:1` work alongside `38;5;1` — turned every styled underline into an
161
+ * accidental italic. Extended colours still read either form, since applySgr
162
+ * looks ahead across parameters for the `38;5;n` spelling.
163
+ */
164
+ export declare function parseSgrCodes(sequence: string): SgrParam[];
106
165
  /**
107
166
  * A run of text with its visual attributes resolved to concrete CSS colour
108
167
  * strings — the shape a canvas/image renderer wants. `color`/`background` are
@@ -165,6 +224,10 @@ export declare class AnsiAwareBuffer {
165
224
  remove(range: [number, number]): this;
166
225
  /** @internal */
167
226
  getSegments(): BufferSegment[];
227
+ /** Whether any segment carries an OSC 8 link with `visibility` settings.
228
+ * A cheap pre-check (no cloning, no grouping) for the concealment pass,
229
+ * which every stored line goes through. */
230
+ hasVisibilityLink(): boolean;
168
231
  toHyperlinkSegments(): {
169
232
  text: string;
170
233
  hyperlink?: FormatHyperlink;
@@ -15,8 +15,15 @@
15
15
  * (owned by the session) conceals them when the matching session event fires.
16
16
  * The first occurrence of each trigger is skipped: it's the response to the very
17
17
  * command the click sent, not a fresh event.
18
+ *
19
+ * **The buffer half.** Hiding an element is not enough for a *delayed reveal*:
20
+ * Mudlet writes such a link into its buffer as spaces and puts the text back
21
+ * when the delay is up, so a script reading the line back (`getLines`) sees what
22
+ * the player sees rather than the secret. {@link concealDelayedReveals} does the
23
+ * same to a stored line here — see the section at the foot of this file.
18
24
  */
19
25
  import type { VisibilitySettings } from "./hyperlinkConfig";
26
+ import type { AnsiAwareBuffer } from "./FormatState";
20
27
  /** Wire one link element's visibility behaviour. Call *after* the element's base
21
28
  * style has been applied (it may set `visibility: hidden`, which a later
22
29
  * `cssText` assignment would wipe). */
@@ -35,3 +42,31 @@ export declare class HyperlinkVisibilityController {
35
42
  onOutput(): void;
36
43
  private fire;
37
44
  }
45
+ /**
46
+ * Whether a link with these settings is written concealed and revealed later.
47
+ *
48
+ * Only the timed reveals are, unlike Mudlet, which also conceals a reveal armed
49
+ * on an expire trigger. mudix drives expire triggers off the rendered element
50
+ * (see {@link HyperlinkVisibilityController}), and that path only ever conceals
51
+ * — a link the buffer blanked with nothing to un-blank it would lose its text
52
+ * for good, which is worse than showing it early.
53
+ */
54
+ export declare function startsConcealed(vis: VisibilitySettings): boolean;
55
+ /**
56
+ * Blank out every delayed-reveal link on a freshly stored line and schedule the
57
+ * text to come back. Called once per line as it enters a console's history.
58
+ */
59
+ export declare function concealDelayedReveals(line: AnsiAwareBuffer): void;
60
+ /**
61
+ * Put back the text of every concealed link whose delay has elapsed, and arm a
62
+ * timer for the earliest one still waiting. Returns true when a line changed.
63
+ *
64
+ * Also the hook the busted harness drives: a spec runs as one synchronous call
65
+ * on top of the event loop, so no `setTimeout` of ours can fire until it
66
+ * returns, and `pumpEvents` calls in here instead.
67
+ */
68
+ export declare function pumpDelayedReveals(now?: number): boolean;
69
+ /** Drop everything still waiting — a profile teardown, or a test resetting
70
+ * between cases. The lines themselves are left as they are; they are going
71
+ * away with the console. */
72
+ export declare function resetDelayedReveals(): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Mudlet's `!osc8-docs` easter egg — the worked OSC 8 examples banner
3
+ * (`TBuffer::injectOSC8DocumentationExamples`).
4
+ *
5
+ * A line printed through the echo family that holds the phrase is swallowed and
6
+ * this banner goes into the MAIN console instead, whichever window the echo was
7
+ * addressed to. It is the one page of OSC 8 documentation that can be read
8
+ * without leaving the client, so every escape in it is load-bearing: the text is
9
+ * a verbatim port of Mudlet's, kept one source line per array entry so the two
10
+ * can be diffed after a re-sync.
11
+ *
12
+ * Not every printing path reaches it — what the game sends and what a trigger
13
+ * echoes onto the matched line print the phrase as ordinary text, exactly as in
14
+ * Mudlet, where those go through `commitLine()` rather than `appendLine()`.
15
+ */
16
+ /** The phrase itself, split so that this file does not set off its own check
17
+ * when something echoes a line of it. */
18
+ export declare const OSC8_DOCS_PHRASE: string;
19
+ /** Once a second at most — an echo and the server response that repeats it must
20
+ * not print the banner twice. Mudlet's `mLastOSC8DocsInjectionTime` window. */
21
+ export declare const OSC8_DOCS_DEBOUNCE_MS = 1000;
22
+ /** The banner as one blob, ready to hand to `Console.echo`. */
23
+ export declare function osc8DocumentationExamples(): string;
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Undoing the game's own line wrapping — Mudlet's `undoServerWrap` /
3
+ * `undoServerWrapWidth` (`TBuffer.cpp`).
4
+ *
5
+ * Many MUDs word-wrap their output server-side at a fixed column. That makes
6
+ * triggers awkward to write (a phrase can be split across two "lines" that were
7
+ * never separate) and pins the display to the game's width instead of the
8
+ * window's. Turning the option on holds back any line that ends where the game's
9
+ * wrapper would have broken it and glues the continuation on, so triggers and
10
+ * rendering see one whole logical line.
11
+ *
12
+ * The heuristics live here as pure functions over *visible* text so they can be
13
+ * tested directly, and so the pipeline glue in `ScriptingEngine` stays readable.
14
+ *
15
+ * **Visible text, not raw.** Mudlet judges `mMudLine`, which is plain characters
16
+ * with the styling held alongside in a `TChar` buffer. mudix keeps styling
17
+ * inline as ANSI escapes, so every predicate here takes text that has been put
18
+ * through {@link visibleText} first — otherwise a colour-heavy line would be
19
+ * measured as far longer than the player sees, and its escape punctuation would
20
+ * drag the letter ratio below the prose threshold.
21
+ */
22
+ /** How far below the configured column a line may end and still count as
23
+ * ending *at* it. The game breaks at the last space that fits, so a wrapped
24
+ * segment falls up to a word short. Mudlet's `csmServerWrapSlack`. */
25
+ export declare const SERVER_WRAP_SLACK = 15;
26
+ /** Refuse to keep joining past this visible length — a runaway join would
27
+ * otherwise swallow a whole screen. Mudlet's `csmServerWrapMaxJoinedLength`. */
28
+ export declare const SERVER_WRAP_MAX_JOINED_LENGTH = 10000;
29
+ /** How much clear room the continuation's first word must have had on the line
30
+ * above before that line counts as one the game ended itself. Mudlet's
31
+ * `csmServerWrapFitTolerance` — only *clear* room disproves a wrap, because a
32
+ * segment inside the join band already sits up to {@link SERVER_WRAP_SLACK}
33
+ * short of the column. */
34
+ export declare const SERVER_WRAP_FIT_TOLERANCE = 8;
35
+ /** Longest run of digits a bare list label may carry (`1.`, `12)`, `999.`).
36
+ * Mudlet's `csmMaxListNumberDigits`. Bracketed labels are exempt — `[1366]` is
37
+ * a label however long it runs, where a bare `1364.` is as likely a price. */
38
+ export declare const MAX_LIST_NUMBER_DIGITS = 3;
39
+ /** How much of a held game line's opening is kept for comparing a continuation
40
+ * against. Mudlet's `csmServerWrapSegmentStartChars` — only the first
41
+ * {@link SERVER_WRAP_REPEATED_WORDS} shared words and the spaces after them
42
+ * have to fall inside it, and that leaves room for it even where a long name
43
+ * is one of them. */
44
+ export declare const SERVER_WRAP_SEGMENT_START_CHARS = 48;
45
+ /** How many opening words a continuation has to share with the held segment
46
+ * before it is a message the game re-prefixed rather than a wrap. Mudlet's
47
+ * `csmServerWrapRepeatedWords` — one is far too common in prose to prove
48
+ * anything. */
49
+ export declare const SERVER_WRAP_REPEATED_WORDS = 2;
50
+ /** How long a held-back line waits for its continuation before being committed
51
+ * on its own. Mudlet's `csmServerWrapFlushDelayMs`. */
52
+ export declare const SERVER_WRAP_FLUSH_DELAY_MS = 300;
53
+ /** Bounds on `undoServerWrapWidth`, matching Mudlet's `setConfig` validation
54
+ * (and the `qBound` its profile-XML import applies). */
55
+ export declare const SERVER_WRAP_WIDTH_MIN = 20;
56
+ export declare const SERVER_WRAP_WIDTH_MAX = 500;
57
+ /** Mudlet's `Host::mUndoServerWrapWidth` default. */
58
+ export declare const SERVER_WRAP_WIDTH_DEFAULT = 80;
59
+ /** Strip ANSI/OSC escapes so a line can be measured and classified the way the
60
+ * player sees it. Uses the shared scanner, so it skips CSI, OSC, DCS-family and
61
+ * short escapes alike rather than guessing at a regex. */
62
+ export declare function visibleText(line: string): string;
63
+ /**
64
+ * Mudlet `TBuffer::endsAtServerWrapColumn`. True when a segment's visible length
65
+ * lands in the `[width - slack, width]` band — i.e. where the game's own word
66
+ * wrap would have broken it.
67
+ */
68
+ export declare function endsAtServerWrapColumn(visibleLength: number, width: number): boolean;
69
+ /**
70
+ * Mudlet `TBuffer::looksLikeWrappedProse`. Word-wrapped prose breaks *between
71
+ * words*, so a wrapped segment ends in a word (or a sentence mark that happened
72
+ * to land on the wrap column) and is mostly letters. ASCII art, dividers and
73
+ * table borders also run right up to the screen width but end in — and are full
74
+ * of — symbols; gluing those back together would mangle them.
75
+ *
76
+ * `visible` must already be ANSI-stripped.
77
+ */
78
+ export declare function looksLikeWrappedProse(visible: string): boolean;
79
+ /**
80
+ * Mudlet `TBuffer::segmentEndsSettledSentence`. Where a game swallows the space
81
+ * it breaks at, a segment that finishes a sentence right on the wrap column
82
+ * looks exactly like one the game ended there itself — and games end lines on
83
+ * sentences all the time: a room description, the exits line below it, the
84
+ * object line below that. Joining those runs separate lines into one and breaks
85
+ * triggers anchored to their starts.
86
+ *
87
+ * A kept break space is the one piece of evidence that settles it, so only a
88
+ * sentence with nothing at all after it is refused — the check reads the very
89
+ * last character rather than the last non-space one. What that costs is a
90
+ * genuine wrap landing exactly after a sentence staying split, which reads as a
91
+ * break at a sentence boundary; far cheaper than the joins it prevents.
92
+ *
93
+ * This belongs at the *hold* decision only: continuations legitimately end
94
+ * sentences, so {@link looksLikeWrappedProse} must keep accepting them.
95
+ *
96
+ * `visible` must already be ANSI-stripped.
97
+ */
98
+ export declare function segmentEndsSettledSentence(visible: string): boolean;
99
+ /**
100
+ * Mudlet `TBuffer::continuationRepeatsSegmentOpening`. Games that wrap a tell,
101
+ * say or channel message themselves put the whole prefix back on every physical
102
+ * line of it (`Anne teilt Dir mit: …`). Those lines break mid-sentence at the
103
+ * wrap column and leave no room for the next word, so every other check reads
104
+ * them as one wrapped paragraph and the join buries the prefix in the middle of
105
+ * it. The repeated opening is what gives them away — two words of it, because
106
+ * prose starts line after line with the same article or pronoun and one proves
107
+ * nothing.
108
+ *
109
+ * `segmentStart` is the held *game line's* opening (see
110
+ * {@link SERVER_WRAP_SEGMENT_START_CHARS}), not the joined paragraph's: a
111
+ * continuation is compared against the line it actually follows.
112
+ *
113
+ * Both strings must already be ANSI-stripped.
114
+ */
115
+ export declare function continuationRepeatsSegmentOpening(segmentStart: string, visibleNext: string): boolean;
116
+ /**
117
+ * Mudlet `TBuffer::startsWithListMarker`. A list entry reads exactly like
118
+ * wrapped prose — a sentence that can end right at the wrap column — so only its
119
+ * marker tells a list apart from a paragraph.
120
+ *
121
+ * Every form accepted here has to be one the game's own word wrap could not
122
+ * produce at the start of a continuation. The ambiguous ones are deliberately
123
+ * left out: a spaced dash opens a continuation whenever the wrap lands on it,
124
+ * and a parenthesised number is as often an aside as a label, so it is capped at
125
+ * {@link MAX_LIST_NUMBER_DIGITS} like a bare one. Only `[...]` is exempt from
126
+ * the cap — nothing writes `[2500]` mid-sentence.
127
+ *
128
+ * `visible` must already be ANSI-stripped.
129
+ */
130
+ export declare function startsWithListMarker(visible: string): boolean;
131
+ /**
132
+ * Mudlet `TBuffer::pendingLineHadRoomForNextWord`. Word wrap breaks a line for
133
+ * one reason only: the next word did not fit. So a continuation whose first word
134
+ * would have gone on the game line above it is not a continuation at all — the
135
+ * game ended that line itself. Without this, two sentences that each stop short
136
+ * of the wrap column read exactly like one wrapped paragraph.
137
+ *
138
+ * `heldSegmentLength` is the length of the last *game line* joined into the held
139
+ * text, not of the held text itself: once a paragraph has been joined the held
140
+ * text is longer than the wrap column, and the line the next word would have
141
+ * landed on is the one the game last sent.
142
+ *
143
+ * Both strings must already be ANSI-stripped.
144
+ */
145
+ export declare function pendingLineHadRoomForNextWord(visiblePending: string, heldSegmentLength: number, visibleNext: string, width: number): boolean;
146
+ /**
147
+ * Mudlet's guard before joining: a genuine continuation of wrapped prose starts
148
+ * with a word — or with the single space some games move the break to instead of
149
+ * swallowing it. *Deeper* indentation, a segment that isn't prose at all, a list
150
+ * entry, or a first word that would have fitted on the line above all mean the
151
+ * held line was complete after all and must be committed on its own first.
152
+ *
153
+ * `visibleNext` must already be ANSI-stripped.
154
+ */
155
+ export declare function shouldCommitPendingBeforeJoin(visibleNext: string, nextIsProse: boolean, fit?: {
156
+ visiblePending: string;
157
+ heldSegmentLength: number;
158
+ segmentStart: string;
159
+ width: number;
160
+ }): boolean;
161
+ /**
162
+ * Mudlet `TBuffer::joinPendingServerWrapOntoCurrent`. Concatenates the held line
163
+ * and its continuation, restoring the space the game's wrapper swallowed when
164
+ * neither side already carries one.
165
+ *
166
+ * Operates on the **raw** lines (escapes and all): mudix's styling is inline, so
167
+ * a plain concatenation carries each half's colour across untouched — the
168
+ * `TChar`-buffer splice Mudlet needs has no equivalent here. The space-insertion
169
+ * test looks at the visible text so a trailing colour reset can't hide the space
170
+ * that is already there.
171
+ */
172
+ export declare function joinWrappedLines(pending: string, next: string): string;
173
+ /** Lines shorter than this carry no signal about a wrap column. */
174
+ export declare const WRAP_DETECT_MIN_LENGTH = 40;
175
+ /** Evaluate the collected sample only once every this many recorded lines. */
176
+ export declare const WRAP_DETECT_SAMPLE_INTERVAL = 100;
177
+ /** How many lines must sit at the ceiling before it's believable. */
178
+ export declare const WRAP_DETECT_THRESHOLD = 40;
179
+ /** A plausible wrap column has to fall in this range. */
180
+ export declare const WRAP_DETECT_CEILING_MIN = 60;
181
+ export declare const WRAP_DETECT_CEILING_MAX = 160;
182
+ /**
183
+ * Given the tally of observed line lengths, report the game's apparent wrap
184
+ * column, or null when the evidence doesn't support one.
185
+ *
186
+ * The ceiling is the longest length seen more than incidentally (≥3 times); it
187
+ * has to be a plausible screen width, carry enough lines just below it, and have
188
+ * almost nothing beyond it — a game that regularly emits longer lines is not
189
+ * wrapping at that column.
190
+ */
191
+ export declare function detectWrapCeiling(counts: ReadonlyMap<number, number>): number | null;