Moving repo to bmci-labs

This commit is contained in:
Christian Weichel
2019-05-06 10:25:29 +02:00
commit 201351fea8
61 changed files with 16427 additions and 0 deletions

1
arduino-ide-extension/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
src/node/cli-protocol

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
{
"name": "arduino-ide-extension",
"version": "0.0.1",
"description": "An extension for Theia building the Arduino IDE",
"license": "MIT",
"dependencies": {
"@theia/core": "next",
"@theia/editor": "next",
"@theia/filesystem": "next",
"@theia/languages": "next",
"@theia/monaco": "next",
"@theia/workspace": "next"
},
"scripts": {
"generate-protoc": "./scripts/generate-protoc.sh",
"prepare": "yarn run clean && yarn generate-protoc && yarn run build",
"clean": "rimraf lib",
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
"build": "tsc && cp -rf src/node/cli-protocol lib/node && yarn lint",
"watch": "tsc -w"
},
"devDependencies": {
"@types/google-protobuf": "^3.2.7",
"grpc-tools": "^1.7.3",
"grpc_tools_node_protoc_ts": "^2.5.0",
"rimraf": "^2.6.1",
"tslint": "^5.5.0",
"typescript": "2.9.1"
},
"files": [
"lib",
"src",
"build",
"data"
],
"theiaExtensions": [
{
"backend": "lib/node/arduino-backend-module",
"frontend": "lib/browser/arduino-frontend-module"
}
]
}

View File

