@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.
- package/README.md +71 -71
- package/lib/browser/diff-widget/memory-diff-options-widget.d.ts +36 -36
- package/lib/browser/diff-widget/memory-diff-options-widget.js +132 -132
- package/lib/browser/diff-widget/memory-diff-select-widget.d.ts +50 -50
- package/lib/browser/diff-widget/memory-diff-select-widget.js +137 -137
- package/lib/browser/diff-widget/memory-diff-table-widget.d.ts +77 -77
- package/lib/browser/diff-widget/memory-diff-table-widget.js +314 -314
- package/lib/browser/diff-widget/memory-diff-widget-types.d.ts +41 -41
- package/lib/browser/diff-widget/memory-diff-widget-types.js +23 -23
- package/lib/browser/editable-widget/memory-editable-table-widget.d.ts +64 -64
- package/lib/browser/editable-widget/memory-editable-table-widget.js +324 -324
- package/lib/browser/memory-inspector-frontend-contribution.d.ts +46 -46
- package/lib/browser/memory-inspector-frontend-contribution.js +291 -291
- package/lib/browser/memory-inspector-frontend-module.d.ts +21 -21
- package/lib/browser/memory-inspector-frontend-module.js +87 -87
- package/lib/browser/memory-provider/cdt-gdb-memory-provider.d.ts +35 -35
- package/lib/browser/memory-provider/cdt-gdb-memory-provider.js +136 -136
- package/lib/browser/memory-provider/memory-provider-service.d.ts +36 -36
- package/lib/browser/memory-provider/memory-provider-service.js +101 -101
- package/lib/browser/memory-provider/memory-provider.d.ts +65 -65
- package/lib/browser/memory-provider/memory-provider.js +96 -96
- package/lib/browser/memory-provider/memory-provider.spec.d.ts +16 -16
- package/lib/browser/memory-provider/memory-provider.spec.js +23 -23
- package/lib/browser/memory-widget/memory-options-widget.d.ts +136 -136
- package/lib/browser/memory-widget/memory-options-widget.js +564 -564
- package/lib/browser/memory-widget/memory-table-widget.d.ts +146 -146
- package/lib/browser/memory-widget/memory-table-widget.js +487 -487
- package/lib/browser/memory-widget/memory-widget.d.ts +33 -33
- package/lib/browser/memory-widget/memory-widget.js +112 -112
- package/lib/browser/register-widget/register-filter-service.d.ts +42 -42
- package/lib/browser/register-widget/register-filter-service.js +81 -81
- package/lib/browser/register-widget/register-options-widget.d.ts +81 -81
- package/lib/browser/register-widget/register-options-widget.js +338 -338
- package/lib/browser/register-widget/register-table-widget.d.ts +77 -77
- package/lib/browser/register-widget/register-table-widget.js +230 -230
- package/lib/browser/register-widget/register-widget-types.d.ts +29 -29
- package/lib/browser/register-widget/register-widget-types.js +35 -35
- package/lib/browser/utils/memory-commands.d.ts +27 -27
- package/lib/browser/utils/memory-commands.js +67 -67
- package/lib/browser/utils/memory-hover-renderer.d.ts +33 -33
- package/lib/browser/utils/memory-hover-renderer.js +111 -111
- package/lib/browser/utils/memory-recents.d.ts +27 -27
- package/lib/browser/utils/memory-recents.js +52 -52
- package/lib/browser/utils/memory-widget-components.d.ts +58 -58
- package/lib/browser/utils/memory-widget-components.js +69 -69
- package/lib/browser/utils/memory-widget-manager.d.ts +50 -50
- package/lib/browser/utils/memory-widget-manager.js +182 -182
- package/lib/browser/utils/memory-widget-utils.d.ts +113 -113
- package/lib/browser/utils/memory-widget-utils.js +42 -42
- package/lib/browser/utils/memory-widget-variable-utils.d.ts +57 -57
- package/lib/browser/utils/memory-widget-variable-utils.js +143 -143
- package/lib/browser/utils/multi-select-bar.d.ts +31 -31
- package/lib/browser/utils/multi-select-bar.js +34 -34
- package/lib/browser/wrapper-widgets/memory-dock-panel.d.ts +24 -24
- package/lib/browser/wrapper-widgets/memory-dock-panel.js +48 -48
- package/lib/browser/wrapper-widgets/memory-dockpanel-placeholder-widget.d.ts +23 -23
- package/lib/browser/wrapper-widgets/memory-dockpanel-placeholder-widget.js +57 -57
- package/lib/browser/wrapper-widgets/memory-layout-widget.d.ts +51 -51
- package/lib/browser/wrapper-widgets/memory-layout-widget.js +191 -191
- package/lib/common/util.d.ts +21 -21
- package/lib/common/util.js +30 -30
- package/lib/common/utils.spec.d.ts +16 -16
- package/lib/common/utils.spec.js +45 -45
- package/package.json +4 -4
- package/src/browser/diff-widget/memory-diff-options-widget.tsx +152 -152
- package/src/browser/diff-widget/memory-diff-select-widget.tsx +163 -163
- package/src/browser/diff-widget/memory-diff-table-widget.tsx +366 -366
- package/src/browser/diff-widget/memory-diff-widget-types.ts +45 -45
- package/src/browser/editable-widget/memory-editable-table-widget.tsx +359 -359
- package/src/browser/memory-inspector-frontend-contribution.ts +301 -301
- package/src/browser/memory-inspector-frontend-module.ts +118 -118
- package/src/browser/memory-provider/cdt-gdb-memory-provider.ts +132 -132
- package/src/browser/memory-provider/memory-provider-service.ts +86 -86
- package/src/browser/memory-provider/memory-provider.spec.ts +23 -23
- package/src/browser/memory-provider/memory-provider.ts +119 -119
- package/src/browser/memory-widget/memory-options-widget.tsx +738 -738
- package/src/browser/memory-widget/memory-table-widget.tsx +625 -625
- package/src/browser/memory-widget/memory-widget.ts +114 -114
- package/src/browser/register-widget/register-filter-service.ts +76 -76
- package/src/browser/register-widget/register-options-widget.tsx +393 -393
- package/src/browser/register-widget/register-table-widget.tsx +276 -276
- package/src/browser/register-widget/register-widget-types.ts +45 -45
- package/src/browser/register-widget/register-widget.css +34 -34
- package/src/browser/style/index.css +746 -746
- package/src/browser/style/memory-lock.svg +21 -21
- package/src/browser/style/memory-view.svg +20 -20
- package/src/browser/style/register-lock.svg +29 -29
- package/src/browser/style/register-view.svg +28 -28
- package/src/browser/utils/memory-commands.ts +76 -76
- package/src/browser/utils/memory-hover-renderer.ts +113 -113
- package/src/browser/utils/memory-recents.ts +58 -58
- package/src/browser/utils/memory-widget-components.tsx +193 -193
- package/src/browser/utils/memory-widget-manager.ts +179 -179
- package/src/browser/utils/memory-widget-utils.tsx +132 -132
- package/src/browser/utils/memory-widget-variable-utils.ts +170 -170
- package/src/browser/utils/multi-select-bar.css +61 -61
- package/src/browser/utils/multi-select-bar.tsx +75 -75
- package/src/browser/wrapper-widgets/memory-dock-panel.ts +51 -51
- package/src/browser/wrapper-widgets/memory-dockpanel-placeholder-widget.tsx +38 -38
- package/src/browser/wrapper-widgets/memory-layout-widget.tsx +167 -167
- package/src/common/util.ts +28 -28
- 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
|
+
}
|