Skip to content

Commit ed66479

Browse files
authored
feat: decoder for esp32
Multi address decoder for esp32 boards with emphasised data output
1 parent d68bc4a commit ed66479

File tree

5 files changed

+571
-0
lines changed

5 files changed

+571
-0
lines changed

Diff for: arduino-ide-extension/src/browser/arduino-ide-frontend-module.ts

+17
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ import {
7272
ConfigServicePath,
7373
} from '../common/protocol/config-service';
7474
import { MonitorWidget } from './serial/monitor/monitor-widget';
75+
import { DecodeWidget } from './serial/decode/decode-widget';
7576
import { MonitorViewContribution } from './serial/monitor/monitor-view-contribution';
7677
import { TabBarDecoratorService as TheiaTabBarDecoratorService } from '@theia/core/lib/browser/shell/tab-bar-decorator';
7778
import { TabBarDecoratorService } from './theia/core/tab-bar-decorator';
@@ -321,6 +322,7 @@ import { InterfaceScale } from './contributions/interface-scale';
321322
import { OpenHandler } from '@theia/core/lib/browser/opener-service';
322323
import { NewCloudSketch } from './contributions/new-cloud-sketch';
323324
import { SketchbookCompositeWidget } from './widgets/sketchbook/sketchbook-composite-widget';
325+
import { DecodeViewContribution } from './serial/decode/decode-view';
324326
import { WindowTitleUpdater } from './theia/core/window-title-updater';
325327
import { WindowTitleUpdater as TheiaWindowTitleUpdater } from '@theia/core/lib/browser/window/window-title-updater';
326328
import { ThemeServiceWithDB } from './theia/core/theming';
@@ -477,6 +479,21 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
477479
.inSingletonScope();
478480
bind(CoreErrorHandler).toSelf().inSingletonScope();
479481