@@ -0,0 +1,45 @@
#!/bin/bash
SCRIPT=`realpath -s $0`
SCRIPTPATH=`dirname $SCRIPT`
WORKDIR=/tmp/arduino-cli-protoc
echo "Working in $WORKDIR"
# this could be a Git submodule, but that feels to clunky for just building the protobuf stuff
mkdir -p $WORKDIR
pushd $WORKDIR
if [ ! -d arduino-cli ]; then
git clone https://github.com/cmaglie/arduino-cli
cd arduino-cli
git checkout daemon
cd -
mkdir -p go/src/github.com/arduino
ln -s $PWD/arduino-cli go/src/github.com/arduino
export GOPATH=$PWD/go
cd go/src/github.com/arduino/arduino-cli
GOOS=linux go build -o arduino-cli.linux
# GOOS=darwin go build -o arduino-cli.darwin
fi
popd
# make sure the output path exists
mkdir -p src/node/cli-protocol
export PATH=$PATH:$PWD/node_modules/.bin
# generate js codes via grpc-tools
grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:./src/node/cli-protocol \
--grpc_out=./src/node/cli-protocol \
--plugin=protoc-gen-grpc=`which grpc_tools_node_protoc_plugin` \
-I /usr/lib/protoc/include \
-I $WORKDIR/arduino-cli/rpc \
$WORKDIR/arduino-cli/rpc/*.proto
# generate d.ts codes
protoc \
--plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
--ts_out=./src/node/cli-protocol \
-I /usr/lib/protoc/include \
-I $WORKDIR/arduino-cli/rpc \
$WORKDIR/arduino-cli/rpc/*.proto

View File

@@ -0,0 +1,13 @@
import { Command } from '@theia/core/lib/common/command';
export namespace ArduinoCommands {
export const VERIFY: Command = {
id: 'arduino-verify'
}
export const UPLOAD: Command = {
id: 'arduino-upload'
}
}

View File

@@ -0,0 +1,103 @@
import * as React from 'react';
import { injectable, inject, postConstruct } from 'inversify';
import URI from '@theia/core/lib/common/uri';
import { EditorWidget } from '@theia/editor/lib/browser/editor-widget';
import { MessageService } from '@theia/core/lib/common/message-service';
import { CommandContribution, CommandRegistry } from '@theia/core/lib/common/command';
import { DefaultFrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { TabBarToolbarContribution, TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { BoardsService } from '../common/protocol/boards-service';
import { ArduinoCommands } from './arduino-commands';
import { ConnectedBoards } from './components/connected-boards';
import { CoreService } from '../common/protocol/core-service';
import { WorkspaceServiceExt } from './workspace-service-ext';
@injectable()
export class ArduinoFrontendContribution extends DefaultFrontendApplicationContribution implements TabBarToolbarContribution, CommandContribution {
@inject(MessageService)
protected readonly messageService: MessageService;
@inject(BoardsService)
protected readonly boardService: BoardsService;
@inject(CoreService)
protected readonly coreService: CoreService;
@inject(WorkspaceServiceExt)
protected readonly workspaceServiceExt: WorkspaceServiceExt;
@postConstruct()
protected async init(): Promise<void> {
// This is a hack. Otherwise, the backend services won't bind.
await this.workspaceServiceExt.roots();
}
registerToolbarItems(registry: TabBarToolbarRegistry): void {
registry.registerItem({
id: ArduinoCommands.VERIFY.id,
command: ArduinoCommands.VERIFY.id,
tooltip: 'Verify',
group: 'arduino',
text: '$(check)'
});
registry.registerItem({
id: ArduinoCommands.UPLOAD.id,
command: ArduinoCommands.UPLOAD.id,
tooltip: 'Upload',
group: 'arduino',
text: '$(arrow-right)'
});
registry.registerItem({
id: ConnectedBoards.TOOLBAR_ID,
render: () => <ConnectedBoards boardsService={this.boardService}/>,
isVisible: widget => this.isArduinoEditor(widget)
})
}
registerCommands(registry: CommandRegistry): void {
registry.registerCommand(ArduinoCommands.VERIFY, {
isVisible: widget => this.isArduinoEditor(widget),
isEnabled: widget => this.isArduinoEditor(widget),
execute: async widget => {
const uri = this.toUri(widget);
if (uri) {
const result = await this.coreService.compile({ uri: uri.toString() });
console.log('compile result', result);
}
}
});
registry.registerCommand(ArduinoCommands.UPLOAD, {
isVisible: widget => this.isArduinoEditor(widget),
isEnabled: widget => this.isArduinoEditor(widget),
execute: widget => {
const uri = this.toUri(widget);
if (uri) {
this.messageService.info(`Uploading ${uri.toString()}`);
}
}
});
}
private isArduinoEditor(maybeEditorWidget: any): boolean {
if (maybeEditorWidget instanceof EditorWidget) {
return maybeEditorWidget.editor.uri.toString().endsWith('.ino');
}
return false;
}
private toUri(arg: any): URI | undefined {
if (arg instanceof URI) {
return arg;
}
if (typeof arg === 'string') {
return new URI(arg);
}
if (arg instanceof EditorWidget) {
return arg.editor.uri;
}
return undefined;
}
}

View File

@@ -0,0 +1,70 @@
import { ContainerModule, interfaces } from 'inversify';
import { WidgetFactory } from '@theia/core/lib/browser/widget-manager';
import { CommandContribution } from '@theia/core/lib/common/command';
import { bindViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { TabBarToolbarContribution } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/ws-connection-provider';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application'
import { LanguageGrammarDefinitionContribution } from '@theia/monaco/lib/browser/textmate';
import { LibraryListWidget } from './library/library-list-widget';
import { ArduinoFrontendContribution } from './arduino-frontend-contribution';
import { ArduinoLanguageGrammarContribution } from './language/arduino-language-grammar-contribution';
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
import { LibraryListWidgetFrontendContribution } from './library/list-widget-frontend-contribution';
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
import { BoardsListWidget } from './boards/boards-list-widget';
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
import { WorkspaceServiceExt, WorkspaceServiceExtPath } from './workspace-service-ext';
import { WorkspaceServiceExtImpl } from './workspace-service-ext-impl';
import '../../src/browser/style/index.css';
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
// Commands and toolbar items
bind(ArduinoFrontendContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(ArduinoFrontendContribution);
bind(TabBarToolbarContribution).toService(ArduinoFrontendContribution);
// `ino` TextMate grammar
bind(LanguageGrammarDefinitionContribution).to(ArduinoLanguageGrammarContribution).inSingletonScope();
// Library service
bind(LibraryService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, LibraryServicePath)).inSingletonScope();
// Library list widget
bind(LibraryListWidget).toSelf();
bindViewContribution(bind, LibraryListWidgetFrontendContribution);
bind(WidgetFactory).toDynamicValue(context => ({
id: LibraryListWidget.WIDGET_ID,
createWidget: () => context.container.get(LibraryListWidget)
}));
bind(FrontendApplicationContribution).toService(LibraryListWidgetFrontendContribution);
// Boards service
bind(BoardsService).toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, BoardsServicePath)).inSingletonScope();
// Boards list widget
bind(BoardsListWidget).toSelf();
bindViewContribution(bind, BoardsListWidgetFrontendContribution);
bind(WidgetFactory).toDynamicValue(context => ({
id: BoardsListWidget.WIDGET_ID,
createWidget: () => context.container.get(BoardsListWidget)
}));
bind(FrontendApplicationContribution).toService(BoardsListWidgetFrontendContribution);
// Core service
bind(CoreService)
.toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath))
.inSingletonScope();
// The workspace service extension
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => {
WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt);
// Eagerly active the core, library, and boards services.
container.get(CoreService);
container.get(LibraryService);
container.get(BoardsService);
return workspaceServiceExt;
});
});

View File

@@ -0,0 +1,16 @@
import { ListWidget } from './list-widget';
export class BoardsListWidget extends ListWidget {
static WIDGET_ID = 'boards-list-widget';
static WIDGET_LABEL = 'Boards Manager';
protected widgetProps(): ListWidget.Props {
return {
id: BoardsListWidget.WIDGET_ID,
title: BoardsListWidget.WIDGET_LABEL,
iconClass: 'fa fa-microchip'
}
}
}

View File

@@ -0,0 +1,32 @@
import { injectable } from 'inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { ListWidget } from './list-widget';
import { BoardsListWidget } from './boards-list-widget';
@injectable()
export abstract class ListWidgetFrontendContribution extends AbstractViewContribution<ListWidget> implements FrontendApplicationContribution {
async initializeLayout(): Promise<void> {
await this.openView();
}
}
@injectable()
export class BoardsListWidgetFrontendContribution extends ListWidgetFrontendContribution {
constructor() {
super({
widgetId: BoardsListWidget.WIDGET_ID,
widgetName: BoardsListWidget.WIDGET_LABEL,
defaultWidgetOptions: {
area: 'left',
rank: 600
},
toggleCommandId: `${BoardsListWidget.WIDGET_ID}:toggle`,
toggleKeybinding: 'ctrlcmd+shift+b'
});
}
}

View File

@@ -0,0 +1,72 @@
import * as React from 'react';
import { inject, injectable, postConstruct } from 'inversify';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
import { BoardsService } from '../../common/protocol/boards-service';
@injectable()
export abstract class ListWidget extends ReactWidget {
@inject(BoardsService)
protected readonly boardsService: BoardsService;
@inject(WindowService)
protected readonly windowService: WindowService;
constructor() {
super();
const { id, title, iconClass } = this.widgetProps();
this.id = id;
this.title.label = title;
this.title.caption = title;
this.title.iconClass = iconClass;
this.title.closable = true;
this.addClass(ListWidget.Styles.LIST_WIDGET_CLASS);
this.node.tabIndex = 0; // To be able to set the focus on the widget.
}
protected abstract widgetProps(): ListWidget.Props;
@postConstruct()
protected init(): void {
this.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.node.focus();
this.render();
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.render();
}
render(): React.ReactNode {
return <FilterableListContainer
service={this.boardsService}
windowService={this.windowService}
/>;
}
}
export namespace ListWidget {
/**
* Props for customizing the abstract list widget.
*/
export interface Props {
readonly id: string;
readonly title: string;
readonly iconClass: string;
}
export namespace Styles {
export const LIST_WIDGET_CLASS = 'arduino-list-widget'
}
}

