From 0cae904752ea833bfbcfbc21d335975fbd356676 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Sat, 16 Sep 2017 03:51:32 -0400 Subject: [PATCH 1/3] basic class autocomplete working, but inefficient --- lib/controller.coffee | 8 ++++++++ lib/repl.coffee | 19 ++++++++++++++++--- lib/supercollider.coffee | 3 +++ package.json | 7 +++++++ 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/controller.coffee b/lib/controller.coffee index 897c341..daf27f8 100755 --- a/lib/controller.coffee +++ b/lib/controller.coffee @@ -17,6 +17,14 @@ class Controller @activeRepl = null @markers = [] @scScope = 'source.supercollider' + @provider = + selector: '.source.supercollider' + inclusionPriority: 1 + # excludeLowerPriority: true + filterSuggestions: true + getSuggestions: (options) => + @activeRepl.getSuggestions(options) + start: -> atom.commands.add 'atom-workspace', diff --git a/lib/repl.coffee b/lib/repl.coffee index 518b1d5..b1d73ae 100644 --- a/lib/repl.coffee +++ b/lib/repl.coffee @@ -1,4 +1,5 @@ PostWindow = require('./post-window') +AutoCompleter = require('./autocomplete') Bacon = require('baconjs') url = require('url') os = require('os') @@ -23,6 +24,7 @@ class Repl @makeBus() @state = null @debug = atom.config.get 'supercollider.debug' + @autocompleter = new AutoCompleter() if @debug console.log 'Supercollider REPL [DEBUG=true]' @@ -38,6 +40,14 @@ class Repl @postWindow = new PostWindow(@uri, @postBus, onClose) + updateClassList: () -> + @eval('Class.allClasses', false, null, false) + .then (result) => + @autocompleter.setClasses(result) + + getSuggestions: (options) -> + @autocompleter.getSuggestions(options) + makeBus: -> @postBus = new Bacon.Bus() @controllerBus = new Bacon.Bus() @@ -80,6 +90,7 @@ class Repl if @debug console.log 'booted' @ready.resolve() + @updateClassList() fail = (error) => # dirs @@ -198,12 +209,14 @@ class Repl return opts - eval: (expression, noecho=false, nowExecutingPath=null) -> + eval: (expression, noecho=false, nowExecutingPath=null, returnString=true) -> deferred = Q.defer() ok = (result) => - @postBus.push "
#{result}
" + # we need to convert to string for printing if our output is a JSON object + printable = if returnString then result else JSON.stringify(result) + @postBus.push "
#{printable}
" deferred.resolve(result) err = (error) => @@ -223,7 +236,7 @@ class Repl @postBus.push "
#{echo}
" # expression path asString postErrors getBacktrace - @sclang.interpret(expression, nowExecutingPath, true, false, true) + @sclang.interpret(expression, nowExecutingPath, returnString, false, true) .then(ok, err) deferred.promise diff --git a/lib/supercollider.coffee b/lib/supercollider.coffee index 3fa94d3..611c36e 100644 --- a/lib/supercollider.coffee +++ b/lib/supercollider.coffee @@ -38,3 +38,6 @@ module.exports = serialize: -> {} + + provide: () -> + @controller.provider diff --git a/package.json b/package.json index 5536f31..e6f477c 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,12 @@ "supercollider:cmd-period", "supercollider:manage-quarks" ] + }, + "providedServices": { + "autocomplete.provider": { + "versions": { + "2.0.0": "provide" + } + } } } From 389ccc2a4af6275bc2501318cfe20597bd26a0a3 Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Fri, 22 Sep 2017 19:52:02 -0400 Subject: [PATCH 2/3] tweaks autocompleter API --- lib/autocomplete.coffee | 19 +++++++++++++++++++ lib/repl.coffee | 9 ++------- 2 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 lib/autocomplete.coffee diff --git a/lib/autocomplete.coffee b/lib/autocomplete.coffee new file mode 100644 index 0000000..4feb21b --- /dev/null +++ b/lib/autocomplete.coffee @@ -0,0 +1,19 @@ +module.exports = +class AutoCompleter + constructor: (repl) -> + @repl = repl + @classes = null + @docs = null + + updateClassList: () -> + @repl.eval('Class.allClasses', false, null, false) + .then (result) => + @classes = result + @repl.eval('SCDoc.documents', false, null, false) + .then (result) => + console.log("got results") + @docs = result + + + getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix, activatedManually}) => + return ({text: cls, type: 'class'} for cls in @classes) diff --git a/lib/repl.coffee b/lib/repl.coffee index b1d73ae..23ab4f0 100644 --- a/lib/repl.coffee +++ b/lib/repl.coffee @@ -24,7 +24,7 @@ class Repl @makeBus() @state = null @debug = atom.config.get 'supercollider.debug' - @autocompleter = new AutoCompleter() + @autocompleter = new AutoCompleter(@) if @debug console.log 'Supercollider REPL [DEBUG=true]' @@ -40,11 +40,6 @@ class Repl @postWindow = new PostWindow(@uri, @postBus, onClose) - updateClassList: () -> - @eval('Class.allClasses', false, null, false) - .then (result) => - @autocompleter.setClasses(result) - getSuggestions: (options) -> @autocompleter.getSuggestions(options) @@ -90,7 +85,7 @@ class Repl if @debug console.log 'booted' @ready.resolve() - @updateClassList() + @autocompleter.updateClassList() fail = (error) => # dirs From 499da83c93080c7030a14e5f8dc8b0c144f09f4a Mon Sep 17 00:00:00 2001 From: Spencer Russell Date: Fri, 29 Sep 2017 02:01:23 -0400 Subject: [PATCH 3/3] implements autocomplete on class names and class methods --- lib/autocomplete.coffee | 86 ++++++++++++++++++++++++++++++++++++++--- lib/controller.coffee | 4 +- lib/repl.coffee | 10 +++-- package.json | 1 + 4 files changed, 89 insertions(+), 12 deletions(-) diff --git a/lib/autocomplete.coffee b/lib/autocomplete.coffee index 4feb21b..0995f04 100644 --- a/lib/autocomplete.coffee +++ b/lib/autocomplete.coffee @@ -1,19 +1,93 @@ +fuzz = require('fuzzaldrin-plus') + module.exports = class AutoCompleter constructor: (repl) -> @repl = repl @classes = null @docs = null + # pre-allocate so it only gets compiled once + # look for some set of period-separated identifiers before the cursor + @prefixRegex = /([A-Za-z0-9_.]*)$/ + + handleNakedPrefix: (prefix) -> + # currently we onyly have classes in our naked database. If we + # add global variables we'll need to tweak this + names = fuzz.filter(@classes, prefix, {maxResults: 20}) + return ({ + text: name, + description: @docs["Classes/#{name}"], + type: "class"} for name in names) + + handleClassMemberPrefix: (className, prefix) -> + candidates = [] + return @getClassMethods(className) + .then (methods) => + for m in methods + candidates.push({ + text: m.name, + description: @makeMethodString(m), + type: "method" + }) + return candidates + + makeMethodString: (method) -> + argStrings = [] + sep = ", " + N = method.args.length + if N == 0 + # no arguments, show without parens + return method.name + for i in [0..(N-1)] + if method.argDefaults[i] == null + argStrings.push(method.args[i]) + else + argStrings.push("#{method.args[i]}=#{method.argDefaults[i]}") + return "#{method.name}(#{argStrings.join(sep)})" + + # these introspection commands return nil instead of an empty list + # if there aren't any methods/vars. bummer. + getClassMethods: (className) -> + code = """ + if(#{className}.class.methods.isNil, {[]}, { + #{className}.class.methods.collect { + arg m; + ( + \\name: m.name, + \\args: if(m.argNames.isNil, {[]}, {m.argNames[1..]}), + \\argDefaults: if(m.prototypeFrame.isNil, {[]}, {m.prototypeFrame[1..]}) + ) + } + }) + """ + return @repl.eval(code, true, null, false, false) + + getClassVars: (className) -> + code = """ + if(#{className}.classVarNames.isNil, {[]}, { + #{className}.classVarNames + }) + """ + return @repl.eval(code, true, null, false, false) updateClassList: () -> - @repl.eval('Class.allClasses', false, null, false) + @repl.eval('Class.allClasses', true, null, false, false) .then (result) => @classes = result - @repl.eval('SCDoc.documents', false, null, false) + @repl.eval('SCDoc.documents.collect {arg doc; doc.summary}', + true, null, false, false) .then (result) => - console.log("got results") @docs = result - - getSuggestions: ({editor, bufferPosition, scopeDescriptor, prefix, activatedManually}) => - return ({text: cls, type: 'class'} for cls in @classes) + getSuggestions: ({editor, bufferPosition}) => + # Get the text for the line up to the triggered buffer position + line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]) + # this regex matches empty, so we should always get something + prefix = line.match(@prefixRegex)[0] + objs = prefix.split('.'); + if objs.length == 1 + return @handleNakedPrefix(objs[0]) + else if objs.length == 2 && objs[0] in @classes + return @handleClassMemberPrefix(objs[0], objs[1]) + else + return [] diff --git a/lib/controller.coffee b/lib/controller.coffee index daf27f8..7250263 100755 --- a/lib/controller.coffee +++ b/lib/controller.coffee @@ -19,8 +19,8 @@ class Controller @scScope = 'source.supercollider' @provider = selector: '.source.supercollider' - inclusionPriority: 1 - # excludeLowerPriority: true + inclusionPriority: 10 + excludeLowerPriority: true filterSuggestions: true getSuggestions: (options) => @activeRepl.getSuggestions(options) diff --git a/lib/repl.coffee b/lib/repl.coffee index 23ab4f0..1f59f07 100644 --- a/lib/repl.coffee +++ b/lib/repl.coffee @@ -204,14 +204,16 @@ class Repl return opts - eval: (expression, noecho=false, nowExecutingPath=null, returnString=true) -> + eval: (expression, noecho=false, nowExecutingPath=null, + returnString=true, printResult=true) -> deferred = Q.defer() ok = (result) => - # we need to convert to string for printing if our output is a JSON object - printable = if returnString then result else JSON.stringify(result) - @postBus.push "
#{printable}
" + if printResult + # we need to convert to string for printing if our output is a JSON object + printable = if returnString then result else JSON.stringify(result) + @postBus.push "
#{printable}
" deferred.resolve(result) err = (error) => diff --git a/package.json b/package.json index e6f477c..02a2916 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "atom-space-pen-views": "^2.2.0", "baconjs": "^0.7.89", "escape-html": "~1.0.3", + "fuzzaldrin-plus": "^0.5.0", "growl": "^1.9.2", "jquery": "^2", "moment": "^2.17.1",