482+
// Decode box
483+
bind(DecodeWidget).toSelf();
484+
bindViewContribution(bind, DecodeViewContribution);
485+
bind(TabBarToolbarContribution).toService(DecodeViewContribution);
486+
bind(WidgetFactory).toDynamicValue((context) => ({
487+
id: DecodeWidget.ID,
488+
createWidget: () => {
489+
return new DecodeWidget(
490+
context.container.get<ConfigService>(ConfigService),
491+
context.container.get<BoardsServiceProvider>(BoardsServiceProvider),
492+
context.container.get<SketchesServiceClientImpl>(SketchesServiceClientImpl)
493+
);
494+
},
495+
}));
496+
480497
// Serial monitor
481498
bind(MonitorWidget).toSelf();
482499
bind(FrontendApplicationContribution).toService(MonitorModel);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import * as React from '@theia/core/shared/react';
2+
import { Event } from '@theia/core/lib/common/event';
3+
import { DisposableCollection } from '@theia/core/lib/common/disposable';
4+
import { spawnCommand } from '../../../node/exec-util';
5+
6+
export type Line = { message: string; lineLen: number };
7+
export type Element = {
8+
address: string;
9+
function: string;
10+
path: {
11+
value: string;
12+
isLink: boolean;
13+
}
14+
lineNumber: string
15+
};
16+
17+
export class DecodeOutput extends React.Component<
18+
DecodeOutput.Props,
19+
DecodeOutput.State
20+
> {
21+
/**
22+
* Do not touch it. It is used to be able to "follow" the serial monitor log.
23+
*/
24+
protected toDisposeBeforeUnmount = new DisposableCollection();
25+
26+
constructor(props: Readonly<DecodeOutput.Props>) {
27+
super(props);
28+
this.state = {
29+
elements: [],
30+
};
31+
}
32+
33+
isClientPath = async (path:string): Promise<boolean> => {
34+
return await spawnCommand("cd", [
35+
path
36+
], (err) => err)
37+
.then((data) => true)
38+
.catch(err => false)
39+
}
40+
41+
openFinder = async (path:string) => {
42+
await spawnCommand("open", [
43+
path
44+
]);
45+
}
46+
47+
retrievePath = (dirPath:string) => {
48+
return dirPath.substring(0,dirPath.lastIndexOf("/")+1);
49+
}
50+
51+
decodeText = async (value: string) => {
52+
const lines = value.split("\n");
53+
54+
// Remove the extra newline at the end
55+
lines.pop();
56+
const elements : Array<Element> = [];
57+
for(let i=0;i<lines.length;i++) {
58+
let line = lines[i].split(/(?!\(.*)\s(?![^(]*?\))/g);
59+
if(line[0] === "") {
60+
line.shift();
61+
}
62+
let pathSplit = line[3].split(":");
63+
let obj: Element = {
64+
address: line[0],
65+
function: line[1],
66+
path: {
67+
value: pathSplit[0],
68+
isLink: false,
69+
},
70+
lineNumber: pathSplit[1]
71+
};
72+
if(await this.isClientPath(this.retrievePath(pathSplit[0]))) {
73+
obj = {
74+
address: line[0],
75+
function: line[1],
76+
path: {
77+
value: pathSplit[0],
78+
isLink: true,
79+
},
80+
lineNumber: pathSplit[1]
81+
};
82+
}
83+
elements.push(obj);
84+
}
85+
this.setState({ elements });
86+
};
87+
88+
override render(): React.ReactNode {
89+
return (
90+
<div style={{ whiteSpace: 'pre-wrap' }}>
91+
{this.state.elements.map((element) => (
92+
<div style={{display: "inline-block"}}>
93+
<span style={{color: "green"}}>{element.address} </span>
94+
<span style={{color: "blue", fontWeight: "bold"}}>{element.function} </span>
95+
at
96+
{ element.path.isLink ? <a><span onClick={async () => await this.openFinder(this.retrievePath(element.path.value))}>{element.path.value}</span></a> : <span> {element.path.value} </span> }
97+
line
98+
<span style={{fontWeight: "bold"}}> {element.lineNumber}</span>
99+
</div>
100+
))}
101+
</div>
102+
);
103+
}
104+
105+
override shouldComponentUpdate(): boolean {
106+
return true;
107+
}
108+
109+
override componentDidMount(): void {
110+
this.toDisposeBeforeUnmount.pushAll([
111+
this.props.clearConsoleEvent(() =>
112+
this.setState({ elements: [] })
113+
),
114+
]);
115+
}
116+
117+
override componentWillUnmount(): void {
118+
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
119+
this.toDisposeBeforeUnmount.dispose();
120+
}
121+
}
122+
123+
export namespace DecodeOutput {
124+
export interface Props {
125+
readonly clearConsoleEvent: Event<void>;
126+
readonly height: number;
127+
}
128+
129+
export interface State {
130+
elements: Element[];
131+
}
132+
133+
export interface SelectOption<T> {
134+
readonly label: string;
135+
readonly value: T;
136+
}
137+
138+
export const MAX_CHARACTERS = 1_000_000;
139+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import * as React from '@theia/core/shared/react';
2+
import { Key, KeyCode } from '@theia/core/lib/browser/keys';
3+
import { DisposableCollection } from '@theia/core/lib/common';
4+
5+
class HistoryList {
6+
private readonly items: string[] = [];
7+
private index = -1;
8+
9+
constructor(private readonly size = 100) {}
10+
11+
push(val: string): void {
12+
if (val !== this.items[this.items.length - 1]) {
13+
this.items.push(val);
14+
}
15+
while (this.items.length > this.size) {
16+
this.items.shift();
17+
}
18+
this.index = -1;
19+
}
20+
21+
previous(): string {
22+
if (this.index === -1) {
23+
this.index = this.items.length - 1;
24+
return this.items[this.index];
25+
}
26+
if (this.hasPrevious) {
27+
return this.items[--this.index];
28+
}
29+
return this.items[this.index];
30+
}
31+
32+
private get hasPrevious(): boolean {
33+
return this.index >= 1;
34+
}
35+
36+
next(): string {
37+
if (this.index === this.items.length - 1) {
38+
this.index = -1;
39+
return '';
40+
}
41+
if (this.hasNext) {
42+
return this.items[++this.index];
43+
}
44+
return '';
45+
}
46+
47+
private get hasNext(): boolean {
48+
return this.index >= 0 && this.index !== this.items.length - 1;
49+
}
50+
}
51+
52+
export namespace DecodeSendInput {
53+
export interface Props {
54+
readonly onSend: (text: string) => void;
55+
readonly resolveFocus: (element: HTMLElement | undefined) => void;
56+
}
57+
export interface State {
58+
text: string;
59+
history: HistoryList;
60+
}
61+
}
62+
63+
export class DecodeSendInput extends React.Component<
64+
DecodeSendInput.Props,
65+
DecodeSendInput.State
66+
> {
67+
protected toDisposeBeforeUnmount = new DisposableCollection();
68+
69+
constructor(props: Readonly<DecodeSendInput.Props>) {
70+
super(props);
71+
this.state = { text: '', history: new HistoryList() };
72+
this.onChange = this.onChange.bind(this);
73+
this.onSend = this.onSend.bind(this);
74+
this.onKeyDown = this.onKeyDown.bind(this);
75+
}
76+
77+
override componentWillUnmount(): void {
78+
// TODO: "Your preferred browser's local storage is almost full." Discard `content` before saving layout?
79+
this.toDisposeBeforeUnmount.dispose();
80+
}
81+
82+
override render(): React.ReactNode {
83+
return (
84+
<input
85+
ref={this.setRef}
86+
type="text"
87+
className='theia-input'
88+
placeholder={this.placeholder}
89+
value={this.state.text}
90+
onChange={this.onChange}
91+
onKeyDown={this.onKeyDown}
92+
/>
93+
);
94+
}
95+
96+
protected get placeholder(): string {
97+
98+
return 'Enter backtrace text to decode. (Ex: Backtrace: 0x40086e7c:0x3ffb4ff0...)';
99+
}
100+
101+
protected setRef = (element: HTMLElement | null): void => {
102+
if (this.props.resolveFocus) {
103+
this.props.resolveFocus(element || undefined);
104+
}
105+
};
106+
107+
protected onChange(event: React.ChangeEvent<HTMLInputElement>): void {
108+
this.setState({ text: event.target.value });
109+
}
110+
111+
protected onSend(): void {
112+
this.props.onSend(this.state.text);
113+
this.setState({ text: '' });
114+
}
115+
116+
protected onKeyDown(event: React.KeyboardEvent<HTMLInputElement>): void {
117+
const keyCode = KeyCode.createKeyCode(event.nativeEvent);
118+
if (keyCode) {
119+
const { key } = keyCode;
120+
if (key === Key.ENTER) {
121+
const { text } = this.state;
122+
this.onSend();
123+
if (text) {
124+
this.state.history.push(text);
125+
}
126+
} else if (key === Key.ARROW_UP) {
127+
this.setState({ text: this.state.history.previous() });
128+
} else if (key === Key.ARROW_DOWN) {
129+
this.setState({ text: this.state.history.next() });
130+
} else if (key === Key.ESCAPE) {
131+
this.setState({ text: '' });
132+
}
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)