View File

@@ -0,0 +1,73 @@
import * as React from 'react';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
export class ComponentListItem extends React.Component<ComponentListItem.Props> {
private onClick = (event: React.SyntheticEvent<HTMLAnchorElement, Event>) => {
const { target } = event.nativeEvent;
if (target instanceof HTMLAnchorElement) {
this.props.windowService.openNewWindow(target.href);
event.nativeEvent.preventDefault();
}
}
render(): React.ReactNode {
const { item } = this.props;
const style = ComponentListItem.Styles;
const name = <span className={style.NAME_CLASS}>{item.name}</span>;
const author = <span className={style.AUTHOR_CLASS}>{item.author}</span>;
const installedVersion = !!item.installedVersion && <React.Fragment>
<span className={style.VERSION_CLASS}>Version {item.installedVersion}</span>
<span className={style.INSTALLED_CLASS}>INSTALLED</span>
</React.Fragment>;
const summary = <div className={style.SUMMARY_CLASS}>{item.summary}</div>;
const description = !!item.description && <div className={style.DESCRIPTION_CLASS}>{item.description}</div>;
const moreInfo = !!item.moreInfoLink && <a href={item.moreInfoLink} onClick={this.onClick}>More info</a>;
const install = item.installable && !item.installedVersion && <button className={style.INSTALL_BTN_CLASS}>INSTALL</button>;
return <div className={[style.LIST_ITEM_CLASS, style.NO_SELECT_CLASS].join(' ')}>
<div className={style.HEADER_CLASS}>
<span>{name} by {author}</span>
{installedVersion}
</div>
<div className={style.CONTENT_CLASS}>
{summary}
{description}
</div>
<div className={style.FOOTER_CLASS}>
{moreInfo}
{install}
</div>
</div>;
}
}
export namespace ComponentListItem {
export interface Props {
readonly item: ArduinoComponent;
readonly windowService: WindowService;
}
export namespace Styles {
export const LIST_ITEM_CLASS = 'component-list-item';
export const HEADER_CLASS = 'header';
export const CONTENT_CLASS = 'content';
export const FOOTER_CLASS = 'footer';
export const INSTALLED_CLASS = 'installed';
export const NO_SELECT_CLASS = 'noselect';
export const NAME_CLASS = 'name';
export const AUTHOR_CLASS = 'author';
export const VERSION_CLASS = 'version';
export const SUMMARY_CLASS = 'summary';
export const DESCRIPTION_CLASS = 'description';
export const INSTALL_BTN_CLASS = 'install';
}
}

View File

@@ -0,0 +1,23 @@
import * as React from 'react';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { ComponentListItem } from './component-list-item';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
export class ComponentList extends React.Component<ComponentList.Props> {
render(): React.ReactNode {
return <div>
{this.props.items.map(item => <ComponentListItem key={item.name} item={item} windowService={this.props.windowService}/>)}
</div>;
}
}
export namespace ComponentList {
export interface Props {
readonly items: ArduinoComponent[];
readonly windowService: WindowService;
}
}

View File

@@ -0,0 +1,68 @@
import * as React from 'react';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { ComponentList } from './component-list';
import { SearchBar } from './search-bar';
import { ArduinoComponent } from '../../../common/protocol/arduino-component';
export class FilterableListContainer extends React.Component<FilterableListContainer.Props, FilterableListContainer.State> {
constructor(props: Readonly<FilterableListContainer.Props>) {
super(props);
this.state = {
filterText: '',
items: []
};
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
}
componentWillMount(): void {
this.handleFilterTextChange('');
}
render(): React.ReactNode {
return <div className={FilterableListContainer.Styles.FILTERABLE_LIST_CONTAINER_CLASS}>
<SearchBar
filterText={this.state.filterText}
onFilterTextChanged={this.handleFilterTextChange}
/>
<ComponentList
items={this.state.items}
windowService={this.props.windowService}
/>
</div>
}
private handleFilterTextChange(filterText: string): void {
this.props.service.search({ query: filterText }).then(result => {
const { items } = result;
this.setState({
filterText,
items
});
});
}
}
export namespace FilterableListContainer {
export interface Props {
readonly service: ComponentSource;
readonly windowService: WindowService;
}
export interface State {
filterText: string;
items: ArduinoComponent[];
}
export namespace Styles {
export const FILTERABLE_LIST_CONTAINER_CLASS = 'filterable-list-container';
}
export interface ComponentSource {
search(req: { query: string }): Promise<{ items: ArduinoComponent[] }>
}
}

View File

@@ -0,0 +1,39 @@
import * as React from 'react';
export class SearchBar extends React.Component<SearchBar.Props> {
constructor(props: Readonly<SearchBar.Props>) {
super(props);
this.handleFilterTextChange = this.handleFilterTextChange.bind(this);
}
render(): React.ReactNode {
return <form className={SearchBar.Styles.SEARCH_BAR_CLASS}>
<input
type='text'
placeholder='Search'
size={1}
value={this.props.filterText}
onChange={this.handleFilterTextChange}
/>
</form>;
}
private handleFilterTextChange(event: React.ChangeEvent<HTMLInputElement>): void {
this.props.onFilterTextChanged(event.target.value);
}
}
export namespace SearchBar {
export interface Props {
filterText: string;
onFilterTextChanged(filterText: string): void;
}
export namespace Styles {
export const SEARCH_BAR_CLASS = 'search-bar';
}
}

View File

