diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..7bf1d1d8 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,70 @@ +{ + // JS Files + "root": true, + "env": { + "atomtest": true, + "es6": true, + "node": true, + "browser": true + }, + "globals": { "atom": "writable" }, + "parser": "babel-eslint", + "parserOptions": { + "ecmaFeatures": { "jsx": true }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": ["only-warn"], + "extends": ["eslint:recommended"], + "overrides": [ + { // Bundled node version with atom has an old ESLint + // TypeScript files + "files": ["**/*.ts", "**/*.tsx"], + "env": { + "atomtest": true, + "es6": true, + "node": true, + "browser": true + }, + "globals": { "atom": "writable" }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { "jsx": true }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint", "only-warn"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/camelcase": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/member-delimiter-style": "off" + } + }, + { + // CoffeeScript files + "files": ["**/*.coffee"], + "env": { + "atomtest": true, + "es6": true, + "node": true, + "browser": true + }, + "globals": { "atom": "writable" }, + // "parser": "eslint-plugin-coffee", + "parser": "eslint-plugin-coffee", + "parserOptions": { + "ecmaFeatures": { "jsx": true }, + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": ["coffee", "only-warn"], + "extends": ["plugin:coffee/eslint-recommended"] + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..f91dda83 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,64 @@ +name: CI +on: + - push + - pull_request + +jobs: + Test: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + name: Julia ${{ matrix.julia_version }} - ${{ matrix.os }} - ${{ matrix.arch }} - Atom ${{ matrix.atom_channel }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - macos-latest + - windows-latest + atom_channel: [stable] # beta + julia_version: ["1.3", "nightly"] + arch: ["x64"] + steps: + - uses: actions/checkout@v2 + - uses: UziTech/action-setup-atom@v1 + with: + channel: ${{ matrix.atom-channel }} + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.julia_version }} + arch: ${{ matrix.arch }} + - name: Versions + run: | + julia -v + apm -v + - name: Install APM dependencies + run: | + apm ci # uses locked module. use `apm install` for non-locked + apm install ink language-julia + node script/postinstall.js + - name: Julia CI + run: julia -e 'include("ci/packages.jl")' + shell: bash +# - name: Run tests 👩🏾‍💻 +# run: atom --test spec + + Lint: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: "13.x" + - name: Install NPM dependencies + run: | + npm ci # uses locked module. use `npm install` for non-locked + - name: Lint ✨ + run: npm run lint + + Skip: + if: contains(github.event.head_commit.message, '[skip ci]') + runs-on: ubuntu-latest + steps: + - name: Skip CI 🚫 + run: echo skip ci diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6674d877..00000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -language: julia - -julia: - - 1.3 - - nightly - -env: - global: - - APM_TEST_PACKAGES="ink language-julia" - - ATOM_LINT_WITH_BUNDLED_NODE="true" - - matrix: - - "" - - ATOMJL=master - -os: - - linux - -matrix: - include: - # # Sanity check for OS X - # - os: osx - # julia: 1.3 - # env: ATOMJL=master - # Sanity check for Atom Beta - - os: linux - julia: 1.3 - env: ATOM_CHANNEL=beta - allow_failures: - - julia: nightly - - env: ATOM_CHANNEL=beta - -script: - - julia ci/packages.jl - - curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh - - chmod u+x build-package.sh - - ./build-package.sh - -dist: xenial -addons: - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - g++-6 - - build-essential - - fakeroot - - git - - libsecret-1-dev - -notifications: - email: false diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 63454715..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,24 +0,0 @@ -### Project specific config ### -environment: - APM_TEST_PACKAGES: "ink language-julia" - ATOM_LINT_WITH_BUNDLED_NODE: "true" - - matrix: - - ATOM_CHANNEL: stable - - ATOM_CHANNEL: beta - -### Generic setup follows ### -build_script: - - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) - - julia: ci/packages.jl - -branches: - only: - - master - -version: "{build}" -platform: x64 -clone_depth: 10 -skip_tags: true -test: off -deploy: off diff --git a/lib/misc/blocks.d.ts b/lib/misc/blocks.d.ts new file mode 100644 index 00000000..f72679fd --- /dev/null +++ b/lib/misc/blocks.d.ts @@ -0,0 +1,19 @@ +import { TextEditor, Selection } from "atom"; +interface LineInfo { + scope: readonly string[]; + line: string; +} +export declare function getLine(editor: TextEditor, l: number): LineInfo; +export declare function moveNext(editor: TextEditor, selection: Selection, range: [[number, number], [number, number]]): void; +export declare function get(editor: TextEditor): { + range: number[][] | undefined; + selection: Selection; + line: number; + text: any; +}[]; +export declare function getLocalContext(editor: TextEditor, row: number): { + context: string; + startRow: number; +}; +export declare function select(editor?: TextEditor | undefined): void; +export {}; diff --git a/lib/misc/blocks.js b/lib/misc/blocks.js index 46417203..e362320c 100644 --- a/lib/misc/blocks.js +++ b/lib/misc/blocks.js @@ -1,16 +1,20 @@ -'use babel' +"use strict" // TODO: docstrings - -import { forLines } from './scopes' - -export function getLine (ed, l) { +// TODO: replace getHeadBufferPosition with getBufferRowRange? - getHeadBufferPosition isn't public API +// TODO: make sure returned range from getRanges is not undefined +Object.defineProperty(exports, "__esModule", { value: true }) +const scopes_1 = require("./scopes") +function getLine(editor, l) { return { - scope: ed.scopeDescriptorForBufferPosition([l, 0]).scopes, - line: ed.getTextInBufferRange([[l, 0], [l, Infinity]]) + scope: editor.scopeDescriptorForBufferPosition([l, 0]).getScopesArray(), + line: editor.getTextInBufferRange([ + [l, 0], + [l, Infinity] + ]) } } - -function isBlank ({line, scope}, allowDocstrings = false) { +exports.getLine = getLine +function isBlank({ line, scope }, allowDocstrings = false) { for (const s of scope) { if (/\bcomment\b/.test(s) || (!allowDocstrings && /\bdocstring\b/.test(s))) { return true @@ -18,43 +22,38 @@ function isBlank ({line, scope}, allowDocstrings = false) { } return /^\s*(#.*)?$/.test(line) } -function isEnd ({ line, scope }) { - if (isStringEnd({ line, scope })) { +function isEnd(lineInfo) { + if (isStringEnd(lineInfo)) { return true } - return /^(end\b|\)|\]|\})/.test(line) + return /^(end\b|\)|\]|\})/.test(lineInfo.line) } -function isStringEnd ({ line, scope }) { - scope = scope.join(' ') - return /\bstring\.multiline\.end\b/.test(scope) || - (/\bstring\.end\b/.test(scope) && /\bbacktick\b/.test(scope)) +function isStringEnd(lineInfo) { + const scope = lineInfo.scope.join(" ") + return /\bstring\.multiline\.end\b/.test(scope) || (/\bstring\.end\b/.test(scope) && /\bbacktick\b/.test(scope)) } -function isCont ({ line, scope }) { - scope = scope.join(' ') - if (/\bstring\b/.test(scope) && !(/\bpunctuation\.definition\.string\b/.test(scope))) { +function isCont(lineInfo) { + const scope = lineInfo.scope.join(" ") + if (/\bstring\b/.test(scope) && !/\bpunctuation\.definition\.string\b/.test(scope)) { return true } - - return line.match(/^(else|elseif|catch|finally)\b/) + return lineInfo.line.match(/^(else|elseif|catch|finally)\b/) } -function isStart (lineInfo) { +function isStart(lineInfo) { return !(/^\s/.test(lineInfo.line) || isBlank(lineInfo) || isEnd(lineInfo) || isCont(lineInfo)) } - -function walkBack(ed, row) { - while ((row > 0) && !isStart(getLine(ed, row))) { +function walkBack(editor, row) { + while (row > 0 && !isStart(getLine(editor, row))) { row-- } return row } - -function walkForward (ed, start) { +function walkForward(editor, start) { let end = start let mark = start - while (mark < ed.getLastBufferRow()) { + while (mark < editor.getLastBufferRow()) { mark++ - const lineInfo = getLine(ed, mark) - + const lineInfo = getLine(editor, mark) if (isStart(lineInfo)) { break } @@ -62,10 +61,7 @@ function walkForward (ed, start) { // An `end` only counts when there still are unclosed blocks (indicated by `forLines` // returning a non-empty array). // If the line closes a multiline string we also take that as ending the block. - if ( - !(forLines(ed, start, mark-1).length === 0) || - isStringEnd(lineInfo) - ) { + if (!(scopes_1.forLines(editor, start, mark - 1).length === 0) || isStringEnd(lineInfo)) { end = mark } } else if (!(isBlank(lineInfo) || isStart(lineInfo))) { @@ -74,77 +70,87 @@ function walkForward (ed, start) { } return end } - -function getRange (ed, row) { - const start = walkBack(ed, row) - const end = walkForward(ed, start) +function getRange(editor, row) { + const start = walkBack(editor, row) + const end = walkForward(editor, start) if (start <= row && row <= end) { - return [[start, 0], [end, Infinity]] + return [ + [start, 0], + [end, Infinity] + ] + } else { + return undefined // TODO } } - -function getSelection (ed, sel) { - const {start, end} = sel.getBufferRange() - const range = [[start.row, start.column], [end.row, end.column]] - while (isBlank(getLine(ed, range[0][0]), true) && (range[0][0] <= range[1][0])) { +function getSelection(editor, selection) { + const { start, end } = selection.getBufferRange() + const range = [ + [start.row, start.column], + [end.row, end.column] + ] + while (isBlank(getLine(editor, range[0][0]), true) && range[0][0] <= range[1][0]) { range[0][0]++ range[0][1] = 0 } - while (isBlank(getLine(ed, range[1][0]), true) && (range[1][0] >= range[0][0])) { + while (isBlank(getLine(editor, range[1][0]), true) && range[1][0] >= range[0][0]) { range[1][0]-- range[1][1] = Infinity } return range } - -export function moveNext (ed, sel, range) { +function moveNext(editor, selection, range) { // Ensure enough room at the end of the buffer const row = range[1][0] let last - while ((last = ed.getLastBufferRow()) < (row+2)) { - if ((last !== row) && !isBlank(getLine(ed, last))) { + while ((last = editor.getLastBufferRow()) < row + 2) { + if (last !== row && !isBlank(getLine(editor, last))) { break } - sel.setBufferRange([[last, Infinity], [last, Infinity]]) - sel.insertText('\n') + selection.setBufferRange([ + [last, Infinity], + [last, Infinity] + ]) + selection.insertText("\n") } // Move the cursor let to = row + 1 - while ((to < ed.getLastBufferRow()) && isBlank(getLine(ed, to))) { + while (to < editor.getLastBufferRow() && isBlank(getLine(editor, to))) { to++ } - to = walkForward(ed, to) - return sel.setBufferRange([[to, Infinity], [to, Infinity]]) + to = walkForward(editor, to) + return selection.setBufferRange([ + [to, Infinity], + [to, Infinity] + ]) } - -function getRanges (ed) { - const ranges = ed.getSelections().map(sel => { +exports.moveNext = moveNext +function getRanges(editor) { + const ranges = editor.getSelections().map(selection => { return { - selection: sel, - range: sel.isEmpty() ? - getRange(ed, sel.getHeadBufferPosition().row) : - getSelection(ed, sel) + selection: selection, + range: selection.isEmpty() + ? getRange(editor, selection.getHeadBufferPosition().row) + : getSelection(editor, selection) } }) return ranges.filter(({ range }) => { - return range && ed.getTextInBufferRange(range).trim() + return range && editor.getTextInBufferRange(range).trim() }) } - -export function get (ed) { - return getRanges(ed).map(({ range, selection }) => { +function get(editor) { + return getRanges(editor).map(({ range, selection }) => { return { range, selection, line: range[0][0], - text: ed.getTextInBufferRange(range) + text: editor.getTextInBufferRange(range) } }) } - -export function getLocalContext (editor, row) { +exports.get = get +function getLocalContext(editor, row) { const range = getRange(editor, row) - const context = range ? editor.getTextInBufferRange(range) : '' + const context = range ? editor.getTextInBufferRange(range) : "" // NOTE: // backend code expects startRow to be number for most cases, e.g.: `row = row - startRow` // so let's just return `0` when there is no local context @@ -155,13 +161,15 @@ export function getLocalContext (editor, row) { startRow } } - -export function select (ed = atom.workspace.getActiveTextEditor()) { - if (!ed) return - return ed.mutateSelectedText(selection => { - const range = getRange(ed, selection.getHeadBufferPosition().row) +exports.getLocalContext = getLocalContext +function select(editor = atom.workspace.getActiveTextEditor()) { + if (!editor) return + return editor.mutateSelectedText(selection => { + const range = getRange(editor, selection.getHeadBufferPosition().row) if (range) { selection.setBufferRange(range) } }) } +exports.select = select +//# sourceMappingURL=blocks.js.map diff --git a/lib/misc/blocks.js.map b/lib/misc/blocks.js.map new file mode 100644 index 00000000..09b9673c --- /dev/null +++ b/lib/misc/blocks.js.map @@ -0,0 +1 @@ +{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../lib_src/misc/blocks.ts"],"names":[],"mappings":";AAAA,mBAAmB;AACnB,uGAAuG;AACvG,iEAAiE;;AAEjE,qCAAmC;AAQnC,SAAgB,OAAO,CAAC,MAAkB,EAAE,CAAS;IACnD,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,gCAAgC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE;QACvE,IAAI,EAAE,MAAM,CAAC,oBAAoB,CAAC;YAChC,CAAC,CAAC,EAAE,CAAC,CAAC;YACN,CAAC,CAAC,EAAE,QAAQ,CAAC;SACd,CAAC;KACH,CAAA;AACH,CAAC;AARD,0BAQC;AAED,SAAS,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAY,EAAE,eAAe,GAAG,KAAK;IACjE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE;QACrB,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;YAC1E,OAAO,IAAI,CAAA;SACZ;KACF;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACjC,CAAC;AAED,SAAS,KAAK,CAAC,QAAkB;IAC/B,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE;QACzB,OAAO,IAAI,CAAA;KACZ;IACD,OAAO,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AAChD,CAAC;AAED,SAAS,WAAW,CAAC,QAAkB;IACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,OAAO,4BAA4B,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;AAClH,CAAC;AAED,SAAS,MAAM,CAAC,QAAkB;IAChC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACtC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QAClF,OAAO,IAAI,CAAA;KACZ;IAED,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAA;AAC9D,CAAC;AAED,SAAS,OAAO,CAAC,QAAkB;IACjC,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;AACjG,CAAC;AAED,SAAS,QAAQ,CAAC,MAAkB,EAAE,GAAW;IAC/C,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE;QAChD,GAAG,EAAE,CAAA;KACN;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,WAAW,CAAC,MAAkB,EAAE,KAAa;IACpD,IAAI,GAAG,GAAG,KAAK,CAAA;IACf,IAAI,IAAI,GAAG,KAAK,CAAA;IAChB,OAAO,IAAI,GAAG,MAAM,CAAC,gBAAgB,EAAE,EAAE;QACvC,IAAI,EAAE,CAAA;QACN,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QAEtC,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE;YACrB,MAAK;SACN;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE;YACnB,sFAAsF;YACtF,gCAAgC;YAChC,+EAA+E;YAC/E,IAAI,CAAC,CAAC,iBAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE;gBAC9E,GAAG,GAAG,IAAI,CAAA;aACX;SACF;aAAM,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE;YACpD,GAAG,GAAG,IAAI,CAAA;SACX;KACF;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,QAAQ,CAAC,MAAkB,EAAE,GAAW;IAC/C,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,GAAG,GAAG,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IACtC,IAAI,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,EAAE;QAC9B,OAAO;YACL,CAAC,KAAK,EAAE,CAAC,CAAC;YACV,CAAC,GAAG,EAAE,QAAQ,CAAC;SAChB,CAAA;KACF;SAAM;QACL,OAAO,SAAS,CAAA,CAAC,OAAO;KACzB;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAkB,EAAE,SAAoB;IAC5D,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,SAAS,CAAC,cAAc,EAAE,CAAA;IACjD,MAAM,KAAK,GAAG;QACZ,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC;QACzB,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC;KACtB,CAAA;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;QAChF,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACb,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;KAChB;IACD,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;QAChF,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACb,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAA;KACvB;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAkB,EAAE,SAAoB,EAAE,KAA2C;IAC5G,8CAA8C;IAC9C,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACvB,IAAI,IAAI,CAAA;IACR,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE;QACnD,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,EAAE;YACnD,MAAK;SACN;QACD,SAAS,CAAC,cAAc,CAAC;YACvB,CAAC,IAAI,EAAE,QAAQ,CAAC;YAChB,CAAC,IAAI,EAAE,QAAQ,CAAC;SACjB,CAAC,CAAA;QACF,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;KAC3B;IACD,kBAAkB;IAClB,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC,CAAA;IAChB,OAAO,EAAE,GAAG,MAAM,CAAC,gBAAgB,EAAE,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE;QACrE,EAAE,EAAE,CAAA;KACL;IACD,EAAE,GAAG,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC5B,OAAO,SAAS,CAAC,cAAc,CAAC;QAC9B,CAAC,EAAE,EAAE,QAAQ,CAAC;QACd,CAAC,EAAE,EAAE,QAAQ,CAAC;KACf,CAAC,CAAA;AACJ,CAAC;AAxBD,4BAwBC;AAED,SAAS,SAAS,CAAC,MAAkB;IACnC,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE;QACpD,OAAO;YACL,SAAS,EAAE,SAAS;YACpB,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE;gBACxB,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAC;gBACzD,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC;SACpC,CAAA;IACH,CAAC,CAAC,CAAA;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE;QACjC,OAAO,KAAK,IAAI,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAgB,GAAG,CAAC,MAAkB;IACpC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE;QACpD,OAAO;YACL,KAAK;YACL,SAAS;YACT,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACjB,IAAI,EAAE,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC;SACzC,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AATD,kBASC;AAED,SAAgB,eAAe,CAAC,MAAkB,EAAE,GAAW;IAC7D,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/D,QAAQ;IACR,0FAA0F;IAC1F,0DAA0D;IAC1D,oEAAoE;IACpE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACxC,OAAO;QACL,OAAO;QACP,QAAQ;KACT,CAAA;AACH,CAAC;AAZD,0CAYC;AAED,SAAgB,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,EAAE;IAClE,IAAI,CAAC,MAAM;QAAE,OAAM;IACnB,OAAO,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE;QAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAC,CAAA;QACrE,IAAI,KAAK,EAAE;YACT,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;SAChC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AARD,wBAQC"} \ No newline at end of file diff --git a/lib/misc/cells.d.ts b/lib/misc/cells.d.ts new file mode 100644 index 00000000..c06a05b1 --- /dev/null +++ b/lib/misc/cells.d.ts @@ -0,0 +1,10 @@ +import { Point, TextEditor } from "atom"; +export declare function getRange(editor: TextEditor): [Point, Point]; +export declare function get(editor: TextEditor): { + range: any[][]; + selection: any; + line: any; + text: any; +}[]; +export declare function moveNext(editor: TextEditor | null | undefined): void | null; +export declare function movePrev(editor: TextEditor | undefined | null): void | null; diff --git a/lib/misc/cells.js b/lib/misc/cells.js index af611973..a4d7b745 100644 --- a/lib/misc/cells.js +++ b/lib/misc/cells.js @@ -1,45 +1,36 @@ -'use babel' - -import { get as weaveGet, - moveNext as weaveMoveNext, - movePrev as weaveMovePrev } from './weave.js' - -import { getLine } from './blocks.js' - -import { Point } from 'atom' - -export function getRange (ed) { +"use strict" +Object.defineProperty(exports, "__esModule", { value: true }) +const weave_js_1 = require("./weave.js") +const blocks_js_1 = require("./blocks.js") +function getRange(editor) { // Cell range is: // Start of line below top delimiter (and/or start of top row of file) to // End of line before end delimiter - var buffer = ed.getBuffer() - var start = buffer.getFirstPosition() - var end = buffer.getEndPosition() - var regexString = '^(' + atom.config.get('julia-client.uiOptions.cellDelimiter').join('|') + ')' - var regex = new RegExp(regexString) - var cursor = ed.getCursorBufferPosition() + const buffer = editor.getBuffer() + const start = buffer.getFirstPosition() + const end = buffer.getEndPosition() + const regexString = "^(" + atom.config.get("julia-client.uiOptions.cellDelimiter").join("|") + ")" + const regex = new RegExp(regexString) + const cursor = editor.getCursorBufferPosition() cursor.column = Infinity // cursor on delimiter line means eval cell below - - let foundDelim = false - for (let i = cursor.row + 1; i <= ed.getLastBufferRow(); i++) { - let {line, scope} = getLine(ed, i) - foundDelim = regex.test(line) && scope.join('.').indexOf('comment.line') > -1 + const editor_getLastBufferRow = editor.getLastBufferRow() + for (let i = cursor.row + 1; i <= editor_getLastBufferRow; i++) { + const { line, scope } = blocks_js_1.getLine(editor, i) + foundDelim = regex.test(line) && scope.join(".").indexOf("comment.line") > -1 end.row = i if (foundDelim) break } - if (foundDelim) { end.row -= 1 if (end.row < 0) end.row = 0 end.column = Infinity } - foundDelim = false if (cursor.row > 0) { for (let i = end.row; i >= 0; i--) { - let {line, scope} = getLine(ed, i) - foundDelim = regex.test(line) && scope.join('.').indexOf('comment.line') > -1 + const { line, scope } = blocks_js_1.getLine(editor, i) + foundDelim = regex.test(line) && scope.join(".").indexOf("comment.line") > -1 start.row = i if (foundDelim) { break @@ -47,63 +38,77 @@ export function getRange (ed) { } start.column = 0 } - return [start, end] } - -export function get (ed) { - if (ed.getGrammar().scopeName.indexOf('source.julia') > -1) { - return jlGet(ed) +exports.getRange = getRange +function get(editor) { + if (editor.getGrammar().scopeName.indexOf("source.julia") > -1) { + return jlGet(editor) } else { - return weaveGet(ed) + return weave_js_1.get(editor) } } - -function jlGet (ed) { - var range = getRange(ed) - var text = ed.getTextInBufferRange(range) - if (text.trim() === '') text = ' ' - var res = { - range: [[range[0].row, range[0].column], [range[1].row, range[1].column]], - selection: ed.getSelections()[0], +exports.get = get +function jlGet(editor) { + const range = getRange(editor) + let text = editor.getTextInBufferRange(range) + if (text.trim() === "") text = " " + const res = { + range: [ + [range[0].row, range[0].column], + [range[1].row, range[1].column] + ], + selection: editor.getSelections()[0], line: range[0].row, - text: text + text } return [res] } - -export function moveNext (ed) { - if (ed == null) { - ed = atom.workspace.getActiveTextEditor() +function moveNext(editor) { + if (!editor) { + editor = atom.workspace.getActiveTextEditor() } - if (ed.getGrammar().scopeName.indexOf('source.julia') > -1) { - return jlMoveNext(ed) + if (editor) { + // TODO: do we need this? + if (editor.getGrammar().scopeName.indexOf("source.julia") > -1) { + return jlMoveNext(editor) + } else { + return weave_js_1.moveNext(editor) + } } else { - return weaveMoveNext(ed) + console.error("editor isn't acquired!") } } - -function jlMoveNext (ed) { - var range = getRange(ed) - var sel = ed.getSelections()[0] - var nextRow = range[1].row + 2 // 2 = 1 to get to delimiter line + 1 more to go past it - return sel.setBufferRange([[nextRow, 0], [nextRow, 0]]) +exports.moveNext = moveNext +function jlMoveNext(editor) { + const range = getRange(editor) + const sel = editor.getSelections()[0] + const nextRow = range[1].row + 2 // 2 = 1 to get to delimiter line + 1 more to go past it + return sel.setBufferRange([ + [nextRow, 0], + [nextRow, 0] + ]) } - -export function movePrev (ed) { - if (ed == null) { - ed = atom.workspace.getActiveTextEditor() +function movePrev(editor) { + if (!editor) { + editor = atom.workspace.getActiveTextEditor() } - if (ed.getGrammar().scopeName.indexOf('source.weave') > -1) { - return weaveMovePrev(ed) - } else { - return jlMovePrev(ed) + if (editor) { + if (editor.getGrammar().scopeName.indexOf("source.weave") > -1) { + return weave_js_1.movePrev(editor) + } else { + return jlMovePrev(editor) + } } } - -function jlMovePrev (ed) { - var range = getRange(ed) - var prevRow = range[0].row - 2 // 2 = 1 to get to delimiter line + 1 more to go past it - var sel = ed.getSelections()[0] - return sel.setBufferRange([[prevRow, 0], [prevRow, 0]]) +exports.movePrev = movePrev +function jlMovePrev(editor) { + const range = getRange(editor) + const prevRow = range[0].row - 2 // 2 = 1 to get to delimiter line + 1 more to go past it + const sel = editor.getSelections()[0] + return sel.setBufferRange([ + [prevRow, 0], + [prevRow, 0] + ]) } +//# sourceMappingURL=cells.js.map diff --git a/lib/misc/cells.js.map b/lib/misc/cells.js.map new file mode 100644 index 00000000..87b35dc3 --- /dev/null +++ b/lib/misc/cells.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cells.js","sourceRoot":"","sources":["../../lib_src/misc/cells.ts"],"names":[],"mappings":";;AAAA,yCAAkG;AAElG,2CAAqC;AAIrC,SAAgB,QAAQ,CAAC,MAAkB;IACzC,iBAAiB;IACjB,0EAA0E;IAC1E,oCAAoC;IACpC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAA;IACjC,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAA;IACvC,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,EAAE,CAAA;IACnC,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,GAAG,CAAA;IAClG,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,WAAW,CAAC,CAAA;IACrC,MAAM,MAAM,GAAG,MAAM,CAAC,uBAAuB,EAAE,CAAA;IAC/C,MAAM,CAAC,MAAM,GAAG,QAAQ,CAAA,CAAC,iDAAiD;IAE1E,IAAI,UAAU,GAAG,KAAK,CAAA;IACtB,MAAM,uBAAuB,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAA;IACzD,KAAK,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,IAAI,uBAAuB,EAAE,CAAC,EAAE,EAAE;QAC9D,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,mBAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;QAC1C,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAA;QAC7E,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;QACX,IAAI,UAAU;YAAE,MAAK;KACtB;IAED,IAAI,UAAU,EAAE;QACd,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;QACZ,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC;YAAE,GAAG,CAAC,GAAG,GAAG,CAAC,CAAA;QAC5B,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAA;KACtB;IAED,UAAU,GAAG,KAAK,CAAA;IAClB,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,EAAE;QAClB,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;YACjC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,mBAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;YAC1C,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAA;YAC7E,KAAK,CAAC,GAAG,GAAG,CAAC,CAAA;YACb,IAAI,UAAU,EAAE;gBACd,MAAK;aACN;SACF;QACD,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;KACjB;IAED,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;AACrB,CAAC;AAzCD,4BAyCC;AAED,SAAgB,GAAG,CAAC,MAAkB;IACpC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE;QAC9D,OAAO,KAAK,CAAC,MAAM,CAAC,CAAA;KACrB;SAAM;QACL,OAAO,cAAQ,CAAC,MAAM,CAAC,CAAA;KACxB;AACH,CAAC;AAND,kBAMC;AAED,SAAS,KAAK,CAAC,MAAkB;IAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,IAAI,IAAI,GAAG,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC7C,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,IAAI,GAAG,GAAG,CAAA;IAClC,MAAM,GAAG,GAAG;QACV,KAAK,EAAE;YACL,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC/B,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;SAChC;QACD,SAAS,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;QACpC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG;QAClB,IAAI;KACL,CAAA;IACD,OAAO,CAAC,GAAG,CAAC,CAAA;AACd,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAqC;IAC5D,IAAI,CAAC,MAAM,EAAE;QACX,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,EAAE,CAAA;KAC9C;IACD,IAAI,MAAM,EAAE;QACV,yBAAyB;QACzB,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE;YAC9D,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;SAC1B;aAAM;YACL,OAAO,mBAAa,CAAC,MAAM,CAAC,CAAA;SAC7B;KACF;SAAM;QACL,OAAO,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAA;KACxC;AACH,CAAC;AAdD,4BAcC;AAED,SAAS,UAAU,CAAC,MAAkB;IACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAA;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAA,CAAC,wDAAwD;IACzF,OAAO,GAAG,CAAC,cAAc,CAAC;QACxB,CAAC,OAAO,EAAE,CAAC,CAAC;QACZ,CAAC,OAAO,EAAE,CAAC,CAAC;KACb,CAAC,CAAA;AACJ,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAqC;IAC5D,IAAI,CAAC,MAAM,EAAE;QACX,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,mBAAmB,EAAE,CAAA;KAC9C;IACD,IAAI,MAAM,EAAE;QACV,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE;YAC9D,OAAO,mBAAa,CAAC,MAAM,CAAC,CAAA;SAC7B;aAAM;YACL,OAAO,UAAU,CAAC,MAAM,CAAC,CAAA;SAC1B;KACF;AACH,CAAC;AAXD,4BAWC;AAED,SAAS,UAAU,CAAC,MAAkB;IACpC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAA,CAAC,wDAAwD;IACzF,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAA;IACrC,OAAO,GAAG,CAAC,cAAc,CAAC;QACxB,CAAC,OAAO,EAAE,CAAC,CAAC;QACZ,CAAC,OAAO,EAAE,CAAC,CAAC;KACb,CAAC,CAAA;AACJ,CAAC"} \ No newline at end of file diff --git a/lib/misc/colors.d.ts b/lib/misc/colors.d.ts new file mode 100644 index 00000000..b2734319 --- /dev/null +++ b/lib/misc/colors.d.ts @@ -0,0 +1,5 @@ +export declare function getColors(selectors: { + [P: string]: string; +}): { + [P: string]: string; +}; diff --git a/lib/misc/colors.js b/lib/misc/colors.js index 9a01c912..8af8ee11 100644 --- a/lib/misc/colors.js +++ b/lib/misc/colors.js @@ -1,44 +1,48 @@ -'use babel' - -export function getColors(selectors) { - let grammar = atom.grammars.grammarForScopeName("source.julia") - - let styled = {} - let color = {} - let div = document.createElement('div') - div.classList.add('editor', 'editor-colors', 'julia-syntax-color-selector') - - for (let style in selectors) { - let child = document.createElement('span') - child.innerText = 'foo' +"use strict" +// TODO make sure rgb2hex returns string +Object.defineProperty(exports, "__esModule", { value: true }) +function getColors(selectors) { + // const grammar = atom.grammars.grammarForScopeName("source.julia") + const styled = {} + const color = {} + const div = document.createElement("div") + div.classList.add("editor", "editor-colors", "julia-syntax-color-selector") + for (const style in selectors) { + const child = document.createElement("span") + child.innerText = "foo" child.classList.add(...selectors[style]) div.appendChild(child) styled[style] = child } - document.body.appendChild(div) // wait till rendered? - for (let style in selectors) { + for (const style in selectors) { + // TODO do we need try catch try { - color[style] = rgb2hex(window.getComputedStyle(styled[style])['color']) + color[style] = rgb2hex(window.getComputedStyle(styled[style]).color) } catch (e) { console.error(e) } } - color['background'] = rgb2hex(window.getComputedStyle(div)['backgroundColor']) + color.background = rgb2hex(window.getComputedStyle(div).backgroundColor) document.body.removeChild(div) - return color } - +exports.getColors = getColors function rgb2hex(rgb) { - if (rgb.search("rgb") == -1) { + if (rgb.search("rgb") === -1) { return rgb } else { - rgb = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/) - function hex(x) { - return ("0" + parseInt(x).toString(16)).slice(-2); + const rgb_match = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/) + if (rgb_match) { + return hex(rgb_match[1]) + hex(rgb_match[2]) + hex(rgb_match[3]) + } else { + console.warn(rgb.concat("rgb_match is undefined!")) + return undefined } - return hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); } } +function hex(x) { + return ("0" + parseInt(x, 10).toString(16)).slice(-2) +} +//# sourceMappingURL=colors.js.map diff --git a/lib/misc/colors.js.map b/lib/misc/colors.js.map new file mode 100644 index 00000000..698a6ed9 --- /dev/null +++ b/lib/misc/colors.js.map @@ -0,0 +1 @@ +{"version":3,"file":"colors.js","sourceRoot":"","sources":["../../lib_src/misc/colors.ts"],"names":[],"mappings":";AAAA,wCAAwC;;AAExC,SAAgB,SAAS,CAAC,SAAkC;IAC1D,oEAAoE;IACpE,MAAM,MAAM,GAAqC,EAAE,CAAA;IACnD,MAAM,KAAK,GAA4B,EAAE,CAAA;IAEzC,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACzC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,eAAe,EAAE,6BAA6B,CAAC,CAAA;IAE3E,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE;QAC7B,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAA;QAC5C,KAAK,CAAC,SAAS,GAAG,KAAK,CAAA;QACvB,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;QACxC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,CAAA;QACtB,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAA;KACtB;IAED,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IAC9B,sBAAsB;IACtB,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE;QAC7B,4BAA4B;QAC5B,IAAI;YACF,KAAK,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;SACrE;QAAC,OAAO,CAAC,EAAE;YACV,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;SACjB;KACF;IACD,KAAK,CAAC,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,eAAe,CAAC,CAAA;IACxE,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IAE9B,OAAO,KAAK,CAAA;AACd,CAAC;AA9BD,8BA8BC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE;QAC5B,OAAO,GAAG,CAAA;KACX;SAAM;QACL,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAA;QAE/E,IAAI,SAAS,EAAE;YACf,OAAO,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;SAC/D;aAAM;YACL,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAA;YACnD,OAAO,SAAS,CAAA;SACjB;KACF;AACH,CAAC;AAED,SAAS,GAAG,CAAC,CAAS;IACpB,OAAO,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACvD,CAAC"} \ No newline at end of file diff --git a/lib/misc/scopes.d.ts b/lib/misc/scopes.d.ts new file mode 100644 index 00000000..43e50376 --- /dev/null +++ b/lib/misc/scopes.d.ts @@ -0,0 +1,10 @@ +import { PointCompatible, TextEditor } from "atom"; +export declare function isStringScope(scopes: readonly string[]): boolean; +export declare function forLines(editor: TextEditor, start: number, end: number): string[]; +export declare function isCommentScope(scopes: readonly string[]): boolean; +/** + * Returns `true` if the scope at `bufferPosition` in `editor` is valid code scope to be inspected. + * Supposed to be used within Atom-IDE integrations, whose `grammarScopes` setting doesn't support + * embedded scopes by default. + */ +export declare function isValidScopeToInspect(editor: TextEditor, bufferPosition: PointCompatible): boolean; diff --git a/lib/misc/scopes.js b/lib/misc/scopes.js index 83efecc7..5719306c 100644 --- a/lib/misc/scopes.js +++ b/lib/misc/scopes.js @@ -1,42 +1,53 @@ -/** @babel */ - -import { Point, Range } from 'atom' - -const juliaScopes = ['source.julia', 'source.embedded.julia'] +"use strict" +Object.defineProperty(exports, "__esModule", { value: true }) +const atom_1 = require("atom") +const juliaScopes = ["source.julia", "source.embedded.julia"] const openers = [ - 'if', 'while', 'for', 'begin', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable', - 'struct', 'mutable struct', 'try', 'let', 'do', 'quote', 'abstract type', 'primitive type' + "if", + "while", + "for", + "begin", + "function", + "macro", + "module", + "baremodule", + "type", + "immutable", + "struct", + "mutable struct", + "try", + "let", + "do", + "quote", + "abstract type", + "primitive type" ] -const reopeners = [ 'else', 'elseif', 'catch', 'finally' ] - -function isKeywordScope (scopes) { +const reopeners = ["else", "elseif", "catch", "finally"] +function isKeywordScope(scopes) { // Skip 'source.julia' return scopes.slice(1).some(scope => { - return scope.indexOf('keyword') > -1 + return scope.indexOf("keyword") > -1 }) } - -export function isStringScope (scopes) { +function isStringScope(scopes) { let isString = false let isInterp = false for (const scope of scopes) { - if (scope.indexOf('string') > -1) { + if (scope.indexOf("string") > -1) { isString = true } - if (scope.indexOf('interpolation') > -1) { + if (scope.indexOf("interpolation") > -1) { isInterp = true } } return isString && !isInterp } - -function forRange (editor, range) { +exports.isStringScope = isStringScope +function forRange(editor, range) { // this should happen here and not a top-level so that we aren't relying on // Atom to load packages in a specific order: - const juliaGrammar = atom.grammars.grammarForScopeName('source.julia') - + const juliaGrammar = atom.grammars.grammarForScopeName("source.julia") if (juliaGrammar === undefined) return [] - const scopes = [] let n_parens = 0 let n_brackets = 0 @@ -45,61 +56,59 @@ function forRange (editor, range) { lineTokens.forEach(token => { const { value } = token if (!isStringScope(token.scopes)) { - if (n_parens > 0 && value === ')') { + if (n_parens > 0 && value === ")") { n_parens -= 1 - scopes.splice(scopes.lastIndexOf('paren'), 1) + scopes.splice(scopes.lastIndexOf("paren"), 1) return - } else if (n_brackets > 0 && value === ']') { + } else if (n_brackets > 0 && value === "]") { n_brackets -= 1 - scopes.splice(scopes.lastIndexOf('bracket'), 1) + scopes.splice(scopes.lastIndexOf("bracket"), 1) return - } else if (value === '(') { + } else if (value === "(") { n_parens += 1 - scopes.push('paren') + scopes.push("paren") return - } else if (value === '[') { + } else if (value === "[") { n_brackets += 1 - scopes.push('bracket') + scopes.push("bracket") return } } - if (!(isKeywordScope(token.scopes))) return + if (!isKeywordScope(token.scopes)) return if (!(n_parens === 0 && n_brackets === 0)) return - const reopen = reopeners.includes(value) - if (value === 'end' || reopen) scopes.pop() + if (value === "end" || reopen) scopes.pop() if (openers.includes(value) || reopen) scopes.push(value) }) }) return scopes } - -export function forLines (editor, start, end) { - const startPoint = new Point(start, 0) - const endPoint = new Point(end, Infinity) - const range = new Range(startPoint, endPoint) +function forLines(editor, start, end) { + const startPoint = new atom_1.Point(start, 0) + const endPoint = new atom_1.Point(end, Infinity) + const range = new atom_1.Range(startPoint, endPoint) return forRange(editor, range) } - -export function isCommentScope (scopes) { +exports.forLines = forLines +function isCommentScope(scopes) { // Skip 'source.julia' return scopes.slice(1).some(scope => { - return scope.indexOf('comment') > -1 + return scope.indexOf("comment") > -1 }) } - +exports.isCommentScope = isCommentScope /** * Returns `true` if the scope at `bufferPosition` in `editor` is valid code scope to be inspected. * Supposed to be used within Atom-IDE integrations, whose `grammarScopes` setting doesn't support * embedded scopes by default. */ -export function isValidScopeToInspect (editor, bufferPosition) { - const scopes = editor - .scopeDescriptorForBufferPosition(bufferPosition) - .getScopesArray() +function isValidScopeToInspect(editor, bufferPosition) { + const scopes = editor.scopeDescriptorForBufferPosition(bufferPosition).getScopesArray() return scopes.some(scope => { return juliaScopes.includes(scope) - }) ? - !isCommentScope(scopes) && !isStringScope(scopes) : - false + }) + ? !isCommentScope(scopes) && !isStringScope(scopes) + : false } +exports.isValidScopeToInspect = isValidScopeToInspect +//# sourceMappingURL=scopes.js.map diff --git a/lib/misc/scopes.js.map b/lib/misc/scopes.js.map new file mode 100644 index 00000000..79dfa81a --- /dev/null +++ b/lib/misc/scopes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../lib_src/misc/scopes.ts"],"names":[],"mappings":";;AAAA,+BAA+E;AAE/E,MAAM,WAAW,GAAG,CAAC,cAAc,EAAE,uBAAuB,CAAC,CAAA;AAC7D,MAAM,OAAO,GAAG;IACd,IAAI;IACJ,OAAO;IACP,KAAK;IACL,OAAO;IACP,UAAU;IACV,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,MAAM;IACN,WAAW;IACX,QAAQ;IACR,gBAAgB;IAChB,KAAK;IACL,KAAK;IACL,IAAI;IACJ,OAAO;IACP,eAAe;IACf,gBAAgB;CACjB,CAAA;AACD,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,CAAA;AAExD,SAAS,cAAc,CAAC,MAAgB;IACtC,sBAAsB;IACtB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QAClC,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAgB,aAAa,CAAC,MAAyB;IACrD,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,IAAI,QAAQ,GAAG,KAAK,CAAA;IACpB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;QAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE;YAChC,QAAQ,GAAG,IAAI,CAAA;SAChB;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE;YACvC,QAAQ,GAAG,IAAI,CAAA;SAChB;KACF;IACD,OAAO,QAAQ,IAAI,CAAC,QAAQ,CAAA;AAC9B,CAAC;AAZD,sCAYC;AAED,SAAS,QAAQ,CAAC,MAAkB,EAAE,KAAsB;IAC1D,2EAA2E;IAC3E,6CAA6C;IAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,cAAc,CAAC,CAAA;IAEtE,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IAEzC,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,MAAM,IAAI,GAAG,MAAM,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAA;IAC/C,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;QACpD,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;YACzB,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAA;YACvB,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;gBAChC,IAAI,QAAQ,GAAG,CAAC,IAAI,KAAK,KAAK,GAAG,EAAE;oBACjC,QAAQ,IAAI,CAAC,CAAA;oBACb,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAA;oBAC7C,OAAM;iBACP;qBAAM,IAAI,UAAU,GAAG,CAAC,IAAI,KAAK,KAAK,GAAG,EAAE;oBAC1C,UAAU,IAAI,CAAC,CAAA;oBACf,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAA;oBAC/C,OAAM;iBACP;qBAAM,IAAI,KAAK,KAAK,GAAG,EAAE;oBACxB,QAAQ,IAAI,CAAC,CAAA;oBACb,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;oBACpB,OAAM;iBACP;qBAAM,IAAI,KAAK,KAAK,GAAG,EAAE;oBACxB,UAAU,IAAI,CAAC,CAAA;oBACf,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;oBACtB,OAAM;iBACP;aACF;YACD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC;gBAAE,OAAM;YACzC,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC,CAAC;gBAAE,OAAM;YAEjD,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;YACxC,IAAI,KAAK,KAAK,KAAK,IAAI,MAAM;gBAAE,MAAM,CAAC,GAAG,EAAE,CAAA;YAC3C,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM;gBAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC3D,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAgB,QAAQ,CAAC,MAAkB,EAAE,KAAa,EAAE,GAAW;IACrE,MAAM,UAAU,GAAG,IAAI,YAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IACtC,MAAM,QAAQ,GAAG,IAAI,YAAK,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,IAAI,YAAK,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IAC7C,OAAO,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;AAChC,CAAC;AALD,4BAKC;AAED,SAAgB,cAAc,CAAC,MAAyB;IACtD,sBAAsB;IACtB,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QAClC,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACtC,CAAC,CAAC,CAAA;AACJ,CAAC;AALD,wCAKC;AAED;;;;GAIG;AACH,SAAgB,qBAAqB,CAAC,MAAkB,EAAE,cAA+B;IACvF,MAAM,MAAM,GAAG,MAAM;SAClB,gCAAgC,CAAC,cAAc,CAAC;SAChD,cAAc,EAAE,CAAA;IACnB,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QACzB,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC,CAAC;QACA,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;QACnD,CAAC,CAAC,KAAK,CAAA;AACX,CAAC;AATD,sDASC"} \ No newline at end of file diff --git a/lib/misc/tsconfig.tsbuildinfo b/lib/misc/tsconfig.tsbuildinfo new file mode 100644 index 00000000..df8e21a9 --- /dev/null +++ b/lib/misc/tsconfig.tsbuildinfo @@ -0,0 +1,3 @@ +{ + "version": "3.8.3" +} \ No newline at end of file diff --git a/lib_src/connection.ts b/lib_src/connection.ts new file mode 100644 index 00000000..365940eb --- /dev/null +++ b/lib_src/connection.ts @@ -0,0 +1,73 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { time } from './misc'; +import externalTerminal from './connection/terminal'; + +export default { + IPC: require('./connection/ipc'), + messages: require('./connection/messages'), + client: require('./connection/client'), + local: require('./connection/local'), + terminal: require('./connection/terminal'), + + activate() { + this.messages.activate(); + this.client.activate(); + this.client.boot = () => this.boot(); + this.local.activate(); + return this.booting = false; + }, + + deactivate() { + return this.client.deactivate(); + }, + + consumeInk(ink) { + this.IPC.consumeInk(ink); + return this.ink = ink; + }, + + consumeGetServerConfig(getconf) { + return this.local.consumeGetServerConfig(getconf); + }, + + consumeGetServerName(name) { + return this.local.consumeGetServerName(name); + }, + + _boot(provider) { + if (!this.client.isActive() && !this.booting) { + let p; + this.booting = true; + this.client.setBootMode(provider); + if (provider === 'External Terminal') { + p = externalTerminal.connectedRepl(); + } else { + p = this.local.start(provider); + } + + if (this.ink != null) { + this.ink.Opener.allowRemoteFiles(provider === 'Remote'); + } + p.then(() => { + return this.booting = false; + }); + p.catch(() => { + return this.booting = false; + }); + return time("Julia Boot", this.client.import('ping')()); + } + }, + + bootRemote() { + return this._boot('Remote'); + }, + + boot() { + return this._boot(atom.config.get('julia-client.juliaOptions.bootMode')); + } +}; diff --git a/lib_src/connection/client.ts b/lib_src/connection/client.ts new file mode 100644 index 00000000..c1fd2cbc --- /dev/null +++ b/lib_src/connection/client.ts @@ -0,0 +1,253 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { throttle } from 'underscore-plus'; +import { Emitter } from 'atom'; +import IPC from './ipc'; + +export default { + + // Connection logic injects a connection via `attach`. + //# Required interface: + // .message(json) + //# Optional interface: + // .stdin(data) + // .interrupt() + // .kill() + + // Messaging + + ipc: new IPC, + + handle(...a) { return this.ipc.handle(...Array.from(a || [])); }, + input(m) { return this.ipc.input(m); }, + readStream(s) { return this.ipc.readStream(s); }, + import(...a) { return this.ipc.import(...Array.from(a || [])); }, + + activate() { + + this.emitter = new Emitter; + + this.bootMode = atom.config.get('julia-client.juliaOptions.bootMode'); + + this.ipc.writeMsg = msg => { + if (this.isActive() && ((typeof this.conn.ready === 'function' ? this.conn.ready() : undefined) !== false)) { + return this.conn.message(msg); + } else { + return this.ipc.queue.push(msg); + } + }; + + this.handle('error', options => { + if (atom.config.get('julia-client.uiOptions.errorNotifications')) { + atom.notifications.addError(options.msg, options); + } + console.error(options.detail); + return atom.beep(); + }); + + let plotpane = null; + + this.onAttached(() => { + const args = atom.config.get('julia-client.juliaOptions.arguments'); + this.import('connected')(); + if (args.length > 0) { + this.import('args')(args); + } + + return plotpane = atom.config.observe('julia-client.uiOptions.usePlotPane', use => { + return this.import('enableplotpane')(use); + }); + }); + + this.onDetached(() => { + return (plotpane != null ? plotpane.dispose() : undefined); + }); + + return this.onBoot(proc => { + return this.remoteConfig = proc.config; + }); + }, + + setBootMode(bootMode) { + this.bootMode = bootMode; + }, + + editorPath(ed) { + if ((ed == null)) { return ed; } + if ((this.bootMode === 'Remote') && (this.remoteConfig != null)) { + let path = ed.getPath(); + if ((path == null)) { return path; } + const ind = path.indexOf(this.remoteConfig.host); + if (ind > -1) { + path = path.slice(ind + this.remoteConfig.host.length, path.length); + path = path.replace(/\\/g, '/'); + return path; + } else { + return path; + } + } else { + return ed.getPath(); + } + }, + + deactivate() { + this.emitter.dispose(); + if (this.isActive()) { return this.detach(); } + }, + + // Basic handlers (communication through stderr) + + basicHandlers: {}, + + basicHandler(s) { + let match; + if (match = s.toString().match(/juno-msg-(.*)/)) { + if (typeof this.basicHandlers[match[1]] === 'function') { + this.basicHandlers[match[1]](); + } + return true; + } + }, + + handleBasic(msg, f) { return this.basicHandlers[msg] = f; }, + + // Connecting & Booting + + emitter: new Emitter, + + onAttached(cb) { return this.emitter.on('attached', cb); }, + onDetached(cb) { return this.emitter.on('detached', cb); }, + + onceAttached(cb) { + let f; + return f = this.onAttached(function(...args) { + f.dispose(); + return cb.call(this, ...Array.from(args)); + }); + }, + + isActive() { return (this.conn != null); }, + + attach(conn) { + this.conn = conn; + if ((typeof this.conn.ready === 'function' ? this.conn.ready() : undefined) !== false) { this.flush(); } + return this.emitter.emit('attached'); + }, + + detach() { + delete this.conn; + this.ipc.reset(); + return this.emitter.emit('detached'); + }, + + flush() { return this.ipc.flush(); }, + + isWorking() { return this.ipc.isWorking(); }, + onWorking(f) { return this.ipc.onWorking(f); }, + onDone(f) { return this.ipc.onDone(f); }, + onceDone(f) { return this.ipc.onceDone(f); }, + + // Management & UI + + onStdout(f) { return this.emitter.on('stdout', f); }, + onStderr(f) { return this.emitter.on('stderr', f); }, + onInfo(f) { return this.emitter.on('info', f); }, + onBoot(f) { return this.emitter.on('boot', f); }, + stdout(data) { return this.emitter.emit('stdout', data); }, + stderr(data) { if (!this.basicHandler(data)) { return this.emitter.emit('stderr', data); } }, + info(data) { return this.emitter.emit('info', data); }, + + clientCall(name, f, ...args) { + if ((this.conn[f] == null)) { + return atom.notifications.addError(`This client doesn't support ${name}.`); + } else { + return this.conn[f].call(this.conn, ...Array.from(args)); + } + }, + + stdin(data) { return this.clientCall('STDIN', 'stdin', data); }, + + interrupt() { + if (this.isActive()) { + return this.clientCall('interrupts', 'interrupt'); + } + }, + + disconnect() { + if (this.isActive()) { + return this.clientCall('disconnecting', 'disconnect'); + } + }, + + kill() { + if (this.isActive()) { + if (!this.isWorking()) { + return this.import('exit')().catch(function() {}); + } else { + return this.clientCall('kill', 'kill'); + } + } else { + return this.ipc.reset(); + } + }, + + clargs() { + const {precompiled, optimisationLevel, deprecationWarnings} = + atom.config.get('julia-client.juliaOptions'); + let as = []; + as.push(`--depwarn=${deprecationWarnings ? 'yes' : 'no'}`); + if (optimisationLevel !== 2) { as.push(`-O${optimisationLevel}`); } + as.push("--color=yes"); + as.push("-i"); + const startupArgs = atom.config.get('julia-client.juliaOptions.startupArguments'); + if (startupArgs.length > 0) { + as = as.concat(startupArgs); + } + as = as.map(arg => arg.trim()); + as = as.filter(arg => arg.length > 0); + return as; + }, + + connectedError(action = 'do that') { + if (this.isActive()) { + atom.notifications.addError(`Can't ${action} with a Julia client running.`, + {description: "Stop the current client with `Packages -> Juno -> Stop Julia`."}); + return true; + } else { + return false; + } + }, + + notConnectedError(action = 'do that') { + if (!this.isActive()) { + atom.notifications.addError(`Can't ${action} without a Julia client running.`, + {description: "Start a client with `Packages -> Juno -> Start Julia`."}); + return true; + } else { + return false; + } + }, + + require(a, f) { + if (f == null) { [a, f] = Array.from([null, a]); } + return this.notConnectedError(a) || f(); + }, + + disrequire(a, f) { + if (f == null) { [a, f] = Array.from([null, a]); } + return this.connectedError(a) || f(); + }, + + withCurrent(f) { + const current = this.conn; + return (...a) => { + if (current !== this.conn) { return; } + return f(...Array.from(a || [])); + }; + } +}; diff --git a/lib_src/connection/ipc.ts b/lib_src/connection/ipc.ts new file mode 100644 index 00000000..0a7f49b4 --- /dev/null +++ b/lib_src/connection/ipc.ts @@ -0,0 +1,160 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS201: Simplify complex destructure assignments + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let IPC; +let Loading = null; +const lwaits = []; +const withLoading = function(f) { if (Loading != null) { return f(); } else { return lwaits.push(f); } }; + +const {bufferLines} = require('../misc'); + +export default IPC = class IPC { + + static consumeInk(ink) { + ({ + Loading + } = ink); + return lwaits.map((f) => f()); + } + + constructor(stream) { + withLoading(() => { + return this.loading = new Loading; + }); + this.handlers = {}; + this.callbacks = {}; + this.queue = []; + this.id = 0; + + if (stream != null) { this.setStream(stream); } + + this.handle({ + cb: (id, result) => { + if (this.callbacks[id] != null) { + this.callbacks[id].resolve(result); + } + return delete this.callbacks[id]; + }, + + cancelCallback: (id, e) => { + return this.callbacks[id].reject(e); + } + }); + } + + handle(type, f?) { + if (f != null) { + return this.handlers[type] = f; + } else { + return (() => { + const result = []; + for (let t in type) { + f = type[t]; + result.push(this.handle(t, f)); + } + return result; + })(); + } + } + + writeMsg() { throw new Error('msg not implemented'); } + + msg(type, ...args) { return this.writeMsg([type, ...Array.from(args)]); } + + rpc(type, ...args) { + const p = new Promise((resolve, reject) => { + this.id += 1; + this.callbacks[this.id] = {resolve, reject}; + return this.msg({type, callback: this.id}, ...Array.from(args)); + }); + return (this.loading != null ? this.loading.monitor(p) : undefined); + } + + flush() { + for (let msg of this.queue) { this.writeMsg(msg); } + return this.queue = []; + } + + reset() { + if (this.loading != null) { + this.loading.reset(); + } + this.queue = []; + for (let id in this.callbacks) { const cb = this.callbacks[id]; cb.reject('disconnected'); } + return this.callbacks = {}; + } + + input(...args) { + let callback, type; + [type, ...args] = Array.from(args[0]); + if (type.constructor === Object) { + ({type, callback} = type); + } + if (this.handlers.hasOwnProperty(type)) { + const result = Promise.resolve().then(() => this.handlers[type](...Array.from(args || []))); + if (callback) { + return result + .then(result => this.msg('cb', callback, result)) + .catch(err => { + console.error(err); + return this.msg('cancelCallback', callback, this.errJson(err)); + }); + } + } else { + return console.log(`julia-client: unrecognised message ${type}`, args); + } + } + + import(fs, rpc = true, mod = {}) { + if (fs == null) { return; } + if (fs.constructor === String) { return this.import([fs], rpc, mod)[fs]; } + if ((fs.rpc != null) || (fs.msg != null)) { + mod = {}; + this.import(fs.rpc, true, mod); + this.import(fs.msg, false, mod); + } else { + fs.forEach(f => { + return mod[f] = (...args) => { + if (rpc) { return this.rpc(f, ...Array.from(args)); } else { return this.msg(f, ...Array.from(args)); } + }; + }); + } + return mod; + } + + isWorking() { return (this.loading != null ? this.loading.isWorking() : undefined); } + onWorking(f) { return (this.loading != null ? this.loading.onWorking(f) : undefined); } + onDone(f) { return (this.loading != null ? this.loading.onDone(f) : undefined); } + onceDone(f) { return (this.loading != null ? this.loading.onceDone(f) : undefined); } + + errJson(obj) { + if (!(obj instanceof Error)) { return; } + return {type: 'error', message: obj.message, stack: obj.stack}; + } + + readStream(s) { + let cb; + s.on('data', (cb = bufferLines(m => { if (m) { return this.input(JSON.parse(m)); } }))); + return this.unreadStream = () => s.removeListener('data', cb); + } + + writeStream(s) { + return this.writeMsg = function(m) { + s.write(JSON.stringify(m)); + return s.write('\n'); + }; + } + + setStream(stream) { + this.stream = stream; + this.readStream(this.stream); + this.writeStream(this.stream); + return this.stream.on('end', () => this.reset()); + } +}; diff --git a/lib_src/connection/local.ts b/lib_src/connection/local.ts new file mode 100644 index 00000000..9bf6fadb --- /dev/null +++ b/lib_src/connection/local.ts @@ -0,0 +1,129 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS201: Simplify complex destructure assignments + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { paths } from '../misc'; +import messages from './messages'; +import client from './client'; + +const junorc = client.import('junorc', false); + +const cycler = require('./process/cycler'); +const ssh = require('./process/remote'); +const basic = require('./process/basic'); + +export default { + consumeGetServerConfig(getconf) { + return ssh.consumeGetServerConfig(getconf); + }, + + consumeGetServerName(name) { + return ssh.consumeGetServerName(name); + }, + + provider(p) { + let bootMode = undefined; + if (p != null) { + bootMode = p; + } else { + bootMode = atom.config.get('julia-client.juliaOptions.bootMode'); + } + switch (bootMode) { + case 'Cycler': return cycler; + case 'Remote': return ssh; + case 'Basic': return basic; + } + }, + + activate() { + if (process.platform === 'win32') { + process.env.JULIA_EDITOR = `\"${process.execPath}\" ${atom.devMode ? '-d' : ''} -a`; + } else { + process.env.JULIA_EDITOR = `atom ${atom.devMode ? '-d' : ''} -a`; + } + + return paths.getVersion() + .then(() => { + return __guardMethod__(this.provider(), 'start', o => o.start(paths.jlpath(), client.clargs())); + }).catch(function() {}); + }, + + monitor(proc) { + client.emitter.emit('boot', proc); + proc.ready = () => false; + client.attach(proc); + return proc; + }, + + connect(proc, sock) { + proc.message = m => sock.write(JSON.stringify(m)); + client.readStream(sock); + sock.on('end', function() { + proc.kill(); + return client.detach(); + }); + sock.on('error', function() { + proc.kill(); + return client.detach(); + }); + proc.ready = () => true; + client.flush(); + return proc; + }, + + start(provider) { + const [path, args] = Array.from([paths.jlpath(), client.clargs()]); + let check = paths.getVersion(); + + if (provider === 'Remote') { + check = Promise.resolve(); + } else { + check.catch(err => { + return messages.jlNotFound(paths.jlpath(), err); + }); + } + + const proc = check + .then(() => this.spawnJulia(path, args, provider)) + .then(proc => this.monitor(proc)); + + // set working directory here, so we queue this task before anything else + if (provider === 'Remote') { + ssh.withRemoteConfig(conf => junorc(conf.remote)).catch(function() {}); + } else { + paths.projectDir().then(dir => junorc(dir)); + } + + proc + .then(proc => { + return Promise.all([proc, proc.socket]); + }) + .then((...args1) => { + let sock; + let proc; + [proc, sock] = Array.from(args1[0]); + return this.connect(proc, sock); + }).catch(function(e) { + client.detach(); + return console.error(`Julia exited with ${e}.`); + }); + return proc; + }, + + spawnJulia(path, args, provider) { + return this.provider(provider).get(path, args); + } +}; + +function __guardMethod__(obj, methodName, transform) { + if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { + return transform(obj, methodName); + } else { + return undefined; + } +} \ No newline at end of file diff --git a/lib_src/connection/messages.ts b/lib_src/connection/messages.ts new file mode 100644 index 00000000..732310d0 --- /dev/null +++ b/lib_src/connection/messages.ts @@ -0,0 +1,138 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import client from './client'; +import tcp from './process/tcp'; + +export default { + activate() { + + client.handleBasic('install', () => { + if (this.note != null) { + this.note.dismiss(); + } + return atom.notifications.addError("Error installing Atom.jl package", { + description: + `\ +Go to the \`Packages -> Juno -> Open REPL\` menu and +run \`Pkg.add("Atom")\` in Julia, then try again. +If you still see an issue, please report it to: + +https://discourse.julialang.org/\ +`, + dismissable: true + } + ); + }); + + client.handleBasic('load', () => { + if (this.note != null) { + this.note.dismiss(); + } + return atom.notifications.addError("Error loading Atom.jl package", { + description: + `\ +Go to the \`Packages -> Juno -> Open REPL\` menu and +run \`Pkg.update()\` in Julia, then try again. +If you still see an issue, please report it to: + +https://discourse.julialang.org/\ +`, + dismissable: true + } + ); + }); + + client.handleBasic('installing', () => { + if (this.note != null) { + this.note.dismiss(); + } + this.note = atom.notifications.addInfo("Installing Julia packages...", { + description: + `\ +Julia's first run will take a couple of minutes. +See the REPL below for progress.\ +`, + dismissable: true + } + ); + return this.openConsole(); + }); + + client.handleBasic('precompiling', () => { + if (this.note != null) { + this.note.dismiss(); + } + this.note = atom.notifications.addInfo("Compiling Julia packages...", { + description: + `\ +Julia's first run will take a couple of minutes. +See the REPL below for progress.\ +`, + dismissable: true + } + ); + return this.openConsole(); + }); + + return client.handle({welcome: () => { + if (this.note != null) { + this.note.dismiss(); + } + atom.notifications.addSuccess("Welcome to Juno!", { + description: + `\ +Success! Juno is set up and ready to roll. +Try entering \`2+2\` in the REPL below.\ +`, + dismissable: true + } + ); + return this.openConsole(); + } + }); + }, + + openConsole() { + return atom.commands.dispatch(atom.views.getView(atom.workspace), + 'julia-client:open-REPL'); + }, + + jlNotFound(path, details = '') { + return atom.notifications.addError("Julia could not be started.", { + description: + `\ +We tried to launch Julia from: \`${path}\` +This path can be changed in the settings.\ +`, + detail: details, + dismissable: true + } + ); + }, + + connectExternal() { + return tcp.listen().then(function(port) { + const code = `using Atom; using Juno; Juno.connect(${port})`; + const msg = atom.notifications.addInfo("Connect an external process", { + description: + `\ +To connect a Julia process running in the terminal, run the command: + + ${code}\ +`, + dismissable: true, + buttons: [{text: 'Copy', onDidClick() { return atom.clipboard.write(code); }}] + }); + return client.onceAttached(function() { + if (!msg.isDismissed()) { + msg.dismiss(); + } + return atom.notifications.addSuccess("Julia is connected."); + }); + }); + } +}; diff --git a/lib_src/connection/process/basic.ts b/lib_src/connection/process/basic.ts new file mode 100644 index 00000000..875a838b --- /dev/null +++ b/lib_src/connection/process/basic.ts @@ -0,0 +1,133 @@ +'use babel' + +import tcp from './tcp' +import * as pty from 'node-pty-prebuilt-multiarch' +import net from 'net' +import { paths, mutex } from '../../misc' +import { jlNotFound } from '../messages' + +export var lock = mutex() + +export function get (path, args) { + return lock((release) => { + let p = get_(path, args) + p.catch((err) => { + release() + }) + release(p.then(({socket}) => socket)) + return p + }) +} + +export function get_ (path, args) { + const env = customEnv() + return getProcess(path, args, env) +} + +export function customEnv (env = process.env) { + let confnt = atom.config.get('julia-client.juliaOptions.numberOfThreads') + let confntInt = parseInt(confnt) + + if (confnt == 'auto') { + env.JULIA_NUM_THREADS = require('physical-cpu-count') + } else if (confntInt != 0 && isFinite(confntInt)) { + env.JULIA_NUM_THREADS = confntInt + } + + if (atom.config.get('julia-client.disableProxy')) { + delete env.HTTP_PROXY + delete env.HTTPS_PROXY + delete env.http_proxy + delete env.https_proxy + } + + return env +} + +function getProcess (path, args, env) { + return new Promise((resolve, reject) => { + tcp.listen().then((port) => { + paths.fullPath(path).then((path) => { + paths.projectDir().then((cwd) => { + // space before port needed for pty.js on windows: + let ty = pty.spawn(path, [...args, paths.script('boot_repl.jl'), ` ${port}`], { + cols: 100, + rows: 30, + env, + cwd, + useConpty: true, + handleFlowControl: true + }) + + let sock = socket(ty) + + sock.catch((err) => { + reject(err) + }) + + // catch errors when interacting with ty, just to be safe (errors might crash Atom) + let proc = { + ty, + kill: () => { + // only kill pty if it's still alive: + if (ty._readable || ty._writable) { + try { + ty.kill() + } catch (err) { + console.log('Terminal:') + console.log(err); + } + } + }, + interrupt: () => { + try { + ty.write('\x03') + } catch (err) { + console.log('Terminal:') + console.log(err); + } + }, + socket: sock, + onExit: (f) => { + try { + ty.on('exit', f) + } catch (err) { + console.log('Terminal:') + console.log(err); + } + }, + onStderr: (f) => {}, + onStdout: (f) => { + try { + ty.on('data', f) + } catch (err) { + console.log('Terminal:') + console.log(err); + } + } + } + + resolve(proc) + }).catch((err) => { + reject(err) + }) + }).catch((err) => { + jlNotFound(path, err) + reject(err) + }) + }).catch((err) => { + reject(err) + }) + }); +} + +function socket (ty) { + conn = tcp.next() + failure = new Promise((resolve, reject) => { + ty.on('exit', (err) => { + conn.dispose() + reject(err) + }) + }) + return Promise.race([conn, failure]) +} diff --git a/lib_src/connection/process/boot.ts b/lib_src/connection/process/boot.ts new file mode 100644 index 00000000..82c40cf0 --- /dev/null +++ b/lib_src/connection/process/boot.ts @@ -0,0 +1,20 @@ +process.on('uncaughtException', function (err) { + if (process.connected) { + process.send({type: 'error', message: err.message, stack: err.stack}) + } + process.exit(1) +}) + +process.on('unhandledRejection', function (err) { + if (process.connected) { + if (err instanceof Error) { + process.send({type: 'rejection', message: err.message, stack: err.stack}) + } else { + process.send({type: 'rejection', err}) + } + } +}) + +const server = require('./server') + +server.serve() diff --git a/lib_src/connection/process/cycler.ts b/lib_src/connection/process/cycler.ts new file mode 100644 index 00000000..483205ce --- /dev/null +++ b/lib_src/connection/process/cycler.ts @@ -0,0 +1,133 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS104: Avoid inline assignments + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { isEqual } from 'underscore-plus'; +import hash from 'object-hash'; +import basic from './basic'; +import IPC from '../ipc'; + +export default { + provider() { + return basic; + }, + + cacheLength: 1, + + procs: {}, + + key(path, args) { return hash([path, ...Array.from(args)].join(' ').trim()); }, + + cache(path, args) { let name; + return this.procs[name = this.key(path, args)] != null ? this.procs[name] : (this.procs[name] = []); }, + + removeFromCache(path, args, obj) { + const key = this.key(path, args); + return this.procs[key] = this.procs[key].filter(x => x !== obj); + }, + + toCache(path, args, proc) { + proc.cached = true; + return this.cache(path, args).push(proc); + }, + + fromCache(path, args) { + const ps = this.cache(path, args); + const p = ps.shift(); + if (p == null) { return; } + p.cached = false; + return p.init.then(() => { + this.start(path, args); + return p.proc; + }); + }, + + start(path, args) { + const allArgs = [args, atom.config.get('julia-client.juliaOptions')]; + this.provider().lock(release => { + if (this.cache(path, allArgs).length < this.cacheLength) { + const p = this.provider().get_(path, args).then(proc => { + const obj = {path, allArgs, proc}; + this.monitor(proc); + this.warmup(obj); + this.toCache(path, allArgs, obj); + proc.socket + .then(() => this.start(path, allArgs)) + .catch(e => this.removeFromCache(path, allArgs, obj)); + return release(proc.socket); + }); + return p.catch(err => { + return release(); + }); + } else { + return release(); + } + }); + }, + + flush(events, out, err) { + return (() => { + const result = []; + for (let {type, data} of events) { + result.push((type === 'stdout' ? out : err)(data)); + } + return result; + })(); + }, + + monitor(proc) { + proc.events = []; + proc.wasCached = true; + proc.onStdout(data => proc.events != null ? proc.events.push({type: 'stdout', data}) : undefined); + proc.onStderr(data => proc.events != null ? proc.events.push({type: 'stderr', data}) : undefined); + return proc.flush = (out, err) => { + this.flush(proc.events, out, err); + return delete proc.events; + }; + }, + + boot(ipc) { return ipc.rpc('ping'); }, + repl(ipc) { return ipc.rpc('changemodule', {mod: 'Main'}); }, + + warmup(obj) { + obj.init = Promise.resolve(); + return obj.proc.socket + .then(sock => { + if (!obj.cached) { return; } + const ipc = new IPC(sock); + [this.boot, this.repl].forEach(f => obj.init = obj.init.then(function() { + if (obj.cached) { return f(ipc); } + })); + obj.init = obj.init + .catch(err => console.warn('julia warmup error:', err)) + .then(() => ipc.unreadStream()); + + }).catch(function() {}); + }, + + get(path, args) { + let p, proc; + const allArgs = [args, atom.config.get('julia-client.juliaOptions')]; + if (proc = this.fromCache(path, allArgs)) { p = proc; + } else { p = this.provider().get(path, args); } + this.start(path, args); + return p; + }, + + reset() { + return (() => { + const result = []; + for (let key in this.procs) { + const ps = this.procs[key]; + result.push(ps.map((p) => + p.proc.kill())); + } + return result; + })(); + } +}; diff --git a/lib_src/connection/process/remote.ts b/lib_src/connection/process/remote.ts new file mode 100644 index 00000000..9042ebe5 --- /dev/null +++ b/lib_src/connection/process/remote.ts @@ -0,0 +1,308 @@ +'use babel' + +import tcp from './tcp' +import net from 'net' +import { paths, mutex } from '../../misc' +import * as ssh from 'ssh2' +import fs from 'fs' + +export var lock = mutex() + +let getRemoteConf = undefined +let getRemoteName = undefined +let serversettings = {} +let currentServer = undefined + +export function get (path, args) { + return lock((release) => { + let p = get_(path, args) + release(p.then(({socket}) => socket)) + p.catch(() => release()) + return p + }) +} + +function getConnectionSettings () { + return new Promise((resolve, reject) => { + if (getRemoteConf) { + let conf = getRemoteConf('Juno requests access to your server configuration to open a terminal.') + conf.then(conf => resolve(conf)).catch(reason => reject(reason)) + } else { + reject('nopackage') + } + }) +} + +export function withRemoteConfig (f) { + return new Promise((resolve, reject) => { + if (getRemoteName === undefined) { + reject() + } else { + getRemoteName().then(name => { + name = name.toString() + let cachedSettings = serversettings[name] + if (cachedSettings) { + resolve(f(maybe_add_agent(cachedSettings))) + } else { + getConnectionSettings().then(conf => { + serversettings[name] = conf + resolve(f(maybe_add_agent(conf))) + }).catch(reason => { + showRemoteError(reason) + reject() + }) + } + }).catch(reason => { + showRemoteError(reason) + reject() + }) + } + }) +} + +function maybe_add_agent (conf) { + if (conf && atom.config.get('julia-client.remoteOptions.agentAuth')) { + let sshsock = ssh_socket() + if (!conf.agent && sshsock) { + conf.agent = sshsock + } + if (!conf.agentForward) { + conf.agentForward = atom.config.get('julia-client.remoteOptions.forwardAgent') + } + } + return conf +} + +function ssh_socket () { + let sock = process.env['SSH_AUTH_SOCK'] + if (sock) { + return sock + } else { + if (process.platform == 'win32') { + return 'pageant' + } else { + return '' + } + } +} + +const storageKey = 'juno_remote_server_exec_key' + +function setRemoteExec (server, command) { + let store = getRemoteStore() + store[server] = command + setRemoteStore(store) +} + +function getRemoteExec (server) { + let store = getRemoteStore() + return store[server] +} + +function setRemoteStore (store) { + localStorage[storageKey] = JSON.stringify(store) +} + +function getRemoteStore () { + let store = localStorage[storageKey] + if (store == undefined) { + store = [] + } else { + store = JSON.parse(store) + } + return store +} + +function showRemoteError (reason) { + if (reason == 'nopackage') { + atom.notifications.addInfo('ftp-remote-edit not installed') + } else if (reason == 'noservers') { + let notif = atom.notifications.addInfo('Please select a project', { + description: `Connect to a server in the ftp-remote-edit tree view.`, + dismissable: true, + buttons: [ + { + text: 'Toggle Remote Tree View', + onDidClick: () => { + let edview = atom.views.getView(atom.workspace) + atom.commands.dispatch(edview, 'ftp-remote-edit:toggle') + notif.dismiss() + } + } + ] + }) + } else { + atom.notifications.addError('Remote Connection Failed', { + details: `Unknown Error: \n\n ${reason}` + }) + } +} + +export function consumeGetServerConfig (getconf) { + getRemoteConf = getconf +} + +export function consumeGetServerName (name) { + getRemoteName = name +} + +export function get_ (path, args) { + return withRemoteConfig(conf => { + let execs = getRemoteExec(conf.name) + if (!execs) { + console.log("open a dialog and get config here") + } + return new Promise((resolve, reject) => { + tcp.listen().then((port) => { + let conn = new ssh.Client() + + conn.on('ready', () => { + conn.forwardIn('127.0.0.1', port, err => { + if (err) { + console.error(`Error while forwarding remote connection from ${port}: ${err}`) + atom.notifications.addError(`Port in use`, { + description: `Port ${port} on the remote server already in use. + Try again with another port.` + }) + reject() + } + }) + let jlpath = atom.config.get('julia-client.remoteOptions.remoteJulia') + + // construct something like + // + // /bin/sh -c 'tmux new -s sessionname '\'' julia -i -e '\'\\\'\''startup_repl'\'\\\'\'' '\''port'\'' '\'' ' + // + // with properly escaped single quotes. + + let exec = '' + if (atom.config.get('julia-client.remoteOptions.tmux')) { + let sessionName = atom.config.get('julia-client.remoteOptions.tmuxName') + exec += `/bin/sh -c '` + exec += `tmux new -s ${sessionName} '\\''` + if (threadCount() !== undefined) { + exec += ` JULIA_NUM_THREADS="${threadCount()}" ` + } + exec += jlpath + exec += ` ${args.join(' ')} -e '\\'\\\\\\'\\''` + // could automatically escape single quotes with `replace(/'/, `'\\'\\\\\\'\\\\\\\\\\\\\\'\\\\\\'\\''`)`, + // but that's so ugly I'd rather not do that + exec += fs.readFileSync(paths.script('boot_repl.jl')).toString() + exec += `'\\'\\\\\\'\\'' ${port} '\\'' ` + exec += `|| tmux send-keys -t ${sessionName}.left ^A ^K ^H '\\''Juno.connect(${port})'\\'' ENTER ` + exec += `&& tmux attach -t ${sessionName} ` + exec += `'` + } else { + exec += `/bin/sh -c '` + if (threadCount() !== undefined) { + exec += `JULIA_NUM_THREADS="${threadCount()}" ` + } + exec += `${jlpath} ${args.join(' ')} -e '\\''` + // could automatically escape single quotes with `replace(/'/, `'\\'\\\\\\'\\''`)`, + // but that's so ugly I'd rather not do that + exec += fs.readFileSync(paths.script('boot_repl.jl')).toString() + exec += `'\\'' ${port}` + exec += `'` + } + + conn.exec(exec, { pty: { term: "xterm-256color" } }, (err, stream) => { + if (err) console.error(`Error while executing command \n\`${exec}\`\n on remote server.`) + + stream.on('close', (err) => { + if (err) { + let description = 'We tried to launch Julia ' + if (atom.config.get('julia-client.remoteOptions.tmux')) { + description += `in a \`tmux\` session named \`${atom.config.get('julia-client.remoteOptions.tmuxName')}\` ` + } + description += `from \`${jlpath}\` but the process failed with \`${err}\`.\n\n` + description += 'Please make sure your settings are correct.' + atom.notifications.addError("Remote Julia session could not be started.", { + description, + dismissable: true + }) + } + conn.end() + }) + stream.on('error', () => { + conn.end() + }) + stream.on('finish', () => { + conn.end() + }) + + let sock = socket(stream) + + // forward resize handling + stream.resize = (cols, rows) => stream.setWindow(rows, cols, 999, 999) + let proc = { + ty: stream, + kill: () => stream.signal('KILL'), + disconnect: () => stream.close(), + interrupt: () => stream.write('\x03'), // signal handling doesn't seem to work :/ + socket: sock, + onExit: (f) => stream.on('close', f), + onStderr: (f) => stream.stderr.on('data', data => f(data.toString())), + onStdout: (f) => stream.on('data', data => f(data.toString())), + config: conf + } + resolve(proc) + }) + }).on('tcp connection', (info, accept, reject) => { + let stream = accept() // connect to forwarded connection + stream.on('close', () => { + conn.end() + }) + stream.on('error', () => { + conn.end() + }) + stream.on('finish', () => { + conn.end() + }) + // start server that the julia server can connect to + let sock = net.createConnection({ port }, () => { + stream.pipe(sock).pipe(stream) + }) + sock.on('close', () => { + conn.end() + }) + sock.on('error', () => { + conn.end() + }) + sock.on('finish', () => { + conn.end() + }) + }).connect(conf) + }).catch((err) => { + let description = 'The following error occured when trying to open a tcp ' + description += 'connection: ' + description += `\`${err}\`` + atom.notifications.addError("Error while connecting to remote Julia session.", { + description, + dismissable: true + }) + reject() + }) + }); + }); +} + +function threadCount () { + let confnt = atom.config.get('julia-client.juliaOptions.numberOfThreads') + let confntInt = parseInt(confnt) + if (confntInt != 0 && isFinite(confntInt)) { + return confntInt + } else { + return undefined + } +} + +function socket (stream) { + conn = tcp.next() + failure = new Promise((resolve, reject) => { + stream.on('close', (err) => { + conn.dispose() + reject(err) + }) + }) + return Promise.race([conn, failure]) +} diff --git a/lib_src/connection/process/server.ts b/lib_src/connection/process/server.ts new file mode 100644 index 00000000..4bcf84de --- /dev/null +++ b/lib_src/connection/process/server.ts @@ -0,0 +1,247 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS201: Simplify complex destructure assignments + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import os from 'os'; +import net from 'net'; +import path from 'path'; +import fs from 'fs'; +import child_process from 'child_process'; +import { exclusive } from '../../misc'; +import IPC from '../ipc'; +import basic from './basic'; +import cycler from './cycler'; + +export default { + + socketPath(name) { + if (process.platform === 'win32') { + return `\\\\.\\pipe\\${name}`; + } else { + return path.join(os.tmpdir(), `${name}.sock`); + } + }, + + removeSocket(name) { + return new Promise((resolve, reject) => { + const p = this.socketPath(name); + return fs.exists(p, function(exists) { + if (!exists) { return resolve(); } + return fs.unlink(p, function(err) { + if (err) { return reject(err); } else { return resolve(); } + }); + }); + }); + }, + + // Client + + boot() { + return this.removeSocket('juno-server').then(() => { + return new Promise((resolve, reject) => { + console.info('booting julia server'); + const proc = child_process.fork(path.join(__dirname, 'boot.js'), { + detached: true, + env: process.env + } + ); + proc.on('message', function(x) { + if (x === 'ready') { return resolve(); + } else { return console.log('julia server:', x); } + }); + return proc.on('exit', function(code, status) { + console.warn('julia server:', [code, status]); + return reject([code, status]); + }); + }); + }); + }, + + connect() { + return new Promise((resolve, reject) => { + var client = net.connect(this.socketPath('juno-server'), () => { + const ipc = new IPC(client); + return resolve(ipc.import(Object.keys(this.serverAPI()), true, {ipc})); + }); + return client.on('error', err => reject(err)); + }); + }, + + activate: exclusive(function() { + if (this.server != null) { return this.server; } + return this.connect() + .catch(err => { + if (['ECONNREFUSED', 'ENOENT'].includes(err.code)) { + return this.boot().then(() => this.connect()); + } else { return Promise.reject(err); } + }).then(server => { + this.server = server; + this.server.ipc.stream.on('end', () => delete this.server); + this.server.setPS(basic.wrapperEnabled()); + return this.server; + }); + }), + + getStream(id, s) { + return this.connect().then(function({ipc}) { + const sock = ipc.stream; + ipc.msg(s, id); + ipc.unreadStream(); + return sock; + }); + }, + + getStreams(id) { return Promise.all((['stdin', 'stdout', 'stderr'].map((s) => this.getStream(id, s)))); }, + + getSocket(id) { + return this.server.onBoot(id).then(() => { + return this.getStream(id, 'socket').then(sock => { + return this.server.onAttach(id).then(() => sock); + }); + }); + }, + + get(path, args) { + return this.activate() + .then(() => this.server.get(path, args)) + .then(id => Promise.all([id, this.getStreams(id), this.server.events(id)])) + .then((...args1) => { + const array = args1[0], id = array[0], [stdin, stdout, stderr] = Array.from(array[1]), events = array[2]; + return { + stdin(data) { return stdin.write(data); }, + onStdout(f) { return stdout.on('data', f); }, + onStderr(f) { return stderr.on('data', f); }, + flush(out, err) { return cycler.flush(events, out, err); }, + interrupt: () => this.server.interrupt(id), + kill: () => this.server.kill(id), + socket: this.getSocket(id), + onExit: f => { + return Promise.race([this.server.onExit(id), + new Promise(resolve => this.server.ipc.stream.on('end', resolve))]) + .then(f); + } + }; + }); + }, + + start(path, args) { + return this.activate() + .then(() => this.server.start(path, args)); + }, + + reset() { + return this.connect() + .then(server => server.exit().catch(function() {})) + .catch(() => atom.notifications.addInfo('No server running.')); + }, + + // Server + + initIPC(sock) { + // TODO: exit once all clients close + const ipc = new IPC(sock); + ipc.handle(this.serverAPI()); + this.streamHandlers(ipc); + return ipc; + }, + + serve() { + cycler.cacheLength = 2; + basic.wrapperEnabled = () => true; + this.server = net.createServer(sock => { + return this.initIPC(sock); + }); + this.server.listen(this.socketPath('juno-server'), () => process.send('ready')); + return this.server.on('error', function(err) { + process.send(err); + return process.exit(); + }); + }, + + pid: 0, + ps: {}, + + serverAPI() { + + return { + setPS(enabled) { return basic.wrapperEnabled = () => enabled; }, + + get: (path, args) => { + return cycler.get(path, args) + .then(p => { + p.id = (this.pid += 1); + this.ps[p.id] = p; + return p.id; + }); + }, + + start(path, args) { return cycler.start(path, args); }, + + onBoot: id => this.ps[id].socket.then(() => true), + onExit: id => new Promise(resolve => this.ps[id].onExit(resolve)), + onAttach: id => this.ps[id].attached.then(function() {}), + interrupt: id => this.ps[id].interrupt(), + kill: id => this.ps[id].kill(), + + events: id => { + const proc = this.ps[id]; + const events = proc.events != null ? proc.events : []; + delete proc.events; + for (let event of events) { + event.data = event.data != null ? event.data.toString() : undefined; + } + return events; + }, + + exit: () => { + cycler.reset(); + for (let id in this.ps) { + const proc = this.ps[id]; + proc.kill(); + } + return process.exit(); + } + }; + }, + + crossStreams(a, b) { + return [[a, b], [b, a]].forEach(function(...args) { + const [from, to] = Array.from(args[0]); + return from.on('data', function(data) { + try { return to.write(data); } + catch (e) { + if (process.connected) { + return process.send({type: 'error', message: e.message, stack: e.stack, data: data.toString()}); + } + } + });}); + }, + + mutualClose(a, b) { + return [[a, b], [b, a]].forEach(function(...args) { + const [from, to] = Array.from(args[0]); + from.on('end', () => to.end()); + return from.on('error', () => to.end()); + }); + }, + + streamHandlers(ipc) { + return ['socket', 'stdout', 'stderr', 'stdin'].forEach(stream => { + return ipc.handle(stream, id => { + const proc = this.ps[id]; + const sock = ipc.stream; + ipc.unreadStream(); + const source = stream === 'socket' ? proc.socket : proc.proc[stream]; + return proc.attached = Promise.resolve(source).then(source => { + this.crossStreams(source, sock); + if (stream === 'socket') { return this.mutualClose(source, sock); + } else { return sock.on('end', () => proc.kill()); } + }); + }); + }); + } +}; diff --git a/lib_src/connection/process/tcp.ts b/lib_src/connection/process/tcp.ts new file mode 100644 index 00000000..d294076e --- /dev/null +++ b/lib_src/connection/process/tcp.ts @@ -0,0 +1,83 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import net from 'net'; +import client from '../client'; + +export default { + server: null, + port: null, + + listeners: [], + + next() { + const conn = new Promise(resolve => { + return this.listeners.push(resolve); + }); + conn.dispose = () => { + return this.listeners = this.listeners.filter(x => x === conn); + }; + return conn; + }, + + connect(sock) { + const message = m => sock.write(JSON.stringify(m)); + client.readStream(sock); + sock.on('end', () => client.detach()); + sock.on('error', () => client.detach()); + return client.attach({message}); + }, + + handle(sock) { + if (this.listeners.length > 0) { + return this.listeners.shift()(sock); + } else if (!client.isActive()) { + return this.connect(sock); + } else { + return sock.end(); + } + }, + + listen() { + if (this.port != null) { return Promise.resolve(this.port); } + return new Promise((resolve, reject) => { + let port; + const externalPort = atom.config.get('julia-client.juliaOptions.externalProcessPort'); + if (externalPort === 'random') { + port = 0; + } else { + port = parseInt(externalPort); + } + this.server = net.createServer(sock => this.handle(sock)); + this.server.on('error', err => { + if (err.code === 'EADDRINUSE') { + let details = ''; + if (port !== 0) { + details = 'Please change to another port in the settings and try again.'; + } + atom.notifications.addError("Julia could not be started.", { + description: `\ +Port \`${port}\` is already in use. +\ +` + (details !== '' ? + `\ +${details}\ +` + : + "Please try again or set a fixed port that you know is unused."), + dismissable: true + } + ); + } + return reject(err); + }); + return this.server.listen(port, '127.0.0.1', () => { + this.port = this.server.address().port; + return resolve(this.port); + }); + }); + } +}; diff --git a/lib_src/connection/terminal.ts b/lib_src/connection/terminal.ts new file mode 100644 index 00000000..7431d02b --- /dev/null +++ b/lib_src/connection/terminal.ts @@ -0,0 +1,70 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import child_process from 'child_process'; +import net from 'net'; +import tcp from './process/tcp'; +import client from './client'; +import { paths } from '../misc'; + +const disrequireClient = (a, f) => client.disrequire(a, f); + +export default { + + escpath(p) { return '"' + p + '"'; }, + escape(sh) { return sh.replace(/"/g, '\\"'); }, + + exec(sh) { + return child_process.exec(sh, function(err, stdout, stderr) { + if (err != null) { + return console.log(err); + } + }); + }, + + term(sh) { + switch (process.platform) { + case "darwin": + this.exec("osascript -e 'tell application \"Terminal\" to activate'"); + return this.exec(`osascript -e 'tell application \"Terminal\" to do script \"${this.escape(sh)}\"'`); + case "win32": + return this.exec(`${this.terminal()} \"${sh}\"`); + default: + return this.exec(`${this.terminal()} \"${this.escape(sh)}\"`); + } + }, + + terminal() { return atom.config.get("julia-client.consoleOptions.terminal"); }, + + defaultShell() { + const sh = process.env["SHELL"]; + if (sh != null) { + return sh; + } else if (process.platform === 'win32') { + return 'powershell.exe'; + } else { + return 'bash'; + } + }, + + defaultTerminal() { + if (process.platform === 'win32') { + return 'cmd /C start cmd /C'; + } else { + return 'x-terminal-emulator -e'; + } + }, + + repl() { return this.term(`${this.escpath(paths.jlpath())}`); }, + + connectCommand() { + return tcp.listen().then(port => { + return `${this.escpath(paths.jlpath())} ${client.clargs().join(' ')} ${paths.script('boot_repl.jl')} ${port}`; + }); + }, + + connectedRepl() { return this.connectCommand().then(cmd => this.term(cmd)); } +}; diff --git a/lib_src/julia-client.ts b/lib_src/julia-client.ts new file mode 100644 index 00000000..ecd2582c --- /dev/null +++ b/lib_src/julia-client.ts @@ -0,0 +1,165 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let JuliaClient; +const etch = require('etch'); +import commands from './package/commands'; +import config from './package/config'; +import menu from './package/menu'; +const settings = require('./package/settings'); +import toolbar from './package/toolbar'; +const semver = require('semver'); + +// TODO: Update me when tagging a new relase: +const INK_VERSION_COMPAT = "^0.12.3" + +const INK_LINK = '[`ink`](https://github.com/JunoLab/atom-ink)'; +const LANGUAGE_JULIA_LINK = '[`language-julia`](https://github.com/JuliaEditorSupport/atom-language-julia)'; + +export default JuliaClient = { + misc: require('./misc'), + ui: require('./ui'), + connection: require('./connection'), + runtime: require('./runtime'), + + activate(state) { + etch.setScheduler(atom.views); + process.env['TERM'] = 'xterm-256color'; + commands.activate(this); + for (let x of [menu, this.connection, this.runtime]) { x.activate(); } + this.ui.activate(this.connection.client); + + return this.requireDeps(() => { + settings.updateSettings(); + + if (atom.config.get('julia-client.firstBoot')) { + return this.ui.layout.queryDefaultLayout(); + } else { + if (atom.config.get('julia-client.uiOptions.layouts.openDefaultPanesOnStartUp')) { + return setTimeout((() => this.ui.layout.restoreDefaultLayout()), 150); + } + } + }); + }, + + requireDeps(fn) { + const isLoaded = atom.packages.isPackageLoaded("ink") && atom.packages.isPackageLoaded("language-julia"); + + if (isLoaded) { + return fn(); + } else { + return require('atom-package-deps').install('julia-client') + .then(() => this.enableDeps(fn)) + .catch(function(err) { + console.error(err); + return atom.notifications.addError('Installing Juno\'s dependencies failed.', { + description: + `\ +Juno requires the packages ${INK_LINK} and ${LANGUAGE_JULIA_LINK} to run. +Please install them manually via \`File -> Settings -> Packages\`, +or open a terminal and run + + apm install ink + apm install language-julia + +and then restart Atom.\ +`, + dismissable: true + } + ); + }); + } + }, + + enableDeps(fn) { + const isEnabled = atom.packages.isPackageLoaded("ink") && atom.packages.isPackageLoaded("language-julia"); + + if (isEnabled) { + return fn(); + } else { + atom.packages.enablePackage('ink'); + atom.packages.enablePackage('language-julia'); + + if (atom.packages.isPackageLoaded("ink") && atom.packages.isPackageLoaded("language-julia")) { + atom.notifications.addSuccess("Automatically enabled Juno's dependencies.", { + description: + `\ +Juno requires the ${INK_LINK} and ${LANGUAGE_JULIA_LINK} packages. +We've automatically enabled them for you.\ +`, + dismissable: true + } + ); + + const inkVersion = atom.packages.loadedPackages["ink"].metadata.version; + if (!atom.devMode && !semver.satisfies(inkVersion, INK_VERSION_COMPAT)) { + atom.notifications.addWarning("Potentially incompatible `ink` version detected.", { + description: + `\ +Please make sure to upgrade ${INK_LINK} to a version compatible with \`${INK_VERSION_COMPAT}\`. +The currently installed version is \`${inkVersion}\`. + +If you cannot install an appropriate version via via \`File -> Settings -> Packages\`, +open a terminal and run + + apm install ink@x.y.z + +where \`x.y.z\` is satisfies \`${INK_VERSION_COMPAT}\`.\ +`, + dismissable: true + } + ); + } + + return fn(); + } else { + return atom.notifications.addError("Failed to enable Juno's dependencies.", { + description: + `\ +Juno requires the ${INK_LINK} and ${LANGUAGE_JULIA_LINK} packages. +Please install them manually via \`File -> Settings -> Packages\`, +or open a terminal and run + + apm install ink + apm install language-julia + +and then restart Atom.\ +`, + dismissable: true + } + ); + } + } + }, + + config, + + deactivate() { + return [commands, menu, toolbar, this.connection, this.runtime, this.ui].map((x) => x.deactivate()); + }, + + consumeInk(ink) { + commands.ink = ink; + return [this.connection, this.runtime, this.ui].map((x) => x.consumeInk(ink)); + }, + + consumeStatusBar(bar) { return this.runtime.consumeStatusBar(bar); }, + + consumeToolBar(bar) { return toolbar.consumeToolBar(bar); }, + + consumeGetServerConfig(conf) { return this.connection.consumeGetServerConfig(conf); }, + + consumeGetServerName(name) { return this.connection.consumeGetServerName(name); }, + + consumeDatatip(datatipService) { return this.runtime.consumeDatatip(datatipService); }, + + provideClient() { return this.connection.client; }, + + provideAutoComplete() { return this.runtime.provideAutoComplete(); }, + + provideHyperclick() { return this.runtime.provideHyperclick(); }, + + handleURI(parsedURI) { return this.runtime.handleURI(parsedURI); } +}; diff --git a/lib_src/misc.ts b/lib_src/misc.ts new file mode 100644 index 00000000..2a261457 --- /dev/null +++ b/lib_src/misc.ts @@ -0,0 +1,94 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { debounce } from 'underscore-plus'; + +export default { + paths: require('./misc/paths'), + blocks: require('./misc/blocks'), + cells: require('./misc/cells'), + words: require('./misc/words'), + weave: require('./misc/weave'), + colors: require('./misc/colors'), + scopes: require('./misc/scopes'), + + bufferLines(t, f) { + if ((f == null)) { [t, f] = Array.from([null, t]); } + const buffer = ['']; + const flush = (t == null) ? function() {} : debounce((function() { + if (buffer[0] !== '') { + f(buffer[0], false); + return buffer[0] = ''; + }}), t); + return function(data) { + const lines = data.toString().split('\n'); + buffer[0] += lines.shift(); + buffer.push(...Array.from(lines || [])); + while (buffer.length > 1) { + f(buffer.shift(), true); + } + return flush(); + }; + }, + + time(desc, p) { + const s = () => new Date().getTime()/1000; + const t = s(); + p.then(() => console.log(`${desc}: ${(s()-t).toFixed(2)}s`)) + .catch(function() {}); + return p; + }, + + hook(obj, method, f) { + const souper = obj[method].bind(obj); + return obj[method] = (...a) => f(souper, ...Array.from(a)); + }, + + once(f) { + let done = false; + return function(...args) { + if (done) { return; } + done = true; + return f.call(this, ...Array.from(args)); + }; + }, + + mutex() { + let wait = Promise.resolve(); + return function(f) { + const current = wait; + let release = null; + wait = new Promise(resolve => release = resolve).catch(function() {}); + return current.then(() => f.call(this, release)); + }; + }, + + exclusive(f) { + const lock = module.exports.mutex(); + return function(...args) { + return lock(release => { + const result = f.call(this, ...Array.from(args)); + release(result); + return result; + }); + }; + }, + + // takes a time period in seconds and formats it as hh:mm:ss + formatTimePeriod(dt) { + if (dt <= 1) { return; } + const h = Math.floor(dt/(60*60)); + const m = Math.floor((dt -= h*60*60)/60); + const s = Math.round((dt - (m*60))); + const parts = [h, m, s]; + for (let i in parts) { + dt = parts[i]; + parts[i] = dt < 10 ? `0${dt}` : `${dt}`; + } + return parts.join(':'); + } +}; diff --git a/lib_src/misc/blocks.ts b/lib_src/misc/blocks.ts new file mode 100644 index 00000000..0fe70b1a --- /dev/null +++ b/lib_src/misc/blocks.ts @@ -0,0 +1,191 @@ +// TODO: docstrings +// TODO: replace getHeadBufferPosition with getBufferRowRange? - getHeadBufferPosition isn't public API +// TODO: make sure returned range from getRanges is not undefined + +import { forLines } from "./scopes" +import { TextEditor, Selection } from "atom" + +interface LineInfo { + scope: readonly string[] + line: string +} + +export function getLine(editor: TextEditor, l: number): LineInfo { + return { + scope: editor.scopeDescriptorForBufferPosition([l, 0]).getScopesArray(), // was .scopes + line: editor.getTextInBufferRange([ + [l, 0], + [l, Infinity] + ]) + } +} + +function isBlank({ line, scope }: LineInfo, allowDocstrings = false) { + for (const s of scope) { + if (/\bcomment\b/.test(s) || (!allowDocstrings && /\bdocstring\b/.test(s))) { + return true + } + } + return /^\s*(#.*)?$/.test(line) +} + +function isEnd(lineInfo: LineInfo) { + if (isStringEnd(lineInfo)) { + return true + } + return /^(end\b|\)|\]|\})/.test(lineInfo.line) +} + +function isStringEnd(lineInfo: LineInfo) { + const scope = lineInfo.scope.join(" ") + return /\bstring\.multiline\.end\b/.test(scope) || (/\bstring\.end\b/.test(scope) && /\bbacktick\b/.test(scope)) +} + +function isCont(lineInfo: LineInfo) { + const scope = lineInfo.scope.join(" ") + if (/\bstring\b/.test(scope) && !/\bpunctuation\.definition\.string\b/.test(scope)) { + return true + } + + return lineInfo.line.match(/^(else|elseif|catch|finally)\b/) +} + +function isStart(lineInfo: LineInfo) { + return !(/^\s/.test(lineInfo.line) || isBlank(lineInfo) || isEnd(lineInfo) || isCont(lineInfo)) +} + +function walkBack(editor: TextEditor, row: number) { + while (row > 0 && !isStart(getLine(editor, row))) { + row-- + } + return row +} + +function walkForward(editor: TextEditor, start: number) { + let end = start + let mark = start + while (mark < editor.getLastBufferRow()) { + mark++ + const lineInfo = getLine(editor, mark) + + if (isStart(lineInfo)) { + break + } + if (isEnd(lineInfo)) { + // An `end` only counts when there still are unclosed blocks (indicated by `forLines` + // returning a non-empty array). + // If the line closes a multiline string we also take that as ending the block. + if (!(forLines(editor, start, mark - 1).length === 0) || isStringEnd(lineInfo)) { + end = mark + } + } else if (!(isBlank(lineInfo) || isStart(lineInfo))) { + end = mark + } + } + return end +} + +function getRange(editor: TextEditor, row: number): [[number, number], [number, number]] | undefined { + const start = walkBack(editor, row) + const end = walkForward(editor, start) + if (start <= row && row <= end) { + return [ + [start, 0], + [end, Infinity] + ] + } else { + return undefined // TODO + } +} + +function getSelection(editor: TextEditor, selection: Selection) { + const { start, end } = selection.getBufferRange() + const range = [ + [start.row, start.column], + [end.row, end.column] + ] + while (isBlank(getLine(editor, range[0][0]), true) && range[0][0] <= range[1][0]) { + range[0][0]++ + range[0][1] = 0 + } + while (isBlank(getLine(editor, range[1][0]), true) && range[1][0] >= range[0][0]) { + range[1][0]-- + range[1][1] = Infinity + } + return range +} + +export function moveNext(editor: TextEditor, selection: Selection, range: [[number, number], [number, number]]) { + // Ensure enough room at the end of the buffer + const row = range[1][0] + let last + while ((last = editor.getLastBufferRow()) < row + 2) { + if (last !== row && !isBlank(getLine(editor, last))) { + break + } + selection.setBufferRange([ + [last, Infinity], + [last, Infinity] + ]) + selection.insertText("\n") + } + // Move the cursor + let to = row + 1 + while (to < editor.getLastBufferRow() && isBlank(getLine(editor, to))) { + to++ + } + to = walkForward(editor, to) + return selection.setBufferRange([ + [to, Infinity], + [to, Infinity] + ]) +} + +function getRanges(editor: TextEditor) { + const ranges = editor.getSelections().map(selection => { + return { + selection: selection, + range: selection.isEmpty() + ? getRange(editor, selection.getHeadBufferPosition().row) + : getSelection(editor, selection) + } + }) + return ranges.filter(({ range }) => { + return range && editor.getTextInBufferRange(range).trim() + }) +} + +export function get(editor: TextEditor) { + return getRanges(editor).map(({ range, selection }) => { + return { + range, + selection, + line: range[0][0], + text: editor.getTextInBufferRange(range) + } + }) +} + +export function getLocalContext(editor: TextEditor, row: number) { + const range = getRange(editor, row) + const context = range ? editor.getTextInBufferRange(range) : "" + // NOTE: + // backend code expects startRow to be number for most cases, e.g.: `row = row - startRow` + // so let's just return `0` when there is no local context + // to check there is a context or not, just check `isempty(context)` + const startRow = range ? range[0][0] : 0 + return { + context, + startRow + } +} + +export function select(editor = atom.workspace.getActiveTextEditor()) { + if (!editor) return + return editor.mutateSelectedText(selection => { + const range = getRange(editor, selection.getHeadBufferPosition().row) + if (range) { + selection.setBufferRange(range) + } + }) +} diff --git a/lib_src/misc/cells.ts b/lib_src/misc/cells.ts new file mode 100644 index 00000000..33d8d6bf --- /dev/null +++ b/lib_src/misc/cells.ts @@ -0,0 +1,121 @@ +import { get as weaveGet, moveNext as weaveMoveNext, movePrev as weaveMovePrev } from "./weave.js" + +import { getLine } from "./blocks.js" + +import { Point, TextEditor } from "atom" + +export function getRange(editor: TextEditor): [Point, Point] { + // Cell range is: + // Start of line below top delimiter (and/or start of top row of file) to + // End of line before end delimiter + const buffer = editor.getBuffer() + const start = buffer.getFirstPosition() + const end = buffer.getEndPosition() + const regexString = "^(" + atom.config.get("julia-client.uiOptions.cellDelimiter").join("|") + ")" + const regex = new RegExp(regexString) + const cursor = editor.getCursorBufferPosition() + cursor.column = Infinity // cursor on delimiter line means eval cell below + + let foundDelim = false + const editor_getLastBufferRow = editor.getLastBufferRow() + for (let i = cursor.row + 1; i <= editor_getLastBufferRow; i++) { + const { line, scope } = getLine(editor, i) + foundDelim = regex.test(line) && scope.join(".").indexOf("comment.line") > -1 + end.row = i + if (foundDelim) break + } + + if (foundDelim) { + end.row -= 1 + if (end.row < 0) end.row = 0 + end.column = Infinity + } + + foundDelim = false + if (cursor.row > 0) { + for (let i = end.row; i >= 0; i--) { + const { line, scope } = getLine(editor, i) + foundDelim = regex.test(line) && scope.join(".").indexOf("comment.line") > -1 + start.row = i + if (foundDelim) { + break + } + } + start.column = 0 + } + + return [start, end] +} + +export function get(editor: TextEditor) { + if (editor.getGrammar().scopeName.indexOf("source.julia") > -1) { + return jlGet(editor) + } else { + return weaveGet(editor) + } +} + +function jlGet(editor: TextEditor) { + const range = getRange(editor) + let text = editor.getTextInBufferRange(range) + if (text.trim() === "") text = " " + const res = { + range: [ + [range[0].row, range[0].column], + [range[1].row, range[1].column] + ], + selection: editor.getSelections()[0], + line: range[0].row, + text + } + return [res] +} + +export function moveNext(editor: TextEditor | null | undefined) { + if (!editor) { + editor = atom.workspace.getActiveTextEditor() + } + if (editor) { + // TODO: do we need this? + if (editor.getGrammar().scopeName.indexOf("source.julia") > -1) { + return jlMoveNext(editor) + } else { + return weaveMoveNext(editor) + } + } else { + console.error("editor isn't acquired!") + } +} + +function jlMoveNext(editor: TextEditor) { + const range = getRange(editor) + const sel = editor.getSelections()[0] + const nextRow = range[1].row + 2 // 2 = 1 to get to delimiter line + 1 more to go past it + return sel.setBufferRange([ + [nextRow, 0], + [nextRow, 0] + ]) +} + +export function movePrev(editor: TextEditor | undefined | null) { + if (!editor) { + editor = atom.workspace.getActiveTextEditor() + } + if (editor) { + if (editor.getGrammar().scopeName.indexOf("source.weave") > -1) { + return weaveMovePrev(editor) + } else { + return jlMovePrev(editor) + } + } +} + +function jlMovePrev(editor: TextEditor) { + const range = getRange(editor) + const prevRow = range[0].row - 2 // 2 = 1 to get to delimiter line + 1 more to go past it + const sel = editor.getSelections()[0] + return sel.setBufferRange([ + [prevRow, 0], + [prevRow, 0] + ]) +} diff --git a/lib_src/misc/colors.ts b/lib_src/misc/colors.ts new file mode 100644 index 00000000..2fef1292 --- /dev/null +++ b/lib_src/misc/colors.ts @@ -0,0 +1,52 @@ +// TODO make sure rgb2hex returns string + +export function getColors(selectors: { [P: string]: string }) { + // const grammar = atom.grammars.grammarForScopeName("source.julia") + const styled: { [P: string]: HTMLSpanElement } = {} + const color: { [P: string]: string } = {} + + const div = document.createElement("div") + div.classList.add("editor", "editor-colors", "julia-syntax-color-selector") + + for (const style in selectors) { + const child = document.createElement("span") + child.innerText = "foo" + child.classList.add(...selectors[style]) + div.appendChild(child) + styled[style] = child + } + + document.body.appendChild(div) + // wait till rendered? + for (const style in selectors) { + // TODO do we need try catch + try { + color[style] = rgb2hex(window.getComputedStyle(styled[style]).color) + } catch (e) { + console.error(e) + } + } + color.background = rgb2hex(window.getComputedStyle(div).backgroundColor) + document.body.removeChild(div) + + return color +} + +function rgb2hex(rgb: string) { + if (rgb.search("rgb") === -1) { + return rgb + } else { + const rgb_match = rgb.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))?\)$/) + + if (rgb_match) { + return hex(rgb_match[1]) + hex(rgb_match[2]) + hex(rgb_match[3]) + } else { + console.warn(rgb.concat("rgb_match is undefined!")) + return undefined + } + } +} + +function hex(x: string) { + return ("0" + parseInt(x, 10).toString(16)).slice(-2) +} diff --git a/lib_src/misc/paths.ts b/lib_src/misc/paths.ts new file mode 100644 index 00000000..9a5e0660 --- /dev/null +++ b/lib_src/misc/paths.ts @@ -0,0 +1,154 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import path from 'path'; +import fs from 'fs'; +import child_process from 'child_process'; + +export default { + + home(...p) { + const key = process.platform === 'win32' ? 'USERPROFILE' : 'HOME'; + return path.join(process.env[key], ...Array.from(p)); + }, + + juliaHome(...p) { + return path.join((process.env.JULIA_HOME || this.home('.julia')), ...Array.from(p)); + }, + + jlpath() { + return this.expandHome(atom.config.get("julia-client.juliaPath")); + }, + + expandHome(p) { + if (p.startsWith('~')) { return p.replace('~', this.home()); } else { return p; } + }, + + fullPath(path) { + return new Promise(function(resolve, reject) { + let proc; + if (fs.existsSync(path)) { resolve(path); } + + if (process.platform === 'win32') { + if (/[a-zA-Z]\:/.test(path)) { + reject("Couldn't resolve path."); + return; + } + } + + const which = process.platform === 'win32' ? 'where' : 'which'; + return proc = child_process.exec(`${which} \"${path}\"`, function(err, stdout, stderr) { + if (err != null) { return reject(stderr); } + const p = stdout.trim(); + if (fs.existsSync(p)) { return resolve(p); } + return reject("Couldn't resolve path."); + }); + }); + }, + + getVersion(path = this.jlpath()) { + return new Promise((resolve, reject) => { + let proc; + return proc = child_process.exec(`\"${path}\" --version`, (err, stdout, stderr) => { + if (err != null) { return reject(stderr); } + const res = stdout.match(/(\d+)\.(\d+)\.(\d+)/); + if (res == null) { return reject("Couldn't resolve version."); } + const [_, major, minor, patch] = Array.from(res); + return resolve({major, minor, patch}); + }); + }); + }, + + projectDir() { + if (atom.config.get('julia-client.juliaOptions.persistWorkingDir')) { + return new Promise(resolve => { + const p = atom.config.get('julia-client.juliaOptions.workingDir'); + try { + return fs.stat(p, (err, stats) => { + if (err) { + return resolve(this.atomProjectDir()); + } else { + return resolve(p); + } + }); + } catch (error) { + const err = error; + return resolve(this.atomProjectDir()); + } + }); + } else { + return this.atomProjectDir(); + } + }, + + atomProjectDir() { + const dirs = atom.workspace.project.getDirectories(); + const ws = process.env.HOME || process.env.USERPROFILE; + if ((dirs.length === 0) || dirs[0].path.match('app.asar')) { + return Promise.resolve(ws); + } + return new Promise(function(resolve) { + // use the first open project folder (or its parent folder for files) if + // it is valid + try { + return fs.stat(dirs[0].path, (err, stats) => { + if (err != null) { + resolve(ws); + return; + } + + if (stats.isFile()) { + return resolve(path.dirname(dirs[0].path)); + } else { + return resolve(dirs[0].path); + } + }); + } catch (error) { + const err = error; + return resolve(ws); + } + }); + }, + + packageDir(...s) { + const packageRoot = path.resolve(__dirname, '..', '..'); + return path.join(packageRoot, ...Array.from(s)); + }, + + script(...s) { return this.packageDir('script', ...Array.from(s)); }, + + getPathFromTreeView(el) { + // invoked from tree-view context menu + let pathEl = el.closest('[data-path]'); + if (!pathEl) { + // invoked from command with focusing on tree-view + const activeEl = el.querySelector('.tree-view .selected'); + if (activeEl) { pathEl = activeEl.querySelector('[data-path]'); } + } + if (pathEl) { return pathEl.dataset.path; } + return null; + }, + + getDirPathFromTreeView(el) { + // invoked from tree-view context menu + let dirEl = el.closest('.directory'); + if (!dirEl) { + // invoked from command with focusing on tree-view + const activeEl = el.querySelector('.tree-view .selected'); + if (activeEl) { dirEl = activeEl.closest('.directory'); } + } + if (dirEl) { + const pathEl = dirEl.querySelector('[data-path]'); + if (pathEl) { return pathEl.dataset.path; } + } + return null; + }, + + readCode(path) { + return fs.readFileSync(path, 'utf-8'); + } +}; diff --git a/lib_src/misc/scopes.ts b/lib_src/misc/scopes.ts new file mode 100644 index 00000000..2f6f41b0 --- /dev/null +++ b/lib_src/misc/scopes.ts @@ -0,0 +1,119 @@ +import {Point, PointCompatible, Range, RangeCompatible, TextEditor} from "atom" + +const juliaScopes = ["source.julia", "source.embedded.julia"] +const openers = [ + "if", + "while", + "for", + "begin", + "function", + "macro", + "module", + "baremodule", + "type", + "immutable", + "struct", + "mutable struct", + "try", + "let", + "do", + "quote", + "abstract type", + "primitive type", +] +const reopeners = ["else", "elseif", "catch", "finally"] + +function isKeywordScope(scopes: string[]) { + // Skip 'source.julia' + return scopes.slice(1).some(scope => { + return scope.indexOf("keyword") > -1 + }) +} + +export function isStringScope(scopes: readonly string[]) { + let isString = false + let isInterp = false + for (const scope of scopes) { + if (scope.indexOf("string") > -1) { + isString = true + } + if (scope.indexOf("interpolation") > -1) { + isInterp = true + } + } + return isString && !isInterp +} + +function forRange(editor: TextEditor, range: RangeCompatible) { + // this should happen here and not a top-level so that we aren't relying on + // Atom to load packages in a specific order: + const juliaGrammar = atom.grammars.grammarForScopeName("source.julia") + + if (juliaGrammar === undefined) return [] + + const scopes: string[] = [] + let n_parens = 0 + let n_brackets = 0 + const text = editor.getTextInBufferRange(range) + juliaGrammar.tokenizeLines(text).forEach(lineTokens => { + lineTokens.forEach(token => { + const { value } = token + if (!isStringScope(token.scopes)) { + if (n_parens > 0 && value === ")") { + n_parens -= 1 + scopes.splice(scopes.lastIndexOf("paren"), 1) + return + } else if (n_brackets > 0 && value === "]") { + n_brackets -= 1 + scopes.splice(scopes.lastIndexOf("bracket"), 1) + return + } else if (value === "(") { + n_parens += 1 + scopes.push("paren") + return + } else if (value === "[") { + n_brackets += 1 + scopes.push("bracket") + return + } + } + if (!isKeywordScope(token.scopes)) return + if (!(n_parens === 0 && n_brackets === 0)) return + + const reopen = reopeners.includes(value) + if (value === "end" || reopen) scopes.pop() + if (openers.includes(value) || reopen) scopes.push(value) + }) + }) + return scopes +} + +export function forLines(editor: TextEditor, start: number, end: number) { + const startPoint = new Point(start, 0) + const endPoint = new Point(end, Infinity) + const range = new Range(startPoint, endPoint) + return forRange(editor, range) +} + +export function isCommentScope(scopes: readonly string[]) { + // Skip 'source.julia' + return scopes.slice(1).some(scope => { + return scope.indexOf("comment") > -1 + }) +} + +/** + * Returns `true` if the scope at `bufferPosition` in `editor` is valid code scope to be inspected. + * Supposed to be used within Atom-IDE integrations, whose `grammarScopes` setting doesn't support + * embedded scopes by default. + */ +export function isValidScopeToInspect(editor: TextEditor, bufferPosition: PointCompatible) { + const scopes = editor + .scopeDescriptorForBufferPosition(bufferPosition) + .getScopesArray() + return scopes.some(scope => { + return juliaScopes.includes(scope) + }) + ? !isCommentScope(scopes) && !isStringScope(scopes) + : false +} diff --git a/lib_src/misc/weave.ts b/lib_src/misc/weave.ts new file mode 100644 index 00000000..a09aaee5 --- /dev/null +++ b/lib_src/misc/weave.ts @@ -0,0 +1,112 @@ +'use babel' + +import 'atom' + +export function getCode (ed) { + const text = ed.getText() + const lines = text.split("\n") + const N = ed.getLineCount() + let code = "" + + for (let i = 0; i < N; i++) { + let { + scopes + } = ed.scopeDescriptorForBufferPosition([i, 0]) + if (scopes.length > 1) { + if (scopes.indexOf("source.embedded.julia") > -1) { + code += lines[i] + "\n" + } + } + } + return code +} + +function getEmbeddedScope (cursor) { + let { + scopes + } = cursor.getScopeDescriptor() + let targetScope = null + for (let scope of scopes) { + if (scope.startsWith('source.embedded.')) { + targetScope = scope + break + } + } + return targetScope +} + +function getCurrentCellRange (ed, cursor) { + let scope = getEmbeddedScope(cursor) + if (scope === null) return null + + let start = cursor.getBufferRow() + let end = start + while (start - 1 >= 0 && + ed.scopeDescriptorForBufferPosition([start - 1, 0]).scopes.indexOf(scope) > -1) { + start -= 1 + } + while (end + 1 <= ed.getLastBufferRow() && + ed.scopeDescriptorForBufferPosition([end + 1, 0]).scopes.indexOf(scope) > -1) { + end += 1 + } + return [[start, 0], [end, Infinity]] +} + +export function getCursorCellRanges (ed) { + let ranges = [] + for (const cursor of ed.getCursors()) { + let range = getCurrentCellRange(ed, cursor) + if (range !== null) { + ranges.push(range) + } + } + return ranges +} + +export function moveNext (ed) { + for (const cursor of ed.getCursors()) { + let scope = getEmbeddedScope(cursor) + if (scope === null) return null + + let range = getCurrentCellRange(ed, cursor) + let endRow = range[1][0] + 1 + while (endRow + 1 <= ed.getLastBufferRow() && + ed.scopeDescriptorForBufferPosition([endRow + 1, 0]).scopes.indexOf(scope) === -1) { + endRow += 1 + } + cursor.setBufferPosition([endRow+1, Infinity]) + } +} + +export function movePrev (ed) { + for (const cursor of ed.getCursors()) { + let scope = getEmbeddedScope(cursor) + if (scope === null) return null + + let range = getCurrentCellRange(ed, cursor) + let startRow = range[0][0] - 1 + while (startRow - 1 >= 0 && + ed.scopeDescriptorForBufferPosition([startRow - 1, 0]).scopes.indexOf(scope) === -1) { + startRow -= 1 + } + cursor.setBufferPosition([startRow-1, Infinity]) + } +} + +export function get (ed) { + let ranges = getCursorCellRanges(ed) + if (ranges.length === 0) return [] + + let processedRanges = [] + for (let range of ranges) { + let text = ed.getTextInBufferRange(range) + range[1][0] += 1 // move result one line down + processedRanges.push({ + range, + selection: ed.getSelections()[0], + line: range[0][0], + text: text || ' ' + }) + } + return processedRanges +} diff --git a/lib_src/misc/words.ts b/lib_src/misc/words.ts new file mode 100644 index 00000000..cf5acc6b --- /dev/null +++ b/lib_src/misc/words.ts @@ -0,0 +1,99 @@ +/** @babel */ + +import { Point, Range } from 'atom' + +export const wordRegex = /[\u00A0-\uFFFF\w_!´\.]*@?[\u00A0-\uFFFF\w_!´]+/ + +/** + * Takes an `editor` and gets the word at current cursor position. If that is nonempty, call + * function `fn` with arguments `word` and `range`. + */ +export function withWord (editor, fn) { + const { word, range } = getWordAndRange(editor) + // If we only find numbers or nothing, return prematurely + if (!isValidWordToInspect(word)) return + fn(word, range) +} + +/** + * Returns the word and its range in the `editor`. + * + * `options` + * - `bufferPosition` {Point}: If given returns the word at the `bufferPosition`, returns the word at the current cursor otherwise. + * - `wordRegex` {RegExp} : A RegExp indicating what constitutes a “word” (default: `wordRegex`). + */ +export function getWordAndRange (editor, options = { + bufferPosition: undefined, + wordRegex +}) { + // @TODO?: + // The following lines are kinda iffy: The regex may or may not be well chosen + // and it duplicates the efforts from atom-language-julia. + // It might be better to select the current word via finding the smallest + // containing the bufferPosition/cursor which also has `function` or `macro` as its class. + const bufferPosition = options.bufferPosition ? + options.bufferPosition : + editor.getLastCursor().getBufferPosition() + const range = getWordRangeAtBufferPosition(editor, bufferPosition, { + wordRegex: options.wordRegex ? options.wordRegex : wordRegex + }) + const word = editor.getTextInBufferRange(range) + return { word, range } +} + +/** + * Returns the range of a word containing the `bufferPosition` in `editor`. + * + * `options` + * - `wordRegex` {RegExp}: A RegExp indicating what constitutes a “word” (default: `wordRegex`). + */ +export function getWordRangeAtBufferPosition (editor, bufferPosition, options = { + wordRegex +}) { + // adapted from https://github.com/atom/atom/blob/v1.38.2/src/cursor.js#L606-L616 + const { row, column } = bufferPosition + const ranges = editor.getBuffer().findAllInRangeSync( + options.wordRegex ? options.wordRegex : wordRegex, + new Range(new Point(row, 0), new Point(row, Infinity)) + ) + const range = ranges.find(range => + range.end.column >= column && range.start.column <= column + ) + return range ? Range.fromObject(range) : new Range(bufferPosition, bufferPosition) +} + +/** + * Examples: `|` represents `bufferPosition`: + * - `"he|ad.word.foot"` => `Range` of `"head"` + * - `"head|.word.foot"` => `Range` of `"head"` + * - `"head.|word.foot"` => `Range` of `"head.word"` + * - `"head.word.fo|ot"` => `Range` of `"head.word.field"` + */ +export function getWordRangeWithoutTrailingDots (word, range, bufferPosition) { + const { start } = range + const { column: startColumn } = start + const { row: endRow } = range.end + let endColumn = startColumn + + const { column } = bufferPosition + + const elements = word.split('.') + for (const element of elements) { + endColumn += element.length + if (column <= endColumn) { + break + } else { + endColumn += 1 + } + } + + const end = new Point(endRow, endColumn) + return new Range(start, end) +} + +/** + * Returns `true` if `word` is valid word to be inspected. + */ +export function isValidWordToInspect (word) { + return word.length > 0 && isNaN(word) +} diff --git a/lib_src/package/commands.ts b/lib_src/package/commands.ts new file mode 100644 index 00000000..171ef555 --- /dev/null +++ b/lib_src/package/commands.ts @@ -0,0 +1,282 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import shell from 'shell'; +import cells from '../misc/cells'; +import { CompositeDisposable } from 'atom'; + +export default { + activate(juno) { + const requireClient = (a, f) => juno.connection.client.require(a, f); + const disrequireClient = (a, f) => juno.connection.client.disrequire(a, f); + const boot = () => juno.connection.boot(); + + const cancelComplete = e => atom.commands.dispatch(e.currentTarget, 'autocomplete-plus:cancel'); + + this.subs = new CompositeDisposable(); + + // atom-text-editors with Julia scopes + for (let scope of atom.config.get('julia-client.juliaSyntaxScopes')) { + this.subs.add(atom.commands.add(`atom-text-editor[data-grammar='${scope.replace(/\./g, ' ')}']`, { + 'julia-client:run-block': event => { + cancelComplete(event); + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.eval(); + }); + }, + 'julia-client:run-and-move': event => { + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.eval({move: true}); + }); + }, + 'julia-client:run-all': event => { + cancelComplete(event); + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.evalAll(); + }); + }, + 'julia-client:run-cell': () => { + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.eval({cell: true}); + }); + }, + 'julia-client:run-cell-and-move': () => { + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.eval({cell: true, move: true}); + }); + }, + 'julia-client:select-block': () => { + return juno.misc.blocks.select(); + }, + 'julia-client:next-cell': () => { + return cells.moveNext(); + }, + 'julia-client:prev-cell': () => { + return cells.movePrev(); + }, + 'julia-client:goto-symbol': () => { + return this.withInk(function() { + boot(); + return juno.runtime.goto.gotoSymbol(); + }); + }, + 'julia-client:show-documentation': () => { + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.toggleDocs(); + }); + }, + // @NOTE: `'clear-workspace'` is now not handled by Atom.jl + // 'julia-client:reset-workspace': => + // requireClient 'reset the workspace', -> + // editor = atom.workspace.getActiveTextEditor() + // atom.commands.dispatch atom.views.getView(editor), 'inline-results:clear-all' + // juno.connection.client.import('clear-workspace')() + 'julia-client:send-to-stdin': e => { + return requireClient(function() { + const ed = e.currentTarget.getModel(); + let done = false; + for (let s of ed.getSelections()) { + if (!s.getText()) { continue; } + done = true; + juno.connection.client.stdin(s.getText()); + } + if (!done) { return juno.connection.client.stdin(ed.getText()); } + }); + }, + 'julia-debug:run-block': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugBlock(false, false); + }); + }, + 'julia-debug:step-through-block': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugBlock(true, false); + }); + }, + 'julia-debug:run-cell': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugBlock(false, true); + }); + }, + 'julia-debug:step-through-cell': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugBlock(true, true); + }); + }, + 'julia-debug:toggle-breakpoint': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.togglebp(); + }); + }, + 'julia-debug:toggle-conditional-breakpoint': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.togglebp(true); + }); + } + } + ) + ); + } + + // atom-text-editor with Julia grammar scope + this.subs.add(atom.commands.add('atom-text-editor[data-grammar="source julia"]', { + 'julia-client:format-code': () => { + return this.withInk(function() { + boot(); + return juno.runtime.formatter.formatCode(); + }); + } + } + ) + ); + + // Where "module" matters + this.subs.add(atom.commands.add(`atom-text-editor[data-grammar="source julia"], \ +.julia-terminal, \ +.ink-workspace`, + {'julia-client:set-working-module'() { return juno.runtime.modules.chooseModule(); }}) + ); + + // tree-view + this.subs.add(atom.commands.add('.tree-view', { + 'julia-client:run-all': ev => { + cancelComplete(ev); + return this.withInk(function() { + boot(); + return juno.runtime.evaluation.evalAll(ev.target); + }); + }, + 'julia-debug:run-file': ev => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugFile(false, ev.target); + }); + }, + 'julia-debug:step-through-file': ev => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugFile(true, ev.target); + }); + } + } + ) + ); + + // atom-work-space + return this.subs.add(atom.commands.add('atom-workspace', { + 'julia-client:open-external-REPL'() { return juno.connection.terminal.repl(); }, + 'julia-client:start-julia'() { return disrequireClient('boot Julia', () => boot()); }, + 'julia-client:start-remote-julia-process'() { return disrequireClient('boot a remote Julia process', () => juno.connection.bootRemote()); }, + 'julia-client:kill-julia'() { return juno.connection.client.kill(); }, + 'julia-client:interrupt-julia': () => requireClient('interrupt Julia', () => juno.connection.client.interrupt()), + 'julia-client:disconnect-julia': () => requireClient('disconnect Julia', () => juno.connection.client.disconnect()), + // 'julia-client:reset-julia-server': -> juno.connection.local.server.reset() # server mode not functional + 'julia-client:connect-external-process'() { return disrequireClient(() => juno.connection.messages.connectExternal()); }, + 'julia-client:connect-terminal'() { return disrequireClient(() => juno.connection.terminal.connectedRepl()); }, + 'julia-client:open-plot-pane': () => this.withInk(() => juno.runtime.plots.open()), + 'julia-client:open-outline-pane': () => this.withInk(() => juno.runtime.outline.open()), + 'julia-client:open-workspace': () => this.withInk(() => juno.runtime.workspace.open()), + 'julia-client:restore-default-layout'() { return juno.ui.layout.restoreDefaultLayout(); }, + 'julia-client:close-juno-panes'() { return juno.ui.layout.closePromises(); }, + 'julia-client:reset-default-layout-settings'() { return juno.ui.layout.resetDefaultLayoutSettings(); }, + 'julia-client:settings'() { return atom.workspace.open('atom://config/packages/julia-client'); }, + + 'julia-debug:run-file': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugFile(false); + }); + }, + 'julia-debug:step-through-file': () => { + return this.withInk(function() { + boot(); + return juno.runtime.debugger.debugFile(true); + }); + }, + 'julia-debug:clear-all-breakpoints': () => juno.runtime.debugger.clearbps(), + 'julia-debug:step-to-next-line': ev => juno.runtime.debugger.nextline(ev), + 'julia-debug:step-to-selected-line': ev => juno.runtime.debugger.toselectedline(ev), + 'julia-debug:step-to-next-expression': ev => juno.runtime.debugger.stepexpr(ev), + 'julia-debug:step-into': ev => juno.runtime.debugger.stepin(ev), + 'julia-debug:stop-debugging': ev => juno.runtime.debugger.stop(ev), + 'julia-debug:step-out': ev => juno.runtime.debugger.finish(ev), + 'julia-debug:continue': ev => juno.runtime.debugger.continueForward(ev), + 'julia-debug:open-debugger-pane': () => juno.runtime.debugger.open(), + + 'julia:new-julia-file': () => { + return atom.workspace.open().then(ed => { + const gr = atom.grammars.grammarForScopeName('source.julia'); + if (gr) { return ed.setGrammar(gr); } + }); + }, + 'julia:open-julia-startup-file'() { return atom.workspace.open(juno.misc.paths.home('.julia', 'config', 'startup.jl')); }, + 'julia:open-juno-startup-file'() { return atom.workspace.open(juno.misc.paths.home('.julia', 'config', 'juno_startup.jl')); }, + 'julia:open-julia-home'() { return shell.openItem(juno.misc.paths.juliaHome()); }, + 'julia:open-package-in-new-window'() { return requireClient('get packages', () => juno.runtime.packages.openPackage()); }, + 'julia:open-package-as-project-folder'() { return requireClient('get packages', () => juno.runtime.packages.openPackage(false)); }, + 'julia:get-help'() { return shell.openExternal('http://discourse.julialang.org'); }, + 'julia-client:debug-info': () => { + boot(); + return juno.runtime.debuginfo(); + }, + + 'julia-client:work-in-current-folder'(ev) { + return requireClient('change working folder', () => juno.runtime.evaluation.cdHere(ev.target)); + }, + 'julia-client:work-in-project-folder'() { + return requireClient('change working folder', () => juno.runtime.evaluation.cdProject()); + }, + 'julia-client:work-in-home-folder'() { + return requireClient('change working folder', () => juno.runtime.evaluation.cdHome()); + }, + 'julia-client:select-working-folder'() { + return requireClient('change working folder', () => juno.runtime.evaluation.cdSelect()); + }, + 'julia-client:activate-environment-in-current-folder'(ev) { + return requireClient('activate an environment', () => juno.runtime.evaluation.activateProject(ev.target)); + }, + 'julia-client:activate-environment-in-parent-folder'(ev) { + return requireClient('activate an environment', () => juno.runtime.evaluation.activateParentProject(ev.target)); + }, + 'julia-client:activate-default-environment'(ev) { + return requireClient('activate an environment', () => juno.runtime.evaluation.activateDefaultProject()); + } + } + ) + ); + }, + + deactivate() { + return this.subs.dispose(); + }, + + withInk(f, err) { + if (this.ink != null) { + return f(); + } else if (err) { + return atom.notifications.addError('Please install the Ink package.', { + detail: `Julia Client requires the Ink package to run. \ +You can install it via \`File -> Settings -> Install\`.`, + dismissable: true + } + ); + } else { + return setTimeout((() => this.withInk(f, true)), 100); + } + } +}; diff --git a/lib_src/package/config.ts b/lib_src/package/config.ts new file mode 100644 index 00000000..123bf108 --- /dev/null +++ b/lib_src/package/config.ts @@ -0,0 +1,757 @@ +import { terminal } from '../connection'; + +const config = { + juliaPath: { + type: 'string', + default: 'julia', + description: 'The location of the Julia binary.', + order: 1 + }, + + juliaOptions: { + type: 'object', + order: 2, + collapsed: true, + properties: { + bootMode: { + title: 'Boot Mode', + type: 'string', + description: `\`Basic\` spins up a local Julia process on demand and is the most \ +robust option. The \`Cycler\` will keep three local Julia processes \ +around at all times to reduce downtime when a process exits. \ +\`External Terminal\` opens an external terminal and connects it to Juno, \ +much like the \`Julia Client: Connect Terminal\` command. \ +\`Remote\` is similar to the \`Julia Client: Start Remote Julia Process\` \ +command but changes the default, so that evaluating a line \ +in the editor or pressing \`Enter\` in the REPL tab will start \ +a remote Julia process instead of a local one.`, + enum: ['Basic', 'Cycler', 'External Terminal', 'Remote'], + default: 'Cycler', + radio: true, + order: 1 + }, + optimisationLevel: { + title: 'Optimisation Level', + description: 'Higher levels take longer to compile, but produce faster code.', + type: 'integer', + enum: [0, 1, 2, 3], + default: 3, + radio: true, + order: 2 + }, + deprecationWarnings: { + title: 'Deprecation Warnings', + type: 'boolean', + description: 'If disabled, hides deprecation warnings.', + default: true, + order: 3 + }, + numberOfThreads: { + title: 'Number of Threads', + type: 'string', + description: '`global` will use global setting, `auto` sets it to number of cores.', + default: 'auto', + order: 4 + }, + startupArguments: { + title: 'Additional Julia Startup Arguments', + type: 'array', + description: `\`-i\`, \`-O\`, and \`--depwarn\` will be set by the above options \ +automatically, but can be overwritten here. Arguments are \ +comma-separated, and you should never need to quote \ +anything (even e.g. paths with spaces in them).`, + default: [], + items: { + type: 'string' + }, + order: 5 + }, + externalProcessPort: { + title: 'Port for Communicating with the Julia Process', + type: 'string', + description: '`random` will use a new port each time, or enter an integer to set the port statically.', + default: 'random', + order: 6 + }, + arguments: { + title: 'Arguments', + type: 'array', + description: 'Set `ARGS` to the following entries (comma-separated). Requires restart of Julia process.', + default: [], + items: { + type: 'string' + }, + order: 7 + }, + persistWorkingDir: { + title: 'Persist Working Directory', + type: 'boolean', + default: false, + order: 8 + }, + workingDir: { + title: 'Working Directory', + type: 'string', + default: '', + order: 9 + }, + autoCompletionSuggestionPriority: { + title: 'Auto-Completion Suggestion Priority', + description: + `\ +Specify the sort order of Auto-completion suggestions provided by Juno. +Note the default providers like snippets have priority of \`1\`. +Requires Atom restart to take an effect.\ +`, + type: 'integer', + default: 3, + order: 11 + }, + noAutoParenthesis: { + title: 'Don\'t Insert Parenthesis on Function Auto-completion', + description: 'If enabled, Juno will not insert parenthesis after completing a function.', + type: 'boolean', + default: false, + order: 12 + }, + formatOnSave: { + title: 'Format the current editor when saving', + description: 'If enabled, Juno will format the current editor on save if a Julia session is running.', + type: 'boolean', + default: false, + order: 13 + }, + formattingOptions: { + title: 'Formatting Options', + description: 'Specifies [formatting options for `JuliaFormatter.format_text`](https://domluna.github.io/JuliaFormatter.jl/stable/#Formatting-Options-1).', + type: 'object', + order: 14, + collapsed: true, + properties: { + indent: { + title: 'indent', + description: + `\ +Specifies the number of spaces used for an indentation. +With the default value (\`-1\`), \`indent\` will be automatically set to the current editor's [tab length](https://flight-manual.atom.io/using-atom/sections/basic-customization/#configuration-key-reference).\ +`, + type: 'integer', + default: -1, + order: 1 + }, + margin: { + title: 'margin', + description: + `\ +Specifies the maximum length of a line. Code exceeding this margin will be formatted +across multiple lines. +With the default value (\`-1\`), \`indent\` will be automatically set to the current editor's [preferred line length](https://flight-manual.atom.io/using-atom/sections/basic-customization/#configuration-key-reference).\ +`, + type: 'integer', + default: -1, + order: 2 + }, + always_for_in: { + title: 'always_for_in', + description: + `\ +If \`always_for_in\` is true \`=\` is always replaced with \`in\` if part of a +\`for\` loop condition. For example, \`for i = 1:10\` will be transformed +to \`for i in 1:10\`.\ +`, + type: 'boolean', + default: false, + order: 3 + }, + whitespace_typedefs: { + title: 'whitespace_typedefs', + description: + `\ +If \`whitespace_typedefs\` is true, whitespace is added for type definitions. +Make this \`true\` if you prefer \`Union{A <: B, C}\` to \`Union{A<:B,C}\`.\ +`, + type: 'boolean', + default: false, + order: 4 + }, + whitespace_ops_in_indices: { + title: 'whitespace_ops_in_indices', + description: + `\ +If \`whitespace_ops_in_indices\` is true, whitespace is added for binary operations +in indices. Make this \`true\` if you prefer \`arr[a + b]\` to \`arr[a+b]\`. Additionally, if there's a colon \`:\` involved, parenthesis will be added to the LHS and RHS.\ +`, + type: 'boolean', + default: false, + order: 5 + }, + remove_extra_newlines: { + title: 'remove_extra_newlines', + description: + `\ +If \`remove_extra_newlines\` is true superflous newlines will be removed.\ +`, + type: 'boolean', + default: false, + order: 6 + } + } + } + } + }, + + uiOptions: { + title: 'UI Options', + type: 'object', + order: 3, + collapsed: true, + properties: { + resultsDisplayMode: { + title: 'Result Display Mode', + type: 'string', + default: 'inline', + enum: [ + {value:'inline', description:'Float results next to code'}, + {value:'block', description:'Display results under code'}, + {value:'console', description:'Display results in the REPL'} + ], + order: 1 + }, + scrollToResult: { + title: 'Scroll to Inline Results', + type: 'boolean', + default: false, + order: 2 + }, + docsDisplayMode: { + title: 'Documentation Display Mode', + type: 'string', + default: 'pane', + enum: [ + {value: 'inline', description: 'Show documentation in the editor'}, + {value: 'pane', description: 'Show documentation in the documentation pane'} + ], + order: 3 + }, + errorNotifications: { + title: 'Error Notifications', + type: 'boolean', + default: true, + description: `When evaluating a script, show errors in a notification as \ +well as in the REPL.`, + order: 4 + }, + errorInRepl: { + title: 'Show Errors in REPL (Inline Evaluation)', + type: 'boolean', + default: false, + description: 'If enabled, Juno always shows errors in the REPL when using inline evaluation.', + order: 5 + }, + enableMenu: { + title: 'Enable Menu', + type: 'boolean', + default: false, + description: 'Show a Julia menu in the menu bar (requires restart).', + order: 6 + }, + enableToolBar: { + title: 'Enable Toolbar', + type: 'boolean', + default: false, + description: 'Show Julia icons in the tool bar (requires restart).', + order: 7 + }, + usePlotPane: { + title: 'Enable Plot Pane', + type: 'boolean', + default: true, + description: 'Show plots in Atom.', + order: 8 + }, + maxNumberPlots: { + title: 'Maximum Number of Plots in History', + type: 'number', + default: 50, + description: 'Increasing this number may lead to high memory consumption and poor performance.', + order: 9 + }, + openNewEditorWhenDebugging: { + title: 'Open New Editor When Debugging', + type: 'boolean', + default: false, + description: `Opens a new editor tab when stepping into a new file instead \ +of reusing the current one (requires restart).`, + order: 10 + }, + cellDelimiter: { + title: 'Cell Delimiter', + type: 'array', + default: ['##', '#---', '#%%', '# %%'], + description: 'Regular expressions for determining cell delimiters.', + order: 11 + }, + highlightCells: { + title: 'Highlight Cells', + type: 'boolean', + description: `Customize the appearence of Juno\'s cell highlighting by \ +adding styles for \`.line.julia-current-cell\` or \ +\`.line-number.julia-current-cell\` to your personal \ +stylesheet.`, + default: true, + order: 12 + }, + layouts: { + title: 'Layout Options', + type: 'object', + order: 13, + collapsed: true, + properties: { + console: { + title: 'REPL', + type: 'object', + order: 1, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of REPL Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'bottom', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of REPL Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'no split', + radio: true, + order: 2 + } + } + }, + terminal: { + title: 'Terminal', + type: 'object', + order: 2, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Terminal Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'bottom', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Terminal Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'no split', + radio: true, + order: 2 + } + } + }, + workspace: { + title: 'Workspace', + type: 'object', + order: 3, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Workspace Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'center', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Workspace Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'right', + radio: true, + order: 2 + } + } + }, + documentation: { + title: 'Documentation Browser', + type: 'object', + order: 4, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Documentation Browser Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'center', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Documentation Browser Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'right', + radio: true, + order: 2 + } + } + }, + plotPane: { + title: 'Plot Pane', + type: 'object', + order: 5, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Plot Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'center', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Plot Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'right', + radio: true, + order: 2 + } + } + }, + debuggerPane: { + title: 'Debugger Pane', + type: 'object', + order: 6, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Debugger Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'right', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Debugger Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'no split', + radio: true, + order: 2 + } + } + }, + profiler: { + title: 'Profiler', + type: 'object', + order: 7, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Profiler Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'center', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Profiler Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'right', + radio: true, + order: 2 + } + } + }, + linter: { + title: 'Linter', + type: 'object', + order: 8, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Linter Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'bottom', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Linter Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'no split', + radio: true, + order: 2 + } + } + }, + outline: { + title: 'Outline', + type: 'object', + order: 9, + collapsed: true, + properties: { + defaultLocation: { + title: 'Default location of Outline Pane', + type: 'string', + enum: ['center', 'left', 'bottom', 'right'], + default: 'left', + radio: true, + order: 1 + }, + split: { + title: 'Splitting rule of Outline Pane', + type: 'string', + enum: ['no split', 'left', 'up', 'right', 'down'], + default: 'down', + radio: true, + order: 2 + } + } + }, + defaultPanes: { + title: 'Default Panes', + description: `Specify panes that are opened by \`Julia Client: Restore Default Layout\`. \ +The location and splitting rule of each pane follow the settings above.`, + type: 'object', + order: 10, + properties: { + console: { + title: 'REPL', + type: 'boolean', + default: true, + order: 1 + }, + workspace: { + title: 'Workspace', + type: 'boolean', + default: true, + order: 2 + }, + documentation: { + title: 'Documentation Browser', + type: 'boolean', + default: true, + order: 3 + }, + plotPane: { + title: 'Plot Pane', + type: 'boolean', + default: true, + order: 4 + }, + debuggerPane: { + title: 'Debugger Pane', + type: 'boolean', + default: false, + order: 5 + }, + linter: { + title: 'Linter', + type: 'boolean', + default: false, + order: 6 + }, + outline: { + title: 'Outline', + type: 'boolean', + default: false, + order: 7 + } + } + }, + openDefaultPanesOnStartUp: { + title: 'Open Default Panes on Startup', + description: `If enabled, opens panes specified above on startup. \ +Note a layout deserialized from a previous window state \ +would be modified by that, i.e.: disable this if you want \ +to keep the deserialized layout.`, + type: 'boolean', + default: true, + order: 11 + } + } + } + } + }, + + consoleOptions: { + type: 'object', + title: 'Terminal Options', + order: 4, + collapsed: true, + properties: { + maximumConsoleSize: { + title: 'Scrollback Buffer Size', + type: 'integer', + default: 10000, + order: 1 + }, + prompt: { + title: 'Terminal Prompt', + type: 'string', + default: 'julia>', + order: 2 + }, + shell: { + title: 'Shell', + type: 'string', + default: terminal.defaultShell(), + description: `The location of an executable shell. Set to \`$SHELL\` by default, \ +and if \`$SHELL\` isn\'t set then fallback to \`bash\` or \`powershell.exe\` (on Windows).`, + order: 3 + }, + terminal: { + title: 'Terminal', + type: 'string', + default: terminal.defaultTerminal(), + description: 'Command used to open an external terminal.', + order: 4 + }, + whitelistedKeybindingsREPL: { + title: 'Whitelisted Keybindings for the Julia REPL', + type: 'array', + default: ['Ctrl-C', 'F5', 'F8', 'F9', 'F10', 'F11', 'Shift-F5', 'Shift-F8', 'Shift-F9', 'Shift-F10', 'Shift-F11'], + description: 'The listed keybindings are not handled by the REPL and instead directly passed to Atom.', + order: 5 + }, + whitelistedKeybindingsTerminal: { + title: 'Whitelisted Keybindings for Terminals', + type: 'array', + default: [], + description: 'The listed keybindings are not handled by any terminals and instead directly passed to Atom.', + order: 6 + }, + cursorStyle: { + title: 'Cursor Style', + type: 'string', + enum: ['block', 'underline', 'bar'], + default: 'block', + radio: true, + order: 7 + }, + cursorBlink: { + title: 'Cursor Blink', + type: 'boolean', + default: false, + order: 8 + }, + terminalRendererType: { + title: 'Terminal Renderer', + type: 'string', + enum: ['webgl', 'canvas', 'dom'], + default: 'webgl', + radio: true, + description: `The \`webgl\` renderer is fastest, but is still experimental. \`canvas\` performs well \ +in many cases, while \`dom\` is a slow falback. Note that it\'s not possible \ +to hot-swap to the \`webgl\` renderer.`, + order: 9 + }, + linkModifier: { + title: 'Ctrl/Cmd modifier for link activation', + type: 'boolean', + default: true, + order: 10 + } + } + }, + + remoteOptions: { + type: 'object', + order: 5, + collapsed: true, + properties: { + remoteJulia: { + title: 'Command to execute Julia on the remote server', + type: 'string', + default: 'julia', + order: 1 + }, + tmux: { + title: 'Use a persistent tmux session', + description: 'Requires tmux to be installed on the server you\'re connecting to.', + type: 'boolean', + default: false, + order: 2 + }, + tmuxName: { + title: 'tmux session name', + type: 'string', + default: 'juno_tmux_session', + order: 3 + }, + agentAuth: { + title: 'Use SSH agent', + description: 'Requires `$SSH_AUTH_SOCKET` to be set. Defaults to putty\'s pageant on Windows.', + type: 'boolean', + default: true, + order: 4 + }, + forwardAgent: { + title: 'Forward SSH agent', + type: 'boolean', + default: true, + order: 5 + } + } + }, + + juliaSyntaxScopes: { + title: 'Julia Syntax Scopes', + description: + `The listed syntax scopes (comma separated) will be recoginized as Julia files. \ +You may have to restart Atom to take an effect.\n \ +**DO NOT** edit this unless you\'re sure about the effect.`, + type: 'array', + default: ['source.julia', 'source.weave.md', 'source.weave.latex'], + order: 6 + }, + + disableProxy: { + title: 'Disable System Proxy for Child Processes', + description: + `This unsets the \`HTTP_PROXY\` and \`HTTPS_PROXY\` environment variables in all integrated \ +terminals. Try this option if you\'re experiencing issues when installing Julia packages \ +in Juno.`, + type: 'boolean', + default: false, + order: 7 + }, + + firstBoot: { + type: 'boolean', + default: true, + order: 99 + } +}; + +if (process.platform !== 'darwin') { + config.consoleOptions.properties.whitelistedKeybindingsREPL.default = + ['Ctrl-C', 'Ctrl-J', 'Ctrl-K', 'Ctrl-E', 'Ctrl-V', 'Ctrl-M', 'F5', 'F8', 'F9', + 'F10', 'F11', 'Shift-F5', 'Shift-F8', 'Shift-F9', 'Shift-F10', 'Shift-F11']; +} + +if (process.platform === 'darwin') { + config.consoleOptions.properties.macOptionIsMeta = { + title: 'Use Option as Meta', + type: 'boolean', + default: false, + order: 5.5 + }; +} + +export default config; diff --git a/lib_src/package/menu.ts b/lib_src/package/menu.ts new file mode 100644 index 00000000..8315a416 --- /dev/null +++ b/lib_src/package/menu.ts @@ -0,0 +1,113 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { CompositeDisposable } from 'atom'; + +export default { + activate() { + this.subs = new CompositeDisposable; + // Package submenu + this.subs.add(atom.menu.add([{ + label: 'Packages', + submenu: this.menu + }])); + + // App Menu + if (atom.config.get('julia-client.uiOptions.enableMenu')) { + this.subs.add = atom.menu.add(this.menu); + // TODO: find a less hacky way to do this + const menu = atom.menu.template.pop(); + return atom.menu.template.splice(3, 0, menu); + } + }, + + deactivate() { + return this.subs.dispose(); + }, + + menu: [{ + label: 'Juno', + submenu: [ + {label: 'Start Julia', command: 'julia-client:start-julia'}, + {label: 'Start Remote Julia Process', command: 'julia-client:start-remote-julia-process'}, + {label: 'Interrupt Julia', command: 'julia-client:interrupt-julia'}, + {label: 'Stop Julia', command: 'julia-client:kill-julia'}, + + {type: 'separator'}, + + {label: 'Open REPL', command: 'julia-client:open-REPL'}, + {label: 'Clear REPL', command: 'julia-client:clear-REPL'}, + {label: 'Open External REPL', command: 'julia-client:open-external-REPL'}, + { + label: 'Working Directory', + submenu: [ + {label: 'Current File\'s Folder', command: 'julia-client:work-in-current-folder'}, + {label: 'Select Project Folder', command: 'julia-client:work-in-project-folder'}, + {label: 'Home Folder', command: 'julia-client:work-in-home-folder'}, + {label: 'Select...', command: 'julia-client:select-working-folder'} + ] + }, + { + label: 'Environment', + submenu: [ + {label: 'Environment in Current File\'s Folder', command: 'julia-client:activate-environment-in-current-folder'}, + {label: 'Environment in Parent Folder', command: 'julia-client:activate-environment-in-parent-folder'}, + {label: 'Default Environment', command: 'julia-client:activate-default-environment'} + ] + }, + {label: 'Set Working Module', command: 'julia-client:set-working-module'}, + + {type: 'separator'}, + + {label: 'Run Block', command: 'julia-client:run-block'}, + {label: 'Run All', command: 'julia-client:run-all'}, + + {type: 'separator'}, + + {label: 'Format Code', command: 'julia-client:format-code'}, + + {type: 'separator'}, + + {label: 'Debug: Run Block', command: 'julia-debug:run-block'}, + {label: 'Debug: Step through Block', command: 'julia-debug:step-through-block'}, + {label: 'Debug: Run File', command: 'julia-debug:run-file'}, + {label: 'Debug: Step through File', command: 'julia-debug:step-through-file'}, + + {type: 'separator'}, + + {label: 'Open Workspace', command: 'julia-client:open-workspace'}, + {label: 'Open Outline Pane', command: 'julia-client:open-outline-pane'}, + {label: 'Open Documentation Browser', command: 'julia-client:open-documentation-browser'}, + {label: 'Open Plot Pane', command: 'julia-client:open-plot-pane'}, + {label: 'Open Debugger Pane', command: 'julia-debug:open-debugger-pane'}, + + {type: 'separator'}, + + {label: 'Open New Julia File', command: 'julia:new-julia-file'}, + {label: 'Open Julia Startup File', command: 'julia:open-julia-startup-file'}, + {label: 'Open Juno Startup File', command: 'julia:open-juno-startup-file'}, + {label: 'Open Julia Home', command: 'julia:open-julia-home'}, + {label: 'Open Package in New Window...', command: 'julia:open-package-in-new-window'}, + {label: 'Open Package as Project Folder...', command: 'julia:open-package-as-project-folder'}, + + {type: 'separator'}, + + { + label: 'New Terminal', + submenu: [ + {label: 'Current File\'s Folder', command: 'julia-client:new-terminal-from-current-folder'}, + {label: 'Select Project Folder', command: 'julia-client:new-terminal'} + ] + }, + {label: 'New Remote Terminal', command: 'julia-client:new-remote-terminal'}, + + {type: 'separator'}, + + {label: 'Debug Information', command: 'julia-client:debug-info'}, + {label: 'Help...', command: 'julia:get-help'}, + {label: 'Settings...', command: 'julia-client:settings'} + ] + }] +}; diff --git a/lib_src/package/settings.ts b/lib_src/package/settings.ts new file mode 100644 index 00000000..78870fb7 --- /dev/null +++ b/lib_src/package/settings.ts @@ -0,0 +1,80 @@ +'use babel' + +let validSchemes = require('../package/config') +let invalidSchemes = [] // Keeps invalid config schemes to be notified to users + +function dispose() { + validSchemes = null + invalidSchemes = null +} + +/** + * Updates settings by removing deprecated (i.e.: not used anymore) configs so that no one tries to + * tweak them. + */ +export function updateSettings() { + const currentConfig = atom.config.get('julia-client') + searchForDeprecated(currentConfig, []) + + if (invalidSchemes.length > 0) { + const message = atom.notifications.addWarning('Julia-Client: Invalid (deprecated) settings found', { + detail: invalidSchemes.join('\n'), + dismissable: true, + description: 'Remove these invalid settings ?', + buttons: [ + { + text: 'Yes', + onDidClick: () => { + message.dismiss() + invalidSchemes.forEach((invalidScheme) => { + atom.config.unset(invalidScheme) + }) + dispose() + } + }, + { + text: 'No', + onDidClick: () => { + message.dismiss() + dispose() + } + } + ] + }) + } +} + +/** + * Recursively search deprecated configs + */ +function searchForDeprecated(config, currentSchemes) { + Object.entries(config).forEach(([key, value]) => { + // @NOTE: Traverse the current config schemes by post-order in order to push all the invalid + // config schemes into `invalidSchemes` + if (Object.prototype.toString.call(value) === '[object Object]') { + const nextSchemes = currentSchemes.slice(0) + nextSchemes.push(key) + searchForDeprecated(value, nextSchemes) + } + + // Make `validScheme` corresponding to `currentSchemes` path for the validity checking below + let validScheme = validSchemes + currentSchemes.forEach((scheme) => { + Object.entries(validScheme).forEach(([_key, _value]) => { + if (_key === scheme) { + validScheme = _value + } else if (_key === 'properties' && _value[scheme]) { + validScheme = _value[scheme] + } + }) + }); + + // Check if the `config` scheme being searched at this recursion is in `validScheme` + if (!validScheme[key] && (!validScheme.properties || !validScheme.properties[key])) { + let invalidScheme = 'julia-client.' + invalidScheme += currentSchemes.length === 0 ? '' : `${currentSchemes.join('.')}.` + invalidScheme += key + invalidSchemes.push(invalidScheme) + } + }); +} diff --git a/lib_src/package/toolbar.ts b/lib_src/package/toolbar.ts new file mode 100644 index 00000000..f5eaad3a --- /dev/null +++ b/lib_src/package/toolbar.ts @@ -0,0 +1,135 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +export default { + consumeToolBar(bar) { + if (!atom.config.get('julia-client.uiOptions.enableToolBar')) { return; } + + this.bar = bar('julia-client'); + + // Files & Folders + + this.bar.addButton({ + icon: 'file-code', + iconset: 'fa', + tooltip: 'New Julia File', + callback: 'julia:new-julia-file' + }); + + this.bar.addButton({ + icon: 'save', + iconset: 'fa', + tooltip: 'Save', + callback: 'core:save' + }); + + this.bar.addButton({ + icon: 'folder-open', + iconset: 'fa', + tooltip: 'Open File...', + callback: 'application:open-file' + }); + + // Julia process + + this.bar.addSpacer(); + + this.bar.addButton({ + icon: 'globe', + tooltip: 'Start Local Julia Process', + callback: 'julia-client:start-julia' + }); + + this.bar.addButton({ + iconset: 'ion', + icon: 'md-planet', + tooltip: 'Start Remote Julia Process', + callback: 'julia-client:start-remote-julia-process' + }); + + this.bar.addButton({ + icon: 'md-pause', + iconset: 'ion', + tooltip: 'Interrupt Julia', + callback: 'julia-client:interrupt-julia' + }); + + this.bar.addButton({ + icon: 'md-square', + iconset: 'ion', + tooltip: 'Stop Julia', + callback: 'julia-client:kill-julia' + }); + + // Evaluation + + this.bar.addSpacer(); + + this.bar.addButton({ + icon: 'zap', + tooltip: 'Run Block', + callback: 'julia-client:run-and-move' + }); + + this.bar.addButton({ + icon: 'md-play', + iconset: 'ion', + tooltip: 'Run All', + callback: 'julia-client:run-all' + }); + + this.bar.addButton({ + icon: 'format-float-none', + iconset: 'mdi', + tooltip: 'Format Code', + callback: 'julia-client:format-code' + }); + + // Windows & Panes + + this.bar.addSpacer(); + + this.bar.addButton({ + icon: 'terminal', + tooltip: 'Show REPL', + callback: 'julia-client:open-REPL' + }); + + this.bar.addButton({ + icon: 'book', + tooltip: 'Show Workspace', + callback: 'julia-client:open-workspace' + }); + + this.bar.addButton({ + icon: 'list-unordered', + tooltip: 'Show Outline', + callback: 'julia-client:open-outline-pane' + }); + + this.bar.addButton({ + icon: 'info', + tooltip: 'Show Documentation Browser', + callback: 'julia-client:open-documentation-browser' + }); + + this.bar.addButton({ + icon: 'graph', + tooltip: 'Show Plot Pane', + callback: 'julia-client:open-plot-pane' + }); + + return this.bar.addButton({ + icon: 'bug', + tooltip: 'Show Debugger Pane', + callback: 'julia-debug:open-debugger-pane' + }); + }, + + deactivate() { + return (this.bar != null ? this.bar.removeItems() : undefined); + } +}; diff --git a/lib_src/runtime.ts b/lib_src/runtime.ts new file mode 100644 index 00000000..ee630782 --- /dev/null +++ b/lib_src/runtime.ts @@ -0,0 +1,101 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { CompositeDisposable, Disposable } from 'atom'; + +export default { + modules: require('./runtime/modules'), + evaluation: require('./runtime/evaluation'), + console: require('./runtime/console'), + workspace: require('./runtime/workspace'), + plots: require('./runtime/plots'), + frontend: require('./runtime/frontend'), + debugger: require('./runtime/debugger'), + profiler: require('./runtime/profiler'), + outline: require('./runtime/outline'), + linter: require('./runtime/linter'), + packages: require('./runtime/packages'), + debuginfo: require('./runtime/debuginfo'), + formatter: require('./runtime/formatter'), + goto: require('./runtime/goto'), + + activate() { + this.subs = new CompositeDisposable(); + this.modules.activate(); + this.frontend.activate(); + + this.subs.add(atom.config.observe('julia-client.juliaOptions.formatOnSave', val => { + if (val) { + return this.formatter.activate(); + } else { + return this.formatter.deactivate(); + } + }) + ); + + return this.subs.add(new Disposable(() => { + return [this.modules, this.frontend, this.formatter].map((mod) => mod.deactivate()); + }) + ); + }, + + deactivate() { + return this.subs.dispose(); + }, + + consumeInk(ink) { + let mod; + this.evaluation.ink = ink; + this.frontend.ink = ink; + for (mod of [this.console, this.debugger, this.profiler, this.linter, this.goto, this.outline]) { + mod.activate(ink); + } + for (mod of [this.workspace, this.plots]) { + mod.ink = ink; + mod.activate(); + } + return this.subs.add(new Disposable(() => { + return (() => { + const result = []; + for (mod of [this.console, this.debugger, this.profiler, this.linter, this.goto, this.outline]) { result.push(mod.deactivate()); + } + return result; + })(); + }) + ); + }, + + provideAutoComplete() { + return require('./runtime/completions'); + }, + + provideHyperclick() { return this.goto.provideHyperclick(); }, + + consumeStatusBar(bar) { + return this.modules.consumeStatusBar(bar); + }, + + consumeDatatip(datatipService) { + const datatipProvider = require('./runtime/datatip'); + // @NOTE: Check if the service is passed by Atom-IDE-UI's datatip service: + // currently atom-ide-datatip can't render code snippets correctly. + if (datatipService.constructor.name === 'DatatipManager') { + datatipProvider.useAtomIDEUI = true; + } else { + // @NOTE: Overwrite the weird default config settings of atom-ide-datatip + atom.config.set('atom-ide-datatip', { + showDataTipOnCursorMove: false, + showDataTipOnMouseMove: true + } + ); + } + const datatipDisposable = datatipService.addProvider(datatipProvider); + this.subs.add(datatipDisposable); + return datatipDisposable; + }, + + handleURI: require('./runtime/urihandler') +}; diff --git a/lib_src/runtime/completions.ts b/lib_src/runtime/completions.ts new file mode 100644 index 00000000..80955192 --- /dev/null +++ b/lib_src/runtime/completions.ts @@ -0,0 +1,146 @@ +/** @babel */ + +/** + * @TODO: Custom sorting? + * @TODO: Complete quotes for strings + */ + +import { Point, Range } from 'atom' + +import { client } from '../connection' +import modules from './modules' + +import { getLocalContext } from '../misc/blocks' + +const bracketScope = 'meta.bracket.julia' +const completions = client.import('completions') +const completionDetail = client.import('completiondetail') + +class AutoCompleteProvider { + public editor: any; + public bufferPosition: any; + public activatedManually: any; + public row: any; + public column: any; + public scopes: any; + public context: any; + public startRow: any; + selector = '.source.julia' + disableForSelector = `.source.julia .comment` + excludeLowerPriority = true + inclusionPriority = 1 + suggestionPriority = atom.config.get('julia-client.juliaOptions.autoCompletionSuggestionPriority') + filterSuggestions = false + + getSuggestions (data) { + if (!client.isActive()) return [] + const { editor, bufferPosition, activatedManually } = data + const { row, column } = bufferPosition + const startPoint = new Point(row, 0) + const endPoint = new Point(row, column) + const lineRange = new Range(startPoint, endPoint) + const line = editor.getTextInBufferRange(lineRange) + + // suppress completions if an whitespace precedes, except the special cases below + // - activatedManually (i.e. an user forces completions) + // - the current position is in function call: show method completions + // - after `using`/`import` keyword: show package completions + if (!activatedManually) { + if (column === 0) return [] + const prevCharPosition = new Point(row, column - 1) + const charRange = new Range(prevCharPosition, bufferPosition) + const char = editor.getTextInBufferRange(charRange) + const { scopes } = editor.scopeDescriptorForBufferPosition(bufferPosition) + if ( + !scopes.includes(bracketScope) && + !(/\b(import|using)\b/.test(line)) && + char === ' ' + ) return [] + } + + const baselineCompletions = this.baselineCompletions(data, line) + return Promise.race([baselineCompletions, this.sleep()]) + } + + baselineCompletions (data, line) { + const { editor, bufferPosition: { row, column }, activatedManually } = data + const { context, startRow } = getLocalContext(editor, row) + + return completions({ + // general + line, + path: editor.getPath(), + mod: modules.current(), + // local context + context, + row: row + 1, + startRow, + column: column + 1, + // configurations + force: activatedManually || false, + }).then(completions => { + return completions.map(completion => { + return this.toCompletion(completion) + }) + }).catch(() => { + return [] + }) + } + + toCompletion (completion) { + const icon = this.makeIcon(completion.icon) + if (icon) completion.iconHTML = icon + // workaround https://github.com/atom/autocomplete-plus/issues/868 + if (!completion.description && completion.descriptionMoreURL) { + completion.description = ' ' + } + return completion + } + + // should sync with atom-ink/lib/workspace/workspace.js + makeIcon(icon) { + // if not specified, just fallback to `completion.type` + if (!icon) return '' + if (icon.startsWith('icon-')) return `` + return icon.length === 1 ? icon : '' + } + + sleep () { + return new Promise(resolve => { + setTimeout(() => { + resolve(null) + }, 1000) + }) + } + + getSuggestionDetailsOnSelect (_completion) { + const completionWithDetail = completionDetail(_completion).then(completion => { + // workaround https://github.com/atom/autocomplete-plus/issues/868 + if (!completion.description && completion.descriptionMoreURL) { + completion.description = ' ' + } + return completion + }).catch(err => { + console.log(err) + }) + return Promise.race([completionWithDetail, this.sleep()]) + } + + onDidInsertSuggestion ({ editor, suggestion: { type } }) { + if (type !== 'function' || atom.config.get('julia-client.juliaOptions.noAutoParenthesis')) return + editor.mutateSelectedText(selection => { + if (!selection.isEmpty()) return + const { row, column } = selection.getBufferRange().start + const currentPoint = new Point(row, column) + const nextPoint = new Point(row, column + 1) + const range = new Range(currentPoint, nextPoint) + const finishRange = new Range(nextPoint, nextPoint) + if (editor.getTextInBufferRange(range) !== '(') { + selection.insertText('()') + } + selection.setBufferRange(finishRange) + }) + } +} + +export default new AutoCompleteProvider() diff --git a/lib_src/runtime/console.ts b/lib_src/runtime/console.ts new file mode 100644 index 00000000..1fed63ce --- /dev/null +++ b/lib_src/runtime/console.ts @@ -0,0 +1,417 @@ +'use babel' + +import { client } from '../connection' +import { customEnv } from '../connection/process/basic' +import { CompositeDisposable } from 'atom' +import { paths } from '../misc' +import evaluation from './evaluation' +import modules from './modules' +import * as pty from 'node-pty-prebuilt-multiarch' +import { debounce } from 'underscore-plus' +import { selector } from '../ui' +import { withRemoteConfig } from '../connection/process/remote' +import * as ssh from 'ssh2' + +const { changeprompt, changemodule, fullpath } = + client.import({ msg: ['changeprompt', 'changemodule', 'resetprompt'], rpc: ['validatepath', 'fullpath'] }) + +const isWindows = process.platform === 'win32' +const uriRegex = isWindows ? + /(@ ([^\s]+)\s(.*?)\:(\d+)|((([a-zA-Z]:|\.\.?|\~)|([^\0<>\?\|\/\s!$`&*()\[\]+'":;])+)?((\\|\/)([^\0<>\?\|\/\s!$`&*()\[\]+'":;])+)+)(\:\d+)?)/ : + /(@ ([^\s]+)\s(.*?)\:(\d+)|(((\.\.?|\~)|([^\0\s!$`&*()\[\]+'":;\\])+)?(\/([^\0\s!$`&*()\[\]+'":;\\])+)+)(\:\d+)?)/ + +let whitelistedKeybindingsREPL = []; +let whitelistedKeybindingsTerminal = []; +let ink = undefined; +let subs = undefined + +export var terminal + +export function activate (_ink) { + ink = _ink + subs = new CompositeDisposable() + + process.env['TERM'] = 'xterm-256color' + + subs.add( + atom.config.observe('julia-client.consoleOptions.whitelistedKeybindingsREPL', (kbds) => { + whitelistedKeybindingsREPL = kbds.map(s => s.toLowerCase()) + }), + atom.config.observe('julia-client.consoleOptions.whitelistedKeybindingsTerminal', (kbds) => { + whitelistedKeybindingsTerminal = kbds.map(s => s.toLowerCase()) + }), + atom.config.observe('julia-client.consoleOptions.cursorStyle', updateTerminalSettings), + atom.config.observe('julia-client.consoleOptions.maximumConsoleSize', updateTerminalSettings), + atom.config.observe('julia-client.consoleOptions.macOptionIsMeta', updateTerminalSettings), + atom.config.observe('julia-client.consoleOptions.terminalRendererType', updateTerminalSettings), + atom.config.observe('julia-client.consoleOptions.cursorBlink', updateTerminalSettings) + ) + + terminal = ink.InkTerminal.fromId('julia-terminal', terminalOptions()) + terminal.setTitle('REPL', true) + terminal.onDidOpenLink(hasKeyboardModifier) + terminal.registerTooltipHandler(showTooltip, hideTooltip) + terminal.class = 'julia-terminal' + + subs.add(atom.config.observe('julia-client.uiOptions.layouts.console.defaultLocation', (defaultLocation) => { + terminal.setDefaultLocation(defaultLocation) + })) + + terminal.write('\x1b[1m\x1b[32mPress Enter to start Julia. \x1b[0m\n\r') + terminal.startRequested = () => { + client.boot() + } + + terminal.attachCustomKeyEventHandler((e) => handleKeybinding(e, terminal, whitelistedKeybindingsREPL)) + + modules.onDidChange(debounce(() => changemodule({mod: modules.current()}), 200)) + + client.handle({ + updateWorkspace: () => require('./workspace').update(), + clearconsole: () => terminal.clear(), + cursorpos: () => terminal.cursorPosition(), + writeToTerminal: (str) => { + if (terminal.ty) { + terminal.ty.write(str) + return true + } + return false + } + }) + + let promptObserver + client.onBoot((proc) => { + terminal.attach(proc.ty) + + if (proc.config) { + terminal.setTitle('REPL @ '+proc.config.name, true) + } else { + terminal.setTitle('REPL', true) + } + + if (proc.flush) { + proc.flush((d) => terminal.write(d), (d) => terminal.write(d)) + } + + promptObserver = atom.config.observe('julia-client.consoleOptions.prompt', (prompt) => { + changeprompt(prompt + ' ') + }) + + addLinkHandler(terminal.terminal) + }) + + client.onDetached(() => { + terminal.setTitle('REPL', true) + terminal.detach() + // make sure to switch to the normal termbuffer, otherwise there might be + // issues when leaving an xterm session: + terminal.write('\x1b[?1049h') + terminal.write('\x1b[?1049l') + // disable mouse event capturing in case it was left enabled + terminal.write('\x1b[?1003h') + terminal.write('\x1b[?1003l') + // reset focus events + terminal.write('\x1b[?1004h') + terminal.write('\x1b[?1004l') + terminal.write('\n\r\x1b[1m\r\x1b[31mJulia has exited.\n\r\x1b[32mPress Enter to start a new session.\x1b[0m\n\r') + if (promptObserver) promptObserver.dispose() + }) + + subs.add( + // repl commands + atom.commands.add('atom-workspace', { + 'julia-client:open-REPL': () => { + open().then(() => terminal.show()) + }, + 'julia-client:clear-REPL': () => { + terminal.clear() + }, + }), + atom.commands.add('.julia-terminal', { + 'julia-client:copy-or-interrupt': () => { + if (!terminal.copySelection()) { + atom.commands.dispatch(terminal.view, 'julia-client:interrupt-julia') + } + } + }), + // terminal commands + atom.commands.add('atom-workspace', { + 'julia-client:new-terminal': () => { + newTerminal() + }, + 'julia-client:new-terminal-from-current-folder': ev => { + const dir = evaluation.currentDir(ev.target) + if (!dir) return + newTerminal(dir) + }, + 'julia-client:new-remote-terminal': () => { + newRemoteTerminal() + } + }) + ) + + // handle deserialized terminals + forEachPane(item => { + if (!item.ty) { + item.attachCustomKeyEventHandler((e) => handleKeybinding(e, item)) + addLinkHandler(item.terminal) + item.onDidOpenLink(hasKeyboardModifier) + item.registerTooltipHandler(showTooltip, hideTooltip) + shellPty(item.persistentState.cwd) + .then(({pty, cwd}) => item.attach(pty, true, cwd)) + .catch(() => {}) + } + }, /terminal\-julia\-\d+/) + forEachPane(item => item.close(), /terminal\-remote\-julia\-\d+/) +} + +export function open () { + return terminal.open({ + split: atom.config.get('julia-client.uiOptions.layouts.console.split') + }) +} + +export function close () { + return terminal.close() +} + +function newTerminal (cwd?) { + const term = ink.InkTerminal.fromId(`terminal-julia-${Math.floor(Math.random()*10000000)}`, terminalOptions()) + term.attachCustomKeyEventHandler((e) => handleKeybinding(e, term)) + term.onDidOpenLink(hasKeyboardModifier) + term.registerTooltipHandler(showTooltip, hideTooltip) + addLinkHandler(term.terminal) + shellPty(cwd).then(({pty, cwd}) => { + term.attach(pty, true, cwd) + term.setDefaultLocation(atom.config.get('julia-client.uiOptions.layouts.terminal.defaultLocation')) + term.open({ + split: atom.config.get('julia-client.uiOptions.layouts.terminal.split') + }).then(() => term.show()).catch(err => { + console.log(err) + }) + }).catch(() => {}) +} + +function newRemoteTerminal () { + const term = ink.InkTerminal.fromId(`terminal-remote-julia-${Math.floor(Math.random()*10000000)}`, terminalOptions()) + term.attachCustomKeyEventHandler((e) => handleKeybinding(e, term)) + term.onDidOpenLink(hasKeyboardModifier) + term.registerTooltipHandler(showTooltip, hideTooltip) + addLinkHandler(term.terminal) + remotePty().then(({pty, cwd, conf}) => { + term.attach(pty, true, cwd) + term.setTitle(`Terminal @ ${conf.name}`) + term.setDefaultLocation(atom.config.get('julia-client.uiOptions.layouts.terminal.defaultLocation')) + term.open({ + split: atom.config.get('julia-client.uiOptions.layouts.terminal.split') + }).then(() => term.show()) + pty.on('close', () => term.detach()) + }).catch((e) => console.error(e)) +} + +function terminalOptions () { + const opts = { + scrollback: atom.config.get('julia-client.consoleOptions.maximumConsoleSize'), + cursorStyle: atom.config.get('julia-client.consoleOptions.cursorStyle'), + rendererType: atom.config.get('julia-client.consoleOptions.terminalRendererType'), + cursorBlink: atom.config.get('julia-client.consoleOptions.cursorBlink') + } + if (process.platform === 'darwin') { + opts.macOptionIsMeta = atom.config.get('julia-client.consoleOptions.macOptionIsMeta') + } + return opts +} + +function updateTerminalSettings () { + const settings = terminalOptions() + forEachPane((item) => { + for (const key in settings) { + item.setOption(key, settings[key]) + } + }, /terminal\-julia\-\d+|julia\-terminal|terminal\-remote\-julia\-\d+/) +} + +function forEachPane (f, id = /terminal\-julia\-\d+/) { + atom.workspace.getPaneItems().forEach((item) => { + if (item.id && item.name === 'InkTerminal' && item.id.match(id)) { + f(item) + } + }) +} + +function hasKeyboardModifier (event) { + if (atom.config.get('julia-client.consoleOptions.linkModifier')) { + return process.platform == 'darwin' ? event.metaKey : event.ctrlKey + } + return true +} + +function handleLink (event, uri) { + if (!hasKeyboardModifier(event)) return false + + if (client.isActive()) { + fullpath(uri).then(([path, line]) => { + ink.Opener.open(path, line - 1, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + }) + } else { + let urimatch = uri.match(/@ ([^\s]+)\s(.*?)\:(\d+)/) + if (urimatch) { + ink.Opener.open(urimatch[1], parseInt(urimatch[2]) - 1, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + } else { + const matchregex = isWindows ? + /(([a-zA-Z]\:)?[^\:]+)(?:\:(\d+))?/ : + /([^\:]+)(?:\:(\d+))?/ + urimatch = uri.match(matchregex) + if (urimatch) { + const line = urimatch[2] !== null ? parseInt(urimatch[2]) : 0 + ink.Opener.open(urimatch[1], line - 1, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + } + } + } +} + +function addLinkHandler (terminal) { + terminal.registerLinkMatcher(uriRegex, handleLink, { + willLinkActivate: ev => hasKeyboardModifier(ev), + tooltipCallback: (ev, uri, location) => showTooltip(ev, uri, location, terminal), + leaveCallback: () => hideTooltip() + }) +} + +let tooltip = null + +function showTooltip (event, uri, location?, terminal?) { + hideTooltip() + + if (atom.config.get('julia-client.consoleOptions.linkModifier')) { + const el = document.createElement('div') + el.classList.add('terminal-link-tooltip') + + const terminalRect = terminal.element.getBoundingClientRect() + const colWidth = terminalRect.width / terminal.cols + const rowHeight = terminalRect.height / terminal.rows + + const leftPosition = location.start.x * colWidth + terminalRect.left + const topPosition = (location.start.y - 1.5) * rowHeight + terminalRect.top + + el.style.top = topPosition + 'px' + el.style.left = leftPosition + 'px' + + el.innerText = (process.platform == 'darwin' ? 'Cmd' : 'Ctrl') + '-Click to open link.' + + tooltip = el + document.body.appendChild(el) + + return true + } else { + return false + } +} + +function hideTooltip () { + if (tooltip) { + try { + document.body.removeChild(tooltip) + } catch (err) { + + } finally { + tooltip = null + } + } +} + +function handleKeybinding (e, term, binds = whitelistedKeybindingsTerminal) { + if (process.platform !== 'win32' && e.keyCode === 13 && (e.altKey || e.metaKey) && e.type === 'keydown') { + // Meta-Enter doesn't work properly with xterm.js atm, so we send the right escape sequence ourselves: + if (term.ty) { + term.ty.write('\x1b\x0d') + } + return false + } else if (binds.indexOf(atom.keymaps.keystrokeForKeyboardEvent(e)) > -1) { + // let certain user defined key events fall through to Atom's handler + return false + } + return e +} + +function remotePty () { + return withRemoteConfig(conf => { + return new Promise((resolve, reject) => { + const conn = new ssh.Client() + conn.on('ready', () => { + conn.shell({ term: "xterm-256color" }, (err, stream) => { + if (err) console.error(`Error while starting remote shell.`) + + stream.on('close', () => { + conn.end() + }) + + // forward resize handling + stream.resize = (cols, rows) => stream.setWindow(rows, cols, 999, 999) + + resolve({pty: stream, cwd: '~', conf}) + }) + }).connect(conf) + }); + }); +} + +function shellPty (cwd) { + process.env['TERM'] = 'xterm-256color' + return new Promise((resolve, reject) => { + let pr + if (cwd) { + pr = new Promise((resolve) => resolve(cwd)) + } else { + // show project paths + pr = selector.show(atom.project.getPaths(), { + emptyMessage: 'Enter a custom path above.', + allowCustom: true + }) + } + pr.then((cwd) => { + if (cwd) { + cwd = paths.expandHome(cwd) + if (!require('fs').existsSync(cwd)) { + atom.notifications.addWarning("Path does not exist.", { + description: "Defaulting to `HOME` for new terminal's working directory." + }) + cwd = paths.home() + } + const env = customEnv() + const ty = pty.spawn(atom.config.get("julia-client.consoleOptions.shell"), [], { + cols: 100, + rows: 30, + cwd, + env, + useConpty: true, + handleFlowControl: true + }) + resolve({ + pty: ty, + cwd}) + } else { + reject() + } + }) + }); +} + +export function deactivate () { + // detach node-pty process from ink terminals; necessary for updates to work cleanly + forEachPane(item => item.detach(), /terminal\-julia\-\d+/) + // remote terminals shouldn't be serialized + forEachPane(item => { + item.detach() + item.close() + }, /terminal\-remote\-julia\-\d+/) + if (terminal) terminal.detach() + if (subs) subs.dispose() + subs = null +} diff --git a/lib_src/runtime/datatip.ts b/lib_src/runtime/datatip.ts new file mode 100644 index 00000000..c5f79f29 --- /dev/null +++ b/lib_src/runtime/datatip.ts @@ -0,0 +1,130 @@ +/** @babel */ + +/** + * @FIXME? + * Use `component` property instaed of `markedStrings` and reuse exisiting our full-featured + * components in ../ui/views.coffee. + * Code in https://github.com/TypeStrong/atom-typescript/blob/master/dist/main/atom-ide/datatipProvider.js + * can be helpful. + */ + +import { client } from '../connection' +import modules from './modules' +import { isValidScopeToInspect } from '../misc/scopes' +import { + getWordAndRange, + getWordRangeWithoutTrailingDots, + isValidWordToInspect +} from '../misc/words' +import { getLocalContext } from '../misc/blocks' + +const datatip = client.import('datatip') + +const grammar = atom.grammars.grammarForScopeName('source.julia') + +class DatatipProvider { + public range: any; + public word: any; + public main: any; + public sub: any; + public column: any; + public row: any; + public context: any; + public startRow: any; + providerName = 'julia-client-datatip-provider' + + priority = 100 + + grammarScopes = atom.config.get('julia-client.juliaSyntaxScopes') + + useAtomIDEUI = false + + async datatip(editor, bufferPosition) { + // If Julia is not running, do nothing + if (!client.isActive()) return + + // If the scope at `bufferPosition` is not valid code scope, do nothing + if (!isValidScopeToInspect(editor, bufferPosition)) return + + // get word without trailing dot accessors at the buffer position + let { range, word } = getWordAndRange(editor, { + bufferPosition + }) + range = getWordRangeWithoutTrailingDots(word, range, bufferPosition) + word = editor.getTextInBufferRange(range) + + // check the validity of code to be inspected + if (!(isValidWordToInspect(word))) return + + const { main, sub } = await modules.getEditorModule(editor, bufferPosition) + const mod = main ? (sub ? `${main}.${sub}` : main) : 'Main' + + const { column, row } = bufferPosition + const { context, startRow } = getLocalContext(editor, row) + + try { + const result = await datatip({ + word, + mod, + path: editor.getPath(), + column: column + 1, + row: row + 1, + startRow, + context + }) + if (result.error) return + if (this.useAtomIDEUI) { + if (result.line) { + const value = editor.lineTextForBufferRow(result.line).trim() + return { + range, + markedStrings: [{ + type: 'snippet', + value, + grammar + }] + } + } else if (result.strings) { + return { + range, + markedStrings: result.strings.map(string => { + return { + type: string.type, + value: string.value, + grammar: string.type === 'snippet' ? grammar : null + } + }) + } + } + } else { + if (result.line) { + const value = editor.lineTextForBufferRow(result.line).trim() + return { + range, + markedStrings: [{ + type: 'snippet', + value, + grammar + }] + } + } else if (result.strings) { + // @NOTE: atom-ide-datatip can't render multiple `snippet`s in `markedStrings` correctly + return { + range, + markedStrings: result.strings.map(string => { + return { + type: 'markdown', + value: string.type === 'snippet' ? `\`\`\`julia\n${string.value}\n\`\`\`` : string.value, + grammar: string.type === 'snippet' ? grammar : null + } + }) + } + } + } + } catch (error) { + return + } + } +} + +export default new DatatipProvider() diff --git a/lib_src/runtime/debugger.ts b/lib_src/runtime/debugger.ts new file mode 100644 index 00000000..00ad03bf --- /dev/null +++ b/lib_src/runtime/debugger.ts @@ -0,0 +1,349 @@ +'use babel' +/** @jsx etch.dom */ + +import { CompositeDisposable } from 'atom' +import { views } from '../ui' +import { client } from '../connection' +import connection from '../connection' +import { blocks, cells, paths } from '../misc' +import modules from './modules' + +import workspace from './workspace' + +const { debugfile, module: getmodule } = client.import(['debugfile', 'module']) + +let active, stepper, subs, breakpoints, debuggerPane, ink + +const buttonSVGs = { + 'step-in': ` + + + + + + + + + `, + 'step-out': ` + + + + + + + + + `, + 'step-to-selection': ` + + + + + + + + + + + `, + 'step-line': ` + + + + + + + + + + + `, + 'step-expr': ` + + + + + + + + + ` +} + +export function activate (_ink) { + ink = _ink + const buttons = [ + {icon: 'playback-fast-forward', tooltip: 'Debug: Continue', command: 'julia-debug:continue', color: 'success'}, + {tooltip: 'Debug: Next Line', command: 'julia-debug:step-to-next-line', svg: buttonSVGs['step-line']}, + {tooltip: 'Debug: Step to Selected Line', command: 'julia-debug:step-to-selected-line', svg: buttonSVGs['step-to-selection']}, + {tooltip: 'Debug: Next Expression', command: 'julia-debug:step-to-next-expression', svg: buttonSVGs['step-expr']}, + {tooltip: 'Debug: Step Into', command: 'julia-debug:step-into', svg: buttonSVGs['step-in']}, + {tooltip: 'Debug: Step Out', command: 'julia-debug:step-out', svg: buttonSVGs['step-out']}, + {icon: 'x', tooltip: 'Debug: Stop Debugging', command: 'julia-debug:stop-debugging', color: 'error'}, + ] + const startButtons = [ + {text: 'Run File', tooltip: 'Debug: Run File', command: 'julia-debug:run-file'}, + {text: 'Step Through File', tooltip: 'Debug: Step Through File', command: 'julia-debug:step-through-file'}, + {text: 'Run Block', tooltip: 'Debug: Run Block', command: 'julia-debug:run-block'}, + {text: 'Step Through Block', tooltip: 'Debug: Step Through Block', command: 'julia-debug:step-through-block'}, + ] + stepper = new ink.Stepper({ + buttons, + pending: !atom.config.get('julia-client.uiOptions.openNewEditorWhenDebugging') + }) + breakpoints = new ink.breakpoints(atom.config.get('julia-client.juliaSyntaxScopes'), { + toggle: toggleJuliaBP, + clear: clearJulia, + toggleUncaught: toggleUncaughtJulia, + toggleException: toggleExceptionJulia, + refresh: getBreakpoints, + addArgs: addArgsJulia, + toggleActive: toggleActiveJulia, + toggleAllActive: toggleAllActiveJulia, + addCondition, + setLevel, + toggleCompiled + }) + debuggerPane = ink.DebuggerPane.fromId('julia-debugger-pane', stepper, breakpoints, buttons, startButtons) + + subs = new CompositeDisposable() + subs.add(atom.config.observe('julia-client.uiOptions.layouts.debuggerPane.defaultLocation', (defaultLocation) => { + debuggerPane.setDefaultLocation(defaultLocation) + })) + subs.add(client.onDetached(() => { + debugmode(false) + breakpoints.clear(true) + })) +} + +export function deactivate() { + breakpoints.destroy() + subs.dispose() +} + +export function open () { + return debuggerPane.open({ + split: atom.config.get('julia-client.uiOptions.layouts.debuggerPane.split') + }) +} + +export function close () { + return debuggerPane.close() +} + +function activeError(ev) { + if (!active) { + // Only show an error when toolbar button or command is used directly. `ev.originalEvent` is + // a `KeyboardEvent` if this was triggered by a keystroke. + if (ev.originalEvent === undefined) { + atom.notifications.addError('You need to be debugging to do that.', { + description: 'You can start debugging by calling `Juno.@enter f(args...)` from the integrated REPL.', + dismissable: true + }) + } + return true + } + return false +} + +function requireDebugging(ev, f) { + if (activeError(ev)) { + ev.abortKeyBinding() + } else { + f() + } +} + +function requireNotDebugging(f) { + if (active) { + atom.notifications.addError('Can\'t start a debugging session while debugging.', { + description: 'Please finish the current session first.', + dismissable: true + }) + } else { + f() + } +} + +function debugmode(a) { + active = a + if (!active) { + stepper.destroy() + workspace.update() + debuggerPane.reset() + } else { + debuggerPane.ensureVisible() + } +} + +client.handle({ + debugmode, + stepto(file, line, text, moreinfo) { + stepper.step(file, line - 1, views.render(text), moreinfo) + workspace.update() + }, + working() { client.ipc.loading.working() }, + doneWorking() { client.ipc.loading.done() }, + getFileBreakpoints() { + const bps = breakpoints.getFileBreakpoints() + return bps.filter(bp => bp.isactive).map(bp => { + return { + file: bp.file, + line: bp.line, + condition: bp.condition + } + }) + } +}) + +export function finish (ev) { requireDebugging(ev, () => client.import('finish')()) } +export function nextline (ev) { requireDebugging(ev, () => client.import('nextline')()) } +export function stepexpr (ev) { requireDebugging(ev, () => client.import('stepexpr')()) } +export function stepin (ev) { requireDebugging(ev, () => client.import('stepin')()) } +export function stop (ev) { requireDebugging(ev, () => client.import('stop')()) } +export function continueForward (ev) { requireDebugging(ev, () => client.import('continue')()) } +export function toselectedline (ev) { + requireDebugging(ev, () => { + const ed = stepper.edForFile(stepper.file) + if (ed != null) { + client.import('toline')(ed.getCursorBufferPosition().row + 1) + } + }) +} + +export function debugFile(shouldStep, el) { + requireNotDebugging(() => { + if (el) { + const path = paths.getPathFromTreeView(el) + if (!path) { + atom.notifications.addError('This file has no path.') + return + } + try { + const code = paths.readCode(path) + const data = { path, code, row: 1, column: 1 } + getmodule(data).then(mod => { + debugfile(modules.current(mod), code, path, shouldStep) + }).catch(err => { + console.log(err) + }) + } catch (err) { + atom.notifications.addError('Error happened', { + detail: err, + dismissable: true + }) + } + } else { + const ed = atom.workspace.getActiveTextEditor() + if (!(ed && ed.getGrammar && ed.getGrammar().id === 'source.julia')) { + atom.notifications.addError('Can\'t debug current file.', { + description: 'Please make sure a Julia file is open in the workspace.' + }) + return + } + const edpath = client.editorPath(ed) || 'untitled-' + ed.getBuffer().id + const mod = modules.current() || 'Main' + debugfile(mod, ed.getText(), edpath, shouldStep) + } + }) +} + +export function debugBlock(shouldStep, cell) { + requireNotDebugging(() => { + const ed = atom.workspace.getActiveTextEditor() + if (!ed) { + atom.notifications.addError('Can\'t debug current code block.', { + description: 'Please make sure a file is open in the workspace.' + }) + return + } + const edpath = client.editorPath(ed) || 'untitled-' + ed.getBuffer().id + const mod = modules.current() || 'Main' + const selector = cell ? cells : blocks + const blks = selector.get(ed) + if (blks.length === 0) { + return + } + const { range, text, line } = blks[0] + const [[start], [end]] = range + ink.highlight(ed, start, end) + debugfile(mod, text, edpath, shouldStep, line) + }) +} + +export function clearbps() { + connection.boot() + breakpoints.clear() + if (client.isActive()) client.import('clearbps')() +} + +function toggleJuliaBP (item) { + connection.boot() + return client.import('toggleBP')(item) +} +function clearJulia () { + connection.boot() + return client.import('clearbps')() +} +function toggleUncaughtJulia () { + connection.boot() + return client.import('toggleUncaught')() +} +function toggleExceptionJulia () { + connection.boot() + return client.import('toggleException')() +} +function toggleCompiled () { + connection.boot() + return client.import('toggleCompiled')() +} +function getBreakpoints () { + connection.boot() + return client.import('getBreakpoints')() +} +function addArgsJulia (args) { + connection.boot() + return client.import('addArgs')(args) +} +function toggleAllActiveJulia (args) { + connection.boot() + return client.import('toggleAllActiveBP')(args) +} +function toggleActiveJulia (item) { + connection.boot() + return client.import('toggleActiveBP')(item) +} +function addCondition (item, cond) { + connection.boot() + return client.import('addConditionById')(item, cond) +} +function setLevel (level) { + connection.boot() + return client.import('setStackLevel')(level) +} + +export function togglebp ( + cond = false, + ed = atom.workspace.getActiveTextEditor() +) { + if (!ed || !ed.getPath()) { + atom.notifications.addError('Need a saved file to add a breakpoint') + return + } + const file = client.editorPath(ed) + ed.getCursors().map((cursor) => { + const line = cursor.getBufferPosition().row + 1 + if (cond) { + breakpoints.toggleConditionAtSourceLocation({ + file, + line + }) + } else { + breakpoints.toggleAtSourceLocation({ + file, + line + }) + } + }) +} diff --git a/lib_src/runtime/debuginfo.ts b/lib_src/runtime/debuginfo.ts new file mode 100644 index 00000000..de71ff26 --- /dev/null +++ b/lib_src/runtime/debuginfo.ts @@ -0,0 +1,58 @@ +'use babel' + +import { client } from '../connection' + +const { reportinfo } = client.import(['reportinfo']) + +export default function debuginfo () { + let atomReport = `# Atom: +Version: ${atom.getVersion()} +Dev Mode: ${atom.inDevMode()} +Official Release: ${atom.isReleasedVersion()} +${JSON.stringify(process.versions, null, 2)} +` + const atomPkgs = ['julia-client', 'ink', 'uber-juno', 'language-julia', 'language-weave', + 'indent-detective', 'latex-completions'] + atomPkgs.forEach((pkg, ind) => { + atomReport += '# ' + atomPkgs[ind] + ':' + let activePkg = atom.packages.getActivePackage(pkg) + if (activePkg) { + atomReport += + ` +Version: ${activePkg.metadata.version} +Config: +${JSON.stringify(activePkg.config.settings[pkg], null, 2)} +` + } else { + atomReport += 'not installed\n' + } + atomReport += '\n\n' + }) + + reportinfo().then(info => { + atomReport += "# versioninfo():\n" + atomReport += info + showNotification(atomReport) + }).catch(err => { + atomReport += 'Could not connect to Julia.' + showNotification(atomReport) + }) +} + +function showNotification (atomReport) { + atom.notifications.addInfo('Juno Debug Info', { + description: 'Please provide the info above when you report an issue. ' + + 'Make sure to strip it of any kind of sensitive info you might ' + + 'not want to share.', + detail: atomReport, + dismissable: true, + buttons: [ + { + text: 'Copy to Clipboard', + onDidClick: () => { + atom.clipboard.write(atomReport) + } + } + ] + }) +} diff --git a/lib_src/runtime/evaluation.ts b/lib_src/runtime/evaluation.ts new file mode 100644 index 00000000..0d80f29f --- /dev/null +++ b/lib_src/runtime/evaluation.ts @@ -0,0 +1,269 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS201: Simplify complex destructure assignments + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// TODO: this is very horrible, refactor +import path from 'path'; + +const {dialog, BrowserWindow} = require('electron').remote; + +import { client } from '../connection'; +import { notifications, views, selector, docpane } from '../ui'; +import { paths, blocks, cells, words, weave } from '../misc'; +import { processLinks } from '../ui/docs'; +import workspace from './workspace'; +import modules from './modules'; +const { + eval: evaluate, evalall, evalshow, module: getmodule, + cd, clearLazy, activateProject, activateParentProject, activateDefaultProject +} = client.import({ + rpc: ['eval', 'evalall', 'evalshow', 'module'], + msg: ['cd', 'clearLazy', 'activateProject', 'activateParentProject', 'activateDefaultProject']}); +const searchDoc = client.import('docs'); + +export default { + _currentContext() { + const editor = atom.workspace.getActiveTextEditor(); + const mod = modules.current() || 'Main'; + const edpath = client.editorPath(editor) || ('untitled-' + editor.getBuffer().id); + return {editor, mod, edpath}; + }, + + _showError(r, lines) { + if (this.errorLines != null) { + this.errorLines.lights.destroy(); + } + const lights = this.ink.highlights.errorLines(((() => { + const result = []; + for (let {file, line} of lines) { result.push({file, line: line-1}); + } + return result; + })())); + this.errorLines = {r, lights}; + return r.onDidDestroy(() => { + if ((this.errorLines != null ? this.errorLines.r : undefined) === r) { return this.errorLines.lights.destroy(); } + }); + }, + + eval({move, cell}={}) { + const {editor, mod, edpath} = this._currentContext(); + const codeSelector = (cell != null) ? cells : blocks; + // global options + const resultsDisplayMode = atom.config.get('julia-client.uiOptions.resultsDisplayMode'); + const errorInRepl = atom.config.get('julia-client.uiOptions.errorInRepl'); + const scrollToResult = atom.config.get('julia-client.uiOptions.scrollToResult'); + + return Promise.all(codeSelector.get(editor).map(({range, line, text, selection}) => { + if (move) { codeSelector.moveNext(editor, selection, range); } + const [start] = Array.from(range[0]), [end] = Array.from(range[1]); + this.ink.highlight(editor, start, end); + let rtype = resultsDisplayMode; + if (cell && !(rtype === 'console')) { + rtype = 'block'; + } + if (rtype === 'console') { + evalshow({text, line: line+1, mod, path: edpath}); + notifications.show("Evaluation Finished"); + return workspace.update(); + } else { + let r = null; + setTimeout((() => r != null ? r : (r = new this.ink.Result(editor, [start, end], {type: rtype, scope: 'julia', goto: scrollToResult}))), 0.1); + return evaluate({text, line: line+1, mod, path: edpath, errorInRepl}) + .catch(() => r != null ? r.destroy() : undefined) + .then(result => { + if ((result == null)) { + if (r != null) { + r.destroy(); + } + console.error('Error: Something went wrong while evaluating.'); + return; + } + const error = result.type === 'error'; + const view = error ? result.view : result; + if ((r == null) || r.isDestroyed) { r = new this.ink.Result(editor, [start, end], {type: rtype, scope: 'julia', goto: scrollToResult}); } + const registerLazy = function(id) { + r.onDidDestroy(client.withCurrent(() => clearLazy([id]))); + return editor.onDidDestroy(client.withCurrent(() => clearLazy(id))); + }; + r.setContent(views.render(view, {registerLazy}), {error}); + if (error) { + if (error) { atom.beep(); } + this.ink.highlight(editor, start, end, 'error-line'); + if (result.highlights != null) { + this._showError(r, result.highlights); + } + } + notifications.show("Evaluation Finished"); + workspace.update(); + return result; + }); + } + }) + ); + }, + + evalAll(el) { + let code; + if (el) { + path = paths.getPathFromTreeView(el); + if (!path) { + return atom.notifications.addError('This file has no path.'); + } + try { + code = paths.readCode(path); + const data = { + path, + code, + row: 1, + column: 1 + }; + return getmodule(data) + .then(mod => { + return evalall({ + path, + module: modules.current(mod), + code + }) + .then(function(result) { + notifications.show("Evaluation Finished"); + return workspace.update();}).catch(err => { + return console.log(err); + }); + }).catch(err => { + return console.log(err); + }); + + } catch (error) { + return atom.notifications.addError('Error happened', { + detail: error, + dismissable: true + } + ); + } + } else { + const {editor, mod, edpath} = this._currentContext(); + atom.commands.dispatch(atom.views.getView(editor), 'inline-results:clear-all'); + const [scope] = Array.from(editor.getRootScopeDescriptor().getScopesArray()); + const weaveScopes = ['source.weave.md', 'source.weave.latex']; + const module = weaveScopes.includes(scope) ? mod : editor.juliaModule; + code = weaveScopes.includes(scope) ? weave.getCode(editor) : editor.getText(); + return evalall({ + path: edpath, + module, + code + }) + .then(function(result) { + notifications.show("Evaluation Finished"); + return workspace.update();}).catch(err => { + return console.log(err); + }); + } + }, + + toggleDocs() { + const { editor, mod, edpath } = this._currentContext(); + const bufferPosition = editor.getLastCursor().getBufferPosition(); + // get word without trailing dot accessors at the buffer position + let { word, range } = words.getWordAndRange(editor, { bufferPosition }); + range = words.getWordRangeWithoutTrailingDots(word, range, bufferPosition); + word = editor.getTextInBufferRange(range); + + if (!words.isValidWordToInspect(word)) { return; } + return searchDoc({word, mod}) + .then(result => { + if (result.error) { return; } + const v = views.render(result); + processLinks(v.getElementsByTagName('a')); + if (atom.config.get('julia-client.uiOptions.docsDisplayMode') === 'inline') { + const d = new this.ink.InlineDoc(editor, range, { + content: v, + highlight: true + } + ); + return d.view.classList.add('julia'); + } else { + docpane.ensureVisible(); + return docpane.showDocument(v, []); + } + }).catch(err => { + return console.log(err); + }); + }, + + // Working Directory + + _cd(dir) { + if (atom.config.get('julia-client.juliaOptions.persistWorkingDir')) { + atom.config.set('julia-client.juliaOptions.workingDir', dir); + } + return cd(dir); + }, + + cdHere(el) { + const dir = this.currentDir(el); + if (dir) { + return this._cd(dir); + } + }, + + activateProject(el) { + const dir = this.currentDir(el); + if (dir) { + return activateProject(dir); + } + }, + + activateParentProject(el) { + const dir = this.currentDir(el); + if (dir) { + return activateParentProject(dir); + } + }, + + activateDefaultProject() { + return activateDefaultProject(); + }, + + currentDir(el) { + const dirPath = paths.getDirPathFromTreeView(el); + if (dirPath) { return dirPath; } + // invoked from normal command usage + const file = client.editorPath(atom.workspace.getCenter().getActiveTextEditor()); + if (file) { return path.dirname(file); } + atom.notifications.addError('This file has no path.'); + return null; + }, + + cdProject() { + const dirs = atom.project.getPaths(); + if (dirs.length < 1) { + return atom.notifications.addError('This project has no folders.'); + } else if (dirs.length === 1) { + return this._cd(dirs[0]); + } else { + return selector.show(dirs) + .then(dir => { + if (dir == null) { return; } + return this._cd(dir); + }).catch(err => { + return console.log(err); + }); + } + }, + + cdHome() { + return this._cd(paths.home()); + }, + + cdSelect() { + const opts = {properties: ['openDirectory']}; + return dialog.showOpenDialog(BrowserWindow.getFocusedWindow(), opts, path => { + if (path != null) { return this._cd(path[0]); } + }); + } +}; diff --git a/lib_src/runtime/formatter.ts b/lib_src/runtime/formatter.ts new file mode 100644 index 00000000..796fbbbb --- /dev/null +++ b/lib_src/runtime/formatter.ts @@ -0,0 +1,116 @@ +/** @babel */ + +import { client } from '../connection' +import { CompositeDisposable } from 'atom' + +const format = client.import('format') + +export function formatCode () { + const editor = atom.workspace.getActiveTextEditor() + if (!editor) return + + const selections = editor.getSelections() + if (selections.length === 1 && !selections[0].getText()) { + formatEditor(editor) + } else { + selections.forEach((selection) => { + formatEditorWithSelection(editor, selection) + }) + } +} + +function formatEditor (editor) { + const range = editor.getBuffer().getRange() + return formatEditorTextInRange(editor, range, editor.getText()) +} + +function formatEditorWithSelection (editor, selection) { + const range = selection.getBufferRange() + return formatEditorTextInRange(editor, range, selection.getText()) +} + +function formatEditorTextInRange (editor, range, text) { + const marker = markRange(editor, range) + // @NOTE: Branch on `getSoftTabs` if supported by formatter. + let indent = atom.config.get('julia-client.juliaOptions.formattingOptions.indent') + if (indent === -1) indent = editor.getTabLength() + let margin = atom.config.get('julia-client.juliaOptions.formattingOptions.margin') + if (margin === -1) margin = editor.getPreferredLineLength() + format({ + text, + indent, + margin, + always_for_in: atom.config.get('julia-client.juliaOptions.formattingOptions.always_for_in'), + whitespace_typedefs: atom.config.get('julia-client.juliaOptions.formattingOptions.whitespace_typedefs'), + whitespace_ops_in_indices: atom.config.get('julia-client.juliaOptions.formattingOptions.whitespace_ops_in_indices'), + remove_extra_newlines: atom.config.get('julia-client.juliaOptions.formattingOptions.remove_extra_newlines') + }).then(({ error, formattedtext }) => { + if (error) { + atom.notifications.addError('Julia-Client: Format-Code', { + detail: error, + dismissable: true + }) + } else { + if (marker.isValid()) { + editor.setTextInBufferRange(marker.getBufferRange(), formattedtext) + } else { + atom.notifications.addError('Julia-Client: Format-Code', { + detail: 'Cancelled the formatting task because the selected code has been manually modified.', + dismissable: true + }) + } + } + }).catch(err => { + console.log(err) + }).finally(() => { + marker.destroy() + }) +} + +function markRange(editor, range) { + const marker = editor.markBufferRange(range, { + invalidate: 'inside' + }) + editor.decorateMarker(marker, { + type: 'highlight', + class: 'ink-block' + }) + return marker +} + +let subs + +export function activate() { + subs = new CompositeDisposable() + const edWatch = new WeakSet() + + subs.add(atom.workspace.observeTextEditors(ed => { + edWatch.add(ed) + // use onDidSave instead of onWillSave to guarantee our formatter is the last to run: + const edsub = ed.getBuffer().onDidSave(() => { + if (ed && ed.getGrammar && ed.getGrammar().id === 'source.julia') { + if (client.isActive() && edWatch.has(ed)) { + formatEditor(ed).then(() => { + edWatch.delete(ed) + ed.save().then(() => { + edWatch.add(ed) + }).catch(err => { + console.log(err) + }) + }).catch(err => { + console.log(err) + }) + } + } + }) + subs.add(edsub) + + subs.add(ed.onDidDestroy(() => { + edsub.dispose() + })) + })) +} + +export function deactivate() { + subs && subs.dispose && subs.dispose() +} diff --git a/lib_src/runtime/frontend.ts b/lib_src/runtime/frontend.ts new file mode 100644 index 00000000..98a0a1ed --- /dev/null +++ b/lib_src/runtime/frontend.ts @@ -0,0 +1,94 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const {BrowserWindow} = require('electron').remote; +import vm from 'vm'; +import { client } from '../connection'; +import { selector, notifications } from '../ui'; +import { colors } from '../misc'; + +export default { + evalwith(obj, code) { + return vm.runInThisContext(`(function(){return ${code}})`).call(obj); + }, + + windows: {}, + + activate() { + client.handle({select(items) { return selector.show(items); }}); + + client.handle({input() { return selector.show([], {allowCustom: true}); }}); + + client.handle({syntaxcolors(selectors) { return colors.getColors(selectors); }}); + + client.handle({openFile: (file, line) => (this.ink != null ? this.ink.Opener.open(file, line, { + pending: atom.config.get('core.allowPendingPaneItems') + }) : undefined) + }); + + client.handle({versionwarning(msg) { + return atom.notifications.addWarning("Outdated version of Atom.jl detected.", { + description: msg, + dismissable: true + } + ); + } + }); + + // Blink APIs + + client.handle({ + createWindow: opts => { + const w = new BrowserWindow(opts); + if (opts.url != null) { + w.loadURL(opts.url); + } + w.setMenu(null); + const wid = w.id; + this.windows[wid] = w; + w.on('close', () => delete this.windows[wid]); + return wid; + }, + + withWin: (id, code) => { + return this.evalwith(this.windows[id], code); + }, + + winActive: id => { + return this.windows.hasOwnProperty(id); + }, + + notify(msg) { + return notifications.show(msg, true); + } + }); + + + return client.onDetached(() => { + return (() => { + const result = []; + for (let id in this.windows) { + const win = this.windows[id]; + delete this.windows[id]; + result.push(win.close()); + } + return result; + })(); + }); + }, + + deactivate() { + return (() => { + const result = []; + for (let id in this.windows) { + const win = this.windows[id]; + result.push(win.close()); + } + return result; + })(); + } +}; diff --git a/lib_src/runtime/goto.ts b/lib_src/runtime/goto.ts new file mode 100644 index 00000000..1ff243de --- /dev/null +++ b/lib_src/runtime/goto.ts @@ -0,0 +1,228 @@ +/** @babel */ + +import path from 'path' +import fs from 'fs' +import { CompositeDisposable, Range } from 'atom' + +import { client } from '../connection' +import modules from './modules' +import { isValidScopeToInspect } from '../misc/scopes' +import { + getWordAndRange, + getWordRangeAtBufferPosition, + getWordRangeWithoutTrailingDots, + isValidWordToInspect +} from '../misc/words' +import { getLocalContext } from '../misc/blocks' + +const { + gotosymbol: gotoSymbol, + regeneratesymbols: regenerateSymbols, + clearsymbols: clearSymbols, +} = client.import(['gotosymbol', 'regeneratesymbols', 'clearsymbols']) + +const includeRegex = /(include|include_dependency)\(".+\.jl"\)/ +const filePathRegex = /".+\.jl"/ + +class Goto { + public ink: any; + public subscriptions: any; + public filePath: any; + public word: any; + public range: any; + public column: any; + public row: any; + public context: any; + public startRow: any; + public main: any; + public sub: any; + + activate (ink) { + this.ink = ink + this.subscriptions = new CompositeDisposable() + this.subscriptions.add( + atom.commands.add('atom-workspace', 'julia-client:regenerate-symbols-cache', () => { + regenerateSymbols() + }), + atom.commands.add('atom-workspace', 'julia-client:clear-symbols-cache', () => { + clearSymbols() + }) + ) + } + + deactivate () { + this.subscriptions.dispose() + } + + getJumpFilePath(editor, bufferPosition) { + const includeRange = getWordRangeAtBufferPosition(editor, bufferPosition, { + wordRegex: includeRegex + }) + if (includeRange.isEmpty()) return false + + // return if the bufferPosition is not on the path string + const filePathRange = getWordRangeAtBufferPosition(editor, bufferPosition, { + wordRegex: filePathRegex + }) + if (filePathRange.isEmpty()) return false + + const filePathText = editor.getTextInBufferRange(filePathRange) + const filePathBody = filePathText.replace(/"/g, '') + const dirPath = path.dirname(editor.getPath()) + const filePath = path.join(dirPath, filePathBody) + + // return if there is not such a file exists + if (!fs.existsSync(filePath)) return false + return { range: filePathRange, filePath } + } + + isClientAndInkReady () { + return client.isActive() && this.ink !== undefined + } + + gotoSymbol () { + const editor = atom.workspace.getActiveTextEditor() + const bufferPosition = editor.getCursorBufferPosition() + + // file jumps + const rangeFilePath = this.getJumpFilePath(editor, bufferPosition) + if (rangeFilePath) { + const { filePath } = rangeFilePath + return atom.workspace.open(filePath, { + pending: atom.config.get('core.allowPendingPaneItems'), + searchAllPanes: true + }) + } + + if (!this.isClientAndInkReady()) return + + // get word without trailing dot accessors at the buffer position + let { word, range } = getWordAndRange(editor, { + bufferPosition + }) + range = getWordRangeWithoutTrailingDots(word, range, bufferPosition) + word = editor.getTextInBufferRange(range) + + // check the validity of code to be inspected + if (!(isValidWordToInspect(word))) return + + // local context + const { column, row } = bufferPosition + const { context, startRow } = getLocalContext(editor, row) + + // module context + const currentModule = modules.current() + const mod = currentModule ? currentModule : 'Main' + const text = editor.getText() // buffer text that will be used for fallback entry + + gotoSymbol({ + word, + path: editor.getPath() || 'untitled-' + editor.getBuffer().getId(), + // local context + column: column + 1, + row: row + 1, + startRow, + context, + onlyGlobal: false, + // module context + mod, + text + }).then(results => { + if (results.error) return + this.ink.goto.goto(results, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + }).catch(err => { + console.log(err) + }) + } + + provideHyperclick () { + const getSuggestion = async (textEditor, bufferPosition) => { + // file jumps -- invoked even if Julia isn't running + const rangeFilePath = this.getJumpFilePath(textEditor, bufferPosition) + if (rangeFilePath) { + const { range, filePath } = rangeFilePath + return { + range, + callback: () => { + atom.workspace.open(filePath, { + pending: atom.config.get('core.allowPendingPaneItems'), + searchAllPanes: true + }) + } + } + } + + // If Julia is not running, do nothing + if (!this.isClientAndInkReady()) return + + // If the scope at `bufferPosition` is not valid code scope, do nothing + if (!isValidScopeToInspect(textEditor, bufferPosition)) return + + // get word without trailing dot accessors at the buffer position + let { word, range } = getWordAndRange(textEditor, { + bufferPosition + }) + range = getWordRangeWithoutTrailingDots(word, range, bufferPosition) + word = textEditor.getTextInBufferRange(range) + + // check the validity of code to be inspected + if (!(isValidWordToInspect(word))) return + + // local context + const { column, row } = bufferPosition + const { context, startRow } = getLocalContext(textEditor, row) + + // module context + const { main, sub } = await modules.getEditorModule(textEditor, bufferPosition) + const mod = main ? (sub ? `${main}.${sub}` : main) : 'Main' + const text = textEditor.getText() // buffer text that will be used for fallback entry + + return new Promise((resolve) => { + gotoSymbol({ + word, + path: textEditor.getPath() || 'untitled-' + textEditor.getBuffer().getId(), + // local context + column: column + 1, + row: row + 1, + startRow, + context, + onlyGlobal: false, + // module context + mod, + text + }).then(results => { + // If the `goto` call fails or there is no where to go to, do nothing + if (results.error) { + resolve({ + range: new Range([0,0], [0,0]), + callback: () => {} + }) + } + resolve({ + range, + callback: () => { + setTimeout(() => { + this.ink.goto.goto(results, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + }, 5) + } + }) + }).catch(err => { + console.log(err) + }) + }) + } + + return { + providerName: 'julia-client-hyperclick-provider', + priority: 100, + grammarScopes: atom.config.get('julia-client.juliaSyntaxScopes'), + getSuggestion + } + } +} + +export default new Goto() diff --git a/lib_src/runtime/linter.ts b/lib_src/runtime/linter.ts new file mode 100644 index 00000000..8414c376 --- /dev/null +++ b/lib_src/runtime/linter.ts @@ -0,0 +1,55 @@ +'use babel' + +import { CompositeDisposable } from 'atom' +import { client } from '../connection' + +let subs, lintPane + +export function activate (ink) { + const linter = ink.Linter + ({ + lintPane + } = linter) + + client.handle({ + staticLint: (warnings) => { + lintPane.ensureVisible({ + split: atom.config.get('julia-client.uiOptions.layouts.linter.split') + }) + linter.setItems(warnings) + }, + clearLint: () => { + linter.clearItems() + }, + showCompiled: (name, ...args) => { + const cp = linter.CompiledPane.fromId(name) + cp.open({split: 'right'}) + cp.showCode(name, ...args) + } + }) + + subs = new CompositeDisposable() + + subs.add(atom.commands.add('.workspace', { + 'julia-client:clear-linter': () => linter.clearItems() + })) + subs.add(atom.config.observe('julia-client.uiOptions.layouts.linter.defaultLocation', (defaultLocation) => { + lintPane.setDefaultLocation(defaultLocation) + })) +} + +export function open () { + return lintPane.open({ + split: atom.config.get('julia-client.uiOptions.layouts.linter.split') + }) +} + +export function close () { + return lintPane.close() +} + +export function deactivate () { + if (subs) { + subs.dispose() + } +} diff --git a/lib_src/runtime/modules.ts b/lib_src/runtime/modules.ts new file mode 100644 index 00000000..9858dd45 --- /dev/null +++ b/lib_src/runtime/modules.ts @@ -0,0 +1,251 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS103: Rewrite code to no longer use __guard__ + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +// TODO: this code is awful, refactor + +import { CompositeDisposable, Disposable, Emitter } from 'atom'; + +import { debounce } from 'underscore-plus'; +import { client } from '../connection'; +import { selector } from '../ui'; + +const {module: getmodule, allmodules, ismodule} = client.import(['module', 'allmodules', 'ismodule']); + +export default { + + activate() { + this.subs = new CompositeDisposable; + this.itemSubs = new CompositeDisposable; + this.subs.add(this.emitter = new Emitter); + + this.subs.add(atom.workspace.observeActivePaneItem(item => this.updateForItem(item))); + this.subs.add(client.onAttached(() => this.updateForItem())); + return this.subs.add(client.onDetached(() => this.updateForItem())); + }, + + deactivate() { + return this.subs.dispose(); + }, + + _current: null, + lastEditorModule: null, + + setCurrent(_current, editor) { + this._current = _current; + if (editor) { this.lastEditorModule = this._current; } + return this.emitter.emit('did-change', this._current); + }, + + onDidChange(f) { return this.emitter.on('did-change', f); }, + + current(m = this._current) { + if (m == null) { return; } + const {main, inactive, sub, subInactive} = m; + if (main === this.follow) { return this.current(this.lastEditorModule); } + if (!main || inactive) { + return "Main"; + } else if (!sub || subInactive) { + return main; + } else { + return `${main}.${sub}`; + } + }, + + // Choosing Modules + + itemSelector: 'atom-text-editor[data-grammar="source julia"], .julia-console.julia, ink-terminal, .ink-workspace', + + isValidItem(item) { return __guard__(atom.views.getView(item), x => x.matches(this.itemSelector)); }, + + autodetect: 'Auto Detect', + follow: 'Follow Editor', + + chooseModule() { + let item = atom.workspace.getActivePaneItem(); + const ised = atom.workspace.isTextEditor(item); + if (!this.isValidItem(item)) { return; } + return client.require('change modules', () => { + if (item = atom.workspace.getActivePaneItem()) { + const active = item.juliaModule || (ised ? this.autodetect : 'Main'); + const modules = allmodules().then(modules => { + if (ised) { + modules.unshift(this.autodetect); + } else if (this.lastEditorModule != null) { + modules.unshift(this.follow); + } + return modules; + }); + modules.catch(err => { + return console.log(err); + }); + return selector.show(modules, {active}).then(mod => { + if (mod == null) { return; } + if (mod === this.autodetect) { + delete item.juliaModule; + } else { + item.juliaModule = mod; + } + if (typeof item.setModule === 'function') { + item.setModule(mod !== this.autodetect ? mod : undefined); + } + return this.updateForItem(item); + }); + } + }); + }, + + updateForItem(item = atom.workspace.getActivePaneItem()) { + this.itemSubs.dispose(); + if (!this.isValidItem(item)) { + this.itemSubs.add(__guardMethod__(item, 'onDidChangeGrammar', o => o.onDidChangeGrammar(() => this.updateForItem()))); + return this.setCurrent(); + } else if (!client.isActive()) { + return this.setCurrent({main: 'Main', inactive: true}); + } else if (atom.workspace.isTextEditor(item)) { + return this.updateForEditor(item); + } else { + const mod = item.juliaModule || 'Main'; + return ismodule(mod) + .then(ismod => { + return this.setCurrent({main: mod, inactive: !ismod}); + }).catch(err => { + return console.log(err); + }); + } + }, + + updateForEditor(editor) { + this.setCurrent({main: editor.juliaModule || 'Main'}, true); + this.setEditorModule(editor); + return this.itemSubs.add(editor.onDidChangeCursorPosition(() => { + return this.setEditorModuleLazy(editor); + }) + ); + }, + + getEditorModule(ed, bufferPosition = null) { + let column, row; + if (!client.isActive()) { return; } + if (bufferPosition) { + ({row, column} = bufferPosition); + } else { + const sels = ed.getSelections(); + ({row, column} = sels[sels.length - 1].getBufferRange().end); + } + const data = { + path: client.editorPath(ed), + code: ed.getText(), + row: row+1, column: column+1, + module: ed.juliaModule + }; + return getmodule(data) + .catch(err => { + return console.log(err); + }); + }, + + setEditorModule(ed) { + const modulePromise = this.getEditorModule(ed); + if (!modulePromise) { return; } + return modulePromise.then(mod => { + if (atom.workspace.getActivePaneItem() === ed) { + return this.setCurrent(mod, true); + } + }); + }, + + setEditorModuleLazy: debounce((function(ed) { return this.setEditorModule(ed); }), 100), + + // The View + + activateView() { + this.onDidChange(c => this.updateView(c)); + + this.dom = document.createElement('span'); + this.dom.classList.add('julia', 'inline-block'); + + this.mainView = document.createElement('a'); + this.dividerView = document.createElement('span'); + this.subView = document.createElement('span'); + + for (let x of [this.mainView, this.dividerView, this.subView]) { this.dom.appendChild(x); } + + this.mainView.onclick = () => { + return atom.commands.dispatch(atom.views.getView(atom.workspace.getActivePaneItem()), + 'julia-client:set-working-module'); + }; + + atom.tooltips.add(this.dom, + {title: () => `Currently working in module ${this.current()}`}); + + // @NOTE: Grammar selector has `priority` 10 and thus set the it to a bit lower + // than that to avoid collision that may cause unexpected result. + this.tile = this.statusBar.addRightTile({item: this.dom, priority: 5}); + const disposable = new Disposable(() => { + this.tile.destroy(); + return delete this.tile; + }); + this.subs.add(disposable); + return disposable; + }, + + updateView(m) { + if (this.tile == null) { return; } + if ((m == null)) { + return this.dom.style.display = 'none'; + } else { + let view; + const {main, sub, inactive, subInactive} = m; + if (main === this.follow) { + return this.updateView(this.lastEditorModule); + } + this.dom.style.display = ''; + this.mainView.innerText = main || 'Main'; + if (sub) { + this.subView.innerText = sub; + this.dividerView.innerText = '/'; + } else { + for (view of [this.subView, this.dividerView]) { view.innerText = ''; } + } + if (inactive) { + return this.dom.classList.add('fade'); + } else { + this.dom.classList.remove('fade'); + return (() => { + const result = []; + for (view of [this.subView, this.dividerView]) { + if (subInactive) { + result.push(view.classList.add('fade')); + } else { + result.push(view.classList.remove('fade')); + } + } + return result; + })(); + } + } + }, + + consumeStatusBar(bar) { + this.statusBar = bar; + const disposable = this.activateView(); + this.updateView(this._current); + return disposable; + } +}; + +function __guard__(value, transform) { + return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined; +} +function __guardMethod__(obj, methodName, transform) { + if (typeof obj !== 'undefined' && obj !== null && typeof obj[methodName] === 'function') { + return transform(obj, methodName); + } else { + return undefined; + } +} \ No newline at end of file diff --git a/lib_src/runtime/outline.ts b/lib_src/runtime/outline.ts new file mode 100644 index 00000000..6d087616 --- /dev/null +++ b/lib_src/runtime/outline.ts @@ -0,0 +1,131 @@ +'use babel' + +import { CompositeDisposable } from 'atom' +import { throttle } from 'underscore-plus' +import { client } from '../connection' +import modules from './modules' + +const updateeditor = client.import('updateeditor') +let pane, subs + +export function activate (ink) { + pane = ink.Outline.fromId('Julia-Outline') + subs = new CompositeDisposable() + + subs.add(atom.config.observe('julia-client.uiOptions.layouts.outline.defaultLocation', (defaultLocation) => { + pane.setDefaultLocation(defaultLocation) + })) + subs.add(client.onDetached(() => pane.setItems([]))) + + let edSubs = new CompositeDisposable() + subs.add(atom.workspace.onDidChangeActiveTextEditor(throttle(ed => { + if (!ed) return + + edSubs.dispose() + + if (ed.getGrammar().id !== 'source.julia') { + pane.setItems([]) + return + } + + edSubs = new CompositeDisposable() + + edSubs.add(ed.onDidDestroy(() => { + edSubs.dispose() + pane.setItems([]) + })) + edSubs.add(ed.onDidChangeGrammar((grammar) => { + if (grammar.id !== 'source.julia') { + edSubs.dispose() + pane.setItems([]) + } + })) + + let outline = [] + + edSubs.add(ed.onDidStopChanging(() => { + updateEditor(ed).then(outlineItems => { + outline = handleOutline(ed, edSubs, outlineItems) + }).catch(err => { + console.log(err); + }) + })) + + edSubs.add(ed.onDidChangeCursorPosition(throttle(() => { + const cursorLine = ed.getCursorBufferPosition().row + 1 + + outline = outline.map(item => { + item.isActive = item.start <= cursorLine && cursorLine <= item.stop + return item + }) + + pane.setItems(outline) + }, 300))) + + updateEditor(ed, { + updateSymbols: false + }).then(outlineItems => { + outline = handleOutline(ed, edSubs, outlineItems) + }).catch(err => { + console.log(err); + }) + }, 300))) +} + +// NOTE: update outline and symbols cache all in one go +function updateEditor (editor, options = { + updateSymbols: true +}) { + if (!client.isActive()) { + return new Promise((resolve) => resolve([])) + } + + const text = editor.getText() + const currentModule = modules.current() + const mod = currentModule ? currentModule : 'Main' + const path = editor.getPath() || 'untitled-' + editor.getBuffer().getId() + return updateeditor({ + text, + mod, + path, + // https://github.com/JunoLab/Juno.jl/issues/407 + updateSymbols: options.updateSymbols + }) +} + +function handleOutline (ed, subs, items) { + const cursorLine = ed.getCursorBufferPosition().row + 1 + + items = items.map(item => { + item.isActive = item.start <= cursorLine && cursorLine <= item.stop + item.onClick = () => { + for (const pane of atom.workspace.getPanes()) { + if (pane.getItems().includes(ed)) { + pane.activate() + pane.setActiveItem(ed) + ed.setCursorBufferPosition([item.start - 1, 0]) + ed.scrollToCursorPosition() + break + } + } + } + return item + }) + + pane.setItems(items) + return items +} + +export function open () { + return pane.open({ + split: atom.config.get('julia-client.uiOptions.layouts.outline.split') + }) +} + +export function close () { + return pane.close() +} + +export function deactivate () { + subs.dispose() +} diff --git a/lib_src/runtime/packages.ts b/lib_src/runtime/packages.ts new file mode 100644 index 00000000..1615edef --- /dev/null +++ b/lib_src/runtime/packages.ts @@ -0,0 +1,30 @@ +'use babel' + +import { client } from '../connection' +import { selector } from '../ui' + +const { packages } = client.import({ rpc: ['packages'] }); + +export function openPackage (newWindow = true) { + let pkgs = packages() + pkgs.then(pkgs => { + ps = [] + for (pkg in pkgs) { + ps.push({primary: pkg, secondary: pkgs[pkg]}) + } + selector.show(ps).then( pkg => { + if (pkg) { + if (newWindow) { + atom.open({ pathsToOpen: [pkgs[pkg.primary]]}) + } else { + atom.project.addPath(pkgs[pkg.primary], { + mustExist: true, + exact: true + }) + } + } + }) + }).catch(() => { + atom.notifications.addError("Couldn't find your Julia packages.") + }) +} diff --git a/lib_src/runtime/plots.ts b/lib_src/runtime/plots.ts new file mode 100644 index 00000000..219c0e08 --- /dev/null +++ b/lib_src/runtime/plots.ts @@ -0,0 +1,163 @@ +'use babel' + +import { client } from '../connection' +import { views } from '../ui' + +const { webview } = views.tags + +function consoleLog (e) { + let log + if (e.level === 0) { + ({ + log + } = console) + } else if (e.level === 1) { + log = console.warn + } else if (e.level === 2) { + log = console.error + } + log(e.message, `\nat ${e.sourceID}:${e.line}`) +} + +// https://stackoverflow.com/a/5100158/12113178 +function dataURItoBlob (dataURI) { + // convert base64/URLEncoded data component to raw binary data held in a string + let byteString + if (dataURI.split(',')[0].indexOf('base64') >= 0) + byteString = atob(dataURI.split(',')[1]) + else + byteString = unescape(dataURI.split(',')[1]) + + // separate out the mime component + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + + // write the bytes of the string to a typed array + const ia = new Uint8Array(byteString.length); + for (let i = 0; i < byteString.length; i++) { + ia[i] = byteString.charCodeAt(i) + } + + return new Blob([ia], {type:mimeString}) +} + +export default { + activate () { + client.handle({ + plot: x => this.show(x), + plotsize: () => this.plotSize(), + ploturl: url => this.ploturl(url), + jlpane: (id, opts) => this.jlpane(id, opts) + }) + this.create() + + atom.config.observe('julia-client.uiOptions.usePlotPane', enabled => { + if (enabled) { + return this.pane.setTitle('Plots') + } else { + return this.pane.setTitle('Plots (disabled)') + } + }) + + return atom.config.observe('julia-client.uiOptions.layouts.plotPane.defaultLocation', defaultLocation => { + this.pane.setDefaultLocation(defaultLocation) + }) + }, + + create () { + return this.pane = this.ink.PlotPane.fromId('default') + }, + + open () { + return this.pane.open({ + split: atom.config.get('julia-client.uiOptions.layouts.plotPane.split')}) + }, + + ensureVisible () { + return this.pane.ensureVisible({ split: atom.config.get('julia-client.uiOptions.layouts.plotPane.split') }) + }, + + close () { + return this.pane.close() + }, + + show (view) { + this.ensureVisible() + const v = views.render(view) + this.pane.show(new this.ink.Pannable(v), { + maxSize: atom.config.get('julia-client.uiOptions.maxNumberPlots') + }) + return v + }, + + plotSize () { + return this.ensureVisible().then(() => { + return { + size: this.pane.size(), + ratio: window.devicePixelRatio + } + }) + }, + + webview (url) { + const isDataURI = url.startsWith('data') + if (isDataURI) { + const object = dataURItoBlob(url) + url = URL.createObjectURL(object) + } + + const v = views.render(webview({ + class: 'blinkjl', + src: url, + style: 'width: 100%; height: 100%' + })) + v.classList.add('native-key-bindings') + v.addEventListener('console-message', e => consoleLog(e)) + if (isDataURI) { + v.addEventListener('dom-ready', e => { + URL.revokeObjectURL(url) + }) + } + return v + }, + + ploturl (url) { + const v = this.webview(url) + this.ensureVisible() + return this.pane.show(v, { + maxSize: atom.config.get('julia-client.uiOptions.maxNumberPlots') + }) + }, + + jlpane (id, opts) { + if (opts == null) { opts = {} } + let v = undefined + if (opts.url) { + v = this.webview(opts.url) + if (opts.devtools) { + v.addEventListener('dom-ready', () => { + return v.openDevTools() + }) + } + } + + const pane = this.ink.HTMLPane.fromId(id) + + if (opts.close) { + return pane.close() + } else if (opts.destroy) { + if (pane.closeAndDestroy) { + return pane.closeAndDestroy() + } + } else { + pane.show({ + item: v, + icon: opts.icon, + title: opts.title + }) + + return pane.ensureVisible({ + split: opts.split || atom.config.get('julia-client.uiOptions.layouts.plotPane.split') + }) + } + } +} diff --git a/lib_src/runtime/profiler.ts b/lib_src/runtime/profiler.ts new file mode 100644 index 00000000..72fbd65c --- /dev/null +++ b/lib_src/runtime/profiler.ts @@ -0,0 +1,48 @@ +'use babel' + +import { client } from '../connection' +import { CompositeDisposable } from 'atom' +import { remote } from 'electron' + +let pane, subs +const {loadProfileTrace, saveProfileTrace} = client.import({msg: ['loadProfileTrace', 'saveProfileTrace']}); + +export function activate (ink) { + pane = ink.PlotPane.fromId('Profile') + pane.getTitle = () => {return 'Profiler'} + subs = new CompositeDisposable() + + subs.add(client.onDetached(() => clear())) + subs.add(atom.config.observe('julia-client.uiOptions.layouts.profiler.defaultLocation', (defaultLocation) => { + pane.setDefaultLocation(defaultLocation) + })) + + client.handle({ + profile(data) { + const save = (path) => saveProfileTrace(path, data) + const profile = new ink.Profiler.ProfileViewer({data, save, customClass: 'julia-profile'}) + pane.ensureVisible({ + split: atom.config.get('julia-client.uiOptions.layouts.profiler.split') + }) + pane.show(new ink.Pannable(profile, {zoomstrategy: 'width', minScale: 0.5})) + } + }) + + subs.add(atom.commands.add('atom-workspace', 'julia-client:clear-profile', () => { + clear() + pane.close() + })) + + subs.add(atom.commands.add('atom-workspace', 'julia-client:load-profile-trace', () => { + const path = remote.dialog.showOpenDialog({title: 'Load Profile Trace', properties: ['openFile']}) + loadProfileTrace(path) + })) +} + +function clear () { + pane.teardown() +} + +export function deactivate () { + subs.dispose() +} diff --git a/lib_src/runtime/urihandler.ts b/lib_src/runtime/urihandler.ts new file mode 100644 index 00000000..c20db964 --- /dev/null +++ b/lib_src/runtime/urihandler.ts @@ -0,0 +1,41 @@ +"use babel" + +import { client } from '../connection' +import { docpane, views } from '../ui' + +const { moduleinfo } = client.import({ rpc: ['moduleinfo'] }) +const docs = client.import('docs') + +export default function handleURI (parsedURI) { + const { query } = parsedURI + + if (query.open) { // open a file + atom.workspace.open(query.file, { + initialLine: Number(query.line), + pending: atom.config.get('core.allowPendingPaneItems') + }) + } else if (query.docs) { // show docs + const { word, mod } = query + docs({ word, mod }).then(result => { + if (result.error) return + const view = views.render(result) + docpane.processLinks(view.getElementsByTagName('a')) + docpane.ensureVisible() + docpane.showDocument(view, []) + }).catch(err => { + console.log(err) + }) + } else if (query.moduleinfo){ // show module info + const { mod } = query + moduleinfo({ mod }).then(({ doc, items }) => { + items.map(item => { + docpane.processItem(item) + }) + const view = views.render(doc) + docpane.ensureVisible() + docpane.showDocument(view, items) + }).catch(err => { + console.log(err) + }) + } +} diff --git a/lib_src/runtime/workspace.ts b/lib_src/runtime/workspace.ts new file mode 100644 index 00000000..5ca9648c --- /dev/null +++ b/lib_src/runtime/workspace.ts @@ -0,0 +1,85 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { CompositeDisposable } from 'atom'; +import { views } from '../ui'; +import { client } from '../connection'; +import modules from './modules'; + +const { workspace, gotosymbol: gotoSymbol, clearLazy } = client.import({rpc: ['workspace', 'gotosymbol'], msg: 'clearLazy'}); + +export default { + activate() { + this.create(); + + client.onDetached(() => { + this.ws.setItems([]); + return this.lazyTrees = []; + }); + + return atom.config.observe('julia-client.uiOptions.layouts.workspace.defaultLocation', defaultLocation => { + return this.ws.setDefaultLocation(defaultLocation); + }); + }, + + lazyTrees: [], + + update() { + if (!client.isActive() || !this.ws.currentPane()) { return this.ws.setItems([]); } + clearLazy(this.lazyTrees); + const registerLazy = id => this.lazyTrees.push(id); + const mod = this.mod === modules.follow ? modules.current() : (this.mod || 'Main'); + const p = workspace(mod).then(ws => { + for (let {items} of ws) { + for (let item of items) { + item.value = views.render(item.value, {registerLazy}); + item.onClick = this.onClick(item.name); + } + } + return this.ws.setItems(ws); + }); + return p.catch(function(err) { + if (err !== 'disconnected') { + console.error('Error refreshing workspace'); + return console.error(err); + } + }); + }, + + onClick(name) { + return () => { + const mod = this.mod === modules.follow ? modules.current() : (this.mod || 'Main'); + return gotoSymbol({ + word: name, + mod}).then(symbols => { + if (symbols.error) { return; } + return this.ink.goto.goto(symbols, + {pending: atom.config.get('core.allowPendingPaneItems')}); + }); + }; + }, + + create() { + this.ws = this.ink.Workspace.fromId('julia'); + this.ws.setModule = mod => { return this.mod = mod; }; + this.ws.refresh = () => this.update(); + return this.ws.refreshModule = () => { + const m = modules.chooseModule(); + if ((m != null ? m.then : undefined) != null) { + return m.then(() => this.update()); + } + }; + }, + + open() { + return this.ws.open({ + split: atom.config.get('julia-client.uiOptions.layouts.workspace.split')}); + }, + + close() { + return this.ws.close(); + } +}; diff --git a/lib_src/tsconfig.json b/lib_src/tsconfig.json new file mode 100644 index 00000000..86bf326b --- /dev/null +++ b/lib_src/tsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + //// Linting Options - Uncomment options to get more features (usually more restrictive) + // "strict": true, // includes all of the following and more + "strictNullChecks": true, + // "forceConsistentCasingInFileNames": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noImplicitAny": true, + // "noImplicitThis": true, + "noFallthroughCasesInSwitch": true, + "allowJs": true, + //// Compilation options + "declaration": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "incremental": true, + // "inlineSourceMap": false, + // "preserveConstEnums": true, + "sourceMap": true, + "preserveSymlinks": true, + // "removeComments": true, + // "jsx": "react", + // "jsxFactory": "etch.dom", + "lib": ["ES2018", "dom"], + "target": "ES2018", + "module": "commonjs", + "moduleResolution": "node", + // "noLib": false, + // "importHelpers": true, // if true you should add tslib to deps + // "skipLibCheck": false, + "outDir": "../lib/misc" + }, + "files" : ["misc/cells.ts"], + "compileOnSave": true +} diff --git a/lib_src/tslint.json b/lib_src/tslint.json new file mode 100644 index 00000000..d03e0eea --- /dev/null +++ b/lib_src/tslint.json @@ -0,0 +1,22 @@ +{ + "defaultSeverity": "error", + "extends": [ + "tslint:recommended" + ], + "jsRules": {}, + "rules": { + "array-type": false, + "no-consecutive-blank-lines": false, + "only-arrow-functions": false, + "no-console": false, + + "semicolon": false, + "whitespace": false, + "no-trailing-whitespace": false, + "curly": false, + "variable-name": false, + "arrow-parens": false, + "object-literal-shorthand": false + }, + "rulesDirectory": [] +} diff --git a/lib_src/ui.ts b/lib_src/ui.ts new file mode 100644 index 00000000..e70ae1bf --- /dev/null +++ b/lib_src/ui.ts @@ -0,0 +1,65 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { CompositeDisposable, Disposable } from 'atom'; + +export default { + notifications: require('./ui/notifications'), + selector: require('./ui/selector'), + views: require('./ui/views'), + progress: require('./ui/progress'), + layout: require('./ui/layout'), + docpane: require('./ui/docs'), + focusutils: require('./ui/focusutils'), + cellhighlighter: require('./ui/cellhighlighter'), + + activate(client) { + this.client = client; + this.subs = new CompositeDisposable; + + this.notifications.activate(); + this.subs.add(atom.config.observe('julia-client.uiOptions.highlightCells', val => { + if (val) { + return this.cellhighlighter.activate(); + } else { + return this.cellhighlighter.deactivate(); + } + }) + ); + this.subs.add(new Disposable(() => { + return this.cellhighlighter.deactivate(); + }) + ); + + this.subs.add(this.client.onAttached(() => { + return this.notifications.show("Client Connected"); + }) + ); + return this.subs.add(this.client.onDetached(() => { + return (this.ink != null ? this.ink.Result.invalidateAll() : undefined); + }) + ); + }, + + deactivate() { + return this.subs.dispose(); + }, + + consumeInk(ink) { + this.ink = ink; + this.views.ink = this.ink; + this.selector.ink = this.ink; + this.progress.ink = this.ink; + this.docpane.activate(this.ink); + this.progress.activate(); + this.focusutils.activate(this.ink); + return this.subs.add(new Disposable(() => { + this.docpane.deactivate(); + this.progress.deactivate(); + return this.focusutils.deactivate(); + })); + } +}; diff --git a/lib_src/ui/cellhighlighter.ts b/lib_src/ui/cellhighlighter.ts new file mode 100644 index 00000000..031ea185 --- /dev/null +++ b/lib_src/ui/cellhighlighter.ts @@ -0,0 +1,124 @@ +'use babel' + +import { getRange } from '../misc/cells' +import { CompositeDisposable } from 'atom' +import { getLine } from '../misc/blocks.js' + +let subs +let edSubs +let marker +let borders = [] + +export function activate () { + subs = new CompositeDisposable() + edSubs = new CompositeDisposable() + + subs.add(atom.workspace.observeActiveTextEditor(ed => { + if (ed && ed.getGrammar && ed.getGrammar().id === 'source.julia') { + if (edSubs && edSubs.dispose) { + edSubs.dispose() + edSubs = new CompositeDisposable() + } + borders = highlightCellBorders(ed, borders) + + marker = highlightCurrentCell(ed, marker, borders) + + edSubs.add(ed.onDidChangeCursorPosition(ev => { + marker = highlightCurrentCell(ed, marker, borders) + })) + + edSubs.add(ed.onDidStopChanging(() => { + borders = highlightCellBorders(ed, borders) + marker = highlightCurrentCell(ed, marker, borders) + })) + + edSubs.add(ed.onDidDestroy(() => { + marker && marker.destroy && marker.destroy() + borders.forEach(m => m.destroy()) + edSubs.dispose() + })) + + edSubs.add(ed.onDidChangeGrammar((grammar) => { + marker && marker.destroy && marker.destroy() + borders.forEach(m => m.destroy()) + + if (ed.getGrammar().id == 'source.julia') { + borders = highlightCellBorders(ed, borders) + marker = highlightCurrentCell(ed, marker, borders) + } + })) + } + })) +} + +function highlightCurrentCell (ed, marker, borders) { + if (borders.length === 0) { + marker && marker.destroy && marker.destroy() + return null + } + + const range = getRange(ed) + + range[1].row +=1 + range[1].column = 0 + + if (marker && marker.destroy) { + const mrange = marker.getBufferRange() + if (mrange.start.row == range[0].row && + mrange.end.row == range[1].row) { + return marker + } else { + marker.destroy() + } + } + + marker = ed.markBufferRange(range) + ed.decorateMarker(marker, { + type: 'line-number', + class: 'julia-current-cell' + }) + ed.decorateMarker(marker, { + type: 'line', + class: 'julia-current-cell' + }) + + return marker +} + +function highlightCellBorders (ed, borders) { + borders.forEach(m => m.destroy()) + + const regexString = '^(' + atom.config.get('julia-client.uiOptions.cellDelimiter').join('|') + ')' + const regex = new RegExp(regexString) + + const buffer = ed.getBuffer() + + borders = [] + + for (let i = 0; i <= buffer.getEndPosition().row; i++) { + const { line, scope } = getLine(ed, i) + if (regex.test(line) && scope.join('.').indexOf('comment.line') > -1) { + const m = ed.markBufferRange([[i, 0], [i, Infinity]]) + ed.decorateMarker(m, { + type: 'line', + class: 'julia-cell-border' + }) + borders.push(m) + } + } + + return borders +} + +function destroyMarkers () { + marker && marker.destroy && marker.destroy() + borders.forEach(m => m.destroy()) + marker = null + borders = [] +} + +export function deactivate () { + destroyMarkers() + subs && subs.dispose && subs.dispose() + edSubs && edSubs.dispose && edSubs.dispose() +} diff --git a/lib_src/ui/docs.ts b/lib_src/ui/docs.ts new file mode 100644 index 00000000..027ccd14 --- /dev/null +++ b/lib_src/ui/docs.ts @@ -0,0 +1,110 @@ +'use babel' + +import { client } from '../connection' +import { CompositeDisposable } from 'atom' +const views = require('./views') + +const { + searchdocs: searchDocs, + gotosymbol: gotoSymbol, + moduleinfo: moduleInfo, + regeneratedocs: regenerateDocs +} = client.import({rpc: ['searchdocs', 'gotosymbol', 'moduleinfo'], msg: ['regeneratedocs']}) + +let ink, subs, pane + +export function activate(_ink) { + ink = _ink + + pane = ink.DocPane.fromId('Documentation') + + pane.search = (text, mod, exportedOnly, allPackages, nameOnly) => { + client.boot() + return new Promise((resolve) => { + searchDocs({query: text, mod, exportedOnly, allPackages, nameOnly}).then((res) => { + if (!res.error) { + for (let i = 0; i < res.items.length; i += 1) { + res.items[i].score = res.scores[i] + res.items[i] = processItem(res.items[i]) + } + // erase module input if the actual searched module has been changed + if (res.shoulderase) { + pane.modEd.setText('') + } + } + resolve(res) + }) + }) + } + + pane.regenerateCache = () => { + regenerateDocs() + } + + subs = new CompositeDisposable() + subs.add(atom.commands.add('atom-workspace', 'julia-client:open-documentation-browser', open)) + subs.add(atom.commands.add('atom-workspace', 'julia-client:regenerate-doc-cache', () => { + regenerateDocs() + })) + subs.add(atom.config.observe('julia-client.uiOptions.layouts.documentation.defaultLocation', (defaultLocation) => { + pane.setDefaultLocation(defaultLocation) + })) +} + +export function open () { + return pane.open({ + split: atom.config.get('julia-client.uiOptions.layouts.documentation.split') + }) +} +export function ensureVisible () { + return pane.ensureVisible({ + split: atom.config.get('julia-client.uiOptions.layouts.documentation.split') + }) +} +export function close () { + return pane.close() +} + +export function processItem (item) { + item.html = views.render(item.html) + + processLinks(item.html.getElementsByTagName('a')) + + item.onClickName = () => { + gotoSymbol({ + word: item.name, + mod: item.mod + }).then(symbols => { + if (symbols.error) return + ink.goto.goto(symbols, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + }) + } + + item.onClickModule = () => { + moduleInfo({mod: item.mod}).then(({doc, items}) => { + items.map((x) => processItem(x)) + showDocument(views.render(doc), items) + }) + } + + return item +} + +export function processLinks (links) { + for (let i = 0; i < links.length; i++) { + const link = links[i] + if (link.attributes['href'].value == '@ref') { + links[i].onclick = () => pane.kwsearch(link.innerText) + } + } +} + +export function showDocument (view, items) { + pane.showDocument(view, items) +} + +export function deactivate () { + subs.dispose() +} diff --git a/lib_src/ui/focusutils.ts b/lib_src/ui/focusutils.ts new file mode 100644 index 00000000..6abdebb8 --- /dev/null +++ b/lib_src/ui/focusutils.ts @@ -0,0 +1,101 @@ +'use babel' + +import {TextEditor, CompositeDisposable} from 'atom' + +let lastEditor +let lastTerminal +let subs + +class FocusHistory { + public size: any; + public history: any; + public openedItem: any; + + constructor (size) { + this.size = size + this.history = [] + this.openedItem = undefined + } + + push (item) { + if (this.openedItem && + this.openedItem.file && + this.openedItem.line && + item.file == this.openedItem.file && + item.line == this.openedItem.line) { + return + } + + this.history.push(item) + while (this.history.length > this.size) { + this.history.shift() + } + return + } + + moveBack () { + const item = this.history.pop() + if (item && item.open) { + const activeItem = atom.workspace.getActivePaneItem() + if (activeItem instanceof TextEditor) { + const file = activeItem.getPath() || 'untitled-' + activeItem.buffer.getId() + const line = activeItem.getCursorBufferPosition().row + this.openedItem = {file, line} + } + item.open() + } + } +} + +export function activate (ink) { + subs = new CompositeDisposable() + + subs.add( + atom.workspace.onDidStopChangingActivePaneItem(item => { + if (item instanceof TextEditor) { + lastEditor = item + } else if (item instanceof ink.InkTerminal) { + lastTerminal = item + } + }), + atom.packages.onDidActivateInitialPackages(() => { + lastEditor = atom.workspace.getActiveTextEditor() + atom.workspace.getPanes().forEach(pane => { + const item = pane.getActiveItem() + if (item instanceof ink.InkTerminal) { + lastTerminal = item + } + }) + }) + ) + + const history = new FocusHistory(30) + ink.Opener.onDidOpen(({newLocation, oldLocation}) => { + if (oldLocation) history.push(oldLocation) + }) + + subs.add(atom.commands.add('atom-workspace', { + 'julia-client:focus-last-editor': () => focusLastEditor(), + 'julia-client:focus-last-terminal': () => focusLastTerminal(), + 'julia-client:return-from-goto': () => history.moveBack() + })) +} + +export function deactivate () { + lastEditor = null + lastTerminal = null + subs.dispose() + subs = null +} + +function focusLastEditor () { + const pane = atom.workspace.paneForItem(lastEditor) + if (pane) { + pane.activate() + pane.activateItem(lastEditor) + } +} + +function focusLastTerminal () { + if (lastTerminal && lastTerminal.open) lastTerminal.open() +} diff --git a/lib_src/ui/highlighter.ts b/lib_src/ui/highlighter.ts new file mode 100644 index 00000000..564d9f91 --- /dev/null +++ b/lib_src/ui/highlighter.ts @@ -0,0 +1,90 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS202: Simplify dynamic range loops + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import _ from 'underscore-plus'; + +// Implementation identical to https://github.com/atom/highlights/blob/master/src/highlights.coffee, +// but uses an externally provided grammar. +export default { + // Highlights some `text` according to the specified `grammar`. + highlight(text, grammar, {scopePrefix, block}={}) { + if (scopePrefix == null) { scopePrefix = ''; } + if (block == null) { block = false; } + const lineTokens = grammar.tokenizeLines(text); + + // Remove trailing newline + if (lineTokens.length > 0) { + const lastLineTokens = lineTokens[lineTokens.length - 1]; + + if ((lastLineTokens.length === 1) && (lastLineTokens[0].value === '')) { + lineTokens.pop(); + } + } + + let html = ''; + for (let tokens of lineTokens) { + const scopeStack = []; + html += `<${block ? "div" : "span"} class=\"line\">`; + for (let {value, scopes} of tokens) { + if (!value) { value = ' '; } + html = this.updateScopeStack(scopeStack, scopes, html, scopePrefix); + html += `${this.escapeString(value)}`; + } + while (scopeStack.length > 0) { html = this.popScope(scopeStack, html); } + html += ``; + } + html += ''; + return html; + }, + + escapeString(string) { + return string.replace(/[&"'<> ]/g, function(match) { + switch (match) { + case '&': return '&'; + case '"': return '"'; + case "'": return '''; + case '<': return '<'; + case '>': return '>'; + case ' ': return ' '; + default: return match; + } + }); + }, + + updateScopeStack(scopeStack, desiredScopes, html, scopePrefix) { + let i; + let asc; + let excessScopes = scopeStack.length - desiredScopes.length; + if (excessScopes > 0) { + while (excessScopes--) { html = this.popScope(scopeStack, html); } + } + + // pop until common prefix + for (i = scopeStack.length, asc = scopeStack.length <= 0; asc ? i <= 0 : i >= 0; asc ? i++ : i--) { + if (_.isEqual(scopeStack.slice(0, i), desiredScopes.slice(0, i))) { break; } + html = this.popScope(scopeStack, html); + } + + // push on top of common prefix until scopeStack is desiredScopes + for (let j = i, end = desiredScopes.length, asc1 = i <= end; asc1 ? j < end : j > end; asc1 ? j++ : j--) { + html = this.pushScope(scopeStack, desiredScopes[j], html, scopePrefix); + } + + return html; + }, + + pushScope(scopeStack, scope, html, scopePrefix) { + scopeStack.push(scope); + const className = scopePrefix + scope.replace(/\.+/g, ` ${scopePrefix}`); + return html += ``; + }, + + popScope(scopeStack, html) { + scopeStack.pop(); + return html += ''; + } +}; diff --git a/lib_src/ui/layout.ts b/lib_src/ui/layout.ts new file mode 100644 index 00000000..0e4dadbc --- /dev/null +++ b/lib_src/ui/layout.ts @@ -0,0 +1,149 @@ +'use babel' + +const repl = () => { + return require('../runtime').console +} +const workspace = () => { + return require('../runtime').workspace +} +const documentation = () => { + return require('../ui').docpane +} +const plotPane = () => { + return require('../runtime').plots +} +const debuggerPane = () => { + return require('../runtime').debugger +} +const linter = () => { + return require('../runtime').linter +} +const outline = () => { + return require('../runtime').outline +} + +function specifiedPanes () { + const panes = [] + // @NOTE: Push panes in order of their 'importance': Refer to function `openPanesHelper` for why + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.console')) panes.push(repl) + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.workspace')) panes.push(workspace) + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.documentation')) panes.push(documentation) + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.plotPane')) panes.push(plotPane) + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.debuggerPane')) panes.push(debuggerPane) + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.linter')) panes.push(linter) + if (atom.config.get('julia-client.uiOptions.layouts.defaultPanes.outline')) panes.push(outline) + + return panes +} + +export function closePromises () { + // Close only specified panes, i.e.: non-specified panes won't be closed/opened + const panes = specifiedPanes() + + const promises = panes.map(pane => { + return pane().close() + }) + + return promises +} + +function bundlePanes () { + const containers = [] + containers.push(atom.workspace.getCenter()) + containers.push(atom.workspace.getLeftDock()) + containers.push(atom.workspace.getBottomDock()) + containers.push(atom.workspace.getRightDock()) + + containers.forEach(container => { + const panes = container.getPanes() + const firstPane = panes[0] + const otherPanes = panes.slice(1) + otherPanes.forEach(pane => { + const items = pane.getItems() + items.forEach(item => { + pane.moveItemToPane(item, firstPane) + }) + }) + }) +} + +function openPanes () { + const panes = specifiedPanes() + + openPanesHelper(panes) +} + +function openPanesHelper (panes) { + if (panes.length === 0) { + // If there is no more pane to be opened, activate the first item in each pane. This works since + // Juno-panes are opened in order of their importance as defined in `specifiedPanes` function + atom.workspace.getPanes().forEach(pane => { + pane.activateItemAtIndex(0) + }) + // Activate `WorkspaceCenter` at last + atom.workspace.getCenter().activate() + return + } + + const pane = panes.shift() + pane().open().catch((err) => { + // @FIXME: This is a temporal remedy for https://github.com/JunoLab/atom-julia-client/pull/561#issuecomment-500150318 + console.error(err) + pane().open() + }).finally(() => { + // Re-focus the previously focused pane (i.e. the bundled pane by `bundlePanes`) after each opening + // This prevents opening multiple panes with the same splitting rule in a same location from + // ending up in a funny state + const container = atom.workspace.getActivePaneContainer() + container.activatePreviousPane() + openPanesHelper(panes) + }) +} + +export function restoreDefaultLayout () { + // Close Juno-specific panes first to reset to default layout + Promise.all(closePromises()).then(() => { + + // Simplify layouts in each container to prevent funny splitting + bundlePanes() + + // Open Juno-specific panes again + openPanes() + }) +} + +export function resetDefaultLayoutSettings () { + const onStartup = atom.config.get('julia-client.uiOptions.layouts.openDefaultPanesOnStartUp') + atom.config.unset('julia-client.uiOptions.layouts') + atom.config.set('julia-client.uiOptions.layouts.openDefaultPanesOnStartUp', onStartup) +} + +export function queryDefaultLayout () { + const message = atom.notifications.addInfo('Julia-Client: Open Juno-specific panes on startup ?', { + buttons: [ + { + text: 'Yes', + onDidClick: () => { + restoreDefaultLayout() + message.dismiss() + atom.config.set('julia-client.firstBoot', false) + atom.config.set('julia-client.uiOptions.layouts.openDefaultPanesOnStartUp', true) + } + }, + { + text: 'No', + onDidClick: () => { + message.dismiss() + atom.config.set('julia-client.firstBoot', false) + atom.config.set('julia-client.uiOptions.layouts.openDefaultPanesOnStartUp', false) + } + } + ], + description: + `You can specify the panes to be opened and their _default location_ and _splitting rule_ in + **\`Packages -> Juno -> Settings -> Julia-Client -> UI Options -> Layout Options\`**. + \`Julia-Client: Restore-Default-Layout\` command will restore the layout at later point in time. + Use \`Julia-Client: Reset-Default-Layout-Settings\` command to reset the layout settings if it gets messed up.`, + dismissable: true + }) +} diff --git a/lib_src/ui/notifications.ts b/lib_src/ui/notifications.ts new file mode 100644 index 00000000..c7f85799 --- /dev/null +++ b/lib_src/ui/notifications.ts @@ -0,0 +1,25 @@ +import remote from 'remote'; + +export default { + // notes: [] + // window: remote.getCurrentWindow() + + activate() {}, + // document.addEventListener 'focusin', => + // @clear() + + enabled() { return atom.config.get('julia-client.uiOptions.notifications'); }, + + show(msg, force) {} +}; +// return unless force or (@enabled() and not document.hasFocus()) +// n = new Notification "Julia Client", +// body: msg +// n.onclick = => +// @window.focus() +// @notes.push(n) + +// clear: -> +// for note in @notes +// note.close() +// @notes = [] diff --git a/lib_src/ui/progress.ts b/lib_src/ui/progress.ts new file mode 100644 index 00000000..2c98fc4c --- /dev/null +++ b/lib_src/ui/progress.ts @@ -0,0 +1,78 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { CompositeDisposable } from 'atom'; +import { client } from '../connection'; +import { formatTimePeriod } from '../misc'; + +export default { + progs: {}, + + activate() { + this.subs = new CompositeDisposable; + client.handle({'progress': (t, id, m) => this[t](id, m)}); + let status = []; + this.subs.add(client.onWorking(() => { + return status = this.ink != null ? this.ink.progress.add(null, {description: 'Julia'}) : undefined; + }) + ); + this.subs.add(client.onDone(() => (status != null ? status.destroy() : undefined))); + return this.subs.add(client.onDetached(() => this.clear())); + }, + + deactivate() { + this.clear(); + return this.subs.dispose(); + }, + + add(id) { + const pr = this.ink.progress.add(); + pr.t0 = Date.now(); + pr.showTime = true; + return this.progs[id] = pr; + }, + + progress(id, prog) { + const pr = this.progs[id]; + if (pr == null) { return; } + pr.level = prog; + if (pr.showTime) { return this.rightText(id, null); } + }, + + message(id, m) { return (this.progs[id] != null ? this.progs[id].message = m : undefined); }, + + leftText(id, m) { return (this.progs[id] != null ? this.progs[id].description = m : undefined); }, + + rightText(id, m) { + const pr = this.progs[id]; + if (pr == null) { return; } + if (m != null ? m.length : undefined) { + pr.rightText = m; + return pr.showTime = false; + } else { + const dt = ((Date.now() - pr.t0)*((1/pr.level) - 1))/1000; + pr.showTime = true; + return pr.rightText = formatTimePeriod(dt); + } + }, + + delete(id) { + const pr = this.progs[id]; + if (pr == null) { return; } + pr.destroy(); + return delete this.progs[id]; + }, + + clear() { + for (let _ in this.progs) { + const p = this.progs[_]; + if (p != null) { + p.destroy(); + } + } + return this.progs = {}; + } +}; diff --git a/lib_src/ui/selector.ts b/lib_src/ui/selector.ts new file mode 100644 index 00000000..49d32618 --- /dev/null +++ b/lib_src/ui/selector.ts @@ -0,0 +1,83 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +import { SelectListView } from 'atom-space-pen-views'; + +export default { + show(xs, {active, emptyMessage, allowCustom} = {}) { + const selector = new SelectListView; + selector.addClass('command-palette'); + selector.addClass('julia-clientselector'); + if (active) { selector.list.addClass('mark-active'); } + selector.getEmptyMessage = () => emptyMessage || ''; + + selector.setItems([]); + selector.storeFocusedElement(); + selector.viewForItem = item => { + let name = item; + if (item instanceof Object) { + name = item.primary; + } + const view = document.createElement('li'); + if (item === active) { view.classList.add('active'); } + const primary = this.ink.matchHighlighter.highlightMatches(name, selector.getFilterQuery(), 0); + view.appendChild(primary); + if (item.secondary) { + view.classList.add('two-lines'); + primary.classList.add('primary-line'); + + const secondary = document.createElement('div'); + secondary.classList.add('secondary-line', 'path'); + secondary.innerText = item.secondary; + view.appendChild(secondary); + } + return view; + }; + + const panel = atom.workspace.addModalPanel({item: selector}); + selector.focusFilterEditor(); + + let confirmed = false; + + return new Promise((resolve, reject) => { + if (xs.constructor === Promise) { + selector.setLoading("Loading..."); + xs.then(xs => { + if ((xs.length > 0) && xs[0] instanceof Object) { + selector.getFilterKey = () => 'primary'; + } + return selector.setItems(xs); + }); + xs.catch(e => { + reject(e); + return selector.cancel(); + }); + } else { + if ((xs.length > 0) && xs[0] instanceof Object) { + selector.getFilterKey = () => 'primary'; + } + selector.setItems(xs); + } + + selector.confirmed = item => { + confirmed = true; + selector.cancel(); + return resolve(item); + }; + return selector.cancelled = () => { + panel.destroy(); + selector.restoreFocus(); + const query = selector.getFilterQuery(); + if (!confirmed) { + if (allowCustom && (query.length > 0)) { + return resolve(query); + } else { + return resolve(); + } + } + }; + }); + } +}; diff --git a/lib_src/ui/views.ts b/lib_src/ui/views.ts new file mode 100644 index 00000000..fb6b6b77 --- /dev/null +++ b/lib_src/ui/views.ts @@ -0,0 +1,191 @@ +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS201: Simplify complex destructure assignments + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let views; +import Highlighter from './highlighter'; +import { client } from '../connection'; +import { once } from '../misc'; + +const getlazy = client.import('getlazy'); + +export default views = { + dom({tag, attrs, contents}, opts) { + const view = document.createElement(tag); + for (let k in attrs) { + let v = attrs[k]; + if (v instanceof Array) { v = v.join(' '); } + view.setAttribute(k, v); + } + if (contents != null) { + if (contents.constructor !== Array) { + contents = [contents]; + } + for (let child of contents) { + view.appendChild(this.render(child, opts)); + } + } + return view; + }, + + html(...args) { + const obj = args[0], + { + content + } = obj, + val = obj.block, + block = val != null ? val : false; + let view = this.render(block ? this.tags.div() : this.tags.span()); + view.innerHTML = content; + return view = view.children.length === 1 ? view.children[0] : view; + }, + + tree({head, children, expand}, opts) { + return this.ink.tree.treeView(this.render(head, opts), + children.map(x=> this.render(this.tags.div([x]), opts)), + {expand}); + }, + + lazy({head, id}, opts) { + let view; + const { + conn + } = client; + if (opts.registerLazy != null) { + opts.registerLazy(id); + } else { + console.warn('Unregistered lazy view'); + } + return view = this.ink.tree.treeView(this.render(head, opts), [], { + onToggle: once(() => { + if (client.conn !== conn) { return; } + return getlazy(id).then(children => { + const body = view.querySelector(':scope > .body'); + return children.map(x => this.render(this.tags.div([x]), opts)).forEach(x => { + return body.appendChild(this.ink.ansiToHTML(x)); + }); + }); + }) + } + ); + }, + + subtree({label, child}, opts) { + return this.render((child.type === "tree" ?{ + type: "tree", + head: this.tags.span([label, child.head]), + children: child.children + } + // children: child.children.map((x) => @tags.span "gutted", x) + : + this.tags.span("gutted", [label, child])), opts); + }, + + copy({view, text}, opts) { + view = this.render(view, opts); + atom.commands.add(view, { + 'core:copy'(e) { + atom.clipboard.write(text); + return e.stopPropagation(); + } + } + ); + return view; + }, + + link({file, line, contents}) { + let tt; + const view = this.render(this.tags.a({href: '#'}, contents)); + // TODO: maybe need to dispose of the tooltip onclick and readd them, but + // that doesn't seem to be necessary + if (this.ink.Opener.isUntitled(file)) { + tt = atom.tooltips.add(view, {title() { return 'untitled'; }}); + } else { + tt = atom.tooltips.add(view, {title() { return file; }}); + } + view.onclick = e => { + this.ink.Opener.open(file, line, { + pending: atom.config.get('core.allowPendingPaneItems') + }); + return e.stopPropagation(); + }; + view.addEventListener('DOMNodeRemovedFromDocument', () => { + return tt.dispose(); + }); + return view; + }, + + number({value, full}) { + let rounded = value.toPrecision(3); + if (rounded.toString().length < full.length) { rounded += '…'; } + const view = this.render(this.tags.span('syntax--constant syntax--numeric', rounded)); + let isfull = false; + view.onclick = function(e) { + view.innerText = !isfull ? full : rounded; + isfull = !isfull; + return e.stopPropagation(); + }; + return view; + }, + + code({text, attrs, scope}) { + const grammar = atom.grammars.grammarForScopeName("source.julia"); + const block = (attrs != null ? attrs.block : undefined) || false; + const highlighted = Highlighter.highlight(text, grammar, {scopePrefix: 'syntax--', block}); + return this.render({type: 'html', block, content: highlighted}); + }, + + latex({attrs, text}) { + const block = (attrs != null ? attrs.block : undefined) || false; + const latex = this.ink.KaTeX.texify(text, block); + return this.render({type: 'html', block, content: latex}); + }, + + views: { + dom(...a) { return views.dom(...Array.from(a || [])); }, + html(...a) { return views.html(...Array.from(a || [])); }, + tree(...a) { return views.tree(...Array.from(a || [])); }, + lazy(...a) { return views.lazy(...Array.from(a || [])); }, + subtree(...a) { return views.subtree(...Array.from(a || [])); }, + link(...a) { return views.link(...Array.from(a || [])); }, + copy(...a) { return views.copy(...Array.from(a || [])); }, + number(...a) { return views.number(...Array.from(a || [])); }, + code(...a) { return views.code(...Array.from(a || [])); }, + latex(...a) { return views.latex(...Array.from(a || [])); } + }, + + render(data, opts = {}) { + if (this.views.hasOwnProperty(data.type)) { + const r = this.views[data.type](data, opts); + this.ink.ansiToHTML(r); + return r; + } else if ((data != null ? data.constructor : undefined) === String) { + return new Text(data); + } else { + return this.render(`julia-client: can't render ${(data != null ? data.type : undefined)}`); + } + }, + + tag(tag, attrs, contents) { + if ((attrs != null ? attrs.constructor : undefined) === String) { + attrs = {class: attrs}; + } + if ((attrs != null ? attrs.constructor : undefined) !== Object) { + [contents, attrs] = Array.from([attrs, undefined]); + } + return { + type: 'dom', + tag, + attrs, + contents + }; + }, + + tags: {} +}; + +['div', 'span', 'a', 'strong', 'table', 'tr', 'td', 'webview'].forEach(tag => views.tags[tag] = (attrs, contents) => views.tag(tag, attrs, contents)); diff --git a/package-lock.json b/package-lock.json index b3de03c3..64506565 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,11 +4,332 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", + "integrity": "sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==", + "dev": true, + "requires": { + "@babel/types": "^7.8.7", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.8.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.8.tgz", + "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==", + "dev": true + }, + "@babel/runtime": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.7.tgz", + "integrity": "sha512-+AATMUFppJDw6aiR5NVPHqIQBlV/Pj8wY/EZH+lmvRdUo9xBaz/rF3alAwFJQavvKfeOlPE7oaaDHVbcySbCsg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, + "@babel/runtime-corejs3": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.8.7.tgz", + "integrity": "sha512-sc7A+H4I8kTd7S61dgB9RomXu/C+F4IrRr4Ytze4dnfx7AXEpCrejSNpjx7vq6y/Bak9S6Kbk65a/WgMLtg43Q==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true + } + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.6", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.8.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", + "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@types/atom": { + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@types/atom/-/atom-1.40.1.tgz", + "integrity": "sha512-NPhcr7dQz36OYZpwIlFdCIiDJAu3QRw3Y197AXn986nxA9IiQIXMrIfh+E9z213rK0QoFXbRp6A13On+sn80kg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "@types/fuzzaldrin-plus": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/fuzzaldrin-plus/-/fuzzaldrin-plus-0.6.0.tgz", + "integrity": "sha512-SSQi/XZ4d+x1xqmabEVrsTkPuiUFx02zne+Y90I9XyCOQxdnyIzRJBRlLRWjJazi7Wls8JVkfdYZ+zbktqDvzA==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, + "@types/node": { + "version": "13.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.1.tgz", + "integrity": "sha512-E6M6N0blf/jiZx8Q3nb0vNaswQeEyn0XlupO+xN6DtJ6r6IT4nXrTry7zhIfYvFCl3/8Cu6WIysmUBKiqV0bqQ==", + "dev": true + }, + "@types/object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-xhLwbVGAXi/LJEowMjRY1anbF5sQWeuocVjaMrFYy4bOEpl79A4eIby3xlowbkXytUdbISwOZB+vQx9Y/n0p8A==", + "dev": true + }, + "@types/semver": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.1.0.tgz", + "integrity": "sha512-pOKLaubrAEMUItGNpgwl0HMFPrSAFic8oSVIvfu1UwcgGNmNyK9gyhBHKmBnUTwwVvpZfkzUC0GaMgnL6P86uA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/ssh2": { + "version": "0.5.41", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.41.tgz", + "integrity": "sha512-C661JCfu/yXUoF3EDKatfNhb8z1iD0NUuVOLfrmQ9fOSp1ClntmnicY2u3sBG7zl2FAvjBKkdvM7j03LZXjQXQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/ssh2-streams": "*" + } + }, + "@types/ssh2-streams": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@types/ssh2-streams/-/ssh2-streams-0.1.6.tgz", + "integrity": "sha512-NB+SYftagfHTDPdgVyvSZCeg5MURyHECd/PycGIW9hwhnEbTFxIdDbFtf3jxUO3rRnfr0lfdtkZFiv28T1cAsg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/underscore": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.9.4.tgz", + "integrity": "sha512-CjHWEMECc2/UxOZh0kpiz3lEyX2Px3rQS9HzD20lxMvx571ivOBQKeLnqEjxUY0BMgp6WJWo/pQLRBwMW5v4WQ==", + "dev": true + }, + "@typescript-eslint/eslint-plugin": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz", + "integrity": "sha512-wJRBeaMeT7RLQ27UQkDFOu25MqFOBus8PtOa9KaT5ZuxC1kAsd7JEHqWt4YXuY9eancX0GK9C68i5OROnlIzBA==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "2.24.0", + "eslint-utils": "^1.4.3", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + } + }, + "@typescript-eslint/experimental-utils": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.24.0.tgz", + "integrity": "sha512-DXrwuXTdVh3ycNCMYmWhUzn/gfqu9N0VzNnahjiDJvcyhfBy4gb59ncVZVxdp5XzBC77dCncu0daQgOkbvPwBw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.24.0", + "eslint-scope": "^5.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.24.0.tgz", + "integrity": "sha512-H2Y7uacwSSg8IbVxdYExSI3T7uM1DzmOn2COGtCahCC3g8YtM1xYAPi2MAHyfPs61VKxP/J/UiSctcRgw4G8aw==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.24.0", + "@typescript-eslint/typescript-estree": "2.24.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.24.0.tgz", + "integrity": "sha512-RJ0yMe5owMSix55qX7Mi9V6z2FDuuDpN6eR5fzRJrp+8in9UF41IGNQHbg5aMK4/PjVaEQksLvz0IA8n+Mr/FA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + } + }, + "acorn": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", + "integrity": "sha512-add7dgA5ppRPxCFJoAGfMDi7PIBXq1RtGo7BhbLaxwrXPOmw8gq48Y9ozT01hUKy9byMjlR20EJhu5zlkErEkg==", + "dev": true + }, + "acorn-jsx": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz", + "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==", + "dev": true + }, + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -23,6 +344,46 @@ "readable-stream": "^2.0.6" } }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -31,6 +392,18 @@ "safer-buffer": "~2.1.0" } }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "atom-package-deps": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/atom-package-deps/-/atom-package-deps-4.6.0.tgz", @@ -66,6 +439,173 @@ "space-pen": "^5.1.2" } }, + "axe-core": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.2.tgz", + "integrity": "sha512-9wBDgdzbn06on6Xt+ay7EM4HV+NBOkeXhjK9DMezD8/qvJKeUTzheGHhM+U1uNaX4OvuIR4BePDStRLF7vyOfg==", + "dev": true + }, + "axobject-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", + "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + } + } + }, + "babylon": { + "version": "7.0.0-beta.47", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", + "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -83,6 +623,16 @@ "safe-buffer": "^5.1.1" } }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "buffer-alloc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", @@ -102,16 +652,99 @@ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "chownr": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, + "coffeescript": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.5.1.tgz", + "integrity": "sha512-J2jRPX0eeFh5VKyVnoLrfVFgLZtnnmp96WQSLAS8OrLm2wtQLcnikYKe1gViJKDH7vucjuhHvBKKBP3rKcD1tQ==", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, "consistent-env": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/consistent-env/-/consistent-env-1.3.1.tgz", @@ -125,11 +758,50 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "dev": true + }, + "core-js-pure": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", + "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "d": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", @@ -138,6 +810,21 @@ "es5-ext": "~0.10.2" } }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, "decompress-response": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", @@ -151,6 +838,21 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -161,6 +863,21 @@ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "emissary": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/emissary/-/emissary-1.3.3.tgz", @@ -172,6 +889,12 @@ "underscore-plus": "1.x" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -180,6 +903,45 @@ "once": "^1.4.0" } }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es5-ext": { "version": "0.10.30", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.30.tgz", @@ -248,6 +1010,432 @@ "es6-symbol": "~2.0.1" } }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + } + } + }, + "eslint-config-airbnb": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz", + "integrity": "sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^14.1.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1" + } + }, + "eslint-config-airbnb-base": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz", + "integrity": "sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.9", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", + "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", + "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-coffee": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/eslint-plugin-coffee/-/eslint-plugin-coffee-0.1.12.tgz", + "integrity": "sha512-ThsCQLHhuxpQrqV7mH5+Xlf6Zt3rrzAgkvoduhdzoIwa8xRLxXUahAW1R3FGT/ci04f+8Z9kqlz52/9QtVWxtw==", + "dev": true, + "requires": { + "axe-core": "^3.4.1", + "babel-eslint": "^7.2.2", + "babylon": "^7.0.0-beta.44", + "coffeescript": "^2.5.0", + "doctrine": "^2.1.0", + "eslint-config-airbnb": "^18.0.1", + "eslint-config-airbnb-base": "^14.0.0", + "eslint-plugin-import": "^2.19.0", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.17.0", + "eslint-plugin-react-native": "^3.8.0", + "eslint-scope": "~3.7.3", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.0.0", + "jsx-ast-utils": "^2.0.1", + "lodash": "^4.17.10" + }, + "dependencies": { + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "babel-traverse": "^6.23.1", + "babel-types": "^6.23.0", + "babylon": "^6.17.0" + }, + "dependencies": { + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + } + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.5", + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.1" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + } + } + }, + "eslint-plugin-only-warn": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-only-warn/-/eslint-plugin-only-warn-1.0.2.tgz", + "integrity": "sha512-DCG8vuUynDnyfkm0POT50JoE9VJfbtKf+COHn3q79+ExW4dg9ZWM/hsMWX1mjZqxMjQledL/9TmGipon/vwWmw==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", + "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.3", + "object.entries": "^1.1.1", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.15.1", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.2", + "xregexp": "^4.3.0" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + } + } + }, + "eslint-plugin-react-native": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native/-/eslint-plugin-react-native-3.8.1.tgz", + "integrity": "sha512-6Z4s4nvgFRdda/1s1+uu4a6EMZwEjjJ9Bk/1yBImv0fd9U2CsGu2cUakAtV83cZKhizbWhSouXoaK4JtlScdFg==", + "dev": true, + "requires": { + "eslint-plugin-react-native-globals": "^0.1.1" + } + }, + "eslint-plugin-react-native-globals": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz", + "integrity": "sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==", + "dev": true + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, "etch": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/etch/-/etch-0.14.0.tgz", @@ -258,11 +1446,102 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "fuzzaldrin": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz", @@ -293,6 +1572,41 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, "grim": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/grim/-/grim-1.5.0.tgz", @@ -301,11 +1615,88 @@ "emissary": "^1.2.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -316,12 +1707,200 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "inquirer": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz", + "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + } + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "has-symbols": "^1.0.1" } }, "is-utf8": { @@ -334,21 +1913,133 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "jquery": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-2.1.4.tgz", "integrity": "sha1-IoveaYoMYUMdwmMKahVPFYkNIxc=" }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "jsx-ast-utils": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", + "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "object.assign": "^4.1.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, "lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", @@ -374,6 +2065,18 @@ } } }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -384,6 +2087,18 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.1.tgz", "integrity": "sha512-boQj1WFgQH3v4clhu3mTNfP+vOBxorDlE8EKiMjUlLG3C4qAESnn9AxIOkFgTR2c9LtzNjPrjS60cT27ZKBhaA==" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, "node-abi": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.9.0.tgz", @@ -413,6 +2128,26 @@ "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", @@ -439,6 +2174,66 @@ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.0.3.tgz", "integrity": "sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg==" }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -447,16 +2242,135 @@ "wrappy": "1" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, "physical-cpu-count": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/physical-cpu-count/-/physical-cpu-count-2.0.0.tgz", "integrity": "sha1-GN4vl+S/epVRrXURlCtUlverpmA=" }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, "prebuild-install": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz", @@ -480,11 +2394,40 @@ "which-pm-runs": "^1.0.0" } }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, "property-accessors": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/property-accessors/-/property-accessors-1.1.3.tgz", @@ -503,6 +2446,12 @@ "once": "^1.3.1" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -514,6 +2463,33 @@ "strip-json-comments": "~2.0.1" } }, + "react-is": { + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.0.tgz", + "integrity": "sha512-GFMtL0vHkiBv9HluwNZTggSn/sCyEt9n02aM0dSAjGGyqyNlAyftYm4phPxdvCigG15JreC5biwxCgTAJZ7yAA==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -528,6 +2504,80 @@ "util-deprecate": "~1.0.1" } }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "dev": true + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz", + "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -591,6 +2641,31 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -611,6 +2686,31 @@ "simple-concat": "^1.0.0" } }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, "space-pen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/space-pen/-/space-pen-5.1.2.tgz", @@ -621,6 +2721,44 @@ "underscore-plus": "1.x" } }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, "ssh2": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-0.8.4.tgz", @@ -654,6 +2792,40 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -670,6 +2842,12 @@ "ansi-regex": "^2.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-bom-buf": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", @@ -683,6 +2861,67 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "tar-fs": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", @@ -719,11 +2958,91 @@ "xtend": "^4.0.0" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "tslib": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true + }, + "tslint": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.0.tgz", + "integrity": "sha512-fXjYd/61vU6da04E505OZQGb2VCN2Mq3doeWcOIryuG+eqdmFUXTYVwdhnbEu2k46LNLgUYt9bI5icQze/j0bQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.10.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -737,6 +3056,27 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", @@ -750,11 +3090,45 @@ "underscore": "~1.6.0" } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "which-pm-runs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", @@ -768,11 +3142,35 @@ "string-width": "^1.0.2 || 2" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xregexp": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", + "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", + "dev": true, + "requires": { + "@babel/runtime-corejs3": "^7.8.3" + } + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 28f46d98..ae25782e 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,27 @@ "ssh2": "^0.8.4", "underscore-plus": "*" }, + "devDependencies": { + "typescript": "latest", + "coffeescript": "latest", + "@types/atom": "^1.40.1", + "@types/fuzzaldrin-plus": "^0.6.0", + "@types/object-hash": "^1.3.1", + "@types/semver": "^7.1.0", + "@types/ssh2": "^0.5.41", + "@types/underscore": "^1.9.4", + "eslint": "latest", + "@typescript-eslint/eslint-plugin": "latest", + "@typescript-eslint/parser": "latest", + "babel-eslint": "latest", + "eslint-plugin-coffee": "latest", + "eslint-plugin-only-warn": "latest", + "prettier": "latest", + "tslint": "latest" + }, "scripts": { - "postinstall": "node script/postinstall.js" + "postinstall": "node script/postinstall.js", + "lint": "eslint . --ext js,ts,coffee" }, "consumedServices": { "status-bar": { @@ -96,5 +115,10 @@ "package-deps": [ "ink", "language-julia" - ] + ], + "prettier": { + "semi": false, + "tabWidth": 2, + "printWidth": 120 + } } diff --git a/spec/client.coffee b/spec/client.coffee deleted file mode 100644 index 6fd8971c..00000000 --- a/spec/client.coffee +++ /dev/null @@ -1,120 +0,0 @@ -path = require 'path' -juno = require '../lib/julia-client' - -{client} = juno.connection - -module.exports = -> - - clientStatus = -> [client.isActive(), client.isWorking()] - {echo, evalsimple} = client.import ['echo', 'evalsimple'] - - describe "before booting", -> - checkPath = (p) -> juno.misc.paths.getVersion p - - it "can invalidate a non-existant julia binary", -> - waitsFor (done) -> - checkPath(path.join(__dirname, "foobar")).catch -> done() - - it "can validate a julia command", -> - waitsFor (done) -> - checkPath("julia").then -> done() - - it "can invalidate a non-existant julia command", -> - waitsFor (done) -> - checkPath("nojulia").catch -> done() - - conn = null - beforeEach -> - if conn? - client.attach conn - - describe "when booting the client", -> - - it "recognises the client's state before boot", -> - expect(clientStatus()).toEqual [false, false] - - it "initiates the boot", -> - waitsForPromise -> juno.connection.local.start() - runs -> - conn = client.conn - - it "waits for the boot to complete", -> - pong = client.import('ping')() - expect(clientStatus()).toEqual [true, true] - waitsFor 'the client to boot', 5*60*1000, (done) -> - pong.then (pong) -> - expect(pong).toBe('pong') - done() - - # it "recognises the client's state after boot", -> - # expect(clientStatus()).toEqual [true, false] - - describe "while the client is active", -> - - it "can send and receive nested objects, strings and arrays", -> - msg = {x: 1, y: [1,2,3], z: "foo"} - waitsForPromise -> - echo(msg).then (response) -> - expect(response).toEqual(msg) - - it "can evaluate code and return the result", -> - remote = [1..10].map (x) -> evalsimple("#{x}^2") - waitsForPromise -> - Promise.all(remote).then (remote) -> - expect(remote).toEqual (Math.pow(x, 2) for x in [1..10]) - - it "can rpc into the frontend", -> - client.handle test: (x) -> Math.pow(x, 2) - remote = (evalsimple("Atom.@rpc test(#{x})") for x in [1..10]) - waitsForPromise -> - Promise.all(remote).then (remote) -> - expect(remote).toEqual (Math.pow(x, 2) for x in [1..10]) - - it "can retrieve promise values from the frontend", -> - client.handle test: (x) -> Promise.resolve x - waitsForPromise -> - evalsimple("Atom.@rpc test(2)").then (x) -> - expect(x).toBe 2 - - describe "when using callbacks", -> - {cbs, workingSpy, doneSpy} = {} - - beforeEach -> - client.onWorking (workingSpy = jasmine.createSpy 'working') - client.onDone (doneSpy = jasmine.createSpy 'done') - cbs = (evalsimple("peakflops(100)") for i in [1..5]) - - it "enters loading state", -> - expect(client.isWorking()).toBe true - - # it "emits a working event", -> - # expect(workingSpy.calls.length).toBe(1) - - it "isn't done yet", -> - expect(doneSpy).not.toHaveBeenCalled() - - describe "when they finish", -> - - beforeEach -> - waitsFor 10*1000, (done) -> - Promise.all(cbs).then done - - it "stops loading after they are done", -> - expect(client.isWorking()).toBe(false) - - it "emits a done event", -> - expect(doneSpy.calls.length).toBe(1) - - it "can handle a large number of concurrent callbacks", -> - n = 100 - cbs = (evalsimple("sleep(rand()); #{i}^2") for i in [0...n]) - waitsForPromise -> - Promise.all(cbs).then (xs) -> - expect(xs).toEqual (Math.pow(x, 2) for x in [0...n]) - - it "handles shutdown correctly", -> - waitsFor (done) -> - evalsimple('exit()').catch -> done() - runs -> - expect(client.isWorking()).toBe(false) - expect(clientStatus()).toEqual [false, false] diff --git a/spec/client.js b/spec/client.js new file mode 100644 index 00000000..f629fda4 --- /dev/null +++ b/spec/client.js @@ -0,0 +1,143 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const path = require('path'); +const juno = require('../lib/julia-client'); + +const {client} = juno.connection; + +module.exports = function() { + + const clientStatus = () => [client.isActive(), client.isWorking()]; + const {echo, evalsimple} = client.import(['echo', 'evalsimple']); + + describe("before booting", function() { + const checkPath = p => juno.misc.paths.getVersion(p); + + it("can invalidate a non-existant julia binary", () => waitsFor(done => checkPath(path.join(__dirname, "foobar")).catch(() => done()))); + + it("can validate a julia command", () => waitsFor(done => checkPath("julia").then(() => done()))); + + return it("can invalidate a non-existant julia command", () => waitsFor(done => checkPath("nojulia").catch(() => done()))); + }); + + let conn = null; + beforeEach(function() { + if (conn != null) { + return client.attach(conn); + } + }); + + describe("when booting the client", function() { + + it("recognises the client's state before boot", () => expect(clientStatus()).toEqual([false, false])); + + it("initiates the boot", function() { + waitsForPromise(() => juno.connection.local.start()); + return runs(() => conn = client.conn); + }); + + return it("waits for the boot to complete", function() { + const pong = client.import('ping')(); + expect(clientStatus()).toEqual([true, true]); + return waitsFor('the client to boot', 5*60*1000, done => pong.then(function(pong) { + expect(pong).toBe('pong'); + return done(); + })); + }); + }); + + // it "recognises the client's state after boot", -> + // expect(clientStatus()).toEqual [true, false] + + describe("while the client is active", function() { + + it("can send and receive nested objects, strings and arrays", function() { + const msg = {x: 1, y: [1,2,3], z: "foo"}; + return waitsForPromise(() => echo(msg).then(response => expect(response).toEqual(msg))); + }); + + it("can evaluate code and return the result", function() { + const remote = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(x => evalsimple(`${x}^2`)); + return waitsForPromise(() => Promise.all(remote).then(remote => expect(remote).toEqual(([1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((x) => Math.pow(x, 2)))))); + }); + + it("can rpc into the frontend", function() { + let x; + client.handle({test(x) { return Math.pow(x, 2); }}); + const remote = ((() => { + let i; + const result = []; + for (i = 1, x = i; i <= 10; i++, x = i) { + result.push(evalsimple(`Atom.@rpc test(${x})`)); + } + return result; + })()); + return waitsForPromise(() => Promise.all(remote).then(remote => expect(remote).toEqual(((() => { + const result1 = []; + for (x = 1; x <= 10; x++) { + result1.push(Math.pow(x, 2)); + } + return result1; + })())))); + }); + + it("can retrieve promise values from the frontend", function() { + client.handle({test(x) { return Promise.resolve(x); }}); + return waitsForPromise(() => evalsimple("Atom.@rpc test(2)").then(x => expect(x).toBe(2))); + }); + + describe("when using callbacks", function() { + let {cbs, workingSpy, doneSpy} = {}; + + beforeEach(function() { + client.onWorking((workingSpy = jasmine.createSpy('working'))); + client.onDone((doneSpy = jasmine.createSpy('done'))); + return cbs = ([1, 2, 3, 4, 5].map((i) => evalsimple("peakflops(100)"))); + }); + + it("enters loading state", () => expect(client.isWorking()).toBe(true)); + + // it "emits a working event", -> + // expect(workingSpy.calls.length).toBe(1) + + it("isn't done yet", () => expect(doneSpy).not.toHaveBeenCalled()); + + return describe("when they finish", function() { + + beforeEach(() => waitsFor(10*1000, done => Promise.all(cbs).then(done))); + + it("stops loading after they are done", () => expect(client.isWorking()).toBe(false)); + + return it("emits a done event", () => expect(doneSpy.calls.length).toBe(1)); + }); + }); + + return it("can handle a large number of concurrent callbacks", function() { + const n = 100; + const cbs = (__range__(0, n, false).map((i) => evalsimple(`sleep(rand()); ${i}^2`))); + return waitsForPromise(() => Promise.all(cbs).then(xs => expect(xs).toEqual((__range__(0, n, false).map((x) => Math.pow(x, 2)))))); + }); + }); + + return it("handles shutdown correctly", function() { + waitsFor(done => evalsimple('exit()').catch(() => done())); + return runs(function() { + expect(client.isWorking()).toBe(false); + return expect(clientStatus()).toEqual([false, false]);}); +}); +}; + +function __range__(left, right, inclusive) { + let range = []; + let ascending = left < right; + let end = !inclusive ? right : ascending ? right + 1 : right - 1; + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i); + } + return range; +} \ No newline at end of file diff --git a/spec/eval.coffee b/spec/eval.coffee deleted file mode 100644 index 42ca5a63..00000000 --- a/spec/eval.coffee +++ /dev/null @@ -1,71 +0,0 @@ -juno = require '../lib/julia-client' -{client} = juno.connection - -module.exports = -> - - editor = null - - command = (ed, c) -> atom.commands.dispatch(atom.views.getView(ed), c) - - waitsForClient = -> waitsFor (done) -> client.onceDone done - - beforeEach -> - waitsForPromise -> atom.workspace.open().then (ed) -> editor = ed - # editor = atom.workspace.buildTextEditor() - runs -> - editor.setGrammar(atom.grammars.grammarForScopeName('source.julia')) - - it 'can evaluate code', -> - client.handle test: (spy = jasmine.createSpy()) - editor.insertText 'Atom.@rpc test()' - command editor, 'julia-client:run-block' - waitsForClient() - runs -> - expect(spy).toHaveBeenCalled() - - describe 'when an expression is evaluated', -> - - results = null - - beforeEach -> - editor.insertText '2+2' - waitsForPromise => - juno.runtime.evaluation.eval().then (x) => results = x - - it 'retrieves the value of the expression', -> - expect(results.length).toBe 1 - view = juno.ui.views.render results[0] - expect(view.innerText).toBe '4' - - it 'displays the result', -> - views = atom.views.getView(editor).querySelectorAll('.result') - expect(views.length).toBe 1 - expect(views[0].innerText).toBe '4' - - describe 'completions', -> - - completionsData = -> - editor: editor - bufferPosition: editor.getCursors()[0].getBufferPosition() - scopeDescriptor: editor.getCursors()[0].getScopeDescriptor() - prefix: editor.getText() - - getSuggestions = -> - completions = require '../lib/runtime/completions' - completions.getSuggestions completionsData() - - describe 'basic module completions', -> - - completions = null - - beforeEach -> - editor.insertText 'sin' - waitsForPromise -> - getSuggestions().then (cs) -> - completions = cs - - it 'retrieves completions', -> - completions = completions.map (c) -> c.text - expect(completions).toContain 'sin' - expect(completions).toContain 'sincos' - expect(completions).toContain 'sinc' diff --git a/spec/eval.js b/spec/eval.js new file mode 100644 index 00000000..f5ae7700 --- /dev/null +++ b/spec/eval.js @@ -0,0 +1,87 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const juno = require('../lib/julia-client'); +const {client} = juno.connection; + +module.exports = function() { + + let editor = null; + + const command = (ed, c) => atom.commands.dispatch(atom.views.getView(ed), c); + + const waitsForClient = () => waitsFor(done => client.onceDone(done)); + + beforeEach(function() { + waitsForPromise(() => atom.workspace.open().then(ed => editor = ed)); + // editor = atom.workspace.buildTextEditor() + return runs(() => editor.setGrammar(atom.grammars.grammarForScopeName('source.julia'))); + }); + + it('can evaluate code', function() { + let spy; + client.handle({test: (spy = jasmine.createSpy())}); + editor.insertText('Atom.@rpc test()'); + command(editor, 'julia-client:run-block'); + waitsForClient(); + return runs(() => expect(spy).toHaveBeenCalled()); + }); + + describe('when an expression is evaluated', function() { + + let results = null; + + beforeEach(function() { + editor.insertText('2+2'); + return waitsForPromise(() => { + return juno.runtime.evaluation.eval().then(x => { return results = x; }); + }); + }); + + it('retrieves the value of the expression', function() { + expect(results.length).toBe(1); + const view = juno.ui.views.render(results[0]); + return expect(view.innerText).toBe('4'); + }); + + return it('displays the result', function() { + const views = atom.views.getView(editor).querySelectorAll('.result'); + expect(views.length).toBe(1); + return expect(views[0].innerText).toBe('4'); + }); + }); + + return describe('completions', function() { + + const completionsData = () => ({ + editor, + bufferPosition: editor.getCursors()[0].getBufferPosition(), + scopeDescriptor: editor.getCursors()[0].getScopeDescriptor(), + prefix: editor.getText() + }); + + const getSuggestions = function() { + const completions = require('../lib/runtime/completions'); + return completions.getSuggestions(completionsData()); + }; + + return describe('basic module completions', function() { + + let completions = null; + + beforeEach(function() { + editor.insertText('sin'); + return waitsForPromise(() => getSuggestions().then(cs => completions = cs)); + }); + + return it('retrieves completions', function() { + completions = completions.map(c => c.text); + expect(completions).toContain('sin'); + expect(completions).toContain('sincos'); + return expect(completions).toContain('sinc'); + }); + }); + }); +}; diff --git a/spec/juno-spec.coffee b/spec/juno-spec.coffee deleted file mode 100644 index de435e6b..00000000 --- a/spec/juno-spec.coffee +++ /dev/null @@ -1,61 +0,0 @@ -juno = require '../lib/julia-client' -{client} = juno.connection - -if process.platform is 'darwin' - process.env.PATH += ':/usr/local/bin' - -basicSetup = -> - jasmine.attachToDOM atom.views.getView atom.workspace - waitsForPromise -> atom.packages.activatePackage 'language-julia' - waitsForPromise -> atom.packages.activatePackage 'ink' - waitsForPromise -> atom.packages.activatePackage 'julia-client' - runs -> - atom.config.set 'julia-client', - juliaPath: 'julia' - juliaOptions: - bootMode: 'Basic' - optimisationLevel: 2 - deprecationWarnings: false - consoleOptions: - rendererType: true - -cyclerSetup = -> - basicSetup() - runs -> atom.config.set 'julia-client.juliaOptions.bootMode', 'Cycler' - -conn = null - -withClient = -> - beforeEach -> - if conn? - client.attach conn - -testClient = require './client' -testEval = require './eval' - -describe "managing a basic client", -> - beforeEach basicSetup - testClient() - -describe "interaction with client cycler", -> - beforeEach cyclerSetup - testClient() - -describe "before use", -> - beforeEach basicSetup - it 'boots the client', -> - waitsFor 5*60*1000, (done) -> - juno.connection.boot().then -> done() - runs -> - conn = client.conn - -describe "in an editor", -> - beforeEach basicSetup - withClient() - testEval() - -describe "after use", -> - beforeEach basicSetup - withClient() - it "kills the client", -> - client.kill() diff --git a/spec/juno-spec.js b/spec/juno-spec.js new file mode 100644 index 00000000..59f42148 --- /dev/null +++ b/spec/juno-spec.js @@ -0,0 +1,77 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const juno = require('../lib/julia-client'); +const {client} = juno.connection; + +if (process.platform === 'darwin') { + process.env.PATH += ':/usr/local/bin'; +} + +const basicSetup = function() { + jasmine.attachToDOM(atom.views.getView(atom.workspace)); + waitsForPromise(() => atom.packages.activatePackage('language-julia')); + waitsForPromise(() => atom.packages.activatePackage('ink')); + waitsForPromise(() => atom.packages.activatePackage('julia-client')); + return runs(() => atom.config.set('julia-client', { + juliaPath: 'julia', + juliaOptions: { + bootMode: 'Basic', + optimisationLevel: 2, + deprecationWarnings: false + }, + consoleOptions: { + rendererType: true + } + } + )); +}; + +const cyclerSetup = function() { + basicSetup(); + return runs(() => atom.config.set('julia-client.juliaOptions.bootMode', 'Cycler')); +}; + +let conn = null; + +const withClient = () => beforeEach(function() { + if (conn != null) { + return client.attach(conn); + } +}); + +const testClient = require('./client'); +const testEval = require('./eval'); + +describe("managing a basic client", function() { + beforeEach(basicSetup); + return testClient(); +}); + +describe("interaction with client cycler", function() { + beforeEach(cyclerSetup); + return testClient(); +}); + +describe("before use", function() { + beforeEach(basicSetup); + return it('boots the client', function() { + waitsFor(5*60*1000, done => juno.connection.boot().then(() => done())); + return runs(() => conn = client.conn); + }); +}); + +describe("in an editor", function() { + beforeEach(basicSetup); + withClient(); + return testEval(); +}); + +describe("after use", function() { + beforeEach(basicSetup); + withClient(); + return it("kills the client", () => client.kill()); +});