From f2597229b5265ed450be4975598e3fcbb208849b Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Fri, 19 Aug 2016 10:46:08 -0700 Subject: [PATCH 01/11] No autocomplete x y, rename Zatanna to Autocomplete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Don’t autocomplete common variables ’x’ or ‘y’: return if /^x$|^y$/i.test(prefix) Renaming zatanna to autocomplete Closes #3865 --- app/schemas/subscriptions/tome.coffee | 4 +-- app/views/play/level/tome/SpellView.coffee | 32 ++++++++--------- .../{zatanna.coffee => autocomplete.coffee} | 11 +++--- .../play/level/tome/editor/snippets.coffee | 36 +++++++++---------- 4 files changed, 43 insertions(+), 40 deletions(-) rename app/views/play/level/tome/editor/{zatanna.coffee => autocomplete.coffee} (97%) diff --git a/app/schemas/subscriptions/tome.coffee b/app/schemas/subscriptions/tome.coffee index f51853c76..cff846e0c 100644 --- a/app/schemas/subscriptions/tome.coffee +++ b/app/schemas/subscriptions/tome.coffee @@ -103,12 +103,12 @@ module.exports = 'tome:change-config': c.object {title: 'Change Config', description: 'Published when you change your tome settings'} - 'tome:update-snippets': c.object {title: 'Update Snippets', description: 'Published when we need to add Zatanna snippets', required: ['propGroups', 'allDocs']}, + 'tome:update-snippets': c.object {title: 'Update Snippets', description: 'Published when we need to add autocomplete snippets', required: ['propGroups', 'allDocs']}, propGroups: {type: 'object'} allDocs: {type: 'object'} language: {type: 'string'} - 'tome:insert-snippet': c.object {title: 'Insert Snippet', description: 'Published when we need to insert a Zatanna snippet', required: ['doc', 'language', 'formatted']}, + 'tome:insert-snippet': c.object {title: 'Insert Snippet', description: 'Published when we need to insert a autocomplete snippet', required: ['doc', 'language', 'formatted']}, doc: {type: 'object'} language: {type: 'string'} formatted: {type: 'object'} diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index f3a759558..fe8d07c0a 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -12,7 +12,7 @@ LevelComponent = require 'models/LevelComponent' UserCodeProblem = require 'models/UserCodeProblem' utils = require 'core/utils' CodeLog = require 'models/CodeLog' -Zatanna = require './editor/zatanna' +Autocomplete = require './editor/autocomplete' module.exports = class SpellView extends CocoView id: 'spell-view' @@ -45,7 +45,7 @@ module.exports = class SpellView extends CocoView 'tome:spell-statement-index-updated': 'onStatementIndexUpdated' 'tome:change-language': 'onChangeLanguage' 'tome:change-config': 'onChangeEditorConfig' - 'tome:update-snippets': 'addZatannaSnippets' + 'tome:update-snippets': 'addAutocompleteSnippets' 'tome:insert-snippet': 'onInsertSnippet' 'tome:spell-beautify': 'onSpellBeautify' 'tome:maximize-toggled': 'onMaximizeToggled' @@ -459,7 +459,7 @@ module.exports = class SpellView extends CocoView if (e.command.name is 'insertstring' and intersects()) or (e.command.name in ['Backspace', 'throttle-backspaces'] and intersectsLeft()) or (e.command.name is 'del' and intersectsRight()) - @zatanna?.off?() + @autocomplete?.off?() pulseLockedCode() return false else if e.command.name in ['enter-skip-delimiters', 'Enter', 'Return'] @@ -468,41 +468,41 @@ module.exports = class SpellView extends CocoView e.editor.navigateLineStart() return false else if e.command.name in ['Enter', 'Return'] and not e.editor?.completer?.popup?.isOpen - @zatanna?.on?() + @autocomplete?.on?() return e.editor.execCommand 'enter-skip-delimiters' - @zatanna?.on?() + @autocomplete?.on?() e.command.exec e.editor, e.args or {} - initAutocomplete: (@autocomplete) -> + initAutocomplete: (@autocompleteOn) -> # TODO: Turn on more autocompletion based on level sophistication # TODO: E.g. using the language default snippets yields a bunch of crazy non-beginner suggestions # TODO: Options logic shouldn't exist both here and in updateAutocomplete() return if @spell.language is 'html' popupFontSizePx = @options.level.get('autocompleteFontSizePx') ? 16 - @zatanna = new Zatanna @ace, + @autocomplete = new Autocomplete @ace, basic: false liveCompletion: false snippetsLangDefaults: false completers: keywords: false - snippets: @autocomplete + snippets: @autocompleteOn autoLineEndings: javascript: ';' popupFontSizePx: popupFontSizePx popupLineHeightPx: 1.5 * popupFontSizePx popupWidthPx: 380 - updateAutocomplete: (@autocomplete) -> - @zatanna?.set 'snippets', @autocomplete + updateAutocomplete: (@autocompleteOn) -> + @autocomplete?.set 'snippets', @autocompleteOn - addZatannaSnippets: (e) -> + addAutocompleteSnippets: (e) -> # Snippet entry format: # content: code inserted into document # meta: displayed right-justfied in popup # name: displayed left-justified in popup, and what's being matched # tabTrigger: fallback for name field - return unless @zatanna and @autocomplete - @zatanna.addCodeCombatSnippets @options.level, @, e + return unless @autocomplete and @autocompleteOn + @autocomplete.addCodeCombatSnippets @options.level, @, e translateFindNearest: -> # If they have advanced glasses but are playing a level which assumes earlier glasses, we'll adjust the sample code to use the more advanced APIs instead. @@ -582,7 +582,7 @@ module.exports = class SpellView extends CocoView @createTranslationView() unless @translationView @toolbarView?.toggleFlow false @updateAether false, false - # @addZatannaSnippets() + # @addAutocompleteSnippets() @highlightCurrentLine() cast: (preload=false, realTime=false, justBegin=false) -> @@ -1219,7 +1219,7 @@ module.exports = class SpellView extends CocoView onChangeLanguage: (e) -> return unless @spell.canWrite() @aceSession.setMode utils.aceEditModes[e.language] - @zatanna?.set 'language', utils.aceEditModes[e.language].substr('ace/mode/') + @autocomplete?.set 'language', utils.aceEditModes[e.language].substr('ace/mode/') wasDefault = @getSource() is @spell.originalSource @spell.setLanguage e.language @reloadCode true if wasDefault @@ -1287,7 +1287,7 @@ module.exports = class SpellView extends CocoView @debugView?.destroy() @translationView?.destroy() @toolbarView?.destroy() - @zatanna?.addSnippets [], @editorLang if @editorLang? + @autocomplete?.addSnippets [], @editorLang if @editorLang? $(window).off 'resize', @onWindowResize window.clearTimeout @saveSpadeTimeout @saveSpadeTimeout = null diff --git a/app/views/play/level/tome/editor/zatanna.coffee b/app/views/play/level/tome/editor/autocomplete.coffee similarity index 97% rename from app/views/play/level/tome/editor/zatanna.coffee rename to app/views/play/level/tome/editor/autocomplete.coffee index 8a672351e..562ffaec0 100644 --- a/app/views/play/level/tome/editor/zatanna.coffee +++ b/app/views/play/level/tome/editor/autocomplete.coffee @@ -23,7 +23,7 @@ defaults = # TODO: Create list of manual test cases -module.exports = class Zatanna +module.exports = class Autocomplete Tokenizer = '' BackgroundTokenizer = '' @@ -43,7 +43,7 @@ module.exports = class Zatanna #TODO: Renable option validation if we care #validationResult = optionsValidator @options #unless validationResult.valid - # throw new Error "Invalid Zatanna options: " + JSON.stringify(validationResult.errors, null, 4) + # throw new Error "Invalid Autocomplete options: " + JSON.stringify(validationResult.errors, null, 4) ace.config.loadModule 'ace/ext/language_tools', () => @snippetManager = ace.require('ace/snippets').snippetManager @@ -154,7 +154,7 @@ module.exports = class Zatanna off: -> @paused = true doLiveCompletion: (e) => - # console.log 'Zatanna doLiveCompletion', e + # console.log 'Autocomplete doLiveCompletion', e return unless @options.basic or @options.liveCompletion or @options.completers.snippets return if @paused @@ -172,6 +172,9 @@ module.exports = class Zatanna # Bake a fresh autocomplete every keystroke editor.completer?.detach() if hasCompleter + # Skip common single letter variable names + return if /^x$|^y$/i.test(prefix) + # Only autocomplete if there's a prefix that can be matched if (prefix) unless (editor.completer) @@ -330,7 +333,7 @@ module.exports = class Zatanna if haveFindNearest and not haveFindNearestEnemy spellView.translateFindNearest() - # window.zatannaInstance = @zatanna # For debugging. Make sure to not leave active when committing. + # window.AutocompleteInstance = @Autocomplete # For debugging. Make sure to not leave active when committing. # window.snippetEntries = snippetEntries lang = utils.aceEditModes[e.language].substr 'ace/mode/'.length @addSnippets snippetEntries, lang diff --git a/app/views/play/level/tome/editor/snippets.coffee b/app/views/play/level/tome/editor/snippets.coffee index 6e234def8..e76b79ce3 100644 --- a/app/views/play/level/tome/editor/snippets.coffee +++ b/app/views/play/level/tome/editor/snippets.coffee @@ -37,8 +37,8 @@ module.exports = (SnippetManager, autoLineEndings) -> range = new Range cursor.row, cursor.column - 1 - prevWord.length, cursor.row, cursor.column editor.session.remove range else - # console.log "Zatanna cursor.column=#{cursor.column} snippet='#{snippet}' line='#{line}' prevWord='#{prevWord}'" - # console.log "Zatanna prevWordIndex=#{prevWordIndex}" + # console.log "Snippets cursor.column=#{cursor.column} snippet='#{snippet}' line='#{line}' prevWord='#{prevWord}'" + # console.log "Snippets prevWordIndex=#{prevWordIndex}" # Lookup original completion # TODO: Can we identify correct completer somehow? @@ -51,10 +51,10 @@ module.exports = (SnippetManager, autoLineEndings) -> break if originalCompletion if originalCompletion? - # console.log 'Zatanna original completion', originalCompletion + # console.log 'Snippets original completion', originalCompletion # Get original snippet prefix (accounting for extra '\n' and possibly autoLineEndings at end) lang = editor.session.getMode()?.$id?.substr 'ace/mode/'.length - # console.log 'Zatanna lang', lang, autoLineEndings[lang]?.length + # console.log 'Snippets lang', lang, autoLineEndings[lang]?.length extraEndLength = 1 extraEndLength += autoLineEndings[lang].length if autoLineEndings[lang]? if snippetIndex = originalCompletion.content.indexOf snippet.substr(0, snippet.length - extraEndLength) @@ -62,21 +62,21 @@ module.exports = (SnippetManager, autoLineEndings) -> else originalPrefix = '' snippetStart = cursor.column - originalPrefix.length - # console.log "Zatanna originalPrefix='#{originalPrefix}' snippetStart=#{snippetStart}" + # console.log "Snippets originalPrefix='#{originalPrefix}' snippetStart=#{snippetStart}" if snippetStart > 0 and snippetStart <= line.length extraIndex = snippetStart - 1 - # console.log "Zatanna prev char='#{line[extraIndex]}'" + # console.log "Snippets prev char='#{line[extraIndex]}'" if line[extraIndex] is '.' # Fuzzy string match previous word before '.', and remove if a match to beginning of snippet originalObject = originalCompletion.content.substring(0, originalCompletion.content.indexOf('.')) prevObjectIndex = extraIndex - 1 - # console.log "Zatanna prevObjectIndex=#{prevObjectIndex}" + # console.log "Snippets prevObjectIndex=#{prevObjectIndex}" if prevObjectIndex >= 0 and /\w/.test(line[prevObjectIndex]) prevObjectIndex-- while prevObjectIndex >= 0 and /\w/.test(line[prevObjectIndex]) prevObjectIndex++ if prevObjectIndex < 0 or not /\w/.test(line[prevObjectIndex]) - # console.log "Zatanna prevObjectIndex=#{prevObjectIndex} extraIndex=#{extraIndex}" + # console.log "Snippets prevObjectIndex=#{prevObjectIndex} extraIndex=#{extraIndex}" prevObject = line.substring prevObjectIndex, extraIndex #TODO: We use to use fuzziac here, but we forgot why. Using @@ -87,7 +87,7 @@ module.exports = (SnippetManager, autoLineEndings) -> if fuzzer finalScore = fuzzer.score prevObject - # console.log "Zatanna originalObject='#{originalObject}' prevObject='#{prevObject}'", finalScore + # console.log "Snippets originalObject='#{originalObject}' prevObject='#{prevObject}'", finalScore if finalScore > 0.5 range = new Range cursor.row, prevObjectIndex, cursor.row, snippetStart editor.session.remove range @@ -113,7 +113,7 @@ module.exports = (SnippetManager, autoLineEndings) -> baseInsertSnippet.call @, editor, snippet getCompletions: (editor, session, pos, prefix, callback) -> - # console.log "Zatanna getCompletions pos.column=#{pos.column} prefix=#{prefix}" + # console.log "Snippets getCompletions pos.column=#{pos.column} prefix=#{prefix}" # Completion format: # prefix: text that will be replaced by snippet # caption: displayed left-justified in popup, and what's being matched @@ -149,7 +149,7 @@ module.exports = (SnippetManager, autoLineEndings) -> continue unless caption [snippet, fuzzScore] = scrubSnippet s.content, caption, line, prefix, pos, lang, autoLineEndings, s.captureReturn completions.push - content: s.content # Used internally by Zatanna, not by ace autocomplete + content: s.content # Used internally by Snippets, not by ace autocomplete caption: caption snippet: snippet score: fuzzScore * s.importance ? 1.0 @@ -162,7 +162,7 @@ module.exports = (SnippetManager, autoLineEndings) -> @completions = _.filter(completions, (x) -> x.caption.indexOf prefix is 0) return callback null, @completions - # console.log 'Zatanna snippet completions', completions + # console.log 'Snippets snippet completions', completions @completions = completions callback null, completions @@ -170,7 +170,7 @@ module.exports = (SnippetManager, autoLineEndings) -> # TODO: https://github.com/ajaxorg/ace/commit/7b01a4273e91985c9177f53d238d6b83fe99dc56 # TODO: But, if it was we could use this and pass a 'completer: @' property for each completion # insertMatch: (editor, data) -> - # console.log 'Zatanna snippets insertMatch', editor, data + # console.log 'Snippets snippets insertMatch', editor, data # if data.snippet # SnippetManager.insertSnippet editor, data.snippet # else @@ -193,7 +193,7 @@ getFullIdentifier = (doc, pos) -> text.substring start, end scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captureReturn) -> - # console.log "Zatanna snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}" + # console.log "Snippets snippet=#{snippet} caption=#{caption} line=#{line} input=#{input} pos.column=#{pos.column} lang=#{lang}" fuzzScore = 0.1 # input will be replaced by snippet # trim snippet prefix and suffix if already in the document (line) @@ -223,7 +223,7 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu # TODO: This is broken for attack(find in Python, but seems ok in JavaScript. # Don't eat existing matched parentheses - # console.log "Zatanna checking parentheses lineSuffix=#{lineSuffix} pos.column=#{pos.column} input.length=#{input.length}, prevChar=#{line[pos.column - input.length - 1]} line.length=#{line.length} nextChar=#{line[pos.column]}" + # console.log "Snippets checking parentheses lineSuffix=#{lineSuffix} pos.column=#{pos.column} input.length=#{input.length}, prevChar=#{line[pos.column - input.length - 1]} line.length=#{line.length} nextChar=#{line[pos.column]}" if pos.column - input.length >= 0 and line[pos.column - input.length - 1] is '(' and pos.column < line.length and line[pos.column] is ')' and lineSuffix is ')' lineSuffix = '' @@ -238,9 +238,9 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu # If at end of line # And, no parentheses are before snippet. E.g. 'if (' # And, line doesn't start with whitespace followed by 'if ' or 'elif ' - # console.log "Zatanna autoLineEndings linePrefixIndex='#{linePrefixIndex}'" + # console.log "Snippets autoLineEndings linePrefixIndex='#{linePrefixIndex}'" if lineSuffix.length is 0 and /^\s*$/.test line.slice pos.column - # console.log 'Zatanna atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line + # console.log 'Snippets atLineEnd', pos.column, lineSuffix.length, line.slice(pos.column + lineSuffix.length), line toLinePrefix = line.substring 0, linePrefixIndex if linePrefixIndex < 0 or linePrefixIndex >= 0 and not /[\(\)]/.test(toLinePrefix) and not /^[ \t]*(?:if\b|elif\b)/.test(toLinePrefix) snippet += autoLineEndings[lang] if snippetLines is 0 and autoLineEndings[lang] @@ -249,7 +249,7 @@ scrubSnippet = (snippet, caption, line, input, pos, lang, autoLineEndings, captu if captureReturn and /^\s*$/.test(toLinePrefix) snippet = captureReturn + linePrefix + snippet - # console.log "Zatanna snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}" + # console.log "Snippets snippetPrefix=#{snippetPrefix} linePrefix=#{linePrefix} snippetSuffix=#{snippetSuffix} lineSuffix=#{lineSuffix} snippet=#{snippet} score=#{fuzzScore}" else fuzzScore += score snippet, input From 7c9f7cbce74ca2c99d1d3131cfa8fa15688f64f7 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 22 Aug 2016 06:11:01 -0700 Subject: [PATCH 02/11] Show correct programming language in level intro --- app/views/play/level/LevelLoadingView.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/play/level/LevelLoadingView.coffee b/app/views/play/level/LevelLoadingView.coffee index ed97cfb39..7218d319b 100644 --- a/app/views/play/level/LevelLoadingView.coffee +++ b/app/views/play/level/LevelLoadingView.coffee @@ -184,7 +184,8 @@ module.exports = class LevelLoadingView extends CocoView #{problem.category} - #{problem.score} points """, sanitize: false else - html = marked utils.filterMarkdownCodeLanguages(utils.i18n(@intro, 'body')) + language = @session?.get('codeLanguage') + html = marked utils.filterMarkdownCodeLanguages(utils.i18n(@intro, 'body'), language) @$el.find('.intro-doc').removeClass('hidden').find('.intro-doc-content').html html @resize() From fe498670431398acd5076ded85bcdf1088b4669b Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Mon, 22 Aug 2016 06:27:02 -0700 Subject: [PATCH 03/11] Don't key off slugs in updateCourses.js --- scripts/mongodb/updateCourses.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/scripts/mongodb/updateCourses.js b/scripts/mongodb/updateCourses.js index bc87db27f..2182f3e94 100644 --- a/scripts/mongodb/updateCourses.js +++ b/scripts/mongodb/updateCourses.js @@ -1,15 +1,19 @@ -// Update course data +// Update courses // Usage: // mongo
:/