@@ -0,0 +1,63 @@
import * as React from 'react';
// TODO: make this `async`.
// import { Async } from 'react-select/lib/Async';
import { BoardsService, Board } from '../../common/protocol/boards-service';
export class ConnectedBoards extends React.Component<ConnectedBoards.Props, ConnectedBoards.State> {
static TOOLBAR_ID: 'connected-boards-toolbar';
render(): React.ReactNode {
return <div className={ConnectedBoards.Styles.CONNECTED_BOARDS_CLASS}>
{this.select(this.state ? this.state.boards : undefined, this.state ? this.state.current : undefined)}
</div>;
}
componentDidMount(): void {
this.props.boardsService.connectedBoards().then(result => {
const { boards, current } = result;
this.setState({
boards,
current
})
});
}
private select(boards: Board[] | undefined, current: Board | undefined): React.ReactNode {
// Initial pessimistic.
const options = [<option>Loading...</option>];
if (boards) {
options.length = 0;
options.push(...boards.map(b => b.name).map(name => <option value={name} key={name}>{name}</option>));
}
const onChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const current = (boards || []).find(board => board.name === event.target.value);
this.setState({ current });
};
return <select
onChange={onChange}
value={current ? current.name : 'Loading...'}
name={current ? current.name : 'Loading...'}
>
{options}
</select>
}
}
export namespace ConnectedBoards {
export interface Props {
readonly boardsService: BoardsService;
}
export interface State {
boards?: Board[];
current?: Board;
}
export namespace Styles {
export const CONNECTED_BOARDS_CLASS = 'connected-boards';
}
}

View File

@@ -0,0 +1,63 @@
import { injectable } from 'inversify';
import { LanguageGrammarDefinitionContribution, TextmateRegistry } from '@theia/monaco/lib/browser/textmate';
@injectable()
export class ArduinoLanguageGrammarContribution implements LanguageGrammarDefinitionContribution {
static INO_LANGUAGE_ID = 'ino';
registerTextmateLanguage(registry: TextmateRegistry) {
monaco.languages.register({
id: ArduinoLanguageGrammarContribution.INO_LANGUAGE_ID,
extensions: ['.ino'],
aliases: ['INO', 'Ino', 'ino'],
});
monaco.languages.setLanguageConfiguration(ArduinoLanguageGrammarContribution.INO_LANGUAGE_ID, this.configuration);
const inoGrammar = require('../../../data/ino.tmLanguage.json');
registry.registerTextmateGrammarScope('source.ino', {
async getGrammarDefinition() {
return {
format: 'json',
content: inoGrammar
};
}
});
registry.mapLanguageIdToTextmateGrammar(ArduinoLanguageGrammarContribution.INO_LANGUAGE_ID, 'source.ino');
}
private readonly configuration: monaco.languages.LanguageConfiguration = {
comments: {
lineComment: '//',
blockComment: ['/*', '*/'],
},
brackets: [
['{', '}'],
['[', ']'],
['(', ')']
],
autoClosingPairs: [
{ open: '[', close: ']' },
{ open: '{', close: '}' },
{ open: '(', close: ')' },
{ open: '\'', close: '\'', notIn: ['string', 'comment'] },
{ open: '"', close: '"', notIn: ['string'] },
{ open: '/*', close: ' */', notIn: ['string'] }
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: '\'', close: '\'' },
],
folding: {
markers: {
start: new RegExp('^\\s*#pragma\\s+region\\b'),
end: new RegExp('^\\s*#pragma\\s+endregion\\b')
}
}
};
}

View File

@@ -0,0 +1,16 @@
import { ListWidget } from './list-widget';
export class LibraryListWidget extends ListWidget {
static WIDGET_ID = 'library-list-widget';
static WIDGET_LABEL = 'Library Manager';
protected widgetProps(): ListWidget.Props {
return {
id: LibraryListWidget.WIDGET_ID,
title: LibraryListWidget.WIDGET_LABEL,
iconClass: 'fa fa-book' // TODO: find a better icon
}
}
}

View File

@@ -0,0 +1,32 @@
import { injectable } from 'inversify';
import { FrontendApplicationContribution } from '@theia/core/lib/browser/frontend-application';
import { AbstractViewContribution } from '@theia/core/lib/browser/shell/view-contribution';
import { ListWidget } from './list-widget';
import { LibraryListWidget } from './library-list-widget';
@injectable()
export abstract class ListWidgetFrontendContribution extends AbstractViewContribution<ListWidget> implements FrontendApplicationContribution {
async initializeLayout(): Promise<void> {
await this.openView();
}
}
@injectable()
export class LibraryListWidgetFrontendContribution extends ListWidgetFrontendContribution {
constructor() {
super({
widgetId: LibraryListWidget.WIDGET_ID,
widgetName: LibraryListWidget.WIDGET_LABEL,
defaultWidgetOptions: {
area: 'left',
rank: 600
},
toggleCommandId: `${LibraryListWidget.WIDGET_ID}:toggle`,
toggleKeybinding: 'ctrlcmd+shift+l'
});
}
}

View File

