@theia/memory-inspector 1.45.0 → 1.46.0-next.72

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 (102) hide show
  1. package/README.md +71 -71
  2. package/lib/browser/diff-widget/memory-diff-options-widget.d.ts +36 -36
  3. package/lib/browser/diff-widget/memory-diff-options-widget.js +132 -132
  4. package/lib/browser/diff-widget/memory-diff-select-widget.d.ts +50 -50
  5. package/lib/browser/diff-widget/memory-diff-select-widget.js +137 -137
  6. package/lib/browser/diff-widget/memory-diff-table-widget.d.ts +77 -77
  7. package/lib/browser/diff-widget/memory-diff-table-widget.js +314 -314
  8. package/lib/browser/diff-widget/memory-diff-widget-types.d.ts +41 -41
  9. package/lib/browser/diff-widget/memory-diff-widget-types.js +23 -23
  10. package/lib/browser/editable-widget/memory-editable-table-widget.d.ts +64 -64
  11. package/lib/browser/editable-widget/memory-editable-table-widget.js +324 -324
  12. package/lib/browser/memory-inspector-frontend-contribution.d.ts +46 -46
  13. package/lib/browser/memory-inspector-frontend-contribution.js +291 -291
  14. package/lib/browser/memory-inspector-frontend-module.d.ts +21 -21
  15. package/lib/browser/memory-inspector-frontend-module.js +87 -87
  16. package/lib/browser/memory-provider/cdt-gdb-memory-provider.d.ts +35 -35
  17. package/lib/browser/memory-provider/cdt-gdb-memory-provider.js +136 -136
  18. package/lib/browser/memory-provider/memory-provider-service.d.ts +36 -36
  19. package/lib/browser/memory-provider/memory-provider-service.js +101 -101
  20. package/lib/browser/memory-provider/memory-provider.d.ts +65 -65
  21. package/lib/browser/memory-provider/memory-provider.js +96 -96
  22. package/lib/browser/memory-provider/memory-provider.spec.d.ts +16 -16
  23. package/lib/browser/memory-provider/memory-provider.spec.js +23 -23
  24. package/lib/browser/memory-widget/memory-options-widget.d.ts +136 -136
  25. package/lib/browser/memory-widget/memory-options-widget.js +564 -564
  26. package/lib/browser/memory-widget/memory-table-widget.d.ts +146 -146
  27. package/lib/browser/memory-widget/memory-table-widget.js +487 -487
  28. package/lib/browser/memory-widget/memory-widget.d.ts +33 -33
  29. package/lib/browser/memory-widget/memory-widget.js +112 -112
  30. package/lib/browser/register-widget/register-filter-service.d.ts +42 -42
  31. package/lib/browser/register-widget/register-filter-service.js +81 -81
  32. package/lib/browser/register-widget/register-options-widget.d.ts +81 -81
  33. package/lib/browser/register-widget/register-options-widget.js +338 -338
  34. package/lib/browser/register-widget/register-table-widget.d.ts +77 -77
  35. package/lib/browser/register-widget/register-table-widget.js +230 -230
  36. package/lib/browser/register-widget/register-widget-types.d.ts +29 -29
  37. package/lib/browser/register-widget/register-widget-types.js +35 -35
  38. package/lib/browser/utils/memory-commands.d.ts +27 -27
  39. package/lib/browser/utils/memory-commands.js +67 -67
  40. package/lib/browser/utils/memory-hover-renderer.d.ts +33 -33
  41. package/lib/browser/utils/memory-hover-renderer.js +111 -111
  42. package/lib/browser/utils/memory-recents.d.ts +27 -27
  43. package/lib/browser/utils/memory-recents.js +52 -52
  44. package/lib/browser/utils/memory-widget-components.d.ts +58 -58
  45. package/lib/browser/utils/memory-widget-components.js +69 -69
  46. package/lib/browser/utils/memory-widget-manager.d.ts +50 -50
  47. package/lib/browser/utils/memory-widget-manager.js +182 -182
  48. package/lib/browser/utils/memory-widget-utils.d.ts +113 -113
  49. package/lib/browser/utils/memory-widget-utils.js +42 -42
  50. package/lib/browser/utils/memory-widget-variable-utils.d.ts +57 -57
  51. package/lib/browser/utils/memory-widget-variable-utils.js +143 -143
  52. package/lib/browser/utils/multi-select-bar.d.ts +31 -31
  53. package/lib/browser/utils/multi-select-bar.js +34 -34
  54. package/lib/browser/wrapper-widgets/memory-dock-panel.d.ts +24 -24
  55. package/lib/browser/wrapper-widgets/memory-dock-panel.js +48 -48
  56. package/lib/browser/wrapper-widgets/memory-dockpanel-placeholder-widget.d.ts +23 -23
  57. package/lib/browser/wrapper-widgets/memory-dockpanel-placeholder-widget.js +57 -57
  58. package/lib/browser/wrapper-widgets/memory-layout-widget.d.ts +51 -51
  59. package/lib/browser/wrapper-widgets/memory-layout-widget.js +191 -191
  60. package/lib/common/util.d.ts +21 -21
  61. package/lib/common/util.js +30 -30
  62. package/lib/common/utils.spec.d.ts +16 -16
  63. package/lib/common/utils.spec.js +45 -45
  64. package/package.json +4 -4
  65. package/src/browser/diff-widget/memory-diff-options-widget.tsx +152 -152
  66. package/src/browser/diff-widget/memory-diff-select-widget.tsx +163 -163
  67. package/src/browser/diff-widget/memory-diff-table-widget.tsx +366 -366
  68. package/src/browser/diff-widget/memory-diff-widget-types.ts +45 -45
  69. package/src/browser/editable-widget/memory-editable-table-widget.tsx +359 -359
  70. package/src/browser/memory-inspector-frontend-contribution.ts +301 -301
  71. package/src/browser/memory-inspector-frontend-module.ts +118 -118
  72. package/src/browser/memory-provider/cdt-gdb-memory-provider.ts +132 -132
  73. package/src/browser/memory-provider/memory-provider-service.ts +86 -86
  74. package/src/browser/memory-provider/memory-provider.spec.ts +23 -23
  75. package/src/browser/memory-provider/memory-provider.ts +119 -119
  76. package/src/browser/memory-widget/memory-options-widget.tsx +738 -738
  77. package/src/browser/memory-widget/memory-table-widget.tsx +625 -625
  78. package/src/browser/memory-widget/memory-widget.ts +114 -114
  79. package/src/browser/register-widget/register-filter-service.ts +76 -76
  80. package/src/browser/register-widget/register-options-widget.tsx +393 -393
  81. package/src/browser/register-widget/register-table-widget.tsx +276 -276
  82. package/src/browser/register-widget/register-widget-types.ts +45 -45
  83. package/src/browser/register-widget/register-widget.css +34 -34
  84. package/src/browser/style/index.css +746 -746
  85. package/src/browser/style/memory-lock.svg +21 -21
  86. package/src/browser/style/memory-view.svg +20 -20
  87. package/src/browser/style/register-lock.svg +29 -29
  88. package/src/browser/style/register-view.svg +28 -28
  89. package/src/browser/utils/memory-commands.ts +76 -76
  90. package/src/browser/utils/memory-hover-renderer.ts +113 -113
  91. package/src/browser/utils/memory-recents.ts +58 -58
  92. package/src/browser/utils/memory-widget-components.tsx +193 -193
  93. package/src/browser/utils/memory-widget-manager.ts +179 -179
  94. package/src/browser/utils/memory-widget-utils.tsx +132 -132
  95. package/src/browser/utils/memory-widget-variable-utils.ts +170 -170
  96. package/src/browser/utils/multi-select-bar.css +61 -61
  97. package/src/browser/utils/multi-select-bar.tsx +75 -75
  98. package/src/browser/wrapper-widgets/memory-dock-panel.ts +51 -51
  99. package/src/browser/wrapper-widgets/memory-dockpanel-placeholder-widget.tsx +38 -38
  100. package/src/browser/wrapper-widgets/memory-layout-widget.tsx +167 -167
  101. package/src/common/util.ts +28 -28
  102. package/src/common/utils.spec.ts +52 -52
