├── tslint.json ├── .prettierrc ├── .gitignore ├── .gitmodules ├── src ├── node.d.ts ├── atom.d.ts ├── util.ts ├── views │ ├── editor.tsx │ ├── button.tsx │ └── ide-haskell-repl-view.tsx ├── command-history.ts ├── ide-haskell-repl-bg.ts ├── setup-ghci-wrapper.ts ├── config.ts ├── ghci.ts ├── ide-haskell-repl.ts ├── upiConsumer.ts ├── interactive-process.ts └── ide-haskell-repl-base.ts ├── spec ├── tsconfig.json ├── tslint.json ├── package.spec.ts └── interactive-process.spec.ts ├── keymaps └── ide-haskell-repl.cson ├── README.md ├── coffeelint.json ├── LICENSE.md ├── .travis.yml ├── tsconfig.json ├── lib ├── views │ ├── editor.js │ └── button.js ├── util.js ├── command-history.js ├── config.js ├── ide-haskell-repl-bg.js ├── setup-ghci-wrapper.js ├── ghci.js ├── ide-haskell-repl.js ├── upiConsumer.js └── interactive-process.js ├── package.json ├── styles └── ide-haskell-repl.less └── CHANGELOG.md /tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "atom-haskell-tslint-rules" } 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | semi: false 2 | singleQuote: true 3 | trailingComma: all 4 | arrowParens: always 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | lib/**/*.d.ts 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "typings"] 2 | path = typings 3 | url = git@github.com:atom-haskell/typings.git 4 | -------------------------------------------------------------------------------- /src/node.d.ts: -------------------------------------------------------------------------------- 1 | namespace NodeJS { 2 | interface Process { 3 | activateUvLoop(): void 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "extends": "../tsconfig.json", 4 | "include": ["./**.ts"], 5 | "exclude": [] 6 | } 7 | -------------------------------------------------------------------------------- /spec/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "no-unused-expression": false, 5 | "no-non-null-assertion": false, 6 | "totality-check": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/atom.d.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | declare module 'atom' { 3 | type TWatchEditor = (editor: TextEditor, labels: string[]) => Disposable 4 | interface CommandRegistryTargetMap { 5 | 'atom-text-editor:not(.ide-haskell-repl)': TextEditorElement 6 | 'atom-text-editor.ide-haskell-repl': TextEditorElement 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import * as UPI from 'atom-haskell-upi' 2 | 3 | export function handlePromise(somePromise: Promise): void { 4 | somePromise.catch((e: Error) => { 5 | atom.notifications.addFatalError(e.name, { 6 | detail: e.message, 7 | stack: e.stack, 8 | }) 9 | }) 10 | } 11 | 12 | export function getText(m: UPI.TMessage): string { 13 | if (typeof m === 'string') { 14 | return m 15 | } else { 16 | if ('text' in m) return m.text 17 | else return m.html 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/views/editor.tsx: -------------------------------------------------------------------------------- 1 | export interface IProps extends JSX.Props { 2 | element: HTMLElement 3 | } 4 | 5 | // tslint:disable-next-line:no-unsafe-any 6 | export class Editor implements JSX.ElementClass { 7 | public element: HTMLElement 8 | constructor(public props: IProps) { 9 | this.element = props.element 10 | this.element.classList.add('ide-haskell-repl') 11 | } 12 | 13 | public async update(props: IProps) { 14 | this.element = props.element 15 | this.element.classList.add('ide-haskell-repl') 16 | return Promise.resolve() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /spec/package.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { join } from 'path' 3 | 4 | const pkg = join(__dirname, '..') 5 | 6 | describe('package', function() { 7 | this.timeout(60000) 8 | it('should activate', async () => { 9 | const packages: any = atom.packages 10 | 11 | // Load package, but it won't activate until the grammar is used 12 | const promise = atom.packages.activatePackage(pkg) 13 | 14 | packages.triggerActivationHook('language-haskell:grammar-used') 15 | packages.triggerDeferredActivationHooks() 16 | 17 | await promise 18 | 19 | expect(atom.packages.isPackageActive('ide-haskell-repl')).to.be.true 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /keymaps/ide-haskell-repl.cson: -------------------------------------------------------------------------------- 1 | '.platform-linux atom-text-editor.ide-haskell-repl': 2 | 'shift-enter': 'ide-haskell-repl:exec-command' 3 | 'shift-up': 'ide-haskell-repl:history-back' 4 | 'shift-down': 'ide-haskell-repl:history-forward' 5 | 'ctrl-shift-r': 'ide-haskell-repl:ghci-reload' 6 | 'ctrl-shift-c': 'ide-haskell-repl:ghci-interrupt' 7 | 8 | '.platform-win32 atom-text-editor.ide-haskell-repl': 9 | 'shift-enter': 'ide-haskell-repl:exec-command' 10 | 'shift-up': 'ide-haskell-repl:history-back' 11 | 'shift-down': 'ide-haskell-repl:history-forward' 12 | 'ctrl-shift-r': 'ide-haskell-repl:ghci-reload' 13 | 'ctrl-shift-c': 'ide-haskell-repl:ghci-interrupt' 14 | 15 | '.platform-darwin atom-text-editor.ide-haskell-repl': 16 | 'cmd-enter': 'ide-haskell-repl:exec-command' 17 | 'shift-up': 'ide-haskell-repl:history-back' 18 | 'shift-down': 'ide-haskell-repl:history-forward' 19 | 'ctrl-shift-r': 'ide-haskell-repl:ghci-reload' 20 | 'ctrl-shift-c': 'ide-haskell-repl:ghci-interrupt' 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDE-Haskell REPL ![](https://david-dm.org/atom-haskell/ide-haskell-repl.svg) 2 | 3 | ***This package is in beta.*** 4 | 5 | This package provides a way to interact with ghci (or, more precisely, `cabal repl` or `stack ghci`) from Atom. 6 | 7 | ![image](https://cloud.githubusercontent.com/assets/7275622/24383478/c3ae1ae4-1365-11e7-88a6-0c4877b23fc1.png) 8 | 9 | ## Documentation 10 | 11 | Please refer to documentation site https://atom-haskell.github.io. In particular, see [here](https://atom-haskell.github.io/extra-packages/ide-haskell-repl/#using-on-windows) when running `ide-haskell-repl` on Windows. 12 | 13 | ## License 14 | 15 | Copyright © 2015 Atom-Haskell. 16 | 17 | Contributors (by number of commits): 18 | 19 | 20 | * Nikolay Yakimov 21 | * yaeti 22 | * Yakov Borevich 23 | * soiamsoNG 24 | 25 | 26 | 27 | See [LICENSE.md][LICENSE] for details. 28 | 29 | [LICENSE]: https://github.com/atom-haskell/ide-haskell-repl/blob/master/LICENSE.md 30 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrow_spacing": { 3 | "name": "arrow_spacing", 4 | "level": "error" 5 | }, 6 | "ensure_comprehensions": { 7 | "name": "ensure_comprehensions", 8 | "level": "error" 9 | }, 10 | "max_line_length": { 11 | "name": "max_line_length", 12 | "value": 120, 13 | "level": "error", 14 | "limitComments": true 15 | }, 16 | "indentation": { 17 | "name": "indentation", 18 | "value": 2, 19 | "level": "error" 20 | }, 21 | "no_empty_param_list": { 22 | "name": "no_empty_param_list", 23 | "level": "error" 24 | }, 25 | "cyclomatic_complexity": { 26 | "name": "cyclomatic_complexity", 27 | "value": 22, 28 | "level": "error" 29 | }, 30 | "no_unnecessary_fat_arrows": { 31 | "name": "no_unnecessary_fat_arrows", 32 | "level": "error" 33 | }, 34 | "space_operators": { 35 | "name": "space_operators", 36 | "level": "error" 37 | }, 38 | "spacing_after_comma": { 39 | "name": "spacing_after_comma", 40 | "level": "error" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Atom-Haskell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | ### Project specific config ### 2 | language: generic 3 | 4 | env: 5 | global: 6 | - APM_TEST_PACKAGES="" 7 | - ATOM_LINT_WITH_BUNDLED_NODE="true" 8 | 9 | matrix: 10 | - ATOM_CHANNEL=stable 11 | - ATOM_CHANNEL=beta 12 | 13 | os: 14 | - linux 15 | 16 | # Use sed to replace the SSH URL with the public URL, then initialize submodules 17 | before_install: 18 | - sed -i 's/git@github.com:/https:\/\/github.com\//' .gitmodules 19 | - git submodule update --init --recursive 20 | 21 | ### Generic setup follows ### 22 | script: 23 | - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh 24 | - chmod u+x build-package.sh 25 | - ./build-package.sh 26 | - npm run build 27 | - npm run test 28 | 29 | notifications: 30 | email: 31 | on_success: never 32 | on_failure: change 33 | 34 | branches: 35 | only: 36 | - master 37 | 38 | git: 39 | depth: 10 40 | submodules: false 41 | 42 | sudo: false 43 | 44 | dist: trusty 45 | 46 | addons: 47 | apt: 48 | packages: 49 | - build-essential 50 | - fakeroot 51 | - git 52 | - libsecret-1-dev 53 | -------------------------------------------------------------------------------- /src/command-history.ts: -------------------------------------------------------------------------------- 1 | export class CommandHistory { 2 | private back: string[] 3 | private current: number 4 | private temp: string 5 | constructor(history: string[] = []) { 6 | this.back = history 7 | this.current = -1 8 | this.temp = '' 9 | } 10 | 11 | public goBack(current: string): string { 12 | if (this.current === -1) { 13 | this.temp = current 14 | } 15 | this.current += 1 16 | if (this.current >= this.back.length) { 17 | this.current = this.back.length - 1 18 | } 19 | if (this.current < 0) { 20 | this.current = -1 21 | return this.temp 22 | } 23 | return this.back[this.current] 24 | } 25 | 26 | public peek(shift: number): string | undefined { 27 | return this.back[this.current - shift] 28 | } 29 | 30 | public goForward(): string { 31 | if (this.current <= 0) { 32 | this.current = -1 33 | return this.temp 34 | } 35 | this.current -= 1 36 | return this.back[this.current] 37 | } 38 | 39 | public save(current: string): void { 40 | this.current = -1 41 | this.back.unshift(current) 42 | } 43 | 44 | public serialize(): string[] { 45 | return this.back 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/interactive-process.spec.ts: -------------------------------------------------------------------------------- 1 | import { InteractiveProcess } from '../lib/interactive-process' 2 | 3 | import { expect } from 'chai' 4 | 5 | describe('InteractiveProcess', () => { 6 | it('works', async () => { 7 | const noop = () => undefined 8 | const ip = new InteractiveProcess('cat', [], noop, {}, /.*$/) 9 | let i = 1 10 | await ip.request('Hello\nworld\n', (line) => { 11 | switch (i) { 12 | case 1: 13 | expect(line.type).to.equal('stdin') 14 | if (line.type === 'stdin') { 15 | expect(line.line).to.equal('Hello\nworld\n') 16 | } 17 | break 18 | case 2: 19 | expect(line.type).to.equal('prompt') 20 | if (line.type === 'prompt') { 21 | expect(line.prompt[0]).to.equal('Hello') 22 | } 23 | break 24 | case 3: 25 | expect(line.type).to.equal('prompt') 26 | if (line.type === 'prompt') { 27 | expect(line.prompt[0]).to.equal('world') 28 | } 29 | break 30 | default: 31 | throw new Error(`Unexpected ${i}: ${JSON.stringify(line)}`) 32 | } 33 | i++ 34 | }) 35 | ip.destroy() 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "isolatedModules": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": true, 11 | "noImplicitAny": true, 12 | "noImplicitUseStrict": false, 13 | "removeComments": true, 14 | "noLib": false, 15 | "jsxFactory": "etch.dom", 16 | "preserveConstEnums": true, 17 | "suppressImplicitAnyIndexErrors": true, 18 | "outDir": "lib", 19 | "inlineSources": true, 20 | "inlineSourceMap": true, 21 | "strictNullChecks": true, 22 | "allowJs": false, 23 | "lib": ["dom", "es2017", "esnext.asynciterable"], 24 | "strict": true, 25 | "importHelpers": true, 26 | "noFallthroughCasesInSwitch": true, 27 | "noImplicitReturns": true, 28 | "noUnusedLocals": true, 29 | "noUnusedParameters": true, 30 | "typeRoots": ["./typings", "./node_modules/@types"], 31 | "plugins": [{ "name": "typescript-tslint-plugin" }], 32 | "newLine": "LF", 33 | "skipLibCheck": true 34 | }, 35 | "exclude": ["node_modules", "spec"], 36 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js"], 37 | "compileOnSave": true, 38 | "experimentalDecorators": true 39 | } 40 | -------------------------------------------------------------------------------- /lib/views/editor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | class Editor { 4 | constructor(props) { 5 | this.props = props; 6 | this.element = props.element; 7 | this.element.classList.add('ide-haskell-repl'); 8 | } 9 | async update(props) { 10 | this.element = props.element; 11 | this.element.classList.add('ide-haskell-repl'); 12 | return Promise.resolve(); 13 | } 14 | } 15 | exports.Editor = Editor; 16 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWRpdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3ZpZXdzL2VkaXRvci50c3giXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFLQSxNQUFhLE1BQU07SUFFakIsWUFBbUIsS0FBYTtRQUFiLFVBQUssR0FBTCxLQUFLLENBQVE7UUFDOUIsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFBO1FBQzVCLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFBO0lBQ2hELENBQUM7SUFFTSxLQUFLLENBQUMsTUFBTSxDQUFDLEtBQWE7UUFDL0IsSUFBSSxDQUFDLE9BQU8sR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFBO1FBQzVCLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQyxDQUFBO1FBQzlDLE9BQU8sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFBO0lBQzFCLENBQUM7Q0FDRjtBQVpELHdCQVlDIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGludGVyZmFjZSBJUHJvcHMgZXh0ZW5kcyBKU1guUHJvcHMge1xuICBlbGVtZW50OiBIVE1MRWxlbWVudFxufVxuXG4vLyB0c2xpbnQ6ZGlzYWJsZS1uZXh0LWxpbmU6bm8tdW5zYWZlLWFueVxuZXhwb3J0IGNsYXNzIEVkaXRvciBpbXBsZW1lbnRzIEpTWC5FbGVtZW50Q2xhc3Mge1xuICBwdWJsaWMgZWxlbWVudDogSFRNTEVsZW1lbnRcbiAgY29uc3RydWN0b3IocHVibGljIHByb3BzOiBJUHJvcHMpIHtcbiAgICB0aGlzLmVsZW1lbnQgPSBwcm9wcy5lbGVtZW50XG4gICAgdGhpcy5lbGVtZW50LmNsYXNzTGlzdC5hZGQoJ2lkZS1oYXNrZWxsLXJlcGwnKVxuICB9XG5cbiAgcHVibGljIGFzeW5jIHVwZGF0ZShwcm9wczogSVByb3BzKSB7XG4gICAgdGhpcy5lbGVtZW50ID0gcHJvcHMuZWxlbWVudFxuICAgIHRoaXMuZWxlbWVudC5jbGFzc0xpc3QuYWRkKCdpZGUtaGFza2VsbC1yZXBsJylcbiAgICByZXR1cm4gUHJvbWlzZS5yZXNvbHZlKClcbiAgfVxufVxuIl19 -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | function handlePromise(somePromise) { 4 | somePromise.catch((e) => { 5 | atom.notifications.addFatalError(e.name, { 6 | detail: e.message, 7 | stack: e.stack, 8 | }); 9 | }); 10 | } 11 | exports.handlePromise = handlePromise; 12 | function getText(m) { 13 | if (typeof m === 'string') { 14 | return m; 15 | } 16 | else { 17 | if ('text' in m) 18 | return m.text; 19 | else 20 | return m.html; 21 | } 22 | } 23 | exports.getText = getText; 24 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBRUEsU0FBZ0IsYUFBYSxDQUFDLFdBQXlCO0lBQ3JELFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFRLEVBQUUsRUFBRTtRQUM3QixJQUFJLENBQUMsYUFBYSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFO1lBQ3ZDLE1BQU0sRUFBRSxDQUFDLENBQUMsT0FBTztZQUNqQixLQUFLLEVBQUUsQ0FBQyxDQUFDLEtBQUs7U0FDZixDQUFDLENBQUE7SUFDSixDQUFDLENBQUMsQ0FBQTtBQUNKLENBQUM7QUFQRCxzQ0FPQztBQUVELFNBQWdCLE9BQU8sQ0FBQyxDQUFlO0lBQ3JDLElBQUksT0FBTyxDQUFDLEtBQUssUUFBUSxFQUFFO1FBQ3pCLE9BQU8sQ0FBQyxDQUFBO0tBQ1Q7U0FBTTtRQUNMLElBQUksTUFBTSxJQUFJLENBQUM7WUFBRSxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUE7O1lBQ3pCLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQTtLQUNuQjtBQUNILENBQUM7QUFQRCwwQkFPQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFVQSSBmcm9tICdhdG9tLWhhc2tlbGwtdXBpJ1xuXG5leHBvcnQgZnVuY3Rpb24gaGFuZGxlUHJvbWlzZShzb21lUHJvbWlzZTogUHJvbWlzZTxhbnk+KTogdm9pZCB7XG4gIHNvbWVQcm9taXNlLmNhdGNoKChlOiBFcnJvcikgPT4ge1xuICAgIGF0b20ubm90aWZpY2F0aW9ucy5hZGRGYXRhbEVycm9yKGUubmFtZSwge1xuICAgICAgZGV0YWlsOiBlLm1lc3NhZ2UsXG4gICAgICBzdGFjazogZS5zdGFjayxcbiAgICB9KVxuICB9KVxufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0VGV4dChtOiBVUEkuVE1lc3NhZ2UpOiBzdHJpbmcge1xuICBpZiAodHlwZW9mIG0gPT09ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIG1cbiAgfSBlbHNlIHtcbiAgICBpZiAoJ3RleHQnIGluIG0pIHJldHVybiBtLnRleHRcbiAgICBlbHNlIHJldHVybiBtLmh0bWxcbiAgfVxufVxuIl19 -------------------------------------------------------------------------------- /src/views/button.tsx: -------------------------------------------------------------------------------- 1 | import { CompositeDisposable } from 'atom' 2 | import etch = require('etch') 3 | import { IdeHaskellReplView } from './ide-haskell-repl-view' 4 | 5 | export interface IProps extends JSX.Props { 6 | cls: string 7 | parent: IdeHaskellReplView 8 | tooltip: string | (() => string) 9 | command: string 10 | state?: boolean 11 | } 12 | 13 | // tslint:disable-next-line:no-unsafe-any 14 | export class Button implements JSX.ElementClass { 15 | private element!: HTMLElement 16 | private target: HTMLElement 17 | private disposables: CompositeDisposable 18 | private clslst: Set 19 | constructor(public props: IProps) { 20 | this.disposables = new CompositeDisposable() 21 | this.clslst = new Set() 22 | this.clslst.add(this.props.cls) 23 | this.updateState(this.props.state) 24 | etch.initialize(this) 25 | this.target = atom.views.getView(this.props.parent.editor) 26 | this.disposables.add( 27 | atom.tooltips.add(this.element, { 28 | title: this.props.tooltip, 29 | keyBindingCommand: this.props.command, 30 | keyBindingTarget: this.target, 31 | }), 32 | ) 33 | } 34 | 35 | public render() { 36 | return ( 37 | // tslint:disable:no-unsafe-any 38 |