@@ -0,0 +1,72 @@
import * as React from 'react';
import { inject, injectable, postConstruct } from 'inversify';
import { Message } from '@phosphor/messaging';
import { ReactWidget } from '@theia/core/lib/browser/widgets/react-widget';
import { WindowService } from '@theia/core/lib/browser/window/window-service';
import { FilterableListContainer } from '../components/component-list/filterable-list-container';
import { LibraryService } from '../../common/protocol/library-service';
@injectable()
export abstract class ListWidget extends ReactWidget {
@inject(LibraryService)
protected readonly libraryService: LibraryService;
@inject(WindowService)
protected readonly windowService: WindowService;
constructor() {
super();
const { id, title, iconClass } = this.widgetProps();
this.id = id;
this.title.label = title;
this.title.caption = title;
this.title.iconClass = iconClass;
this.title.closable = true;
this.addClass(ListWidget.Styles.LIST_WIDGET_CLASS);
this.node.tabIndex = 0; // To be able to set the focus on the widget.
}
protected abstract widgetProps(): ListWidget.Props;
@postConstruct()
protected init(): void {
this.update();
}
protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
this.node.focus();
this.render();
}
protected onUpdateRequest(msg: Message): void {
super.onUpdateRequest(msg);
this.render();
}
render(): React.ReactNode {
return <FilterableListContainer
service={this.libraryService}
windowService={this.windowService}
/>;
}
}
export namespace ListWidget {
/**
* Props for customizing the abstract list widget.
*/
export interface Props {
readonly id: string;
readonly title: string;
readonly iconClass: string;
}
export namespace Styles {
export const LIST_WIDGET_CLASS = 'arduino-list-widget'
}
}

View File

@@ -0,0 +1 @@
@import './list-widget.css';

View File

@@ -0,0 +1,76 @@
.arduino-list-widget {
color: var(--theia-ui-font-color1);
}
.arduino-list-widget .search-bar > input {
margin: 0px 5px 0px 5px;
width: 100%;
}
.component-list-item {
padding: 10px;
font-size: var(--theia-ui-font-size1);
}
.component-list-item:hover {
background: var(--theia-accent-color4);
cursor: pointer;
}
.component-list-item:hover .meta-info {
color: var(--theia-ui-font-color1);
}
.component-list-item .meta-info {
color: var(--theia-ui-font-color3);
}
.component-list-item .header {
padding-bottom: 2px;
display: flex;
}
.component-list-item .header .name {
font-weight: bold;
}
.component-list-item .header .author {
font-weight: bold;
color: var(--theia-ui-font-color2);
}
.component-list-item .header .version {
margin-left: auto;
justify-self: end;
color: var(--theia-ui-font-color2);
}
.component-list-item .header .installed {
margin-left: 4px;
justify-self: end;
background-color: var(--theia-accent-color2);
padding: 4px;
font-size: 10px;
font-weight: bold;
}
.component-list-item .footer {
display: flex;
}
.component-list-item .footer a {
color: var(--theia-brand-color1);
text-decoration: none;
font-weight: 600;
}
.component-list-item .footer .install {
margin-left: auto;
}
.component-list-item a:hover {
text-decoration: underline;
}
.component-list-item strong.installed {
color: rgb(0, 151, 157)
}

View File

@@ -0,0 +1,19 @@
import { inject, injectable } from 'inversify';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { WorkspaceServiceExt } from './workspace-service-ext';
/**
* This is a workaround to be able to inject the workspace service to the backend with its service path.
*/
@injectable()
export class WorkspaceServiceExtImpl implements WorkspaceServiceExt {
@inject(WorkspaceService)
protected readonly delegate: WorkspaceService;
async roots(): Promise<string[]> {
const stats = await this.delegate.roots;
return stats.map(stat => stat.uri);
}
}

View File

@@ -0,0 +1,5 @@
export const WorkspaceServiceExtPath = '/services/workspace-service-ext';
export const WorkspaceServiceExt = Symbol('WorkspaceServiceExt');
export interface WorkspaceServiceExt {
roots(): Promise<string[]>;
}

View File

@@ -0,0 +1,13 @@
export interface ArduinoComponent {
readonly name: string;
readonly author: string;
readonly summary: string;
readonly description: string;
readonly moreInfoLink?: string;
readonly availableVersions: string[];
readonly installable: boolean;
readonly installedVersion?: string;
}

View File

@@ -0,0 +1,11 @@
import { ArduinoComponent } from "./arduino-component";
export const BoardsServicePath = '/services/boards-service';
export const BoardsService = Symbol('BoardsService');
export interface BoardsService {
connectedBoards(): Promise<{ boards: Board[], current?: Board }>;
search(options: { query?: string }): Promise<{ items: Board[] }>;
}
export interface Board extends ArduinoComponent {
}

View File

@@ -0,0 +1,14 @@
export const CoreServicePath = '/services/core-service';
export const CoreService = Symbol('CoreService');
export interface CoreService {
compile(options: CoreService.Compile.Options): Promise<string>;
upload(): Promise<void>;
}
export namespace CoreService {
export namespace Compile {
export interface Options {
readonly uri: string;
}
}
}

View File

@@ -0,0 +1,16 @@
import { ArduinoComponent } from "./arduino-component";
export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
export interface LibraryService {
search(options: { query?: string }): Promise<{ items: Library[] }>;
}
export interface Library extends ArduinoComponent {
readonly builtIn?: boolean;
}
export namespace Library {
// TODO: figure out whether we need a dedicated `version` type.
export type Version = string;
}

View File