@@ -1,738 +1,738 @@
1
- /********************************************************************************
2
- * Copyright (C) 2021 Ericsson and others.
3
- *
4
- * This program and the accompanying materials are made available under the
5
- * terms of the Eclipse Public License v. 2.0 which is available at
6
- * http://www.eclipse.org/legal/epl-2.0.
7
- *
8
- * This Source Code may also be made available under the following Secondary
9
- * Licenses when the conditions for such availability set forth in the Eclipse
10
- * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- * with the GNU Classpath Exception which is available at
12
- * https://www.gnu.org/software/classpath/license.html.
13
- *
14
- * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- ********************************************************************************/
16
-
17
- import { deepFreeze, Disposable, DisposableCollection, Emitter, nls } from '@theia/core';
18
- import { Key, KeyCode, Message, ReactWidget, StatefulWidget } from '@theia/core/lib/browser';
19
- import { Deferred } from '@theia/core/lib/common/promise-util';
20
- import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
21
- import * as React from '@theia/core/shared/react';
22
- import { DebugSession, DebugState } from '@theia/debug/lib/browser/debug-session';
23
- import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
24
- import * as Long from 'long';
25
- import { MemoryProviderService } from '../memory-provider/memory-provider-service';
26
- import { Recents } from '../utils/memory-recents';
27
- import { MWInput, MWInputWithSelect, MWSelect } from '../utils/memory-widget-components';
28
- import { Constants, Interfaces, MemoryWidgetOptions, Utils } from '../utils/memory-widget-utils';
29
- import { VariableRange } from '../utils/memory-widget-variable-utils';
30
- import { MWMultiSelect, SingleSelectItemProps } from '../utils/multi-select-bar';
31
- import debounce = require('@theia/core/shared/lodash.debounce');
32
-
33
- export const EMPTY_MEMORY: Interfaces.MemoryReadResult = deepFreeze({
34
- bytes: new Uint8Array(),
35
- address: new Long(0, 0, true),
36
- });
37
-
38
- export const LOCATION_FIELD_ID = 't-mv-location';
39
- export const LENGTH_FIELD_ID = 't-mv-length';
40
- export const LOCATION_OFFSET_FIELD_ID = 't-mv-location-offset';
41
- export const BYTES_PER_ROW_FIELD_ID = 't-mv-bytesrow';
42
- export const BYTES_PER_GROUP_FIELD_ID = 't-mv-bytesgroup';
43
- export const ENDIAN_SELECT_ID = 't-mv-endiannesss';
44
- export const ASCII_TOGGLE_ID = 't-mv-ascii-toggle';
45
- export const AUTO_UPDATE_TOGGLE_ID = 't-mv-auto-update-toggle';
46
-
47
- @injectable()
48
- export class MemoryOptionsWidget extends ReactWidget implements StatefulWidget {
49
- static ID = 'memory-view-options-widget';
50
- static LABEL = nls.localize('theia/memory-inspector/memoryTitle', 'Memory');
51
- iconClass = 'memory-view-icon';
52
- lockIconClass = 'memory-lock-icon';
53
-
54
- static WIDGET_H2_CLASS = 'memory-widget-header';
55
- static WIDGET_HEADER_INPUT_CLASS = 'memory-widget-header-input';
56
-
57
- protected additionalColumnSelectLabel = nls.localize('theia/memory-inspector/extraColumn', 'Extra Column');
58
-
59
- protected sessionListeners = new DisposableCollection();
60
-
61
- protected readonly onOptionsChangedEmitter = new Emitter<string | undefined>();
62
- readonly onOptionsChanged = this.onOptionsChangedEmitter.event;
63
- protected readonly onMemoryChangedEmitter = new Emitter<Interfaces.MemoryReadResult>();
64
- readonly onMemoryChanged = this.onMemoryChangedEmitter.event;
65
- protected pinnedMemoryReadResult: Deferred<Interfaces.MemoryReadResult | false> | undefined;
66
-
67
- protected memoryReadResult: Interfaces.MemoryReadResult = EMPTY_MEMORY;
68
- protected columnsDisplayed: Interfaces.ColumnsDisplayed = {
69
- address: {
70
- label: nls.localizeByDefault('Address'),
71
- doRender: true
72
- },
73
- data: {
74
- label: nls.localize('theia/memory-inspector/data', 'Data'),
75
- doRender: true
76
- },
77
- variables: {
78
- label: nls.localizeByDefault('Variables'),
79
- doRender: true
80
- },
81
- ascii: {
82
- label: nls.localize('theia/memory-inspector/ascii', 'ASCII'),
83
- doRender: false
84
- },
85
- };
86
-
87
- protected byteSize = 8;
88
-
89
- protected bytesPerGroup = 1;
90
- protected groupsPerRow = 4;
91
- protected variables: VariableRange[] = [];
92
- protected endianness: Interfaces.Endianness = Interfaces.Endianness.Little;
93
-
94
- protected memoryReadError = nls.localize('theia/memory-inspector/memory/readError/noContents', 'No memory contents currently available.');
95
-
96
- protected address: string | number = 0;
97
- protected offset = 0;
98
- protected readLength = 256;
99
- protected doDisplaySettings = false;
100
- protected doUpdateAutomatically = true;
101
- protected showMemoryError = false;
102
- protected errorTimeout: NodeJS.Timeout | undefined = undefined;
103
- protected addressField: HTMLInputElement | undefined;
104
- protected offsetField: HTMLInputElement | undefined;
105
- protected readLengthField: HTMLInputElement | undefined;
106
- protected headerInputField: HTMLInputElement | undefined;
107
- protected recentLocations = new Recents();
108
- protected showTitleEditIcon = false;
109
- protected isTitleEditable = false;
110
-
111
- @inject(MemoryProviderService) protected readonly memoryProvider: MemoryProviderService;
112
- @inject(DebugSessionManager) protected readonly sessionManager: DebugSessionManager;
113
- @inject(MemoryWidgetOptions) protected readonly memoryWidgetOptions: MemoryWidgetOptions;
114
-
115
- get memory(): Interfaces.WidgetMemoryState {
116
- return {
117
- ...this.memoryReadResult,
118
- variables: this.variables,
119
- };
120
- }
121
-
122
- get options(): Interfaces.MemoryOptions {
123
- return this.storeState();
124
- }
125
-
126
- @postConstruct()
127
- protected init(): void {
128
- this.addClass(MemoryOptionsWidget.ID);
129
-
130
- this.title.label = nls.localize('theia/memory-inspector/memory', 'Memory ({0})', this.memoryWidgetOptions.displayId);
131
- this.title.caption = nls.localize('theia/memory-inspector/memory', 'Memory ({0})', this.memoryWidgetOptions.displayId);
132
- this.title.iconClass = this.iconClass;
133
- this.title.closable = true;
134
-
135
- if (this.memoryWidgetOptions.dynamic !== false) {
136
- this.toDispose.push(this.sessionManager.onDidChangeActiveDebugSession(({ current }) => {
137
- this.setUpListeners(current);
138
- }));
139
-
140
- this.toDispose.push(this.sessionManager.onDidCreateDebugSession(current => {
141
- this.setUpListeners(current);
142
- }));
143
- this.setUpListeners(this.sessionManager.currentSession);
144
- }
145
- this.toDispose.push(this.onOptionsChanged(() => this.update()));
146
-
147
- this.update();
148
- }
149
-
150
- async setAddressAndGo(
151
- newAddress: string,
152
- newOffset?: number,
153
- newLength?: number,
154
- direction?: 'above' | 'below',
155
- ): Promise<Interfaces.MemoryReadResult | false | undefined> {
156
- let doUpdate = false;
157
- const originalValues = {
158
- offset: '',
159
- length: '',
160
- };
161
- if (this.addressField) {
162
- this.addressField.value = newAddress;
163
- doUpdate = true;
164
- }
165
- if (this.offsetField && newOffset !== undefined) {
166
- originalValues.offset = this.offsetField.value;
167
- this.offsetField.value = newOffset.toString();
168
- doUpdate = true;
169
- }
170
- if (this.readLengthField && newLength !== undefined) {
171
- originalValues.length = this.readLengthField.value;
172
- this.readLengthField.value = newLength.toString();
173
- doUpdate = true;
174
- }
175
- if (doUpdate && this.readLengthField && this.offsetField) {
176
- this.pinnedMemoryReadResult = new Deferred<Interfaces.MemoryReadResult>();
177
- this.updateMemoryView();
178
- const result = await this.pinnedMemoryReadResult.promise;
179
- if (result === false) {
180
- // Memory request errored
181
- this.readLengthField.value = originalValues.length;
182
- this.offsetField.value = originalValues.offset;
183
- }
184
- if (result) {
185
- // Memory request returned some memory
186
- const resultLength = result.bytes.length * 8 / this.byteSize;
187
- const lengthFieldValue = parseInt(this.readLengthField.value);
188
- if (lengthFieldValue !== resultLength) {
189
- this.memoryReadError = nls.localize('theia/memory-inspector/memory/readError/bounds', 'Memory bounds exceeded, result will be truncated.');
190
- this.doShowMemoryErrors();
191
- this.readLengthField.value = resultLength.toString();
192
- if (direction === 'above') {
193
- this.offsetField.value = `${parseInt(originalValues.offset) - (resultLength - parseInt(originalValues.length))}`;
194
- }
195
- this.update();
196
- }
197
- }
198
- }
199
- return undefined;
200
- }
201
-
202
- protected setUpListeners(session?: DebugSession): void {
203
- this.sessionListeners.dispose();
204
- this.sessionListeners = new DisposableCollection(Disposable.create(() => this.handleActiveSessionChange()));
205
- if (session) {
206
- this.sessionListeners.push(session.onDidChange(() => this.handleSessionChange()));
207
- }
208
- }
209
-
210
- protected handleActiveSessionChange(): void {
211
- const isDynamic = this.memoryWidgetOptions.dynamic !== false;
212
- if (isDynamic && this.doUpdateAutomatically) {
213
- this.memoryReadResult = EMPTY_MEMORY;
214
- this.fireDidChangeMemory();
215
- }
216
- }
217
-
218
- protected handleSessionChange(): void {
219
- const isStopped = this.sessionManager.currentSession?.state === DebugState.Stopped;
220
- const isReadyForQuery = !!this.sessionManager.currentSession?.currentFrame;
221
- const isDynamic = this.memoryWidgetOptions.dynamic !== false;
222
- if (isStopped && isReadyForQuery && isDynamic && this.doUpdateAutomatically && this.memoryReadResult !== EMPTY_MEMORY) {
223
- this.updateMemoryView();
224
- }
225
- }
226
-
227
- protected override onActivateRequest(msg: Message): void {
228
- super.onActivateRequest(msg);
229
- this.acceptFocus();
230
- }
231
-
232
- protected acceptFocus(): void {
233
- if (this.doUpdateAutomatically) {
234
- if (this.addressField) {
235
- this.addressField.focus();
236
- this.addressField.select();
237
- }
238
- } else {
239
- const settingsCog = this.node.querySelector('.toggle-settings-click-zone') as HTMLDivElement;
240
- settingsCog?.focus();
241
- }
242
- }
243
-
244
- protected handleColumnSelectionChange = (columnLabel: string, doShow: boolean): void => this.doHandleColumnSelectionChange(columnLabel, doShow);
245
-
246
- protected doHandleColumnSelectionChange(columnLabel: string, doShow: boolean): void {
247
- if (columnLabel in this.columnsDisplayed) {
248
- this.columnsDisplayed[columnLabel].doRender = doShow;
249
- this.fireDidChangeOptions(ASCII_TOGGLE_ID);
250
- }
251
- }
252
-
253
- protected toggleAutoUpdate = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
254
- if (e.nativeEvent.type === 'click') {
255
- e.currentTarget.blur();
256
- }
257
- if ('key' in e && KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.TAB.keyCode) {
258
- return;
259
- }
260
- this.doUpdateAutomatically = !this.doUpdateAutomatically;
261
- if (this.doUpdateAutomatically) {
262
- this.title.iconClass = this.iconClass;
263
- } else {
264
- this.title.iconClass = this.lockIconClass;
265
- }
266
- this.fireDidChangeOptions();
267
- };
268
-
269
- protected onByteSizeChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
270
- this.byteSize = parseInt(event.target.value);
271
- this.fireDidChangeOptions(event.target.id);
272
- };
273
-
274
- protected override onAfterAttach(msg: Message): void {
275
- super.onAfterAttach(msg);
276
- if (this.memoryWidgetOptions.dynamic !== false) {
277
- if (this.addressField) {
278
- this.addressField.value = this.address.toString();
279
- }
280
- }
281
- }
282
-
283
- protected toggleDoShowSettings = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
284
- if (!('key' in e) || KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.TAB.keyCode) {
285
- this.doDisplaySettings = !this.doDisplaySettings;
286
- this.update();
287
- }
288
- };
289
-
290
- protected render(): React.ReactNode {
291
- return (
292
- <div className='t-mv-container'>
293
- {this.renderInputContainer()}
294
- </div>
295
- );
296
- }
297
-
298
- protected renderInputContainer(): React.ReactNode {
299
- return (
300
- <div className='t-mv-settings-container'>
301
- <div className='t-mv-wrapper'>
302
- {this.renderToolbar()}
303
- {this.renderMemoryLocationGroup()}
304
- {this.doDisplaySettings && (
305
- <div className='t-mv-toggle-settings-wrapper'>
306
- {this.renderByteDisplayGroup()}
307
- </div>
308
- )}
309
- </div>
310
- </div>
311
- );
312
- }
313
-
314
- protected renderByteDisplayGroup(): React.ReactNode {
315
- return (
316
- <div className='t-mv-group settings-group'>
317
- <MWSelect
318
- id='byte-size-select'
319
- label={nls.localize('theia/memory-inspector/byteSize', 'Byte Size')}
320
- value={this.byteSize.toString()}
321
- onChange={this.onByteSizeChange}
322
- options={['8', '16', '32', '64']}
323
- />
324
- <MWSelect
325
- id={BYTES_PER_GROUP_FIELD_ID}
326
- label={nls.localize('theia/memory-inspector/bytesPerGroup', 'Bytes Per Group')}
327
- value={this.bytesPerGroup.toString()}
328
- onChange={this.onBytesPerGroupChange}
329
- options={['1', '2', '4', '8', '16']}
330
- />
331
- <MWSelect
332
- id={BYTES_PER_ROW_FIELD_ID}
333
- label={nls.localize('theia/memory-inspector/groupsPerRow', 'Groups Per Row')}
334
- value={this.groupsPerRow.toString()}
335
- onChange={this.onGroupsPerRowChange}
336
- options={['1', '2', '4', '8', '16', '32']}
337
- />
338
- <MWSelect
339
- id={ENDIAN_SELECT_ID}
340
- label={nls.localize('theia/memory-inspector/endianness', 'Endianness')}
341
- value={this.endianness}
342
- onChange={this.onEndiannessChange}
343
- options={[Interfaces.Endianness.Little, Interfaces.Endianness.Big]}
344
- />
345
- <MWMultiSelect
346
- id={ASCII_TOGGLE_ID}
347
- label={nls.localize('theia/memory-inspector/columns', 'Columns')}
348
- items={this.getOptionalColumns()}
349
- onSelectionChanged={this.handleColumnSelectionChange}
350
- />
351
- </div>
352
- );
353
- }
354
-
355
- protected getObligatoryColumnIds(): string[] {
356
- return ['address', 'data'];
357
- }
358
-
359
- protected getOptionalColumns(): SingleSelectItemProps[] {
360
- const obligatoryColumns = new Set(this.getObligatoryColumnIds());
361
- return Object.entries(this.columnsDisplayed)
362
- .reduce<SingleSelectItemProps[]>((accumulated, [id, { doRender, label }]) => {
363
- if (!obligatoryColumns.has(id)) {
364
- accumulated.push({ id, label, defaultChecked: doRender });
365
- }
366
- return accumulated;
367
- }, []);
368
- }
369
-
370
- protected assignLocationRef: React.LegacyRef<HTMLInputElement> = location => {
371
- this.addressField = location ?? undefined;
372
- };
373
-
374
- protected assignReadLengthRef: React.LegacyRef<HTMLInputElement> = readLength => {
375
- this.readLengthField = readLength ?? undefined;
376
- };
377
-
378
- protected assignOffsetRef: React.LegacyRef<HTMLInputElement> = offset => {
379
- this.offsetField = offset ?? undefined;
380
- };
381
-
382
- protected setAddressFromSelect = (e: React.ChangeEvent<HTMLSelectElement>): void => {
383
- if (this.addressField) {
384
- this.addressField.value = e.target.value;
385
- }
386
- };
387
-
388
- protected renderMemoryLocationGroup(): React.ReactNode {
389
- return (
390
- <>
391
- <div className='t-mv-group view-group'>
392
- <MWInputWithSelect
393
- id={LOCATION_FIELD_ID}
394
- label={nls.localizeByDefault('Address')}
395
- title={nls.localize('theia/memory-inspector/addressTooltip', 'Memory location to display, an address or expression evaluating to an address')}
396
- defaultValue={`${this.address}`}
397
- onSelectChange={this.setAddressFromSelect}
398
- passRef={this.assignLocationRef}
399
- onKeyDown={this.doRefresh}
400
- options={[...this.recentLocations.values]}
401
- disabled={!this.doUpdateAutomatically}
402
- />
403
- <MWInput
404
- id={LOCATION_OFFSET_FIELD_ID}
405
- label={nls.localize('theia/memory-inspector/offset', 'Offset')}
406
- title={nls.localize('theia/memory-inspector/offsetTooltip', 'Offset to be added to the current memory location, when navigating')}
407
- defaultValue='0'
408
- passRef={this.assignOffsetRef}
409
- onKeyDown={this.doRefresh}
410
- disabled={!this.doUpdateAutomatically}
411
- />
412
- <MWInput
413
- id={LENGTH_FIELD_ID}
414
- label={nls.localize('theia/memory-inspector/length', 'Length')}
415
- title={nls.localize('theia/memory-inspector/lengthTooltip', 'Number of bytes to fetch, in decimal or hexadecimal')}
416
- defaultValue={this.readLength.toString()}
417
- passRef={this.assignReadLengthRef}
418
- onChange={Utils.validateNumericalInputs}
419
- onKeyDown={this.doRefresh}
420
- disabled={!this.doUpdateAutomatically}
421
- />
422
- <button
423
- type='button'
424
- className='theia-button main view-group-go-button'
425
- onClick={this.doRefresh}
426
- disabled={!this.doUpdateAutomatically}
427
- title={nls.localizeByDefault('Go')}
428
- >
429
- {nls.localizeByDefault('Go')}
430
- </button>
431
- </div>
432
- <div className={`t-mv-memory-fetch-error${this.showMemoryError ? ' show' : ' hide'}`}>
433
- {this.memoryReadError}
434
- </div>
435
- </>
436
- );
437
- }
438
-
439
- protected activateHeaderInputField = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
440
- if (!this.isTitleEditable) {
441
- const isMouseDown = !('key' in e);
442
- const isActivationKey = 'key' in e && (
443
- KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.SPACE.keyCode
444
- || KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.ENTER.keyCode
445
- );
446
- if (isMouseDown || isActivationKey) {
447
- if (isMouseDown) {
448
- e.currentTarget.blur();
449
- }
450
- this.isTitleEditable = true;
451
- this.update();
452
- }
453
- }
454
- };
455
-
456
- protected saveHeaderInputValue = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
457
- const isMouseDown = !('key' in e);
458
- const isSaveKey = 'key' in e && e.key === 'Enter';
459
- const isCancelKey = 'key' in e && e.key === 'Escape';
460
- e.stopPropagation();
461
- if (isMouseDown || isSaveKey || isCancelKey) {
462
- this.updateHeader(isCancelKey);
463
- }
464
- };
465
-
466
- protected assignHeaderInputRef = (element: HTMLInputElement): void => {
467
- if (element) {
468
- this.headerInputField = element;
469
- element.focus();
470
- }
471
- };
472
-
473
- protected updateHeader(isCancelKey: boolean): void {
474
- if (!isCancelKey && this.headerInputField) {
475
- this.title.label = this.headerInputField.value;
476
- this.title.caption = this.headerInputField.value;
477
- }
478
-
479
- this.isTitleEditable = false;
480
- this.update();
481
- }
482
-
483
- protected renderToolbar(): React.ReactNode {
484
- return (
485
- <div className='memory-widget-toolbar'>
486
- {this.renderLockIcon()}
487
- {this.renderEditableTitleField()}
488
- {this.renderSettingsContainer()}
489
- </div>
490
- );
491
- }
492
-
493
- protected renderSettingsContainer(): React.ReactNode {
494
- return <div className='toggle-settings-container'>
495
- <div
496
- className='toggle-settings-click-zone no-select'
497
- tabIndex={0}
498
- aria-label={this.doDisplaySettings ?
499
- nls.localize('theia/memory-inspector/memory/hideSettings', 'Hide Settings Panel') :
500
- nls.localize('theia/memory-inspector/memory/showSettings', 'Show Settings Panel')}
501
- role='button'
502
- onClick={this.toggleDoShowSettings}
503
- onKeyDown={this.toggleDoShowSettings}
504
- title={this.doDisplaySettings ?
505
- nls.localize('theia/memory-inspector/memory/hideSettings', 'Hide Settings Panel') :
506
- nls.localize('theia/memory-inspector/memory/showSettings', 'Show Settings Panel')}>
507
- <i className='codicon codicon-settings-gear' />
508
- <span>{this.doDisplaySettings ?
509
- nls.localize('theia/memory-inspector/closeSettings', 'Close Settings') :
510
- nls.localizeByDefault('Settings')}
511
- </span>
512
- </div>
513
- </div>;
514
- }
515
-
516
- protected renderLockIcon(): React.ReactNode {
517
- return this.memoryWidgetOptions.dynamic !== false && (
518
- <div className='memory-widget-auto-updates-container'>
519
- <div
520
- className={`fa fa-${this.doUpdateAutomatically ? 'unlock' : 'lock'}`}
521
- id={AUTO_UPDATE_TOGGLE_ID}
522
- title={this.doUpdateAutomatically ?
523
- nls.localize('theia/memory-inspector/memory/freeze', 'Freeze Memory View') :
524
- nls.localize('theia/memory-inspector/memory/unfreeze', 'Unfreeze Memory View')}
525
- onClick={this.toggleAutoUpdate}
526
- onKeyDown={this.toggleAutoUpdate}
527
- role='button'
528
- tabIndex={0} />
529
- </div>
530
- );
531
- }
532
-
533
- protected renderEditableTitleField(): React.ReactNode {
534
- return (
535
- <div
536
- className='memory-widget-header-click-zone'
537
- tabIndex={0}
538
- onClick={this.activateHeaderInputField}
539
- onKeyDown={this.activateHeaderInputField}
540
- role='button'
541
- >
542
- {!this.isTitleEditable
543
- ? (
544
- <h2 className={`${MemoryOptionsWidget.WIDGET_H2_CLASS}${!this.doUpdateAutomatically ? ' disabled' : ''} no-select`}>
545
- {this.title.label}
546
- </h2>
547
- )
548
- : <input
549
- className='theia-input'
550
- type='text'
551
- defaultValue={this.title.label}
552
- onKeyDown={this.saveHeaderInputValue}
553
- spellCheck={false}
554
- ref={this.assignHeaderInputRef}
555
- />}
556
- {!this.isTitleEditable && (
557
- <div className={`fa fa-pencil${this.showTitleEditIcon ? ' show' : ' hide'}`} />
558
- )}
559
- {this.isTitleEditable && (
560
- <div
561
- className='fa fa-save'
562
- onClick={this.saveHeaderInputValue}
563
- onKeyDown={this.saveHeaderInputValue}
564
- role='button'
565
- tabIndex={0}
566
- title={nls.localizeByDefault('Save')}
567
- />
568
- )}
569
- </div>
570
- );
571
- }
572
-
573
- storeState(): Interfaces.MemoryOptions {
574
- return {
575
- address: this.addressField?.value ?? this.address,
576
- offset: parseInt(`${this.offsetField?.value}`) ?? this.offset,
577
- length: parseInt(`${this.readLengthField?.value}`) ?? this.readLength,
578
- byteSize: this.byteSize,
579
- bytesPerGroup: this.bytesPerGroup,
580
- groupsPerRow: this.groupsPerRow,
581
- endianness: this.endianness,
582
- doDisplaySettings: this.doDisplaySettings,
583
- columnsDisplayed: this.columnsDisplayed,
584
- recentLocationsArray: this.recentLocations.values,
585
- isFrozen: !this.doUpdateAutomatically,
586
- doUpdateAutomatically: this.doUpdateAutomatically,
587
- };
588
- }
589
-
590
- restoreState(oldState: Interfaces.MemoryOptions): void {
591
- this.address = oldState.address ?? this.address;
592
- this.offset = oldState.offset ?? this.offset;
593
- this.readLength = oldState.length ?? this.readLength;
594
- this.byteSize = oldState.byteSize ?? this.byteSize;
595
- this.bytesPerGroup = oldState.bytesPerGroup ?? this.bytesPerGroup;
596
- this.groupsPerRow = oldState.groupsPerRow ?? this.groupsPerRow;
597
- this.endianness = oldState.endianness ?? this.endianness;
598
- this.recentLocations = new Recents(oldState.recentLocationsArray) ?? this.recentLocations;
599
- this.doDisplaySettings = !!oldState.doDisplaySettings;
600
- if (oldState.columnsDisplayed) {
601
- this.columnsDisplayed = oldState.columnsDisplayed;
602
- }
603
- }
604
-
605
- protected doShowMemoryErrors = (doClearError = false): void => {
606
- if (this.errorTimeout !== undefined) {
607
- clearTimeout(this.errorTimeout);
608
- }
609
- if (doClearError) {
610
- this.showMemoryError = false;
611
- this.update();
612
- this.errorTimeout = undefined;
613
- return;
614
- }
615
- this.showMemoryError = true;
616
- this.update();
617
- this.errorTimeout = setTimeout(() => {
618
- this.showMemoryError = false;
619
- this.update();
620
- this.errorTimeout = undefined;
621
- }, Constants.ERROR_TIMEOUT);
622
- };
623
-
624
- fetchNewMemory(): void {
625
- this.updateMemoryView();
626
- }
627
-
628
- protected doRefresh = (event: React.KeyboardEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
629
- if ('key' in event && event.key !== 'Enter') {
630
- return;
631
- }
632
- this.updateMemoryView();
633
- };
634
-
635
- protected updateMemoryView = debounce(this.doUpdateMemoryView.bind(this), Constants.DEBOUNCE_TIME, { trailing: true });
636
-
637
- protected async doUpdateMemoryView(): Promise<void> {
638
- if (!(this.addressField && this.readLengthField)) { return; }
639
-
640
- if (this.addressField?.value.trim().length === 0) {
641
- this.memoryReadError = nls.localize('theia/memory-inspector/memory/addressField/memoryReadError', 'Enter an address or expression in the Location field.');
642
- this.doShowMemoryErrors();
643
- return;
644
- }
645
- if (this.readLengthField.value.trim().length === 0) {
646
- this.memoryReadError = nls.localize('theia/memory-inspector/memory/readLength/memoryReadError', 'Enter a length (decimal or hexadecimal number) in the Length field.');
647
- this.doShowMemoryErrors();
648
- return;
649
- }
650
-
651
- const startAddress = this.addressField.value;
652
- const locationOffset = parseInt(`${this.offsetField?.value}`) || 0;
653
- const readLength = parseInt(this.readLengthField.value);
654
-
655
- try {
656
- this.memoryReadResult = await this.getMemory(startAddress, readLength, locationOffset);
657
- this.fireDidChangeMemory();
658
- if (this.pinnedMemoryReadResult) {
659
- this.pinnedMemoryReadResult.resolve(this.memoryReadResult);
660
- }
661
- this.doShowMemoryErrors(true);
662
- } catch (err) {
663
- this.memoryReadError = this.getUserError(err);
664
- console.error('Failed to read memory', err);
665
- this.doShowMemoryErrors();
666
- if (this.pinnedMemoryReadResult) {
667
- this.pinnedMemoryReadResult.resolve(this.memoryReadResult);
668
- }
669
- } finally {
670
- this.pinnedMemoryReadResult = undefined;
671
- this.update();
672
- }
673
- }
674
-
675
- protected getUserError(err: unknown): string {
676
- return err instanceof Error ? err.message : nls.localize('theia/memory-inspector/memory/userError', 'There was an error fetching memory.');
677
- }
678
-
679
- protected async getMemory(memoryReference: string, count: number, offset: number): Promise<Interfaces.MemoryReadResult> {
680
- const result = await this.retrieveMemory(memoryReference, count, offset);
681
- try {
682
- this.variables = await this.memoryProvider.getLocals();
683
- } catch {
684
- this.variables = [];
685
- }
686
- this.recentLocations.add(memoryReference);
687
- this.updateDefaults(memoryReference, count, offset);
688
- return result;
689
- }
690
-
691
- protected async retrieveMemory(memoryReference: string, count: number, offset: number): Promise<Interfaces.MemoryReadResult> {
692
- return this.memoryProvider.readMemory({ memoryReference, count, offset });
693
- }
694
-
695
- // TODO: This may not be necessary if we change how state is stored (currently in the text fields themselves.)
696
- protected updateDefaults(address: string, readLength: number, offset: number): void {
697
- this.address = address;
698
- this.readLength = readLength;
699
- this.offset = offset;
700
- }
701
-
702
- // Callbacks for when the various view parameters change.
703
- /**
704
- * Handle bytes per row changed event.
705
- */
706
- protected onGroupsPerRowChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
707
- const { value, id } = event.target;
708
- this.groupsPerRow = parseInt(value);
709
- this.fireDidChangeOptions(id);
710
- };
711
-
712
- /**
713
- * Handle bytes per group changed event.
714
- */
715
- protected onBytesPerGroupChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
716
- const { value, id } = event.target;
717
- this.bytesPerGroup = parseInt(value);
718
- this.fireDidChangeOptions(id);
719
- };
720
-
721
- /**
722
- * Handle endianness changed event.
723
- */
724
- protected onEndiannessChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
725
- const { value, id } = event.target;
726
- if (value !== Interfaces.Endianness.Big && value !== Interfaces.Endianness.Little) { return; }
727
- this.endianness = value;
728
- this.fireDidChangeOptions(id);
729
- };
730
-
731
- protected fireDidChangeOptions(targetId?: string): void {
732
- this.onOptionsChangedEmitter.fire(targetId);
733
- }
734
-
735
- protected fireDidChangeMemory(): void {
736
- this.onMemoryChangedEmitter.fire(this.memoryReadResult);
737
- }
738
- }
1
+ /********************************************************************************
2
+ * Copyright (C) 2021 Ericsson and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+
17
+ import { deepFreeze, Disposable, DisposableCollection, Emitter, nls } from '@theia/core';
18
+ import { Key, KeyCode, Message, ReactWidget, StatefulWidget } from '@theia/core/lib/browser';
19
+ import { Deferred } from '@theia/core/lib/common/promise-util';
20
+ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
21
+ import * as React from '@theia/core/shared/react';
22
+ import { DebugSession, DebugState } from '@theia/debug/lib/browser/debug-session';
23
+ import { DebugSessionManager } from '@theia/debug/lib/browser/debug-session-manager';
24
+ import * as Long from 'long';
25
+ import { MemoryProviderService } from '../memory-provider/memory-provider-service';
26
+ import { Recents } from '../utils/memory-recents';
27
+ import { MWInput, MWInputWithSelect, MWSelect } from '../utils/memory-widget-components';
28
+ import { Constants, Interfaces, MemoryWidgetOptions, Utils } from '../utils/memory-widget-utils';
29
+ import { VariableRange } from '../utils/memory-widget-variable-utils';
30
+ import { MWMultiSelect, SingleSelectItemProps } from '../utils/multi-select-bar';
31
+ import debounce = require('@theia/core/shared/lodash.debounce');
32
+
33
+ export const EMPTY_MEMORY: Interfaces.MemoryReadResult = deepFreeze({
34
+ bytes: new Uint8Array(),
35
+ address: new Long(0, 0, true),
36
+ });
37
+
38
+ export const LOCATION_FIELD_ID = 't-mv-location';
39
+ export const LENGTH_FIELD_ID = 't-mv-length';
40
+ export const LOCATION_OFFSET_FIELD_ID = 't-mv-location-offset';
41
+ export const BYTES_PER_ROW_FIELD_ID = 't-mv-bytesrow';
42
+ export const BYTES_PER_GROUP_FIELD_ID = 't-mv-bytesgroup';
43
+ export const ENDIAN_SELECT_ID = 't-mv-endiannesss';
44
+ export const ASCII_TOGGLE_ID = 't-mv-ascii-toggle';
45
+ export const AUTO_UPDATE_TOGGLE_ID = 't-mv-auto-update-toggle';
46
+
47
+ @injectable()
48
+ export class MemoryOptionsWidget extends ReactWidget implements StatefulWidget {
49
+ static ID = 'memory-view-options-widget';
50
+ static LABEL = nls.localize('theia/memory-inspector/memoryTitle', 'Memory');
51
+ iconClass = 'memory-view-icon';
52
+ lockIconClass = 'memory-lock-icon';
53
+
54
+ static WIDGET_H2_CLASS = 'memory-widget-header';
55
+ static WIDGET_HEADER_INPUT_CLASS = 'memory-widget-header-input';
56
+
57
+ protected additionalColumnSelectLabel = nls.localize('theia/memory-inspector/extraColumn', 'Extra Column');
58
+
59
+ protected sessionListeners = new DisposableCollection();
60
+
61
+ protected readonly onOptionsChangedEmitter = new Emitter<string | undefined>();
62
+ readonly onOptionsChanged = this.onOptionsChangedEmitter.event;
63
+ protected readonly onMemoryChangedEmitter = new Emitter<Interfaces.MemoryReadResult>();
64
+ readonly onMemoryChanged = this.onMemoryChangedEmitter.event;
65
+ protected pinnedMemoryReadResult: Deferred<Interfaces.MemoryReadResult | false> | undefined;
66
+
67
+ protected memoryReadResult: Interfaces.MemoryReadResult = EMPTY_MEMORY;
68
+ protected columnsDisplayed: Interfaces.ColumnsDisplayed = {
69
+ address: {
70
+ label: nls.localizeByDefault('Address'),
71
+ doRender: true
72
+ },
73
+ data: {
74
+ label: nls.localize('theia/memory-inspector/data', 'Data'),
75
+ doRender: true
76
+ },
77
+ variables: {
78
+ label: nls.localizeByDefault('Variables'),
79
+ doRender: true
80
+ },
81
+ ascii: {
82
+ label: nls.localize('theia/memory-inspector/ascii', 'ASCII'),
83
+ doRender: false
84
+ },
85
+ };
86
+
87
+ protected byteSize = 8;
88
+
89
+ protected bytesPerGroup = 1;
90
+ protected groupsPerRow = 4;
91
+ protected variables: VariableRange[] = [];
92
+ protected endianness: Interfaces.Endianness = Interfaces.Endianness.Little;
93
+
94
+ protected memoryReadError = nls.localize('theia/memory-inspector/memory/readError/noContents', 'No memory contents currently available.');
95
+
96
+ protected address: string | number = 0;
97
+ protected offset = 0;
98
+ protected readLength = 256;
99
+ protected doDisplaySettings = false;
100
+ protected doUpdateAutomatically = true;
101
+ protected showMemoryError = false;
102
+ protected errorTimeout: NodeJS.Timeout | undefined = undefined;
103
+ protected addressField: HTMLInputElement | undefined;
104
+ protected offsetField: HTMLInputElement | undefined;
105
+ protected readLengthField: HTMLInputElement | undefined;
106
+ protected headerInputField: HTMLInputElement | undefined;
107
+ protected recentLocations = new Recents();
108
+ protected showTitleEditIcon = false;
109
+ protected isTitleEditable = false;
110
+
111
+ @inject(MemoryProviderService) protected readonly memoryProvider: MemoryProviderService;
112
+ @inject(DebugSessionManager) protected readonly sessionManager: DebugSessionManager;
113
+ @inject(MemoryWidgetOptions) protected readonly memoryWidgetOptions: MemoryWidgetOptions;
114
+
115
+ get memory(): Interfaces.WidgetMemoryState {
116
+ return {
117
+ ...this.memoryReadResult,
118
+ variables: this.variables,
119
+ };
120
+ }
121
+
122
+ get options(): Interfaces.MemoryOptions {
123
+ return this.storeState();
124
+ }
125
+
126
+ @postConstruct()
127
+ protected init(): void {
128
+ this.addClass(MemoryOptionsWidget.ID);
129
+
130
+ this.title.label = nls.localize('theia/memory-inspector/memory', 'Memory ({0})', this.memoryWidgetOptions.displayId);
131
+ this.title.caption = nls.localize('theia/memory-inspector/memory', 'Memory ({0})', this.memoryWidgetOptions.displayId);
132
+ this.title.iconClass = this.iconClass;
133
+ this.title.closable = true;
134
+
135
+ if (this.memoryWidgetOptions.dynamic !== false) {
136
+ this.toDispose.push(this.sessionManager.onDidChangeActiveDebugSession(({ current }) => {
137
+ this.setUpListeners(current);
138
+ }));
139
+
140
+ this.toDispose.push(this.sessionManager.onDidCreateDebugSession(current => {
141
+ this.setUpListeners(current);
142
+ }));
143
+ this.setUpListeners(this.sessionManager.currentSession);
144
+ }
145
+ this.toDispose.push(this.onOptionsChanged(() => this.update()));
146
+
147
+ this.update();
148
+ }
149
+
150
+ async setAddressAndGo(
151
+ newAddress: string,
152
+ newOffset?: number,
153
+ newLength?: number,
154
+ direction?: 'above' | 'below',
155
+ ): Promise<Interfaces.MemoryReadResult | false | undefined> {
156
+ let doUpdate = false;
157
+ const originalValues = {
158
+ offset: '',
159
+ length: '',
160
+ };
161
+ if (this.addressField) {
162
+ this.addressField.value = newAddress;
163
+ doUpdate = true;
164
+ }
165
+ if (this.offsetField && newOffset !== undefined) {
166
+ originalValues.offset = this.offsetField.value;
167
+ this.offsetField.value = newOffset.toString();
168
+ doUpdate = true;
169
+ }
170
+ if (this.readLengthField && newLength !== undefined) {
171
+ originalValues.length = this.readLengthField.value;
172
+ this.readLengthField.value = newLength.toString();
173
+ doUpdate = true;
174
+ }
175
+ if (doUpdate && this.readLengthField && this.offsetField) {
176
+ this.pinnedMemoryReadResult = new Deferred<Interfaces.MemoryReadResult>();
177
+ this.updateMemoryView();
178
+ const result = await this.pinnedMemoryReadResult.promise;
179
+ if (result === false) {
180
+ // Memory request errored
181
+ this.readLengthField.value = originalValues.length;
182
+ this.offsetField.value = originalValues.offset;
183
+ }
184
+ if (result) {
185
+ // Memory request returned some memory
186
+ const resultLength = result.bytes.length * 8 / this.byteSize;
187
+ const lengthFieldValue = parseInt(this.readLengthField.value);
188
+ if (lengthFieldValue !== resultLength) {
189
+ this.memoryReadError = nls.localize('theia/memory-inspector/memory/readError/bounds', 'Memory bounds exceeded, result will be truncated.');
190
+ this.doShowMemoryErrors();
191
+ this.readLengthField.value = resultLength.toString();
192
+ if (direction === 'above') {
193
+ this.offsetField.value = `${parseInt(originalValues.offset) - (resultLength - parseInt(originalValues.length))}`;
194
+ }
195
+ this.update();
196
+ }
197
+ }
198
+ }
199
+ return undefined;
200
+ }
201
+
202
+ protected setUpListeners(session?: DebugSession): void {
203
+ this.sessionListeners.dispose();
204
+ this.sessionListeners = new DisposableCollection(Disposable.create(() => this.handleActiveSessionChange()));
205
+ if (session) {
206
+ this.sessionListeners.push(session.onDidChange(() => this.handleSessionChange()));
207
+ }
208
+ }
209
+
210
+ protected handleActiveSessionChange(): void {
211
+ const isDynamic = this.memoryWidgetOptions.dynamic !== false;
212
+ if (isDynamic && this.doUpdateAutomatically) {
213
+ this.memoryReadResult = EMPTY_MEMORY;
214
+ this.fireDidChangeMemory();
215
+ }
216
+ }
217
+
218
+ protected handleSessionChange(): void {
219
+ const isStopped = this.sessionManager.currentSession?.state === DebugState.Stopped;
220
+ const isReadyForQuery = !!this.sessionManager.currentSession?.currentFrame;
221
+ const isDynamic = this.memoryWidgetOptions.dynamic !== false;
222
+ if (isStopped && isReadyForQuery && isDynamic && this.doUpdateAutomatically && this.memoryReadResult !== EMPTY_MEMORY) {
223
+ this.updateMemoryView();
224
+ }
225
+ }
226
+
227
+ protected override onActivateRequest(msg: Message): void {
228
+ super.onActivateRequest(msg);
229
+ this.acceptFocus();
230
+ }
231
+
232
+ protected acceptFocus(): void {
233
+ if (this.doUpdateAutomatically) {
234
+ if (this.addressField) {
235
+ this.addressField.focus();
236
+ this.addressField.select();
237
+ }
238
+ } else {
239
+ const settingsCog = this.node.querySelector('.toggle-settings-click-zone') as HTMLDivElement;
240
+ settingsCog?.focus();
241
+ }
242
+ }
243
+
244
+ protected handleColumnSelectionChange = (columnLabel: string, doShow: boolean): void => this.doHandleColumnSelectionChange(columnLabel, doShow);
245
+
246
+ protected doHandleColumnSelectionChange(columnLabel: string, doShow: boolean): void {
247
+ if (columnLabel in this.columnsDisplayed) {
248
+ this.columnsDisplayed[columnLabel].doRender = doShow;
249
+ this.fireDidChangeOptions(ASCII_TOGGLE_ID);
250
+ }
251
+ }
252
+
253
+ protected toggleAutoUpdate = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
254
+ if (e.nativeEvent.type === 'click') {
255
+ e.currentTarget.blur();
256
+ }
257
+ if ('key' in e && KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.TAB.keyCode) {
258
+ return;
259
+ }
260
+ this.doUpdateAutomatically = !this.doUpdateAutomatically;
261
+ if (this.doUpdateAutomatically) {
262
+ this.title.iconClass = this.iconClass;
263
+ } else {
264
+ this.title.iconClass = this.lockIconClass;
265
+ }
266
+ this.fireDidChangeOptions();
267
+ };
268
+
269
+ protected onByteSizeChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
270
+ this.byteSize = parseInt(event.target.value);
271
+ this.fireDidChangeOptions(event.target.id);
272
+ };
273
+
274
+ protected override onAfterAttach(msg: Message): void {
275
+ super.onAfterAttach(msg);
276
+ if (this.memoryWidgetOptions.dynamic !== false) {
277
+ if (this.addressField) {
278
+ this.addressField.value = this.address.toString();
279
+ }
280
+ }
281
+ }
282
+
283
+ protected toggleDoShowSettings = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
284
+ if (!('key' in e) || KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.TAB.keyCode) {
285
+ this.doDisplaySettings = !this.doDisplaySettings;
286
+ this.update();
287
+ }
288
+ };
289
+
290
+ protected render(): React.ReactNode {
291
+ return (
292
+ <div className='t-mv-container'>
293
+ {this.renderInputContainer()}
294
+ </div>
295
+ );
296
+ }
297
+
298
+ protected renderInputContainer(): React.ReactNode {
299
+ return (
300
+ <div className='t-mv-settings-container'>
301
+ <div className='t-mv-wrapper'>
302
+ {this.renderToolbar()}
303
+ {this.renderMemoryLocationGroup()}
304
+ {this.doDisplaySettings && (
305
+ <div className='t-mv-toggle-settings-wrapper'>
306
+ {this.renderByteDisplayGroup()}
307
+ </div>
308
+ )}
309
+ </div>
310
+ </div>
311
+ );
312
+ }
313
+
314
+ protected renderByteDisplayGroup(): React.ReactNode {
315
+ return (
316
+ <div className='t-mv-group settings-group'>
317
+ <MWSelect
318
+ id='byte-size-select'
319
+ label={nls.localize('theia/memory-inspector/byteSize', 'Byte Size')}
320
+ value={this.byteSize.toString()}
321
+ onChange={this.onByteSizeChange}
322
+ options={['8', '16', '32', '64']}
323
+ />
324
+ <MWSelect
325
+ id={BYTES_PER_GROUP_FIELD_ID}
326
+ label={nls.localize('theia/memory-inspector/bytesPerGroup', 'Bytes Per Group')}
327
+ value={this.bytesPerGroup.toString()}
328
+ onChange={this.onBytesPerGroupChange}
329
+ options={['1', '2', '4', '8', '16']}
330
+ />
331
+ <MWSelect
332
+ id={BYTES_PER_ROW_FIELD_ID}
333
+ label={nls.localize('theia/memory-inspector/groupsPerRow', 'Groups Per Row')}
334
+ value={this.groupsPerRow.toString()}
335
+ onChange={this.onGroupsPerRowChange}
336
+ options={['1', '2', '4', '8', '16', '32']}
337
+ />
338
+ <MWSelect
339
+ id={ENDIAN_SELECT_ID}
340
+ label={nls.localize('theia/memory-inspector/endianness', 'Endianness')}
341
+ value={this.endianness}
342
+ onChange={this.onEndiannessChange}
343
+ options={[Interfaces.Endianness.Little, Interfaces.Endianness.Big]}
344
+ />
345
+ <MWMultiSelect
346
+ id={ASCII_TOGGLE_ID}
347
+ label={nls.localize('theia/memory-inspector/columns', 'Columns')}
348
+ items={this.getOptionalColumns()}
349
+ onSelectionChanged={this.handleColumnSelectionChange}
350
+ />
351
+ </div>
352
+ );
353
+ }
354
+
355
+ protected getObligatoryColumnIds(): string[] {
356
+ return ['address', 'data'];
357
+ }
358
+
359
+ protected getOptionalColumns(): SingleSelectItemProps[] {
360
+ const obligatoryColumns = new Set(this.getObligatoryColumnIds());
361
+ return Object.entries(this.columnsDisplayed)
362
+ .reduce<SingleSelectItemProps[]>((accumulated, [id, { doRender, label }]) => {
363
+ if (!obligatoryColumns.has(id)) {
364
+ accumulated.push({ id, label, defaultChecked: doRender });
365
+ }
366
+ return accumulated;
367
+ }, []);
368
+ }
369
+
370
+ protected assignLocationRef: React.LegacyRef<HTMLInputElement> = location => {
371
+ this.addressField = location ?? undefined;
372
+ };
373
+
374
+ protected assignReadLengthRef: React.LegacyRef<HTMLInputElement> = readLength => {
375
+ this.readLengthField = readLength ?? undefined;
376
+ };
377
+
378
+ protected assignOffsetRef: React.LegacyRef<HTMLInputElement> = offset => {
379
+ this.offsetField = offset ?? undefined;
380
+ };
381
+
382
+ protected setAddressFromSelect = (e: React.ChangeEvent<HTMLSelectElement>): void => {
383
+ if (this.addressField) {
384
+ this.addressField.value = e.target.value;
385
+ }
386
+ };
387
+
388
+ protected renderMemoryLocationGroup(): React.ReactNode {
389
+ return (
390
+ <>
391
+ <div className='t-mv-group view-group'>
392
+ <MWInputWithSelect
393
+ id={LOCATION_FIELD_ID}
394
+ label={nls.localizeByDefault('Address')}
395
+ title={nls.localize('theia/memory-inspector/addressTooltip', 'Memory location to display, an address or expression evaluating to an address')}
396
+ defaultValue={`${this.address}`}
397
+ onSelectChange={this.setAddressFromSelect}
398
+ passRef={this.assignLocationRef}
399
+ onKeyDown={this.doRefresh}
400
+ options={[...this.recentLocations.values]}
401
+ disabled={!this.doUpdateAutomatically}
402
+ />
403
+ <MWInput
404
+ id={LOCATION_OFFSET_FIELD_ID}
405
+ label={nls.localize('theia/memory-inspector/offset', 'Offset')}
406
+ title={nls.localize('theia/memory-inspector/offsetTooltip', 'Offset to be added to the current memory location, when navigating')}
407
+ defaultValue='0'
408
+ passRef={this.assignOffsetRef}
409
+ onKeyDown={this.doRefresh}
410
+ disabled={!this.doUpdateAutomatically}
411
+ />
412
+ <MWInput
413
+ id={LENGTH_FIELD_ID}
414
+ label={nls.localize('theia/memory-inspector/length', 'Length')}
415
+ title={nls.localize('theia/memory-inspector/lengthTooltip', 'Number of bytes to fetch, in decimal or hexadecimal')}
416
+ defaultValue={this.readLength.toString()}
417
+ passRef={this.assignReadLengthRef}
418
+ onChange={Utils.validateNumericalInputs}
419
+ onKeyDown={this.doRefresh}
420
+ disabled={!this.doUpdateAutomatically}
421
+ />
422
+ <button
423
+ type='button'
424
+ className='theia-button main view-group-go-button'
425
+ onClick={this.doRefresh}
426
+ disabled={!this.doUpdateAutomatically}
427
+ title={nls.localizeByDefault('Go')}
428
+ >
429
+ {nls.localizeByDefault('Go')}
430
+ </button>
431
+ </div>
432
+ <div className={`t-mv-memory-fetch-error${this.showMemoryError ? ' show' : ' hide'}`}>
433
+ {this.memoryReadError}
434
+ </div>
435
+ </>
436
+ );
437
+ }
438
+
439
+ protected activateHeaderInputField = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
440
+ if (!this.isTitleEditable) {
441
+ const isMouseDown = !('key' in e);
442
+ const isActivationKey = 'key' in e && (
443
+ KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.SPACE.keyCode
444
+ || KeyCode.createKeyCode(e.nativeEvent).key?.keyCode === Key.ENTER.keyCode
445
+ );
446
+ if (isMouseDown || isActivationKey) {
447
+ if (isMouseDown) {
448
+ e.currentTarget.blur();
449
+ }
450
+ this.isTitleEditable = true;
451
+ this.update();
452
+ }
453
+ }
454
+ };
455
+
456
+ protected saveHeaderInputValue = (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void => {
457
+ const isMouseDown = !('key' in e);
458
+ const isSaveKey = 'key' in e && e.key === 'Enter';
459
+ const isCancelKey = 'key' in e && e.key === 'Escape';
460
+ e.stopPropagation();
461
+ if (isMouseDown || isSaveKey || isCancelKey) {
462
+ this.updateHeader(isCancelKey);
463
+ }
464
+ };
465
+
466
+ protected assignHeaderInputRef = (element: HTMLInputElement): void => {
467
+ if (element) {
468
+ this.headerInputField = element;
469
+ element.focus();
470
+ }
471
+ };
472
+
473
+ protected updateHeader(isCancelKey: boolean): void {
474
+ if (!isCancelKey && this.headerInputField) {
475
+ this.title.label = this.headerInputField.value;
476
+ this.title.caption = this.headerInputField.value;
477
+ }
478
+
479
+ this.isTitleEditable = false;
480
+ this.update();
481
+ }
482
+
483
+ protected renderToolbar(): React.ReactNode {
484
+ return (
485
+ <div className='memory-widget-toolbar'>
486
+ {this.renderLockIcon()}
487
+ {this.renderEditableTitleField()}
488
+ {this.renderSettingsContainer()}
489
+ </div>
490
+ );
491
+ }
492
+
493
+ protected renderSettingsContainer(): React.ReactNode {
494
+ return <div className='toggle-settings-container'>
495
+ <div
496
+ className='toggle-settings-click-zone no-select'
497
+ tabIndex={0}
498
+ aria-label={this.doDisplaySettings ?
499
+ nls.localize('theia/memory-inspector/memory/hideSettings', 'Hide Settings Panel') :
500
+ nls.localize('theia/memory-inspector/memory/showSettings', 'Show Settings Panel')}
501
+ role='button'
502
+ onClick={this.toggleDoShowSettings}
503
+ onKeyDown={this.toggleDoShowSettings}
504
+ title={this.doDisplaySettings ?
505
+ nls.localize('theia/memory-inspector/memory/hideSettings', 'Hide Settings Panel') :
506
+ nls.localize('theia/memory-inspector/memory/showSettings', 'Show Settings Panel')}>
507
+ <i className='codicon codicon-settings-gear' />
508
+ <span>{this.doDisplaySettings ?
509
+ nls.localize('theia/memory-inspector/closeSettings', 'Close Settings') :
510
+ nls.localizeByDefault('Settings')}
511
+ </span>
512
+ </div>
513
+ </div>;
514
+ }
515
+
516
+ protected renderLockIcon(): React.ReactNode {
517
+ return this.memoryWidgetOptions.dynamic !== false && (
518
+ <div className='memory-widget-auto-updates-container'>
519
+ <div
520
+ className={`fa fa-${this.doUpdateAutomatically ? 'unlock' : 'lock'}`}
521
+ id={AUTO_UPDATE_TOGGLE_ID}
522
+ title={this.doUpdateAutomatically ?
523
+ nls.localize('theia/memory-inspector/memory/freeze', 'Freeze Memory View') :
524
+ nls.localize('theia/memory-inspector/memory/unfreeze', 'Unfreeze Memory View')}
525
+ onClick={this.toggleAutoUpdate}
526
+ onKeyDown={this.toggleAutoUpdate}
527
+ role='button'
528
+ tabIndex={0} />
529
+ </div>
530
+ );
531
+ }
532
+
533
+ protected renderEditableTitleField(): React.ReactNode {
534
+ return (
535
+ <div
536
+ className='memory-widget-header-click-zone'
537
+ tabIndex={0}
538
+ onClick={this.activateHeaderInputField}
539
+ onKeyDown={this.activateHeaderInputField}
540
+ role='button'
541
+ >
542
+ {!this.isTitleEditable
543
+ ? (
544
+ <h2 className={`${MemoryOptionsWidget.WIDGET_H2_CLASS}${!this.doUpdateAutomatically ? ' disabled' : ''} no-select`}>
545
+ {this.title.label}
546
+ </h2>
547
+ )
548
+ : <input
549
+ className='theia-input'
550
+ type='text'
551
+ defaultValue={this.title.label}
552
+ onKeyDown={this.saveHeaderInputValue}
553
+ spellCheck={false}
554
+ ref={this.assignHeaderInputRef}
555
+ />}
556
+ {!this.isTitleEditable && (
557
+ <div className={`fa fa-pencil${this.showTitleEditIcon ? ' show' : ' hide'}`} />
558
+ )}
559
+ {this.isTitleEditable && (
560
+ <div
561
+ className='fa fa-save'
562
+ onClick={this.saveHeaderInputValue}
563
+ onKeyDown={this.saveHeaderInputValue}
564
+ role='button'
565
+ tabIndex={0}
566
+ title={nls.localizeByDefault('Save')}
567
+ />
568
+ )}
569
+ </div>
570
+ );
571
+ }
572
+
573
+ storeState(): Interfaces.MemoryOptions {
574
+ return {
575
+ address: this.addressField?.value ?? this.address,
576
+ offset: parseInt(`${this.offsetField?.value}`) ?? this.offset,
577
+ length: parseInt(`${this.readLengthField?.value}`) ?? this.readLength,
578
+ byteSize: this.byteSize,
579
+ bytesPerGroup: this.bytesPerGroup,
580
+ groupsPerRow: this.groupsPerRow,
581
+ endianness: this.endianness,
582
+ doDisplaySettings: this.doDisplaySettings,
583
+ columnsDisplayed: this.columnsDisplayed,
584
+ recentLocationsArray: this.recentLocations.values,
585
+ isFrozen: !this.doUpdateAutomatically,
586
+ doUpdateAutomatically: this.doUpdateAutomatically,
587
+ };
588
+ }
589
+
590
+ restoreState(oldState: Interfaces.MemoryOptions): void {
591
+ this.address = oldState.address ?? this.address;
592
+ this.offset = oldState.offset ?? this.offset;
593
+ this.readLength = oldState.length ?? this.readLength;
594
+ this.byteSize = oldState.byteSize ?? this.byteSize;
595
+ this.bytesPerGroup = oldState.bytesPerGroup ?? this.bytesPerGroup;
596
+ this.groupsPerRow = oldState.groupsPerRow ?? this.groupsPerRow;
597
+ this.endianness = oldState.endianness ?? this.endianness;
598
+ this.recentLocations = new Recents(oldState.recentLocationsArray) ?? this.recentLocations;
599
+ this.doDisplaySettings = !!oldState.doDisplaySettings;
600
+ if (oldState.columnsDisplayed) {
601
+ this.columnsDisplayed = oldState.columnsDisplayed;
602
+ }
603
+ }
604
+
605
+ protected doShowMemoryErrors = (doClearError = false): void => {
606
+ if (this.errorTimeout !== undefined) {
607
+ clearTimeout(this.errorTimeout);
608
+ }
609
+ if (doClearError) {
610
+ this.showMemoryError = false;
611
+ this.update();
612
+ this.errorTimeout = undefined;
613
+ return;
614
+ }
615
+ this.showMemoryError = true;
616
+ this.update();
617
+ this.errorTimeout = setTimeout(() => {
618
+ this.showMemoryError = false;
619
+ this.update();
620
+ this.errorTimeout = undefined;
621
+ }, Constants.ERROR_TIMEOUT);
622
+ };
623
+
624
+ fetchNewMemory(): void {
625
+ this.updateMemoryView();
626
+ }
627
+
628
+ protected doRefresh = (event: React.KeyboardEvent<HTMLInputElement> | React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
629
+ if ('key' in event && event.key !== 'Enter') {
630
+ return;
631
+ }
632
+ this.updateMemoryView();
633
+ };
634
+
635
+ protected updateMemoryView = debounce(this.doUpdateMemoryView.bind(this), Constants.DEBOUNCE_TIME, { trailing: true });
636
+
637
+ protected async doUpdateMemoryView(): Promise<void> {
638
+ if (!(this.addressField && this.readLengthField)) { return; }
639
+
640
+ if (this.addressField?.value.trim().length === 0) {
641
+ this.memoryReadError = nls.localize('theia/memory-inspector/memory/addressField/memoryReadError', 'Enter an address or expression in the Location field.');
642
+ this.doShowMemoryErrors();
643
+ return;
644
+ }
645
+ if (this.readLengthField.value.trim().length === 0) {
646
+ this.memoryReadError = nls.localize('theia/memory-inspector/memory/readLength/memoryReadError', 'Enter a length (decimal or hexadecimal number) in the Length field.');
647
+ this.doShowMemoryErrors();
648
+ return;
649
+ }
650
+
651
+ const startAddress = this.addressField.value;
652
+ const locationOffset = parseInt(`${this.offsetField?.value}`) || 0;
653
+ const readLength = parseInt(this.readLengthField.value);
654
+
655
+ try {
656
+ this.memoryReadResult = await this.getMemory(startAddress, readLength, locationOffset);
657
+ this.fireDidChangeMemory();
658
+ if (this.pinnedMemoryReadResult) {
659
+ this.pinnedMemoryReadResult.resolve(this.memoryReadResult);
660
+ }
661
+ this.doShowMemoryErrors(true);
662
+ } catch (err) {
663
+ this.memoryReadError = this.getUserError(err);
664
+ console.error('Failed to read memory', err);
665
+ this.doShowMemoryErrors();
666
+ if (this.pinnedMemoryReadResult) {
667
+ this.pinnedMemoryReadResult.resolve(this.memoryReadResult);
668
+ }
669
+ } finally {
670
+ this.pinnedMemoryReadResult = undefined;
671
+ this.update();
672
+ }
673
+ }
674
+
675
+ protected getUserError(err: unknown): string {
676
+ return err instanceof Error ? err.message : nls.localize('theia/memory-inspector/memory/userError', 'There was an error fetching memory.');
677
+ }
678
+
679
+ protected async getMemory(memoryReference: string, count: number, offset: number): Promise<Interfaces.MemoryReadResult> {
680
+ const result = await this.retrieveMemory(memoryReference, count, offset);
681
+ try {
682
+ this.variables = await this.memoryProvider.getLocals();
683
+ } catch {
684
+ this.variables = [];
685
+ }
686
+ this.recentLocations.add(memoryReference);
687
+ this.updateDefaults(memoryReference, count, offset);
688
+ return result;
689
+ }
690
+
691
+ protected async retrieveMemory(memoryReference: string, count: number, offset: number): Promise<Interfaces.MemoryReadResult> {
692
+ return this.memoryProvider.readMemory({ memoryReference, count, offset });
693
+ }
694
+
695
+ // TODO: This may not be necessary if we change how state is stored (currently in the text fields themselves.)
696
+ protected updateDefaults(address: string, readLength: number, offset: number): void {
697
+ this.address = address;
698
+ this.readLength = readLength;
699
+ this.offset = offset;
700
+ }
701
+
702
+ // Callbacks for when the various view parameters change.
703
+ /**
704
+ * Handle bytes per row changed event.
705
+ */
706
+ protected onGroupsPerRowChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
707
+ const { value, id } = event.target;
708
+ this.groupsPerRow = parseInt(value);
709
+ this.fireDidChangeOptions(id);
710
+ };
711
+
712
+ /**
713
+ * Handle bytes per group changed event.
714
+ */
715
+ protected onBytesPerGroupChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
716
+ const { value, id } = event.target;
717
+ this.bytesPerGroup = parseInt(value);
718
+ this.fireDidChangeOptions(id);
719
+ };
720
+
721
+ /**
722
+ * Handle endianness changed event.
723
+ */
724
+ protected onEndiannessChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
725
+ const { value, id } = event.target;
726
+ if (value !== Interfaces.Endianness.Big && value !== Interfaces.Endianness.Little) { return; }
727
+ this.endianness = value;
728
+ this.fireDidChangeOptions(id);
729
+ };
730
+
731
+ protected fireDidChangeOptions(targetId?: string): void {
732
+ this.onOptionsChangedEmitter.fire(targetId);
733
+ }
734
+
735
+ protected fireDidChangeMemory(): void {
736
+ this.onMemoryChangedEmitter.fire(this.memoryReadResult);
737
+ }
738
+ }