diff --git a/lib/misc/blocks.js b/lib/misc/blocks.js index 4c281215..2a4204a0 100644 --- a/lib/misc/blocks.js +++ b/lib/misc/blocks.js @@ -132,6 +132,7 @@ export function getLocalContext (editor, row) { const context = range ? editor.getTextInBufferRange(range) : '' const startRow = range ? range[0][0] : undefined return { + range, context, startRow } diff --git a/lib/misc/words.js b/lib/misc/words.js index 35776ebe..f2e9533d 100644 --- a/lib/misc/words.js +++ b/lib/misc/words.js @@ -2,7 +2,7 @@ import { Point, Range } from 'atom' -export const wordRegex = /[\u00A0-\uFFFF\w_!´\.]*@?[\u00A0-\uFFFF\w_!´]+/ +const wordRegex = /[\u00A0-\uFFFF\w_!´\.]*@?[\u00A0-\uFFFF\w_!´]+/ /** * Takes an `editor` and gets the word at current cursor position. If that is nonempty, call diff --git a/lib/package/commands.coffee b/lib/package/commands.coffee index 72b4a7af..55bdacb8 100644 --- a/lib/package/commands.coffee +++ b/lib/package/commands.coffee @@ -52,6 +52,10 @@ module.exports = @withInk -> boot() juno.runtime.evaluation.toggleDocs() + 'julia-client:rename-refactor': => + @withInk -> + boot() + juno.runtime.refactor.renameRefactor() # @NOTE: `'clear-workspace'` is now not handled by Atom.jl # 'julia-client:reset-workspace': => # requireClient 'reset the workspace', -> diff --git a/lib/runtime.coffee b/lib/runtime.coffee index 2eb63211..f3818a5f 100644 --- a/lib/runtime.coffee +++ b/lib/runtime.coffee @@ -15,6 +15,8 @@ module.exports = debuginfo: require './runtime/debuginfo' formatter: require './runtime/formatter' goto: require './runtime/goto' + urihandler: require './runtime/urihandler' + refactor: require './runtime/refactor' activate: -> @subs = new CompositeDisposable() @@ -29,7 +31,7 @@ module.exports = consumeInk: (ink) -> @evaluation.ink = ink @frontend.ink = ink - for mod in [@console, @debugger, @profiler, @linter, @goto, @outline] + for mod in [@console, @debugger, @profiler, @linter, @goto, @outline, @urihandler, @refactor] mod.activate(ink) for mod in [@workspace, @plots] mod.ink = ink @@ -60,4 +62,4 @@ module.exports = @subs.add(datatipDisposable) datatipDisposable - handleURI: require './runtime/urihandler' + handleURI: (parsedURI) -> @urihandler.handleURI parsedURI diff --git a/lib/runtime/goto.js b/lib/runtime/goto.js index 7c8a01f8..b8bfb317 100644 --- a/lib/runtime/goto.js +++ b/lib/runtime/goto.js @@ -101,7 +101,7 @@ class Goto { // module context const currentModule = modules.current() const mod = currentModule ? currentModule : 'Main' - const text = editor.getText() // buffer text that will be used for fallback entry + const text = editor.getText() // will be used as a fallback entry, e.g.: when in Main module gotoSymbol({ word, diff --git a/lib/runtime/profiler.js b/lib/runtime/profiler.js index c627c8dd..ca36fa7d 100644 --- a/lib/runtime/profiler.js +++ b/lib/runtime/profiler.js @@ -9,7 +9,7 @@ var {loadProfileTrace, saveProfileTrace} = client.import({msg: ['loadProfileTrac export function activate (ink) { pane = ink.PlotPane.fromId('Profile') - pane.getTitle = () => {return 'Profiler'} + pane.setTitle('Profiler') subs = new CompositeDisposable() subs.add(client.onDetached(() => clear())) @@ -24,7 +24,10 @@ export function activate (ink) { pane.ensureVisible({ split: atom.config.get('julia-client.uiOptions.layouts.profiler.split') }) - pane.show(new ink.Pannable(profile, {zoomstrategy: 'width', minScale: 0.5})) + pane.show(new ink.Pannable(profile, { + zoomstrategy: 'width', + minScale: 0.5 + })) } }) diff --git a/lib/runtime/refactor.js b/lib/runtime/refactor.js new file mode 100644 index 00000000..03998139 --- /dev/null +++ b/lib/runtime/refactor.js @@ -0,0 +1,101 @@ +/** @babel */ + +import { client } from '../connection' +import modules from './modules' +import { getWordRangeAtBufferPosition, isValidWordToInspect } from '../misc/words' +import { getLocalContext } from '../misc/blocks' + +const renamerefactor = client.import('renamerefactor') +const wordRegexWithoutDotAccessor = /@?[\u00A0-\uFFFF\w_!´]+/ + +class Refactor { + activate (ink) { + this.ink = ink + } + + renameRefactor () { + const editor = atom.workspace.getActiveTextEditor() + const bufferPosition = editor.getCursorBufferPosition() + + if (!client.isActive()) return + + const range = getWordRangeAtBufferPosition(editor, bufferPosition, { + wordRegex: wordRegexWithoutDotAccessor + }) + if (range.isEmpty()) return + const oldWord = editor.getTextInBufferRange(range) + if (!isValidWordToInspect(oldWord)) return + + const rangeFull = getWordRangeAtBufferPosition(editor, bufferPosition) + const fullWord = editor.getTextInBufferRange(rangeFull) + + this.ink.showBasicModal([{ + name: 'Rename', + defaultText: oldWord, + message: `Enter an new name to which \`${oldWord}\` will be renamed.` + }]).then(items => { + // check the new name is a valid identifier + const newWord = items['Rename'] + if (!isValidWordToInspect(newWord) || newWord.match(wordRegexWithoutDotAccessor) != newWord) { + atom.notifications.addWarning('Julia Client: Rename Refactor', { + description: `\`${newWord}\` isn't a valid identifier` + }) + return + } + + // local context + const { column, row } = bufferPosition + const { range, context, startRow } = getLocalContext(editor, row) + + // module context + const currentModule = modules.current() + const mod = currentModule ? currentModule : 'Main' + + renamerefactor({ + oldWord, + fullWord, + newWord, + // local context + column: column + 1, + row: row + 1, + startRow, + context, + // module context + mod, + }).then(result => { + // local refactoring + if (result.text) { + editor.setTextInBufferRange(range, result.text) + } + if (result.success) { + atom.notifications.addSuccess('Julia Client: Rename Refactor', { + description: result.success, + dismissable: true + }) + } + if (result.info) { + atom.notifications.addInfo('Julia Client: Rename Refactor', { + description: result.info, + dismissable: true + }) + } + if (result.warning) { + atom.notifications.addWarning('Julia Client: Rename Refactor', { + description: result.warning, + dismissable: true + }) + } + if (result.error) { + atom.notifications.addError('Julia Client: Rename Refactor', { + description: result.error, + dismissable: true + }) + } + }) + }).catch((err) => { + if (err) console.error(err) + }) + } +} + +export default new Refactor() diff --git a/lib/runtime/urihandler.js b/lib/runtime/urihandler.js index e06a8148..9888c721 100644 --- a/lib/runtime/urihandler.js +++ b/lib/runtime/urihandler.js @@ -1,37 +1,59 @@ -"use babel" +/** @babel */ import { client } from '../connection' import { docpane, views } from '../ui' -const { moduleinfo } = client.import({ rpc: ['moduleinfo'] }) const docs = client.import('docs') +const { + gotosymbol: gotoSymbol, + moduleinfo: moduleInfo +} = client.import({ rpc: [ 'gotosymbol', 'moduleinfo' ] }) -export default function handleURI (parsedURI) { - const { query } = parsedURI +class URIHandler { + activate(ink) { + this.ink = ink + } + + 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, []) - }) - } else if (query.moduleinfo){ // show module info - const { mod } = query - moduleinfo({ mod }).then(({ doc, items }) => { - items.map(item => { - docpane.processItem(item) + 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, []) }) - const view = views.render(doc) - docpane.ensureVisible() - docpane.showDocument(view, items) - }) + } else if (query.goto) { + const { word, mod } = query + gotoSymbol({ + word, + mod + }).then(symbols => { + if (symbols.error) return + this.ink.goto.goto(symbols, { + pending: atom.config.get('core.allowPendingPaneItems') + }) + }) + } 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) + }) + } } } + +export default new URIHandler()