@@ -0,0 +1,66 @@
import { ContainerModule } from 'inversify';
import { ArduinoDaemon } from './arduino-daemon';
import { ILogger } from '@theia/core/lib/common/logger';
import { BackendApplicationContribution } from '@theia/core/lib/node/backend-application';
import { LibraryService, LibraryServicePath } from '../common/protocol/library-service';
import { BoardsService, BoardsServicePath } from '../common/protocol/boards-service';
import { LibraryServiceImpl } from './library-service-impl';
import { BoardsServiceImpl } from './boards-service-impl';
import { CoreServiceImpl } from './core-service-impl';
import { CoreService, CoreServicePath } from '../common/protocol/core-service';
import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connection-container-module';
import { WorkspaceServiceExtPath, WorkspaceServiceExt } from '../browser/workspace-service-ext';
import { CoreClientProviderImpl } from './core-client-provider-impl';
import { CoreClientProviderPath, CoreClientProvider } from './core-client-provider';
export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ArduinoDaemon).toSelf().inSingletonScope();
bind(BackendApplicationContribution).toService(ArduinoDaemon);
// Library service
const libraryServiceConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(LibraryServiceImpl).toSelf().inSingletonScope();
bind(LibraryService).toService(LibraryServiceImpl);
bindBackendService(LibraryServicePath, LibraryService);
});
bind(ConnectionContainerModule).toConstantValue(libraryServiceConnectionModule);
// Boards service
const boardsServiceConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(BoardsServiceImpl).toSelf().inSingletonScope();
bind(BoardsService).toService(BoardsServiceImpl);
bindBackendService(BoardsServicePath, BoardsService);
});
bind(ConnectionContainerModule).toConstantValue(boardsServiceConnectionModule);
// Arduino core client provider per Theia connection.
const coreClientProviderConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(CoreClientProviderImpl).toSelf().inSingletonScope();
bind(CoreClientProvider).toService(CoreClientProviderImpl);
bindBackendService(CoreClientProviderPath, CoreClientProvider);
});
bind(ConnectionContainerModule).toConstantValue(coreClientProviderConnectionModule);
// Core service -> `verify` and `upload`. One per Theia connection.
const connectionConnectionModule = ConnectionContainerModule.create(({ bind, bindBackendService }) => {
bind(CoreServiceImpl).toSelf().inSingletonScope();
bind(CoreService).toService(CoreServiceImpl);
bindBackendService(BoardsServicePath, BoardsService);
bindBackendService(CoreClientProviderPath, CoreClientProvider);
bindBackendService(CoreServicePath, CoreService);
});
bind(ConnectionContainerModule).toConstantValue(connectionConnectionModule);
// Bind the workspace service extension to the backend per Theia connection.
// So that we can access the workspace roots of the frontend.
const workspaceServiceExtConnectionModule = ConnectionContainerModule.create(({ bindFrontendService }) => {
bindFrontendService(WorkspaceServiceExtPath, WorkspaceServiceExt);
});
bind(ConnectionContainerModule).toConstantValue(workspaceServiceExtConnectionModule);
// Logger for the Arduino daemon
bind(ILogger).toDynamicValue(ctx => {
const parentLogger = ctx.container.get<ILogger>(ILogger);
return parentLogger.child('daemon');
}).inSingletonScope().whenTargetNamed('daemon');
});

View File

@@ -0,0 +1,46 @@
import * as os from 'os';
import { exec } from 'child_process';
import { join, resolve } from 'path';
import { inject, injectable, named } from 'inversify';
import { ILogger } from '@theia/core/lib/common/logger';
import { BackendApplicationContribution } from '@theia/core/lib/node';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { DaemonLog } from './daemon-log';
const EXECUTABLE_PATH = resolve(join(__dirname, '..', '..', 'build', `arduino-cli.${os.platform()}`))
@injectable()
export class ArduinoDaemon implements BackendApplicationContribution {
@inject(ILogger)
@named('daemon')
protected readonly logger: ILogger
protected isReady = new Deferred<boolean>();
async onStart() {
try {
const daemon = exec(`${EXECUTABLE_PATH} --debug daemon`, (err, stdout, stderr) => {
if (err || stderr) {
console.log(err || new Error(stderr));
return;
}
console.log(stdout);
});
if (daemon.stdout) {
daemon.stdout.on('data', data => DaemonLog.log(this.logger, data.toString()));
}
if (daemon.stderr) {
daemon.stderr.on('data', data => DaemonLog.log(this.logger, data.toString()));
}
if (daemon.stderr) {
daemon.on('exit', (code, signal) => DaemonLog.log(this.logger, `Daemon exited with code: ${code}. Signal was: ${signal}.`));
}
await new Promise((resolve, reject) => setTimeout(resolve, 2000));
this.isReady.resolve();
} catch (error) {
this.isReady.reject(error || new Error('failed to start arduino-cli'));
}
}
}

View File

@@ -0,0 +1,37 @@
import { injectable, inject } from 'inversify';
import { BoardsService, Board } from '../common/protocol/boards-service';
import { PlatformSearchReq, PlatformSearchResp } from './cli-protocol/core_pb';
import { CoreClientProvider } from './core-client-provider';
@injectable()
export class BoardsServiceImpl implements BoardsService {
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
async connectedBoards(): Promise<{ boards: Board[], current?: Board }> {
return { boards: [] };
}
async search(options: { query?: string }): Promise<{ items: Board[] }> {
let items: Board[] = [];
const { client, instance } = await this.coreClientProvider.getClient();
const req = new PlatformSearchReq();
req.setSearchArgs(options.query || "");
req.setInstance(instance);
const resp = await new Promise<PlatformSearchResp>((resolve, reject) => client.platformSearch(req, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
items = resp.getSearchOutputList().map(o => <Board>{
name: o.getName(),
author: "Someone",
availableVersions: [],
description: "lorem ipsum sit dolor amet",
installable: false,
summary: "has none"
});
return { items };
}
}

View File

@@ -0,0 +1,91 @@
import { inject, injectable } from 'inversify';
import * as grpc from 'grpc';
import { ArduinoCoreClient } from './cli-protocol/commands_grpc_pb';
import { InitResp, InitReq, Configuration, UpdateIndexReq, UpdateIndexResp } from './cli-protocol/commands_pb';
import { WorkspaceServiceExt } from '../browser/workspace-service-ext';
import { FileSystem } from '@theia/filesystem/lib/common';
import URI from '@theia/core/lib/common/uri';
import { CoreClientProvider, Client } from './core-client-provider';
@injectable()
export class CoreClientProviderImpl implements CoreClientProvider {
@inject(FileSystem)
protected readonly fileSystem: FileSystem;
@inject(WorkspaceServiceExt)
protected readonly workspaceServiceExt: WorkspaceServiceExt;
protected clients = new Map<string, Client>();
async getClient(workspaceRootOrResourceUri?: string): Promise<Client> {
const roots = await this.workspaceServiceExt.roots();
if (!workspaceRootOrResourceUri) {
return this.getOrCreateClient(roots[0]);
}
const root = roots
.sort((left, right) => right.length - left.length) // Longest "paths" first
.map(uri => new URI(uri))
.find(uri => uri.isEqualOrParent(new URI(workspaceRootOrResourceUri)));
if (!root) {
console.warn(`Could not retrieve the container workspace root for URI: ${workspaceRootOrResourceUri}.`);
console.warn(`Falling back to ${roots[0]}`);
return this.getOrCreateClient(roots[0]);
}
return this.getOrCreateClient(root.toString());
}
protected async getOrCreateClient(rootUri: string): Promise<Client> {
const existing = this.clients.get(rootUri);
if (existing) {
console.debug(`Reusing existing client for ${rootUri}.`);
return existing;
}
console.info(` >>> Creating and caching a new client for ${rootUri}...`);
const client = new ArduinoCoreClient('localhost:50051', grpc.credentials.createInsecure());
const config = new Configuration();
const path = await this.fileSystem.getFsPath(rootUri);
if (!path) {
throw new Error(`Could not resolve file-system path of URI: ${rootUri}.`);
}
config.setSketchbookdir(path);
config.setDatadir(path);
config.setDownloadsdir(path);
const initReq = new InitReq();
initReq.setConfiguration(config);
const initResp = await new Promise<InitResp>((resolve, reject) => client.init(initReq, (err, resp) => (!!err ? reject : resolve)(!!err ? err : resp)));
const instance = initResp.getInstance();
if (!instance) {
throw new Error(`Could not retrieve instance from the initialize response.`);
}
const updateReq = new UpdateIndexReq();
updateReq.setInstance(instance);
const updateResp = client.updateIndex(updateReq);
updateResp.on('data', (o: UpdateIndexResp) => {
const progress = o.getDownloadProgress();
if (progress) {
if (progress.getCompleted()) {
console.log(`Download${progress.getFile() ? ` of ${progress.getFile()}` : ''} completed.`);
} else {
console.log(`Downloading${progress.getFile() ? ` ${progress.getFile()}:` : ''} ${progress.getDownloaded()}.`);
}
}
});
// TODO: revisit this!!!
// `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly.
// await new Promise<void>((resolve, reject) => {
// updateResp.on('close', resolve);
// updateResp.on('error', reject);
// });
const result = {
client,
instance
}
this.clients.set(rootUri, result);
console.info(` <<< New client has been successfully created and cached for ${rootUri}.`);
return result;
}
}

View File

@@ -0,0 +1,13 @@
import { Instance } from './cli-protocol/common_pb';
import { ArduinoCoreClient } from './cli-protocol/commands_grpc_pb';
export const CoreClientProviderPath = '/services/core-client-provider';
export const CoreClientProvider = Symbol('CoreClientProvider');
export interface CoreClientProvider {
getClient(workspaceRootOrResourceUri?: string): Promise<Client>;
}
export interface Client {
readonly client: ArduinoCoreClient;
readonly instance: Instance;
}

View File

@@ -0,0 +1,84 @@
import { inject, injectable } from 'inversify';
import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
import { CoreService } from '../common/protocol/core-service';
import { CompileReq } from './cli-protocol/compile_pb';
import { BoardsService } from '../common/protocol/boards-service';
import { CoreClientProvider } from './core-client-provider';
import { PlatformInstallReq } from './cli-protocol/core_pb';
import { LibraryInstallReq } from './cli-protocol/lib_pb';
@injectable()
export class CoreServiceImpl implements CoreService {
@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;
@inject(FileSystem)
protected readonly fileSystem: FileSystem;
@inject(BoardsService)
protected readonly boardsService: BoardsService;
async compile(options: CoreService.Compile.Options): Promise<string> {
console.log('compile', options);
const { uri } = options;
const sketchpath = await this.fileSystem.getFsPath(options.uri);
if (!sketchpath) {
throw new Error(`Cannot resolve FS path for URI: ${uri}.`);
}
const { client, instance } = await this.coreClientProvider.getClient(uri);
// const boards = await this.boardsService.connectedBoards();
// if (!boards.current) {
// throw new Error(`No selected board. The connected boards were: ${boards.boards}.`);
// }
// https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88
const installPlatformReq = new PlatformInstallReq();
installPlatformReq.setArchitecture('samd');
installPlatformReq.setVersion('1.6.0');
installPlatformReq.setInstance(instance);
const resp = client.platformInstall(installPlatformReq);
console.log(resp);
const installLibReq = new LibraryInstallReq();
installLibReq.setInstance(instance);
installLibReq.setName('arduino:samd');
const installResp = client.libraryInstall(installLibReq);
const xxx = await new Promise<string>((resolve, reject) => {
const chunks: Buffer[] = [];
installResp.on('data', (chunk: Buffer) => chunks.push(chunk));
installResp.on('error', error => reject(error));
installResp.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim()))
});
console.log('xxx', xxx);
const compilerReq = new CompileReq();
compilerReq.setInstance(instance);
compilerReq.setSketchpath(sketchpath);
compilerReq.setFqbn('arduino:samd'/*boards.current.name*/);
// request.setShowproperties(false);
// request.setPreprocess(false);
// request.setBuildcachepath('');
// request.setBuildpath('');
// request.setBuildpropertiesList([]);
// request.setWarnings('none');
// request.setVerbose(true);
// request.setQuiet(false);
// request.setVidpid('');
// request.setExportfile('');
const result = client.compile(compilerReq);
return new Promise<string>((resolve, reject) => {
const chunks: Buffer[] = [];
result.on('data', (chunk: Buffer) => chunks.push(chunk));
result.on('error', error => reject(error));
result.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim()))
});
}
upload(): Promise<void> {
throw new Error("Method not implemented.");
}
}

View File

@@ -0,0 +1,67 @@
import { ILogger, LogLevel } from '@theia/core/lib/common/logger';
export interface DaemonLog {
readonly time: string;
readonly level: DaemonLog.Level;
readonly msg: string;
}
export namespace DaemonLog {
export type Level = 'info' | 'debug' | 'warning' | 'error';
export function is(arg: any | undefined): arg is DaemonLog {
return !!arg
&& typeof arg.time === 'string'
&& typeof arg.level === 'string'
&& typeof arg.msg === 'string'
}
export function toLogLevel(log: DaemonLog): LogLevel {
const { level } = log;
switch (level) {
case 'info': return LogLevel.INFO;
case 'debug': return LogLevel.DEBUG;
case 'error': return LogLevel.ERROR;
case 'warning': return LogLevel.WARN;
default: return LogLevel.INFO;
}
}
export function log(logger: ILogger, toLog: string): void {
const segments = toLog.split('time').filter(s => s.trim().length > 0);
for (const segment of segments) {
const maybeDaemonLog = parse(`time${segment}`.trim());
if (is(maybeDaemonLog)) {
const { msg } = maybeDaemonLog;
logger.log(toLogLevel(maybeDaemonLog), msg);
} else {
logger.info(toLog.trim());
}
}
}
// Super naive.
function parse(toLog: string): string | DaemonLog {
const rawSegments = toLog.split(/(\s+)/)
.map(segment => segment.replace(/['"]+/g, ''))
.map(segment => segment.trim())
.filter(segment => segment.length > 0);
const timeIndex = rawSegments.findIndex(segment => segment.startsWith('time='));
const levelIndex = rawSegments.findIndex(segment => segment.startsWith('level='));
const msgIndex = rawSegments.findIndex(segment => segment.startsWith('msg='));
if (rawSegments.length > 2
&& timeIndex !== -1
&& levelIndex !== -1
&& msgIndex !== -1) {
return {
time: rawSegments[timeIndex].split('=')[1],
level: rawSegments[levelIndex].split('=')[1] as Level,
msg: [rawSegments[msgIndex].split('=')[1], ...rawSegments.slice(msgIndex + 1)].join(' ')
}
}
// Otherwise, log the string as is.
return toLog;
}
}

View File

@@ -0,0 +1,46 @@
import { injectable } from 'inversify';
import { Library, LibraryService } from '../common/protocol/library-service';
@injectable()
export class LibraryServiceImpl implements LibraryService {
async search(options: { query?: string; }): Promise<{ items: Library[] }> {
const { query } = options;
const allItems: Library[] = [
<Library>{
name: 'Keyboard',
availableVersions: ['1.0.0', '1.0.1', '1.02'],
author: 'Arduino',
summary: 'Allows an Arduino/Genuino board with USB capabilities to act as a Keyboard',
description: 'This library plugs on the HID library. It can be used with or without other HIG-based libraries (Mouse, Gamepad etc)',
installedVersion: '1.0.1',
moreInfoLink: 'https://www.arduino.cc/reference/en/language/functions/usb/keyboard/',
builtIn: true
},
<Library>{
name: 'Mouse',
availableVersions: ['1.0.0', '1.0.1'],
author: 'Arduino',
summary: 'Allows an Arduino board with USB capabilities to act as a Mouse. For Leonardo/Micro only',
description: 'This library plugs on the HID library. Can be used with ot without other HID-based libraries (Keyboard, Gamepad etc)',
installedVersion: '1.0.1',
moreInfoLink: 'https://www.arduino.cc/reference/en/language/functions/usb/mouse/',
builtIn: true
},
<Library>{
name: 'USBHost',
availableVersions: ['1.0.0', '1.0.1', '1.02', '1.0.3', '1.0.3', '1.0.4', '1.0.5'],
author: 'Arduino',
summary: 'Allows communication with USB peripherals like mice, keyboard, and thumbdrives.',
// tslint:disable-next-line:max-line-length
description: 'This USBHost library allows an Arduino Due board to appear as a USB host, enabling it to communicate with peripherals like USB mice and keyboards. USBHost does not support devices that ace corrected through USB hubs. This includes some keyboards that have an internal hub.',
moreInfoLink: 'https://www.arduino.cc/en/Reference/USBHost',
installable: true
}
];
return {
items: allItems.filter(item => !query || item.name.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) !== -1)
};
}
}

View File

@@ -0,0 +1,16 @@
import * as grpc from "grpc";
import * as jspb from "google-protobuf";
export type GrpcMethod<Req, Resp> = (request: Req, callback: (error: grpc.ServiceError | null, response: Resp) => void) => grpc.ClientUnaryCall
export function promisify<M extends GrpcMethod<Req, Resp>, Req, Resp extends jspb.Message>(m: M, req: Req): Promise<Resp> {
return new Promise<Resp>((resolve, reject) => {
m(req, (err, resp) => {
if (!!err) {
reject(err);
} else {
resolve(resp);
}
});
});
}

View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"noImplicitAny": true,
"noEmitOnError": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es5",
"outDir": "lib",
"lib": [
"es6",
"dom"
],
"jsx": "react",
"sourceMap": true,
"skipLibCheck": true
},
"include": [
"src"
],
"files": [
"../node_modules/@theia/monaco/src/typings/monaco/index.d.ts"
]
}

View File

@@ -0,0 +1,38 @@
{
"rules": {
"class-name": true,
"comment-format": [true, "check-space"],
"curly": false,
"forin": false,
"indent": [true, "spaces"],
"max-line-length": [true, 180],
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"one-line": [true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"radix": true,
"trailing-comma": [false],
"triple-equals": [true, "allow-null-check"],
"typedef-whitespace": [true, {
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}],
"variable-name": false,
"whitespace": [true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}