diff --git a/client/constants.js b/client/constants.js index 2678e6950b..ec0e4107ac 100644 --- a/client/constants.js +++ b/client/constants.js @@ -75,6 +75,7 @@ export const SET_TEXT_OUTPUT = 'SET_TEXT_OUTPUT'; export const SET_GRID_OUTPUT = 'SET_GRID_OUTPUT'; export const SET_SOUND_OUTPUT = 'SET_SOUND_OUTPUT'; export const SET_AUTOCLOSE_BRACKETS_QUOTES = 'SET_AUTOCLOSE_BRACKETS_QUOTES'; +export const SET_AUTOCOMPLETE_HINTER = 'SET_AUTOCOMPLETE_HINTER'; export const OPEN_PROJECT_OPTIONS = 'OPEN_PROJECT_OPTIONS'; export const CLOSE_PROJECT_OPTIONS = 'CLOSE_PROJECT_OPTIONS'; diff --git a/client/modules/IDE/actions/preferences.js b/client/modules/IDE/actions/preferences.js index 919a8c94f7..700f676bdd 100644 --- a/client/modules/IDE/actions/preferences.js +++ b/client/modules/IDE/actions/preferences.js @@ -16,7 +16,8 @@ function updatePreferences(formParams, dispatch) { } export function setFontSize(value) { - return (dispatch, getState) => { // eslint-disable-line + return (dispatch, getState) => { + // eslint-disable-line dispatch({ type: ActionTypes.SET_FONT_SIZE, value @@ -69,6 +70,24 @@ export function setAutocloseBracketsQuotes(value) { }; } +export function setAutocompleteHinter(value) { + return (dispatch, getState) => { + dispatch({ + type: ActionTypes.SET_AUTOCOMPLETE_HINTER, + value + }); + const state = getState(); + if (state.user.authenticated) { + const formParams = { + preferences: { + autocompleteHinter: value + } + }; + updatePreferences(formParams, dispatch); + } + }; +} + export function setAutosave(value) { return (dispatch, getState) => { dispatch({ diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index e1899e0bfe..8040146b02 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import CodeMirror from 'codemirror'; +import Fuse from 'fuse.js'; import emmet from '@emmetio/codemirror-plugin'; import prettier from 'prettier/standalone'; import babelParser from 'prettier/parser-babel'; @@ -29,6 +30,7 @@ import 'codemirror/addon/search/jump-to-line'; import 'codemirror/addon/edit/matchbrackets'; import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/selection/mark-selection'; +import 'codemirror/addon/hint/css-hint'; import 'codemirror-colorpicker'; import { JSHINT } from 'jshint'; @@ -43,6 +45,8 @@ import '../../../utils/p5-javascript'; import Timer from '../components/Timer'; import EditorAccessibility from '../components/EditorAccessibility'; import { metaKey } from '../../../utils/metaKey'; +import './show-hint'; +import * as hinter from '../../../utils/p5-hinter'; import '../../../utils/codemirror-search'; @@ -94,7 +98,6 @@ class Editor extends React.Component { this.beep = new Audio(beepUrl); this.widgets = []; this._cm = CodeMirror(this.codemirrorContainer, { - // eslint-disable-line theme: `p5-${this.props.theme}`, lineNumbers: this.props.lineNumbers, styleActiveLine: true, @@ -131,6 +134,11 @@ class Editor extends React.Component { } }); + this.hinter = new Fuse(hinter.p5Hinter, { + threshold: 0.05, + keys: ['text'] + }); + delete this._cm.options.lint.options.errors; const replaceCommand = @@ -186,16 +194,21 @@ class Editor extends React.Component { }); this._cm.on('keydown', (_cm, e) => { - // 70 === f if ( ((metaKey === 'Cmd' && e.metaKey) || (metaKey === 'Ctrl' && e.ctrlKey)) && e.shiftKey && - e.keyCode === 70 + e.key === 'f' ) { e.preventDefault(); this.tidyCode(); } + + // Show hint + const mode = this._cm.getOption('mode'); + if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) { + this.showHint(_cm); + } }); this._cm.getWrapperElement().style[ @@ -253,6 +266,12 @@ class Editor extends React.Component { this.props.autocloseBracketsQuotes ); } + if (this.props.autocompleteHinter !== prevProps.autocompleteHinter) { + if (!this.props.autocompleteHinter) { + // close the hinter window once the preference is turned off + CodeMirror.showHint(this._cm, () => {}); + } + } if (this.props.runtimeErrorWarningVisible) { if (this.props.consoleEvents.length !== prevProps.consoleEvents.length) { @@ -331,6 +350,97 @@ class Editor extends React.Component { this._cm.execCommand('findPersistent'); } + showHint(_cm) { + if (!this.props.autocompleteHinter) { + CodeMirror.showHint(_cm, () => {}); + return; + } + + let focusedLinkElement = null; + const setFocusedLinkElement = (set) => { + if (set && !focusedLinkElement) { + const activeItemLink = document.querySelector( + `.CodeMirror-hint-active a` + ); + if (activeItemLink) { + focusedLinkElement = activeItemLink; + focusedLinkElement.classList.add('focused-hint-link'); + focusedLinkElement.parentElement.parentElement.classList.add( + 'unfocused' + ); + } + } + }; + const removeFocusedLinkElement = () => { + if (focusedLinkElement) { + focusedLinkElement.classList.remove('focused-hint-link'); + focusedLinkElement.parentElement.parentElement.classList.remove( + 'unfocused' + ); + focusedLinkElement = null; + return true; + } + return false; + }; + + const hintOptions = { + _fontSize: this.props.fontSize, + completeSingle: false, + extraKeys: { + 'Shift-Right': (cm, e) => { + const activeItemLink = document.querySelector( + `.CodeMirror-hint-active a` + ); + if (activeItemLink) activeItemLink.click(); + }, + Right: (cm, e) => { + setFocusedLinkElement(true); + }, + Left: (cm, e) => { + removeFocusedLinkElement(); + }, + Up: (cm, e) => { + const onLink = removeFocusedLinkElement(); + e.moveFocus(-1); + setFocusedLinkElement(onLink); + }, + Down: (cm, e) => { + const onLink = removeFocusedLinkElement(); + e.moveFocus(1); + setFocusedLinkElement(onLink); + }, + Enter: (cm, e) => { + if (focusedLinkElement) focusedLinkElement.click(); + else e.pick(); + } + }, + closeOnUnfocus: false + }; + + if (_cm.options.mode === 'javascript') { + // JavaScript + CodeMirror.showHint( + _cm, + () => { + const c = _cm.getCursor(); + const token = _cm.getTokenAt(c); + + const hints = this.hinter.search(token.string); + + return { + list: hints, + from: CodeMirror.Pos(c.line, token.start), + to: CodeMirror.Pos(c.line, c.ch) + }; + }, + hintOptions + ); + } else if (_cm.options.mode === 'css') { + // CSS + CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions); + } + } + showReplace() { this._cm.execCommand('replace'); } @@ -437,6 +547,7 @@ class Editor extends React.Component { Editor.propTypes = { autocloseBracketsQuotes: PropTypes.bool.isRequired, + autocompleteHinter: PropTypes.bool.isRequired, lineNumbers: PropTypes.bool.isRequired, lintWarning: PropTypes.bool.isRequired, linewrap: PropTypes.bool.isRequired, @@ -482,7 +593,6 @@ Editor.propTypes = { collapseSidebar: PropTypes.func.isRequired, expandSidebar: PropTypes.func.isRequired, clearConsole: PropTypes.func.isRequired, - // showRuntimeErrorWarning: PropTypes.func.isRequired, hideRuntimeErrorWarning: PropTypes.func.isRequired, runtimeErrorWarningVisible: PropTypes.bool.isRequired, provideController: PropTypes.func.isRequired, diff --git a/client/modules/IDE/components/KeyboardShortcutModal.jsx b/client/modules/IDE/components/KeyboardShortcutModal.jsx index d924a21efc..093630498a 100644 --- a/client/modules/IDE/components/KeyboardShortcutModal.jsx +++ b/client/modules/IDE/components/KeyboardShortcutModal.jsx @@ -100,6 +100,10 @@ function KeyboardShortcutModal() { {t('KeyboardShortcuts.General.TurnOffAccessibleOutput')} +
  • + {'\u21E7'} + Right + Go to Reference for Selected Item in Hinter +
  • ); diff --git a/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx b/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx index 204b947794..fe414ffb58 100644 --- a/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx +++ b/client/modules/IDE/components/Preferences/Preferences.unit.test.jsx @@ -7,6 +7,7 @@ import Preferences from './index'; * - this.props.fontSize : number * - this.props.autosave : bool * - this.props.autocloseBracketsQuotes : bool + * - this.props.autocompleteHinter : bool * - this.props.linewrap : bool * - this.props.lineNumbers : bool * - this.props.theme : string @@ -35,6 +36,7 @@ describe('', () => { fontSize: 12, autosave: false, autocloseBracketsQuotes: false, + autocompleteHinter: false, linewrap: false, lineNumbers: false, theme: 'contrast', @@ -45,6 +47,7 @@ describe('', () => { setFontSize: jest.fn(), setAutosave: jest.fn(), setAutocloseBracketsQuotes: jest.fn(), + setAutocompleteHinter: jest.fn(), setLinewrap: jest.fn(), setLineNumbers: jest.fn(), setTheme: jest.fn(), @@ -426,6 +429,28 @@ describe('', () => { ); }); + it('autocompleteHinter toggle, starting at false', () => { + // render the component with autocompleteHinter prop set to false + act(() => { + subject(); + }); + + // get ahold of the radio buttons for toggling autocompleteHinter + const autocompleteRadioFalse = screen.getByRole('radio', { + name: /autocomplete hinter off/i + }); + const autocompleteRadioTrue = screen.getByRole('radio', { + name: /autocomplete hinter on/i + }); + + testToggle( + autocompleteRadioFalse, + autocompleteRadioTrue, + props.setAutocompleteHinter, + true + ); + }); + describe('start autosave value at true', () => { beforeAll(() => { props.autosave = true; @@ -481,6 +506,34 @@ describe('', () => { }); }); + describe('start autocomplete hinter value at true', () => { + beforeAll(() => { + props.autocompleteHinter = true; + }); + + it('autocompleteHinter toggle, starting at true', () => { + // render the component with autocompleteHinter prop set to true + act(() => { + subject(); + }); + + // get ahold of the radio buttons for toggling autocompleteHinter + const autocompleteRadioFalse = screen.getByRole('radio', { + name: /autocomplete hinter off/i + }); + const autocompleteRadioTrue = screen.getByRole('radio', { + name: /autocomplete hinter on/i + }); + + testToggle( + autocompleteRadioTrue, + autocompleteRadioFalse, + props.setAutocompleteHinter, + false + ); + }); + }); + describe('start linewrap at false', () => { beforeAll(() => { props.linewrap = false; @@ -492,7 +545,7 @@ describe('', () => { subject(); }); - // get ahold of the radio buttons for toggling autocloseBracketsQuotes + // get ahold of the radio buttons for toggling linewrap const linewrapRadioFalse = screen.getByRole('radio', { name: /linewrap off/i }); @@ -520,7 +573,7 @@ describe('', () => { subject(); }); - // get ahold of the radio buttons for toggling autocloseBracketsQuotes + // get ahold of the radio buttons for toggling linewrap const linewrapRadioFalse = screen.getByRole('radio', { name: /linewrap off/i }); diff --git a/client/modules/IDE/components/Preferences/index.jsx b/client/modules/IDE/components/Preferences/index.jsx index 090d7aeca3..b72b108cde 100644 --- a/client/modules/IDE/components/Preferences/index.jsx +++ b/client/modules/IDE/components/Preferences/index.jsx @@ -287,6 +287,49 @@ class Preferences extends React.Component { +
    +

    + {this.props.t('Preferences.AutocompleteHinter')} +

    +
    + this.props.setAutocompleteHinter(true)} + aria-label={this.props.t( + 'Preferences.AutocompleteHinterOnARIA' + )} + name="autocompletehinter" + id="autocompletehinter-on" + className="preference__radio-button" + value="On" + checked={this.props.autocompleteHinter} + /> + + this.props.setAutocompleteHinter(false)} + aria-label={this.props.t( + 'Preferences.AutocompleteHinterOffARIA' + )} + name="autocompletehinter" + id="autocompletehinter-off" + className="preference__radio-button" + value="Off" + checked={!this.props.autocompleteHinter} + /> + +
    +

    {this.props.t('Preferences.WordWrap')} @@ -472,6 +515,8 @@ Preferences.propTypes = { setTheme: PropTypes.func.isRequired, autocloseBracketsQuotes: PropTypes.bool.isRequired, setAutocloseBracketsQuotes: PropTypes.func.isRequired, + autocompleteHinter: PropTypes.bool.isRequired, + setAutocompleteHinter: PropTypes.func.isRequired, t: PropTypes.func.isRequired }; diff --git a/client/modules/IDE/components/show-hint.js b/client/modules/IDE/components/show-hint.js new file mode 100644 index 0000000000..ab51a85899 --- /dev/null +++ b/client/modules/IDE/components/show-hint.js @@ -0,0 +1,719 @@ +/* eslint-disable */ + +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +// Modified for p5.js-web-editor + +// declare global: DOMRect + +(function (mod) { + if (typeof exports == 'object' && typeof module == 'object') + // CommonJS + mod(require('codemirror')); + else if (typeof define == 'function' && define.amd) + // AMD + define(['codemirror'], mod); + // Plain browser env + else mod(CodeMirror); +})(function (CodeMirror) { + 'use strict'; + + var HINT_ELEMENT_CLASS = 'CodeMirror-hint'; + var ACTIVE_HINT_ELEMENT_CLASS = 'CodeMirror-hint-active'; + + // This is the old interface, kept around for now to stay + // backwards-compatible. + CodeMirror.showHint = function (cm, getHints, options) { + if (!getHints) return cm.showHint(options); + if (options && options.async) getHints.async = true; + var newOpts = { hint: getHints }; + if (options) for (var prop in options) newOpts[prop] = options[prop]; + return cm.showHint(newOpts); + }; + + CodeMirror.defineExtension('showHint', function (options) { + options = parseOptions(this, this.getCursor('start'), options); + var selections = this.listSelections(); + if (selections.length > 1) return; + // By default, don't allow completion when something is selected. + // A hint function can have a `supportsSelection` property to + // indicate that it can handle selections. + if (this.somethingSelected()) { + if (!options.hint.supportsSelection) return; + // Don't try with cross-line selections + for (var i = 0; i < selections.length; i++) + if (selections[i].head.line != selections[i].anchor.line) return; + } + + if (this.state.completionActive) this.state.completionActive.close(); + var completion = (this.state.completionActive = new Completion( + this, + options + )); + if (!completion.options.hint) return; + + CodeMirror.signal(this, 'startCompletion', this); + completion.update(true); + }); + + CodeMirror.defineExtension('closeHint', function () { + if (this.state.completionActive) this.state.completionActive.close(); + }); + + function Completion(cm, options) { + this.cm = cm; + this.options = options; + this.widget = null; + this.debounce = 0; + this.tick = 0; + this.startPos = this.cm.getCursor('start'); + this.startLen = + this.cm.getLine(this.startPos.line).length - + this.cm.getSelection().length; + + if (this.options.updateOnCursorActivity) { + var self = this; + cm.on( + 'cursorActivity', + (this.activityFunc = function () { + self.cursorActivity(); + }) + ); + } + } + + var requestAnimationFrame = + window.requestAnimationFrame || + function (fn) { + return setTimeout(fn, 1000 / 60); + }; + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; + + Completion.prototype = { + close: function () { + if (!this.active()) return; + this.cm.state.completionActive = null; + this.tick = null; + if (this.options.updateOnCursorActivity) { + this.cm.off('cursorActivity', this.activityFunc); + } + + if (this.widget && this.data) CodeMirror.signal(this.data, 'close'); + if (this.widget) this.widget.close(); + CodeMirror.signal(this.cm, 'endCompletion', this.cm); + }, + + active: function () { + return this.cm.state.completionActive == this; + }, + + pick: function (data, i) { + var completion = data.list[i], + self = this; + this.cm.operation(function () { + if (completion.hint) completion.hint(self.cm, data, completion); + else + self.cm.replaceRange( + getText(completion), + completion.from || data.from, + completion.to || data.to, + 'complete' + ); + CodeMirror.signal(data, 'pick', completion); + self.cm.scrollIntoView(); + }); + if (this.options.closeOnPick) { + this.close(); + } + }, + + cursorActivity: function () { + if (this.debounce) { + cancelAnimationFrame(this.debounce); + this.debounce = 0; + } + + var identStart = this.startPos; + if (this.data) { + identStart = this.data.from; + } + + var pos = this.cm.getCursor(), + line = this.cm.getLine(pos.line); + if ( + pos.line != this.startPos.line || + line.length - pos.ch != this.startLen - this.startPos.ch || + pos.ch < identStart.ch || + this.cm.somethingSelected() || + !pos.ch || + this.options.closeCharacters.test(line.charAt(pos.ch - 1)) + ) { + this.close(); + } else { + var self = this; + this.debounce = requestAnimationFrame(function () { + self.update(); + }); + if (this.widget) this.widget.disable(); + } + }, + + update: function (first) { + if (this.tick == null) return; + var self = this, + myTick = ++this.tick; + fetchHints(this.options.hint, this.cm, this.options, function (data) { + if (self.tick == myTick) self.finishUpdate(data, first); + }); + }, + + finishUpdate: function (data, first) { + if (this.data) CodeMirror.signal(this.data, 'update'); + + var picked = + (this.widget && this.widget.picked) || + (first && this.options.completeSingle); + if (this.widget) this.widget.close(); + + this.data = data; + + if (data && data.list.length) { + if (picked && data.list.length == 1) { + this.pick(data, 0); + } else { + this.widget = new Widget(this, data); + CodeMirror.signal(data, 'shown'); + } + } + } + }; + + function parseOptions(cm, pos, options) { + var editor = cm.options.hintOptions; + var out = {}; + for (var prop in defaultOptions) out[prop] = defaultOptions[prop]; + if (editor) + for (var prop in editor) + if (editor[prop] !== undefined) out[prop] = editor[prop]; + if (options) + for (var prop in options) + if (options[prop] !== undefined) out[prop] = options[prop]; + if (out.hint.resolve) out.hint = out.hint.resolve(cm, pos); + return out; + } + + function getText(completion) { + if (typeof completion === 'string') return completion; + else return completion.item.text; + } + + function buildKeyMap(completion, handle) { + var baseMap = { + Up: function () { + handle.moveFocus(-1); + }, + Down: function () { + handle.moveFocus(1); + }, + PageUp: function () { + handle.moveFocus(-handle.menuSize() + 1, true); + }, + PageDown: function () { + handle.moveFocus(handle.menuSize() - 1, true); + }, + Home: function () { + handle.setFocus(0); + }, + End: function () { + handle.setFocus(handle.length - 1); + }, + Enter: handle.pick, + Tab: handle.pick, + Esc: handle.close + }; + + var mac = /Mac/.test(navigator.platform); + + if (mac) { + baseMap['Ctrl-P'] = function () { + handle.moveFocus(-1); + }; + baseMap['Ctrl-N'] = function () { + handle.moveFocus(1); + }; + } + + var custom = completion.options.customKeys; + var ourMap = custom ? {} : baseMap; + function addBinding(key, val) { + var bound; + if (typeof val != 'string') + bound = function (cm) { + return val(cm, handle); + }; + // This mechanism is deprecated + else if (baseMap.hasOwnProperty(val)) bound = baseMap[val]; + else bound = val; + ourMap[key] = bound; + } + if (custom) + for (var key in custom) + if (custom.hasOwnProperty(key)) addBinding(key, custom[key]); + var extra = completion.options.extraKeys; + if (extra) + for (var key in extra) + if (extra.hasOwnProperty(key)) addBinding(key, extra[key]); + return ourMap; + } + + function getHintElement(hintsElement, el) { + while (el && el != hintsElement) { + if (el.nodeName.toUpperCase() === 'LI' && el.parentNode == hintsElement) + return el; + el = el.parentNode; + } + } + + function displayHint(name, type) { + return `

    \ +${name}\ +, \ +${type}\ +, \ +\ +open ${name} reference\ +

    `; + } + + function Widget(completion, data) { + this.id = 'cm-complete-' + Math.floor(Math.random(1e6)); + this.completion = completion; + this.data = data; + this.picked = false; + var widget = this, + cm = completion.cm; + var ownerDocument = cm.getInputField().ownerDocument; + var parentWindow = ownerDocument.defaultView || ownerDocument.parentWindow; + + var fontSize = completion.options._fontSize; + + var hints = (this.hints = ownerDocument.createElement('ul')); + hints.setAttribute('role', 'listbox'); + hints.setAttribute('aria-expanded', 'true'); + hints.id = this.id; + var theme = completion.cm.options.theme; + hints.className = 'CodeMirror-hints ' + theme; + this.selectedHint = data.selectedHint || 0; + + var completions = data.list; + + for (var i = 0; i < completions.length; ++i) { + var elt = hints.appendChild(ownerDocument.createElement('li')), + cur = completions[i]; + var className = + HINT_ELEMENT_CLASS + + (i != this.selectedHint ? '' : ' ' + ACTIVE_HINT_ELEMENT_CLASS); + if (cur.className != null) className = cur.className + ' ' + className; + elt.className = className; + if (i == this.selectedHint) elt.setAttribute('aria-selected', 'true'); + elt.id = this.id + '-' + i; + elt.setAttribute('role', 'option'); + if (cur.render) cur.render(elt, data, cur); + else { + const e = ownerDocument.createElement('p'); + const name = getText(cur); + + if (cur.item && cur.item.type) { + cur.displayText = displayHint(name, cur.item.type); + } + + elt.appendChild(e); + e.outerHTML = + cur.displayText || `${name}`; + } + elt.hintId = i; + } + + var container = completion.options.container || ownerDocument.body; + var pos = cm.cursorCoords( + completion.options.alignWithWord ? data.from : null + ); + var left = pos.left, + top = pos.bottom, + below = true; + var offsetLeft = 0, + offsetTop = 0; + if (container !== ownerDocument.body) { + // We offset the cursor position because left and top are relative to the offsetParent's top left corner. + var isContainerPositioned = + ['absolute', 'relative', 'fixed'].indexOf( + parentWindow.getComputedStyle(container).position + ) !== -1; + var offsetParent = isContainerPositioned + ? container + : container.offsetParent; + var offsetParentPosition = offsetParent.getBoundingClientRect(); + var bodyPosition = ownerDocument.body.getBoundingClientRect(); + offsetLeft = + offsetParentPosition.left - bodyPosition.left - offsetParent.scrollLeft; + offsetTop = + offsetParentPosition.top - bodyPosition.top - offsetParent.scrollTop; + } + hints.style.left = left - offsetLeft + 'px'; + hints.style.top = top - offsetTop + 'px'; + + // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. + var winW = + parentWindow.innerWidth || + Math.max( + ownerDocument.body.offsetWidth, + ownerDocument.documentElement.offsetWidth + ); + var winH = + parentWindow.innerHeight || + Math.max( + ownerDocument.body.offsetHeight, + ownerDocument.documentElement.offsetHeight + ); + container.appendChild(hints); + cm.getInputField().setAttribute('aria-autocomplete', 'list'); + cm.getInputField().setAttribute('aria-owns', this.id); + cm.getInputField().setAttribute( + 'aria-activedescendant', + this.id + '-' + this.selectedHint + ); + + var box = completion.options.moveOnOverlap + ? hints.getBoundingClientRect() + : new DOMRect(); + var scrolls = completion.options.paddingForScrollbar + ? hints.scrollHeight > hints.clientHeight + 1 + : false; + + // Compute in the timeout to avoid reflow on init + var startScroll; + setTimeout(function () { + startScroll = cm.getScrollInfo(); + }); + + var overlapY = box.bottom - winH; + if (overlapY > 0) { + var height = box.bottom - box.top, + curTop = pos.top - (pos.bottom - box.top); + if (curTop - height > 0) { + // Fits above cursor + hints.style.top = (top = pos.top - height - offsetTop) + 'px'; + below = false; + } else if (height > winH) { + hints.style.height = winH - 5 + 'px'; + hints.style.top = (top = pos.bottom - box.top - offsetTop) + 'px'; + var cursor = cm.getCursor(); + if (data.from.ch != cursor.ch) { + pos = cm.cursorCoords(cursor); + hints.style.left = (left = pos.left - offsetLeft) + 'px'; + box = hints.getBoundingClientRect(); + } + } + } + var overlapX = box.right - winW; + if (scrolls) overlapX += cm.display.nativeBarWidth; + if (overlapX > 0) { + if (box.right - box.left > winW) { + hints.style.width = winW - 5 + 'px'; + overlapX -= box.right - box.left - winW; + } + hints.style.left = (left = pos.left - overlapX - offsetLeft) + 'px'; + } + // if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling) + // node.style.paddingRight = cm.display.nativeBarWidth + "px" + + cm.addKeyMap( + (this.keyMap = buildKeyMap(completion, { + moveFocus: function (n, avoidWrap) { + widget.changeActive(widget.selectedHint + n, avoidWrap); + }, + setFocus: function (n) { + widget.changeActive(n); + }, + menuSize: function () { + return widget.screenAmount(); + }, + length: completions.length, + close: function () { + completion.close(); + }, + pick: function () { + widget.pick(); + }, + data: data + })) + ); + + if (completion.options.closeOnUnfocus) { + var closingOnBlur; + cm.on( + 'blur', + (this.onBlur = function () { + closingOnBlur = setTimeout(function () { + completion.close(); + }, 100); + }) + ); + cm.on( + 'focus', + (this.onFocus = function () { + clearTimeout(closingOnBlur); + }) + ); + } + + cm.on( + 'scroll', + (this.onScroll = function () { + var curScroll = cm.getScrollInfo(), + editor = cm.getWrapperElement().getBoundingClientRect(); + if (!startScroll) startScroll = cm.getScrollInfo(); + var newTop = top + startScroll.top - curScroll.top; + var point = + newTop - + (parentWindow.pageYOffset || + (ownerDocument.documentElement || ownerDocument.body).scrollTop); + if (!below) point += hints.offsetHeight; + if (point <= editor.top || point >= editor.bottom) + return completion.close(); + hints.style.top = newTop + 'px'; + hints.style.left = left + startScroll.left - curScroll.left + 'px'; + }) + ); + + CodeMirror.on(hints, 'dblclick', function (e) { + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) { + widget.changeActive(t.hintId); + widget.pick(); + } + }); + + CodeMirror.on(hints, 'click', function (e) { + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) { + widget.changeActive(t.hintId); + if (completion.options.completeOnSingleClick) widget.pick(); + } + }); + + CodeMirror.on(hints, 'mousedown', function () { + setTimeout(function () { + cm.focus(); + }, 20); + }); + + // The first hint doesn't need to be scrolled to on init + var selectedHintRange = this.getSelectedHintRange(); + if (selectedHintRange.from !== 0 || selectedHintRange.to !== 0) { + this.scrollToActive(); + } + + CodeMirror.signal( + data, + 'select', + completions[this.selectedHint], + hints.childNodes[this.selectedHint] + ); + return true; + } + + Widget.prototype = { + close: function () { + if (this.completion.widget != this) return; + this.completion.widget = null; + if (this.hints.parentNode) this.hints.parentNode.removeChild(this.hints); + this.completion.cm.removeKeyMap(this.keyMap); + var input = this.completion.cm.getInputField(); + input.removeAttribute('aria-activedescendant'); + input.removeAttribute('aria-owns'); + + var cm = this.completion.cm; + if (this.completion.options.closeOnUnfocus) { + cm.off('blur', this.onBlur); + cm.off('focus', this.onFocus); + } + cm.off('scroll', this.onScroll); + }, + + disable: function () { + this.completion.cm.removeKeyMap(this.keyMap); + var widget = this; + this.keyMap = { + Enter: function () { + widget.picked = true; + } + }; + this.completion.cm.addKeyMap(this.keyMap); + }, + + pick: function () { + this.completion.pick(this.data, this.selectedHint); + }, + + changeActive: function (i, avoidWrap) { + if (i >= this.data.list.length) + i = avoidWrap ? this.data.list.length - 1 : 0; + else if (i < 0) i = avoidWrap ? 0 : this.data.list.length - 1; + if (this.selectedHint == i) return; + var node = this.hints.childNodes[this.selectedHint]; + if (node) { + node.className = node.className.replace( + ' ' + ACTIVE_HINT_ELEMENT_CLASS, + '' + ); + node.removeAttribute('aria-selected'); + } + node = this.hints.childNodes[(this.selectedHint = i)]; + node.className += ' ' + ACTIVE_HINT_ELEMENT_CLASS; + node.setAttribute('aria-selected', 'true'); + this.completion.cm + .getInputField() + .setAttribute('aria-activedescendant', node.id); + this.scrollToActive(); + CodeMirror.signal( + this.data, + 'select', + this.data.list[this.selectedHint], + node + ); + }, + + scrollToActive: function () { + var selectedHintRange = this.getSelectedHintRange(); + var node1 = this.hints.childNodes[selectedHintRange.from]; + var node2 = this.hints.childNodes[selectedHintRange.to]; + var firstNode = this.hints.firstChild; + if (node1.offsetTop < this.hints.scrollTop) + this.hints.scrollTop = node1.offsetTop - firstNode.offsetTop; + else if ( + node2.offsetTop + node2.offsetHeight > + this.hints.scrollTop + this.hints.clientHeight + ) + this.hints.scrollTop = + node2.offsetTop + + node2.offsetHeight - + this.hints.clientHeight + + firstNode.offsetTop; + }, + + screenAmount: function () { + return ( + Math.floor( + this.hints.clientHeight / this.hints.firstChild.offsetHeight + ) || 1 + ); + }, + + getSelectedHintRange: function () { + var margin = this.completion.options.scrollMargin || 0; + return { + from: Math.max(0, this.selectedHint - margin), + to: Math.min(this.data.list.length - 1, this.selectedHint + margin) + }; + } + }; + + function applicableHelpers(cm, helpers) { + if (!cm.somethingSelected()) return helpers; + var result = []; + for (var i = 0; i < helpers.length; i++) + if (helpers[i].supportsSelection) result.push(helpers[i]); + return result; + } + + function fetchHints(hint, cm, options, callback) { + if (hint.async) { + hint(cm, callback, options); + } else { + var result = hint(cm, options); + if (result && result.then) result.then(callback); + else callback(result); + } + } + + function resolveAutoHints(cm, pos) { + var helpers = cm.getHelpers(pos, 'hint'), + words; + if (helpers.length) { + var resolved = function (cm, callback, options) { + var app = applicableHelpers(cm, helpers); + function run(i) { + if (i == app.length) return callback(null); + fetchHints(app[i], cm, options, function (result) { + if (result && result.list.length > 0) callback(result); + else run(i + 1); + }); + } + run(0); + }; + resolved.async = true; + resolved.supportsSelection = true; + return resolved; + } else if ((words = cm.getHelper(cm.getCursor(), 'hintWords'))) { + return function (cm) { + return CodeMirror.hint.fromList(cm, { words: words }); + }; + } else if (CodeMirror.hint.anyword) { + return function (cm, options) { + return CodeMirror.hint.anyword(cm, options); + }; + } else { + return function () {}; + } + } + + CodeMirror.registerHelper('hint', 'auto', { + resolve: resolveAutoHints + }); + + CodeMirror.registerHelper('hint', 'fromList', function (cm, options) { + var cur = cm.getCursor(), + token = cm.getTokenAt(cur); + var term, + from = CodeMirror.Pos(cur.line, token.start), + to = cur; + if ( + token.start < cur.ch && + /\w/.test(token.string.charAt(cur.ch - token.start - 1)) + ) { + term = token.string.substr(0, cur.ch - token.start); + } else { + term = ''; + from = cur; + } + var found = []; + for (var i = 0; i < options.words.length; i++) { + var word = options.words[i]; + if (word.slice(0, term.length) == term) found.push(word); + } + + if (found.length) return { list: found, from: from, to: to }; + }); + + CodeMirror.commands.autocomplete = CodeMirror.showHint; + + var defaultOptions = { + hint: CodeMirror.hint.auto, + completeSingle: true, + alignWithWord: true, + closeCharacters: /[\s()\[\]{};:>,]/, + closeOnPick: true, + closeOnUnfocus: true, + updateOnCursorActivity: true, + completeOnSingleClick: true, + container: null, + customKeys: null, + extraKeys: null, + paddingForScrollbar: true, + moveOnOverlap: true + }; + + CodeMirror.defineOption('hintOptions', null); +}); diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index 3c323fe88c..5158e33c44 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -290,6 +290,8 @@ class IDEView extends React.Component { this.props.preferences.autocloseBracketsQuotes } setAutocloseBracketsQuotes={this.props.setAutocloseBracketsQuotes} + autocompleteHinter={this.props.preferences.autocompleteHinter} + setAutocompleteHinter={this.props.setAutocompleteHinter} /> )} @@ -510,10 +512,12 @@ IDEView.propTypes = { theme: PropTypes.string.isRequired, autorefresh: PropTypes.bool.isRequired, language: PropTypes.string.isRequired, - autocloseBracketsQuotes: PropTypes.bool.isRequired + autocloseBracketsQuotes: PropTypes.bool.isRequired, + autocompleteHinter: PropTypes.bool.isRequired }).isRequired, closePreferences: PropTypes.func.isRequired, setAutocloseBracketsQuotes: PropTypes.func.isRequired, + setAutocompleteHinter: PropTypes.func.isRequired, setFontSize: PropTypes.func.isRequired, setAutosave: PropTypes.func.isRequired, setLineNumbers: PropTypes.func.isRequired, diff --git a/client/modules/IDE/reducers/preferences.js b/client/modules/IDE/reducers/preferences.js index f308ffe524..7fe1163a91 100644 --- a/client/modules/IDE/reducers/preferences.js +++ b/client/modules/IDE/reducers/preferences.js @@ -11,7 +11,8 @@ const initialState = { theme: 'light', autorefresh: false, language: 'en-US', - autocloseBracketsQuotes: true + autocloseBracketsQuotes: true, + autocompleteHinter: true }; const preferences = (state = initialState, action) => { @@ -42,6 +43,10 @@ const preferences = (state = initialState, action) => { return Object.assign({}, state, { autocloseBracketsQuotes: action.value }); + case ActionTypes.SET_AUTOCOMPLETE_HINTER: + return Object.assign({}, state, { + autocompleteHinter: action.value + }); default: return state; } diff --git a/client/modules/Mobile/MobilePreferences.jsx b/client/modules/Mobile/MobilePreferences.jsx index 307145f3a2..5dcb115092 100644 --- a/client/modules/Mobile/MobilePreferences.jsx +++ b/client/modules/Mobile/MobilePreferences.jsx @@ -40,6 +40,7 @@ const MobilePreferences = () => { theme, autosave, linewrap, + autocompleteHinter, textOutput, gridOutput, lineNumbers, @@ -51,6 +52,7 @@ const MobilePreferences = () => { setTheme, setAutosave, setLinewrap, + setAutocompleteHinter, setTextOutput, setGridOutput, setLineNumbers, @@ -80,6 +82,12 @@ const MobilePreferences = () => { setAutosave, 'autosave' ), + preferenceOnOff( + t('MobilePreferences.AutocompleteHinter'), + autocompleteHinter, + setAutocompleteHinter, + 'autocompleteHinter' + ), preferenceOnOff( t('MobilePreferences.WordWrap'), linewrap, diff --git a/client/styles/abstracts/_variables.scss b/client/styles/abstracts/_variables.scss index b475f21b38..26da9d6530 100644 --- a/client/styles/abstracts/_variables.scss +++ b/client/styles/abstracts/_variables.scss @@ -116,7 +116,26 @@ $themes: ( form-secondary-title-color: $medium-dark, form-input-text-color: $dark, form-input-placeholder-text-color: $middle-light, - form-navigation-options-color: $middle-dark + form-navigation-options-color: $middle-dark, + + hint-background-color: $white, + hint-text-color: $dark, + hint-item-border-bottom-color: $white, + hint-fun-text-color: #0B7CA9, + hint-var-text-color: #D52889, + hint-type-text-color: $medium-dark, + hint-arrow-color: $lightest, + hint-arrow-background-color: #ed225ddd, + hint-arrow-background-active-color: $p5js-active-pink, + hint-arrow-focus-outline-color: $middle-dark, + hint-item-hover-background-color: #f4f4f4, + hint-item-active-text-color: $white, + hint-item-active-background-color: $middle-gray, + hint-fun-active-border-bottom-color: #0B7CA9, + hint-var-active-border-bottom-color: #D52889, + hint-item-active-type-text-color: $white, + hint-item-active-outline: none, + hint-item-active-outline-offset: 0 ), dark: ( logo-color: $p5js-pink, @@ -196,7 +215,26 @@ $themes: ( form-title-color: $lightest, form-secondary-title-color: $medium-light, - form-navigation-options-color: $middle-light + form-navigation-options-color: $middle-light, + + hint-background-color: $darker, + hint-text-color: $light, + hint-item-border-bottom-color: $darker, + hint-fun-text-color: #0F9DD7, + hint-var-text-color: #DE4A9B, + hint-type-text-color: $light, + hint-arrow-color: $lightest, + hint-arrow-background-color: #ed225ddd, + hint-arrow-background-active-color: $p5js-active-pink, + hint-arrow-focus-outline-color: #cfcfcf, + hint-item-hover-background-color: $medium-dark, + hint-item-active-text-color: $darker, + hint-item-active-background-color: #cfcfcf, + hint-fun-active-border-bottom-color: #0F9DD7, + hint-var-active-border-bottom-color: #DE4A9B, + hint-item-active-type-text-color: $darker, + hint-item-active-outline: none, + hint-item-active-outline-offset: 0 ), contrast: ( logo-color: $yellow, @@ -276,7 +314,26 @@ $themes: ( form-title-color: $lightest, form-secondary-title-color: $medium-light, - form-navigation-options-color: $middle-light + form-navigation-options-color: $middle-light, + + hint-background-color: $darkest, + hint-text-color: $medium-light, + hint-item-border-bottom-color: $medium-dark, + hint-fun-text-color: #00FFFF, + hint-var-text-color: #FFA9D9, + hint-type-text-color: $middle-light, + hint-arrow-color: $darker, + hint-arrow-background-color: #F5DC23DD, + hint-arrow-background-active-color: #F5DC23, + hint-arrow-focus-outline-color: $lighter, + hint-item-hover-background-color: $dark, + hint-item-active-text-color: $lighter, + hint-item-active-background-color: unset, + hint-fun-active-border-bottom-color: none, + hint-var-active-border-bottom-color: none, + hint-item-active-type-text-color: $lighter, + hint-item-active-outline: 2px solid $lighter, + hint-item-active-outline-offset: -2px ) ); diff --git a/client/styles/components/_hints.scss b/client/styles/components/_hints.scss new file mode 100644 index 0000000000..1eabbcfeef --- /dev/null +++ b/client/styles/components/_hints.scss @@ -0,0 +1,162 @@ +.CodeMirror-hints { + position: absolute; + z-index: 10; + overflow: hidden; + list-style: none; + + margin: 0; + padding: 0; + + box-shadow: 0 0 #{18 / $base-font-size}rem 0 rgba(0, 0, 0, 0.16); + border: #{1 / $base-font-size}rem solid #A6A6A6; + + font-size: 100%; + font-family: Inconsolata, monospace; + + width: 18rem; + max-height: 20rem; + overflow-y: auto; + + transform-origin: top left; + + @include themify() { + background: getThemifyVariable('hint-background-color'); + + .CodeMirror-hint { + color: getThemifyVariable('hint-text-color'); + border-bottom: #{1 / $base-font-size}rem solid getThemifyVariable('hint-item-border-bottom-color'); + } + + .hint-name { + height: 100%; + } + + .fun-name { + color: getThemifyVariable('hint-fun-text-color'); + } + + .var-name { + color: getThemifyVariable('hint-var-text-color'); + } + + .hint-type { + color: getThemifyVariable('hint-type-text-color'); + margin-right: #{10 / $base-font-size}rem; + } + + a { + color: getThemifyVariable('hint-arrow-color'); + background: getThemifyVariable('hint-arrow-background-color'); + + &:hover, &:active, &.focused-hint-link { + background: getThemifyVariable('hint-arrow-background-active-color'); + } + + &.focused-hint-link { + outline: #{3 / $base-font-size}rem solid getThemifyVariable('hint-arrow-focus-outline-color'); + outline-offset: #{-3 / $base-font-size}rem; + } + } + + li.CodeMirror-hint-active:not(.unfocused) { + background: getThemifyVariable('hint-item-active-background-color'); + outline: getThemifyVariable('hint-item-active-outline'); + outline-offset: getThemifyVariable('hint-item-active-outline-offset'); + + // .fun-item { + // border-bottom: #{2 / $base-font-size}rem solid getThemifyVariable('hint-fun-active-border-bottom-color'); + // } + + // .var-item { + // border-bottom: #{2 / $base-font-size}rem solid getThemifyVariable('hint-var-active-border-bottom-color'); + // } + + .hint-name { + color: getThemifyVariable('hint-item-active-text-color'); + } + + .fun-name { + background-color: getThemifyVariable('hint-fun-text-color'); + } + + .var-name { + background-color: getThemifyVariable('hint-var-text-color'); + } + + .hint-type, .plain-hint-item { + color: getThemifyVariable('hint-item-active-type-text-color'); + } + } + + .CodeMirror-hint:hover:not(.CodeMirror-hint-active) { + background: getThemifyVariable('hint-item-hover-background-color'); + } + } + + .CodeMirror-hint { + display: flex; + align-items: center; + justify-content: space-between; + + position: relative; + margin: 0; + padding: 0; + height: 2rem; + white-space: pre; + cursor: pointer; + + &:has(.focused-hint-link) { + z-index: 999; + } + + &:only-child, &:last-child { + border-bottom: none !important; + } + + p { + display: flex; + width: 100%; + height: 100%; + } + + .hint-name, .plain-hint-item { + display: flex; + align-items: center; + padding: 0 0.5rem; + width: min-content; + font-size: 1.2rem; + line-height: 100%; + font-weight: bold; + } + + .hint-type { + margin: 0.5rem 2.4rem 0.5rem auto; + font-size: 1rem; + line-height: 100%; + font-weight: normal; + } + + .hint-hidden { + @extend %hidden-element; + } + + a { + position: absolute; + top: 0; + right: 0; + height: 100%; + width: calc(2rem - #{1 / $base-font-size}rem); + margin: 0; + padding-top: 0.4rem; + font-size: 1.2rem; + line-height: 100%; + text-align: center; + outline: none; + z-index: 1; + } + + a:focus, a:active { + outline: 0; + } + } +} diff --git a/client/styles/main.scss b/client/styles/main.scss index 61397cbc32..4d1a08058f 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -18,6 +18,7 @@ @import 'components/account'; @import 'components/api-key'; @import 'components/editor'; +@import 'components/hints'; @import 'components/nav'; @import 'components/preview-nav'; @import 'components/toolbar'; diff --git a/client/testData/testReduxStore.js b/client/testData/testReduxStore.js index c87a4e5973..5e475df75e 100644 --- a/client/testData/testReduxStore.js +++ b/client/testData/testReduxStore.js @@ -95,7 +95,8 @@ const initialTestState = { theme: 'light', autorefresh: false, language: 'en-US', - autocloseBracketsQuotes: true + autocloseBracketsQuotes: true, + autocompleteHinter: true }, user: { email: 'happydog@example.com', diff --git a/client/testData/testServerResponses.js b/client/testData/testServerResponses.js index e6c0f875f1..abf47baad9 100644 --- a/client/testData/testServerResponses.js +++ b/client/testData/testServerResponses.js @@ -18,7 +18,8 @@ export const userResponse = { theme: 'light', autorefresh: false, language: 'en-US', - autocloseBracketsQuotes: true + autocloseBracketsQuotes: true, + autocompleteHinter: true }, apiKeys: [], verified: 'verified', diff --git a/client/utils/p5-hinter.js b/client/utils/p5-hinter.js new file mode 100644 index 0000000000..62110ba879 --- /dev/null +++ b/client/utils/p5-hinter.js @@ -0,0 +1,3 @@ +/* eslint-disable */ +/* generated: do not edit! helper file for hinter. generated by update-p5-hinter script */ +exports.p5Hinter = [{"text":"describe","type":"fun"},{"text":"describeElement","type":"fun"},{"text":"textOutput","type":"fun"},{"text":"gridOutput","type":"fun"},{"text":"alpha","type":"fun"},{"text":"blue","type":"fun"},{"text":"brightness","type":"fun"},{"text":"color","type":"fun"},{"text":"green","type":"fun"},{"text":"hue","type":"fun"},{"text":"lerpColor","type":"fun"},{"text":"lightness","type":"fun"},{"text":"red","type":"fun"},{"text":"saturation","type":"fun"},{"text":"background","type":"fun"},{"text":"clear","type":"fun"},{"text":"colorMode","type":"fun"},{"text":"fill","type":"fun"},{"text":"noFill","type":"fun"},{"text":"noStroke","type":"fun"},{"text":"stroke","type":"fun"},{"text":"erase","type":"fun"},{"text":"noErase","type":"fun"},{"text":"arc","type":"fun"},{"text":"ellipse","type":"fun"},{"text":"circle","type":"fun"},{"text":"line","type":"fun"},{"text":"point","type":"fun"},{"text":"quad","type":"fun"},{"text":"rect","type":"fun"},{"text":"square","type":"fun"},{"text":"triangle","type":"fun"},{"text":"ellipseMode","type":"fun"},{"text":"noSmooth","type":"fun"},{"text":"rectMode","type":"fun"},{"text":"smooth","type":"fun"},{"text":"strokeCap","type":"fun"},{"text":"strokeJoin","type":"fun"},{"text":"strokeWeight","type":"fun"},{"text":"bezier","type":"fun"},{"text":"bezierDetail","type":"fun"},{"text":"bezierPoint","type":"fun"},{"text":"bezierTangent","type":"fun"},{"text":"curve","type":"fun"},{"text":"curveDetail","type":"fun"},{"text":"curveTightness","type":"fun"},{"text":"curvePoint","type":"fun"},{"text":"curveTangent","type":"fun"},{"text":"beginContour","type":"fun"},{"text":"beginShape","type":"fun"},{"text":"bezierVertex","type":"fun"},{"text":"curveVertex","type":"fun"},{"text":"endContour","type":"fun"},{"text":"endShape","type":"fun"},{"text":"quadraticVertex","type":"fun"},{"text":"vertex","type":"fun"},{"text":"normal","type":"fun"},{"text":"VERSION","type":"var"},{"text":"P2D","type":"var"},{"text":"WEBGL","type":"var"},{"text":"ARROW","type":"var"},{"text":"CROSS","type":"var"},{"text":"HAND","type":"var"},{"text":"MOVE","type":"var"},{"text":"TEXT","type":"var"},{"text":"WAIT","type":"var"},{"text":"HALF_PI","type":"var"},{"text":"PI","type":"var"},{"text":"QUARTER_PI","type":"var"},{"text":"TAU","type":"var"},{"text":"TWO_PI","type":"var"},{"text":"DEGREES","type":"var"},{"text":"RADIANS","type":"var"},{"text":"CORNER","type":"var"},{"text":"CORNERS","type":"var"},{"text":"RADIUS","type":"var"},{"text":"RIGHT","type":"var"},{"text":"LEFT","type":"var"},{"text":"CENTER","type":"var"},{"text":"TOP","type":"var"},{"text":"BOTTOM","type":"var"},{"text":"BASELINE","type":"var"},{"text":"POINTS","type":"var"},{"text":"LINES","type":"var"},{"text":"LINE_STRIP","type":"var"},{"text":"LINE_LOOP","type":"var"},{"text":"TRIANGLES","type":"var"},{"text":"TRIANGLE_FAN","type":"var"},{"text":"TRIANGLE_STRIP","type":"var"},{"text":"QUADS","type":"var"},{"text":"QUAD_STRIP","type":"var"},{"text":"TESS","type":"var"},{"text":"CLOSE","type":"var"},{"text":"OPEN","type":"var"},{"text":"CHORD","type":"var"},{"text":"PIE","type":"var"},{"text":"PROJECT","type":"var"},{"text":"SQUARE","type":"var"},{"text":"ROUND","type":"var"},{"text":"BEVEL","type":"var"},{"text":"MITER","type":"var"},{"text":"RGB","type":"var"},{"text":"HSB","type":"var"},{"text":"HSL","type":"var"},{"text":"AUTO","type":"var"},{"text":"ALT","type":"var"},{"text":"BACKSPACE","type":"var"},{"text":"CONTROL","type":"var"},{"text":"DELETE","type":"var"},{"text":"DOWN_ARROW","type":"var"},{"text":"ENTER","type":"var"},{"text":"ESCAPE","type":"var"},{"text":"LEFT_ARROW","type":"var"},{"text":"OPTION","type":"var"},{"text":"RETURN","type":"var"},{"text":"RIGHT_ARROW","type":"var"},{"text":"SHIFT","type":"var"},{"text":"TAB","type":"var"},{"text":"UP_ARROW","type":"var"},{"text":"BLEND","type":"var"},{"text":"REMOVE","type":"var"},{"text":"ADD","type":"var"},{"text":"DARKEST","type":"var"},{"text":"LIGHTEST","type":"var"},{"text":"DIFFERENCE","type":"var"},{"text":"SUBTRACT","type":"var"},{"text":"EXCLUSION","type":"var"},{"text":"MULTIPLY","type":"var"},{"text":"SCREEN","type":"var"},{"text":"REPLACE","type":"var"},{"text":"OVERLAY","type":"var"},{"text":"HARD_LIGHT","type":"var"},{"text":"SOFT_LIGHT","type":"var"},{"text":"DODGE","type":"var"},{"text":"BURN","type":"var"},{"text":"THRESHOLD","type":"var"},{"text":"GRAY","type":"var"},{"text":"OPAQUE","type":"var"},{"text":"INVERT","type":"var"},{"text":"POSTERIZE","type":"var"},{"text":"DILATE","type":"var"},{"text":"ERODE","type":"var"},{"text":"BLUR","type":"var"},{"text":"NORMAL","type":"var"},{"text":"ITALIC","type":"var"},{"text":"BOLD","type":"var"},{"text":"BOLDITALIC","type":"var"},{"text":"CHAR","type":"var"},{"text":"WORD","type":"var"},{"text":"LINEAR","type":"var"},{"text":"QUADRATIC","type":"var"},{"text":"BEZIER","type":"var"},{"text":"CURVE","type":"var"},{"text":"STROKE","type":"var"},{"text":"FILL","type":"var"},{"text":"TEXTURE","type":"var"},{"text":"IMMEDIATE","type":"var"},{"text":"IMAGE","type":"var"},{"text":"NEAREST","type":"var"},{"text":"REPEAT","type":"var"},{"text":"CLAMP","type":"var"},{"text":"MIRROR","type":"var"},{"text":"LANDSCAPE","type":"var"},{"text":"PORTRAIT","type":"var"},{"text":"GRID","type":"var"},{"text":"AXES","type":"var"},{"text":"LABEL","type":"var"},{"text":"FALLBACK","type":"var"},{"text":"CONTAIN","type":"var"},{"text":"COVER","type":"var"},{"text":"print","type":"fun"},{"text":"frameCount","type":"var"},{"text":"deltaTime","type":"var"},{"text":"focused","type":"var"},{"text":"cursor","type":"fun"},{"text":"frameRate","type":"fun"},{"text":"getTargetFrameRate","type":"fun"},{"text":"noCursor","type":"fun"},{"text":"displayWidth","type":"var"},{"text":"displayHeight","type":"var"},{"text":"windowWidth","type":"var"},{"text":"windowHeight","type":"var"},{"text":"windowResized","type":"fun"},{"text":"width","type":"var"},{"text":"height","type":"var"},{"text":"fullscreen","type":"fun"},{"text":"pixelDensity","type":"fun"},{"text":"displayDensity","type":"fun"},{"text":"getURL","type":"fun"},{"text":"getURLPath","type":"fun"},{"text":"getURLParams","type":"fun"},{"text":"preload","type":"fun"},{"text":"setup","type":"fun"},{"text":"draw","type":"fun"},{"text":"remove","type":"fun"},{"text":"disableFriendlyErrors","type":"var"},{"text":"createCanvas","type":"fun"},{"text":"resizeCanvas","type":"fun"},{"text":"noCanvas","type":"fun"},{"text":"createGraphics","type":"fun"},{"text":"blendMode","type":"fun"},{"text":"drawingContext","type":"var"},{"text":"noLoop","type":"fun"},{"text":"loop","type":"fun"},{"text":"isLooping","type":"fun"},{"text":"push","type":"fun"},{"text":"pop","type":"fun"},{"text":"redraw","type":"fun"},{"text":"p5","type":"fun"},{"text":"applyMatrix","type":"fun"},{"text":"resetMatrix","type":"fun"},{"text":"rotate","type":"fun"},{"text":"rotateX","type":"fun"},{"text":"rotateY","type":"fun"},{"text":"rotateZ","type":"fun"},{"text":"scale","type":"fun"},{"text":"shearX","type":"fun"},{"text":"shearY","type":"fun"},{"text":"translate","type":"fun"},{"text":"storeItem","type":"fun"},{"text":"getItem","type":"fun"},{"text":"clearStorage","type":"fun"},{"text":"removeItem","type":"fun"},{"text":"createStringDict","type":"fun"},{"text":"createNumberDict","type":"fun"},{"text":"select","type":"fun"},{"text":"selectAll","type":"fun"},{"text":"removeElements","type":"fun"},{"text":"changed","type":"fun"},{"text":"input","type":"fun"},{"text":"createDiv","type":"fun"},{"text":"createP","type":"fun"},{"text":"createSpan","type":"fun"},{"text":"createImg","type":"fun"},{"text":"createA","type":"fun"},{"text":"createSlider","type":"fun"},{"text":"createButton","type":"fun"},{"text":"createCheckbox","type":"fun"},{"text":"createSelect","type":"fun"},{"text":"createRadio","type":"fun"},{"text":"createColorPicker","type":"fun"},{"text":"createInput","type":"fun"},{"text":"createFileInput","type":"fun"},{"text":"createVideo","type":"fun"},{"text":"createAudio","type":"fun"},{"text":"createCapture","type":"fun"},{"text":"createElement","type":"fun"},{"text":"deviceOrientation","type":"var"},{"text":"accelerationX","type":"var"},{"text":"accelerationY","type":"var"},{"text":"accelerationZ","type":"var"},{"text":"pAccelerationX","type":"var"},{"text":"pAccelerationY","type":"var"},{"text":"pAccelerationZ","type":"var"},{"text":"rotationX","type":"var"},{"text":"rotationY","type":"var"},{"text":"rotationZ","type":"var"},{"text":"pRotationX","type":"var"},{"text":"pRotationY","type":"var"},{"text":"pRotationZ","type":"var"},{"text":"turnAxis","type":"var"},{"text":"setMoveThreshold","type":"fun"},{"text":"setShakeThreshold","type":"fun"},{"text":"deviceMoved","type":"fun"},{"text":"deviceTurned","type":"fun"},{"text":"deviceShaken","type":"fun"},{"text":"keyIsPressed","type":"var"},{"text":"key","type":"var"},{"text":"keyCode","type":"var"},{"text":"keyPressed","type":"fun"},{"text":"keyReleased","type":"fun"},{"text":"keyTyped","type":"fun"},{"text":"keyIsDown","type":"fun"},{"text":"movedX","type":"var"},{"text":"movedY","type":"var"},{"text":"mouseX","type":"var"},{"text":"mouseY","type":"var"},{"text":"pmouseX","type":"var"},{"text":"pmouseY","type":"var"},{"text":"winMouseX","type":"var"},{"text":"winMouseY","type":"var"},{"text":"pwinMouseX","type":"var"},{"text":"pwinMouseY","type":"var"},{"text":"mouseButton","type":"var"},{"text":"mouseIsPressed","type":"var"},{"text":"mouseMoved","type":"fun"},{"text":"mouseDragged","type":"fun"},{"text":"mousePressed","type":"fun"},{"text":"mouseReleased","type":"fun"},{"text":"mouseClicked","type":"fun"},{"text":"doubleClicked","type":"fun"},{"text":"mouseWheel","type":"fun"},{"text":"requestPointerLock","type":"fun"},{"text":"exitPointerLock","type":"fun"},{"text":"touches","type":"var"},{"text":"touchStarted","type":"fun"},{"text":"touchMoved","type":"fun"},{"text":"touchEnded","type":"fun"},{"text":"createImage","type":"fun"},{"text":"saveCanvas","type":"fun"},{"text":"saveFrames","type":"fun"},{"text":"loadImage","type":"fun"},{"text":"saveGif","type":"fun"},{"text":"image","type":"fun"},{"text":"tint","type":"fun"},{"text":"noTint","type":"fun"},{"text":"imageMode","type":"fun"},{"text":"pixels","type":"var"},{"text":"blend","type":"fun"},{"text":"copy","type":"fun"},{"text":"filter","type":"fun"},{"text":"get","type":"fun"},{"text":"loadPixels","type":"fun"},{"text":"set","type":"fun"},{"text":"updatePixels","type":"fun"},{"text":"loadJSON","type":"fun"},{"text":"loadStrings","type":"fun"},{"text":"loadTable","type":"fun"},{"text":"loadXML","type":"fun"},{"text":"loadBytes","type":"fun"},{"text":"httpGet","type":"fun"},{"text":"httpPost","type":"fun"},{"text":"httpDo","type":"fun"},{"text":"createWriter","type":"fun"},{"text":"save","type":"fun"},{"text":"saveJSON","type":"fun"},{"text":"saveStrings","type":"fun"},{"text":"saveTable","type":"fun"},{"text":"abs","type":"fun"},{"text":"ceil","type":"fun"},{"text":"constrain","type":"fun"},{"text":"dist","type":"fun"},{"text":"exp","type":"fun"},{"text":"floor","type":"fun"},{"text":"lerp","type":"fun"},{"text":"log","type":"fun"},{"text":"mag","type":"fun"},{"text":"map","type":"fun"},{"text":"max","type":"fun"},{"text":"min","type":"fun"},{"text":"norm","type":"fun"},{"text":"pow","type":"fun"},{"text":"round","type":"fun"},{"text":"sq","type":"fun"},{"text":"sqrt","type":"fun"},{"text":"fract","type":"fun"},{"text":"createVector","type":"fun"},{"text":"noise","type":"fun"},{"text":"noiseDetail","type":"fun"},{"text":"noiseSeed","type":"fun"},{"text":"randomSeed","type":"fun"},{"text":"random","type":"fun"},{"text":"randomGaussian","type":"fun"},{"text":"acos","type":"fun"},{"text":"asin","type":"fun"},{"text":"atan","type":"fun"},{"text":"atan2","type":"fun"},{"text":"cos","type":"fun"},{"text":"sin","type":"fun"},{"text":"tan","type":"fun"},{"text":"degrees","type":"fun"},{"text":"radians","type":"fun"},{"text":"angleMode","type":"fun"},{"text":"textAlign","type":"fun"},{"text":"textLeading","type":"fun"},{"text":"textSize","type":"fun"},{"text":"textStyle","type":"fun"},{"text":"textWidth","type":"fun"},{"text":"textAscent","type":"fun"},{"text":"textDescent","type":"fun"},{"text":"textWrap","type":"fun"},{"text":"loadFont","type":"fun"},{"text":"text","type":"fun"},{"text":"textFont","type":"fun"},{"text":"append","type":"fun"},{"text":"arrayCopy","type":"fun"},{"text":"concat","type":"fun"},{"text":"reverse","type":"fun"},{"text":"shorten","type":"fun"},{"text":"shuffle","type":"fun"},{"text":"sort","type":"fun"},{"text":"splice","type":"fun"},{"text":"subset","type":"fun"},{"text":"float","type":"fun"},{"text":"int","type":"fun"},{"text":"str","type":"fun"},{"text":"boolean","type":"fun"},{"text":"byte","type":"fun"},{"text":"char","type":"fun"},{"text":"unchar","type":"fun"},{"text":"hex","type":"fun"},{"text":"unhex","type":"fun"},{"text":"join","type":"fun"},{"text":"match","type":"fun"},{"text":"matchAll","type":"fun"},{"text":"nf","type":"fun"},{"text":"nfc","type":"fun"},{"text":"nfp","type":"fun"},{"text":"nfs","type":"fun"},{"text":"split","type":"fun"},{"text":"splitTokens","type":"fun"},{"text":"trim","type":"fun"},{"text":"day","type":"fun"},{"text":"hour","type":"fun"},{"text":"minute","type":"fun"},{"text":"millis","type":"fun"},{"text":"month","type":"fun"},{"text":"second","type":"fun"},{"text":"year","type":"fun"},{"text":"plane","type":"fun"},{"text":"box","type":"fun"},{"text":"sphere","type":"fun"},{"text":"cylinder","type":"fun"},{"text":"cone","type":"fun"},{"text":"ellipsoid","type":"fun"},{"text":"torus","type":"fun"},{"text":"orbitControl","type":"fun"},{"text":"debugMode","type":"fun"},{"text":"noDebugMode","type":"fun"},{"text":"ambientLight","type":"fun"},{"text":"specularColor","type":"fun"},{"text":"directionalLight","type":"fun"},{"text":"pointLight","type":"fun"},{"text":"lights","type":"fun"},{"text":"lightFalloff","type":"fun"},{"text":"spotLight","type":"fun"},{"text":"noLights","type":"fun"},{"text":"loadModel","type":"fun"},{"text":"model","type":"fun"},{"text":"loadShader","type":"fun"},{"text":"createShader","type":"fun"},{"text":"shader","type":"fun"},{"text":"resetShader","type":"fun"},{"text":"texture","type":"fun"},{"text":"textureMode","type":"fun"},{"text":"textureWrap","type":"fun"},{"text":"normalMaterial","type":"fun"},{"text":"ambientMaterial","type":"fun"},{"text":"emissiveMaterial","type":"fun"},{"text":"specularMaterial","type":"fun"},{"text":"shininess","type":"fun"},{"text":"camera","type":"fun"},{"text":"perspective","type":"fun"},{"text":"ortho","type":"fun"},{"text":"frustum","type":"fun"},{"text":"createCamera","type":"fun"},{"text":"setCamera","type":"fun"},{"text":"setAttributes","type":"fun"},{"text":"getAudioContext","type":"fun"},{"text":"userStartAudio","type":"fun"},{"text":"getOutputVolume","type":"fun"},{"text":"outputVolume","type":"fun"},{"text":"soundOut","type":"var"},{"text":"sampleRate","type":"fun"},{"text":"freqToMidi","type":"fun"},{"text":"midiToFreq","type":"fun"},{"text":"soundFormats","type":"fun"},{"text":"saveSound","type":"fun"},{"text":"loadSound","type":"fun"},{"text":"createConvolver","type":"fun"},{"text":"setBPM","type":"fun"}]; diff --git a/client/utils/p5-keywords.js b/client/utils/p5-keywords.js index f25aab2841..16db750a3a 100644 --- a/client/utils/p5-keywords.js +++ b/client/utils/p5-keywords.js @@ -2,7 +2,7 @@ /* generated: do not edit! helper file for syntax highlighting. generated by update-syntax-highlighting script */ var p5Function = {type: "variable", style: "p5-function"}; var p5Variable = {type: "variable", style: "p5-variable"}; -let p5VariableKeywords = {"P2D":p5Variable,"WEBGL":p5Variable,"ARROW":p5Variable,"CROSS":p5Variable,"HAND":p5Variable,"MOVE":p5Variable,"TEXT":p5Variable,"WAIT":p5Variable,"HALF_PI":p5Variable,"PI":p5Variable,"QUARTER_PI":p5Variable,"TAU":p5Variable,"TWO_PI":p5Variable,"DEGREES":p5Variable,"RADIANS":p5Variable,"CORNER":p5Variable,"CORNERS":p5Variable,"RADIUS":p5Variable,"RIGHT":p5Variable,"LEFT":p5Variable,"CENTER":p5Variable,"TOP":p5Variable,"BOTTOM":p5Variable,"BASELINE":p5Variable,"POINTS":p5Variable,"LINES":p5Variable,"LINE_STRIP":p5Variable,"LINE_LOOP":p5Variable,"TRIANGLES":p5Variable,"TRIANGLE_FAN":p5Variable,"TRIANGLE_STRIP":p5Variable,"QUADS":p5Variable,"QUAD_STRIP":p5Variable,"TESS":p5Variable,"CLOSE":p5Variable,"OPEN":p5Variable,"CHORD":p5Variable,"PIE":p5Variable,"PROJECT":p5Variable,"SQUARE":p5Variable,"ROUND":p5Variable,"BEVEL":p5Variable,"MITER":p5Variable,"RGB":p5Variable,"HSB":p5Variable,"HSL":p5Variable,"AUTO":p5Variable,"ALT":p5Variable,"BACKSPACE":p5Variable,"CONTROL":p5Variable,"DELETE":p5Variable,"DOWN_ARROW":p5Variable,"ENTER":p5Variable,"ESCAPE":p5Variable,"LEFT_ARROW":p5Variable,"OPTION":p5Variable,"RETURN":p5Variable,"RIGHT_ARROW":p5Variable,"SHIFT":p5Variable,"TAB":p5Variable,"UP_ARROW":p5Variable,"BLEND":p5Variable,"REMOVE":p5Variable,"ADD":p5Variable,"DARKEST":p5Variable,"LIGHTEST":p5Variable,"DIFFERENCE":p5Variable,"SUBTRACT":p5Variable,"EXCLUSION":p5Variable,"MULTIPLY":p5Variable,"SCREEN":p5Variable,"REPLACE":p5Variable,"OVERLAY":p5Variable,"HARD_LIGHT":p5Variable,"SOFT_LIGHT":p5Variable,"DODGE":p5Variable,"BURN":p5Variable,"THRESHOLD":p5Variable,"GRAY":p5Variable,"OPAQUE":p5Variable,"INVERT":p5Variable,"POSTERIZE":p5Variable,"DILATE":p5Variable,"ERODE":p5Variable,"BLUR":p5Variable,"NORMAL":p5Variable,"ITALIC":p5Variable,"BOLD":p5Variable,"BOLDITALIC":p5Variable,"LINEAR":p5Variable,"QUADRATIC":p5Variable,"BEZIER":p5Variable,"CURVE":p5Variable,"STROKE":p5Variable,"FILL":p5Variable,"TEXTURE":p5Variable,"IMMEDIATE":p5Variable,"IMAGE":p5Variable,"NEAREST":p5Variable,"REPEAT":p5Variable,"CLAMP":p5Variable,"MIRROR":p5Variable,"LANDSCAPE":p5Variable,"PORTRAIT":p5Variable,"GRID":p5Variable,"AXES":p5Variable,"frameCount":p5Variable,"deltaTime":p5Variable,"focused":p5Variable,"displayWidth":p5Variable,"displayHeight":p5Variable,"windowWidth":p5Variable,"windowHeight":p5Variable,"width":p5Variable,"height":p5Variable,"disableFriendlyErrors":p5Variable,"drawingContext":p5Variable,"VIDEO":p5Variable,"AUDIO":p5Variable,"deviceOrientation":p5Variable,"accelerationX":p5Variable,"accelerationY":p5Variable,"accelerationZ":p5Variable,"pAccelerationX":p5Variable,"pAccelerationY":p5Variable,"pAccelerationZ":p5Variable,"rotationX":p5Variable,"rotationY":p5Variable,"rotationZ":p5Variable,"pRotationX":p5Variable,"pRotationY":p5Variable,"pRotationZ":p5Variable,"turnAxis":p5Variable,"keyIsPressed":p5Variable,"key":p5Variable,"keyCode":p5Variable,"movedX":p5Variable,"movedY":p5Variable,"mouseX":p5Variable,"mouseY":p5Variable,"pmouseX":p5Variable,"pmouseY":p5Variable,"winMouseX":p5Variable,"winMouseY":p5Variable,"pwinMouseX":p5Variable,"pwinMouseY":p5Variable,"mouseButton":p5Variable,"mouseIsPressed":p5Variable,"touches":p5Variable,"pixels":p5Variable}; -let p5FunctionKeywords = {"alpha":p5Function,"blue":p5Function,"brightness":p5Function,"color":p5Function,"green":p5Function,"hue":p5Function,"lerpColor":p5Function,"lightness":p5Function,"red":p5Function,"saturation":p5Function,"background":p5Function,"clear":p5Function,"colorMode":p5Function,"fill":p5Function,"noFill":p5Function,"noStroke":p5Function,"stroke":p5Function,"erase":p5Function,"noErase":p5Function,"arc":p5Function,"ellipse":p5Function,"circle":p5Function,"line":p5Function,"point":p5Function,"quad":p5Function,"rect":p5Function,"square":p5Function,"triangle":p5Function,"ellipseMode":p5Function,"noSmooth":p5Function,"rectMode":p5Function,"smooth":p5Function,"strokeCap":p5Function,"strokeJoin":p5Function,"strokeWeight":p5Function,"bezier":p5Function,"bezierDetail":p5Function,"bezierPoint":p5Function,"bezierTangent":p5Function,"curve":p5Function,"curveDetail":p5Function,"curveTightness":p5Function,"curvePoint":p5Function,"curveTangent":p5Function,"beginContour":p5Function,"beginShape":p5Function,"bezierVertex":p5Function,"curveVertex":p5Function,"endContour":p5Function,"endShape":p5Function,"quadraticVertex":p5Function,"vertex":p5Function,"print":p5Function,"cursor":p5Function,"frameRate":p5Function,"noCursor":p5Function,"windowResized":p5Function,"fullscreen":p5Function,"pixelDensity":p5Function,"displayDensity":p5Function,"getURL":p5Function,"getURLPath":p5Function,"getURLParams":p5Function,"preload":p5Function,"setup":p5Function,"draw":p5Function,"remove":p5Function,"createCanvas":p5Function,"resizeCanvas":p5Function,"noCanvas":p5Function,"createGraphics":p5Function,"blendMode":p5Function,"noLoop":p5Function,"loop":p5Function,"push":p5Function,"pop":p5Function,"redraw":p5Function,"p5":p5Function,"applyMatrix":p5Function,"resetMatrix":p5Function,"rotate":p5Function,"rotateX":p5Function,"rotateY":p5Function,"rotateZ":p5Function,"scale":p5Function,"shearX":p5Function,"shearY":p5Function,"translate":p5Function,"storeItem":p5Function,"getItem":p5Function,"clearStorage":p5Function,"removeItem":p5Function,"createStringDict":p5Function,"createNumberDict":p5Function,"select":p5Function,"selectAll":p5Function,"removeElements":p5Function,"changed":p5Function,"input":p5Function,"createDiv":p5Function,"createP":p5Function,"createSpan":p5Function,"createImg":p5Function,"createA":p5Function,"createSlider":p5Function,"createButton":p5Function,"createCheckbox":p5Function,"createSelect":p5Function,"createRadio":p5Function,"createColorPicker":p5Function,"createInput":p5Function,"createFileInput":p5Function,"createVideo":p5Function,"createAudio":p5Function,"createCapture":p5Function,"createElement":p5Function,"setMoveThreshold":p5Function,"setShakeThreshold":p5Function,"deviceMoved":p5Function,"deviceTurned":p5Function,"deviceShaken":p5Function,"keyPressed":p5Function,"keyReleased":p5Function,"keyTyped":p5Function,"keyIsDown":p5Function,"mouseMoved":p5Function,"mouseDragged":p5Function,"mousePressed":p5Function,"mouseReleased":p5Function,"mouseClicked":p5Function,"doubleClicked":p5Function,"mouseWheel":p5Function,"requestPointerLock":p5Function,"exitPointerLock":p5Function,"touchStarted":p5Function,"touchMoved":p5Function,"touchEnded":p5Function,"createImage":p5Function,"saveCanvas":p5Function,"saveFrames":p5Function,"loadImage":p5Function,"image":p5Function,"tint":p5Function,"noTint":p5Function,"imageMode":p5Function,"blend":p5Function,"copy":p5Function,"filter":p5Function,"get":p5Function,"loadPixels":p5Function,"set":p5Function,"updatePixels":p5Function,"loadJSON":p5Function,"loadStrings":p5Function,"loadTable":p5Function,"loadXML":p5Function,"loadBytes":p5Function,"httpGet":p5Function,"httpPost":p5Function,"httpDo":p5Function,"createWriter":p5Function,"save":p5Function,"saveJSON":p5Function,"saveStrings":p5Function,"saveTable":p5Function,"abs":p5Function,"ceil":p5Function,"constrain":p5Function,"dist":p5Function,"exp":p5Function,"floor":p5Function,"lerp":p5Function,"log":p5Function,"mag":p5Function,"map":p5Function,"max":p5Function,"min":p5Function,"norm":p5Function,"pow":p5Function,"round":p5Function,"sq":p5Function,"sqrt":p5Function,"fract":p5Function,"createVector":p5Function,"noise":p5Function,"noiseDetail":p5Function,"noiseSeed":p5Function,"randomSeed":p5Function,"random":p5Function,"randomGaussian":p5Function,"acos":p5Function,"asin":p5Function,"atan":p5Function,"atan2":p5Function,"cos":p5Function,"sin":p5Function,"tan":p5Function,"degrees":p5Function,"radians":p5Function,"angleMode":p5Function,"textAlign":p5Function,"textLeading":p5Function,"textSize":p5Function,"textStyle":p5Function,"textWidth":p5Function,"textAscent":p5Function,"textDescent":p5Function,"loadFont":p5Function,"text":p5Function,"textFont":p5Function,"append":p5Function,"arrayCopy":p5Function,"concat":p5Function,"reverse":p5Function,"shorten":p5Function,"shuffle":p5Function,"sort":p5Function,"splice":p5Function,"subset":p5Function,"float":p5Function,"int":p5Function,"str":p5Function,"boolean":p5Function,"byte":p5Function,"char":p5Function,"unchar":p5Function,"hex":p5Function,"unhex":p5Function,"join":p5Function,"match":p5Function,"matchAll":p5Function,"nf":p5Function,"nfc":p5Function,"nfp":p5Function,"nfs":p5Function,"split":p5Function,"splitTokens":p5Function,"trim":p5Function,"day":p5Function,"hour":p5Function,"minute":p5Function,"millis":p5Function,"month":p5Function,"second":p5Function,"year":p5Function,"plane":p5Function,"box":p5Function,"sphere":p5Function,"cylinder":p5Function,"cone":p5Function,"ellipsoid":p5Function,"torus":p5Function,"orbitControl":p5Function,"debugMode":p5Function,"noDebugMode":p5Function,"ambientLight":p5Function,"specularColor":p5Function,"directionalLight":p5Function,"pointLight":p5Function,"lights":p5Function,"lightFalloff":p5Function,"spotLight":p5Function,"noLights":p5Function,"loadModel":p5Function,"model":p5Function,"loadShader":p5Function,"createShader":p5Function,"shader":p5Function,"resetShader":p5Function,"normalMaterial":p5Function,"texture":p5Function,"textureMode":p5Function,"textureWrap":p5Function,"ambientMaterial":p5Function,"emissiveMaterial":p5Function,"specularMaterial":p5Function,"shininess":p5Function,"camera":p5Function,"perspective":p5Function,"ortho":p5Function,"frustum":p5Function,"createCamera":p5Function,"setCamera":p5Function,"setAttributes":p5Function,"sampleRate":p5Function,"freqToMidi":p5Function,"midiToFreq":p5Function,"soundFormats":p5Function,"getAudioContext":p5Function,"userStartAudio":p5Function,"loadSound":p5Function,"createConvolver":p5Function,"setBPM":p5Function,"saveSound":p5Function}; +let p5VariableKeywords = {"VERSION":p5Variable,"P2D":p5Variable,"WEBGL":p5Variable,"ARROW":p5Variable,"CROSS":p5Variable,"HAND":p5Variable,"MOVE":p5Variable,"TEXT":p5Variable,"WAIT":p5Variable,"HALF_PI":p5Variable,"PI":p5Variable,"QUARTER_PI":p5Variable,"TAU":p5Variable,"TWO_PI":p5Variable,"DEGREES":p5Variable,"RADIANS":p5Variable,"CORNER":p5Variable,"CORNERS":p5Variable,"RADIUS":p5Variable,"RIGHT":p5Variable,"LEFT":p5Variable,"CENTER":p5Variable,"TOP":p5Variable,"BOTTOM":p5Variable,"BASELINE":p5Variable,"POINTS":p5Variable,"LINES":p5Variable,"LINE_STRIP":p5Variable,"LINE_LOOP":p5Variable,"TRIANGLES":p5Variable,"TRIANGLE_FAN":p5Variable,"TRIANGLE_STRIP":p5Variable,"QUADS":p5Variable,"QUAD_STRIP":p5Variable,"TESS":p5Variable,"CLOSE":p5Variable,"OPEN":p5Variable,"CHORD":p5Variable,"PIE":p5Variable,"PROJECT":p5Variable,"SQUARE":p5Variable,"ROUND":p5Variable,"BEVEL":p5Variable,"MITER":p5Variable,"RGB":p5Variable,"HSB":p5Variable,"HSL":p5Variable,"AUTO":p5Variable,"ALT":p5Variable,"BACKSPACE":p5Variable,"CONTROL":p5Variable,"DELETE":p5Variable,"DOWN_ARROW":p5Variable,"ENTER":p5Variable,"ESCAPE":p5Variable,"LEFT_ARROW":p5Variable,"OPTION":p5Variable,"RETURN":p5Variable,"RIGHT_ARROW":p5Variable,"SHIFT":p5Variable,"TAB":p5Variable,"UP_ARROW":p5Variable,"BLEND":p5Variable,"REMOVE":p5Variable,"ADD":p5Variable,"DARKEST":p5Variable,"LIGHTEST":p5Variable,"DIFFERENCE":p5Variable,"SUBTRACT":p5Variable,"EXCLUSION":p5Variable,"MULTIPLY":p5Variable,"SCREEN":p5Variable,"REPLACE":p5Variable,"OVERLAY":p5Variable,"HARD_LIGHT":p5Variable,"SOFT_LIGHT":p5Variable,"DODGE":p5Variable,"BURN":p5Variable,"THRESHOLD":p5Variable,"GRAY":p5Variable,"OPAQUE":p5Variable,"INVERT":p5Variable,"POSTERIZE":p5Variable,"DILATE":p5Variable,"ERODE":p5Variable,"BLUR":p5Variable,"NORMAL":p5Variable,"ITALIC":p5Variable,"BOLD":p5Variable,"BOLDITALIC":p5Variable,"CHAR":p5Variable,"WORD":p5Variable,"LINEAR":p5Variable,"QUADRATIC":p5Variable,"BEZIER":p5Variable,"CURVE":p5Variable,"STROKE":p5Variable,"FILL":p5Variable,"TEXTURE":p5Variable,"IMMEDIATE":p5Variable,"IMAGE":p5Variable,"NEAREST":p5Variable,"REPEAT":p5Variable,"CLAMP":p5Variable,"MIRROR":p5Variable,"LANDSCAPE":p5Variable,"PORTRAIT":p5Variable,"GRID":p5Variable,"AXES":p5Variable,"LABEL":p5Variable,"FALLBACK":p5Variable,"CONTAIN":p5Variable,"COVER":p5Variable,"frameCount":p5Variable,"deltaTime":p5Variable,"focused":p5Variable,"displayWidth":p5Variable,"displayHeight":p5Variable,"windowWidth":p5Variable,"windowHeight":p5Variable,"width":p5Variable,"height":p5Variable,"disableFriendlyErrors":p5Variable,"drawingContext":p5Variable,"deviceOrientation":p5Variable,"accelerationX":p5Variable,"accelerationY":p5Variable,"accelerationZ":p5Variable,"pAccelerationX":p5Variable,"pAccelerationY":p5Variable,"pAccelerationZ":p5Variable,"rotationX":p5Variable,"rotationY":p5Variable,"rotationZ":p5Variable,"pRotationX":p5Variable,"pRotationY":p5Variable,"pRotationZ":p5Variable,"turnAxis":p5Variable,"keyIsPressed":p5Variable,"key":p5Variable,"keyCode":p5Variable,"movedX":p5Variable,"movedY":p5Variable,"mouseX":p5Variable,"mouseY":p5Variable,"pmouseX":p5Variable,"pmouseY":p5Variable,"winMouseX":p5Variable,"winMouseY":p5Variable,"pwinMouseX":p5Variable,"pwinMouseY":p5Variable,"mouseButton":p5Variable,"mouseIsPressed":p5Variable,"touches":p5Variable,"pixels":p5Variable,"soundOut":p5Variable}; +let p5FunctionKeywords = {"describe":p5Function,"describeElement":p5Function,"textOutput":p5Function,"gridOutput":p5Function,"alpha":p5Function,"blue":p5Function,"brightness":p5Function,"color":p5Function,"green":p5Function,"hue":p5Function,"lerpColor":p5Function,"lightness":p5Function,"red":p5Function,"saturation":p5Function,"background":p5Function,"clear":p5Function,"colorMode":p5Function,"fill":p5Function,"noFill":p5Function,"noStroke":p5Function,"stroke":p5Function,"erase":p5Function,"noErase":p5Function,"arc":p5Function,"ellipse":p5Function,"circle":p5Function,"line":p5Function,"point":p5Function,"quad":p5Function,"rect":p5Function,"square":p5Function,"triangle":p5Function,"ellipseMode":p5Function,"noSmooth":p5Function,"rectMode":p5Function,"smooth":p5Function,"strokeCap":p5Function,"strokeJoin":p5Function,"strokeWeight":p5Function,"bezier":p5Function,"bezierDetail":p5Function,"bezierPoint":p5Function,"bezierTangent":p5Function,"curve":p5Function,"curveDetail":p5Function,"curveTightness":p5Function,"curvePoint":p5Function,"curveTangent":p5Function,"beginContour":p5Function,"beginShape":p5Function,"bezierVertex":p5Function,"curveVertex":p5Function,"endContour":p5Function,"endShape":p5Function,"quadraticVertex":p5Function,"vertex":p5Function,"normal":p5Function,"print":p5Function,"cursor":p5Function,"frameRate":p5Function,"getTargetFrameRate":p5Function,"noCursor":p5Function,"windowResized":p5Function,"fullscreen":p5Function,"pixelDensity":p5Function,"displayDensity":p5Function,"getURL":p5Function,"getURLPath":p5Function,"getURLParams":p5Function,"preload":p5Function,"setup":p5Function,"draw":p5Function,"remove":p5Function,"createCanvas":p5Function,"resizeCanvas":p5Function,"noCanvas":p5Function,"createGraphics":p5Function,"blendMode":p5Function,"noLoop":p5Function,"loop":p5Function,"isLooping":p5Function,"push":p5Function,"pop":p5Function,"redraw":p5Function,"p5":p5Function,"applyMatrix":p5Function,"resetMatrix":p5Function,"rotate":p5Function,"rotateX":p5Function,"rotateY":p5Function,"rotateZ":p5Function,"scale":p5Function,"shearX":p5Function,"shearY":p5Function,"translate":p5Function,"storeItem":p5Function,"getItem":p5Function,"clearStorage":p5Function,"removeItem":p5Function,"createStringDict":p5Function,"createNumberDict":p5Function,"select":p5Function,"selectAll":p5Function,"removeElements":p5Function,"changed":p5Function,"input":p5Function,"createDiv":p5Function,"createP":p5Function,"createSpan":p5Function,"createImg":p5Function,"createA":p5Function,"createSlider":p5Function,"createButton":p5Function,"createCheckbox":p5Function,"createSelect":p5Function,"createRadio":p5Function,"createColorPicker":p5Function,"createInput":p5Function,"createFileInput":p5Function,"createVideo":p5Function,"createAudio":p5Function,"createCapture":p5Function,"createElement":p5Function,"setMoveThreshold":p5Function,"setShakeThreshold":p5Function,"deviceMoved":p5Function,"deviceTurned":p5Function,"deviceShaken":p5Function,"keyPressed":p5Function,"keyReleased":p5Function,"keyTyped":p5Function,"keyIsDown":p5Function,"mouseMoved":p5Function,"mouseDragged":p5Function,"mousePressed":p5Function,"mouseReleased":p5Function,"mouseClicked":p5Function,"doubleClicked":p5Function,"mouseWheel":p5Function,"requestPointerLock":p5Function,"exitPointerLock":p5Function,"touchStarted":p5Function,"touchMoved":p5Function,"touchEnded":p5Function,"createImage":p5Function,"saveCanvas":p5Function,"saveFrames":p5Function,"loadImage":p5Function,"saveGif":p5Function,"image":p5Function,"tint":p5Function,"noTint":p5Function,"imageMode":p5Function,"blend":p5Function,"copy":p5Function,"filter":p5Function,"get":p5Function,"loadPixels":p5Function,"set":p5Function,"updatePixels":p5Function,"loadJSON":p5Function,"loadStrings":p5Function,"loadTable":p5Function,"loadXML":p5Function,"loadBytes":p5Function,"httpGet":p5Function,"httpPost":p5Function,"httpDo":p5Function,"createWriter":p5Function,"save":p5Function,"saveJSON":p5Function,"saveStrings":p5Function,"saveTable":p5Function,"abs":p5Function,"ceil":p5Function,"constrain":p5Function,"dist":p5Function,"exp":p5Function,"floor":p5Function,"lerp":p5Function,"log":p5Function,"mag":p5Function,"map":p5Function,"max":p5Function,"min":p5Function,"norm":p5Function,"pow":p5Function,"round":p5Function,"sq":p5Function,"sqrt":p5Function,"fract":p5Function,"createVector":p5Function,"noise":p5Function,"noiseDetail":p5Function,"noiseSeed":p5Function,"randomSeed":p5Function,"random":p5Function,"randomGaussian":p5Function,"acos":p5Function,"asin":p5Function,"atan":p5Function,"atan2":p5Function,"cos":p5Function,"sin":p5Function,"tan":p5Function,"degrees":p5Function,"radians":p5Function,"angleMode":p5Function,"textAlign":p5Function,"textLeading":p5Function,"textSize":p5Function,"textStyle":p5Function,"textWidth":p5Function,"textAscent":p5Function,"textDescent":p5Function,"textWrap":p5Function,"loadFont":p5Function,"text":p5Function,"textFont":p5Function,"append":p5Function,"arrayCopy":p5Function,"concat":p5Function,"reverse":p5Function,"shorten":p5Function,"shuffle":p5Function,"sort":p5Function,"splice":p5Function,"subset":p5Function,"float":p5Function,"int":p5Function,"str":p5Function,"boolean":p5Function,"byte":p5Function,"char":p5Function,"unchar":p5Function,"hex":p5Function,"unhex":p5Function,"join":p5Function,"match":p5Function,"matchAll":p5Function,"nf":p5Function,"nfc":p5Function,"nfp":p5Function,"nfs":p5Function,"split":p5Function,"splitTokens":p5Function,"trim":p5Function,"day":p5Function,"hour":p5Function,"minute":p5Function,"millis":p5Function,"month":p5Function,"second":p5Function,"year":p5Function,"plane":p5Function,"box":p5Function,"sphere":p5Function,"cylinder":p5Function,"cone":p5Function,"ellipsoid":p5Function,"torus":p5Function,"orbitControl":p5Function,"debugMode":p5Function,"noDebugMode":p5Function,"ambientLight":p5Function,"specularColor":p5Function,"directionalLight":p5Function,"pointLight":p5Function,"lights":p5Function,"lightFalloff":p5Function,"spotLight":p5Function,"noLights":p5Function,"loadModel":p5Function,"model":p5Function,"loadShader":p5Function,"createShader":p5Function,"shader":p5Function,"resetShader":p5Function,"texture":p5Function,"textureMode":p5Function,"textureWrap":p5Function,"normalMaterial":p5Function,"ambientMaterial":p5Function,"emissiveMaterial":p5Function,"specularMaterial":p5Function,"shininess":p5Function,"camera":p5Function,"perspective":p5Function,"ortho":p5Function,"frustum":p5Function,"createCamera":p5Function,"setCamera":p5Function,"setAttributes":p5Function,"getAudioContext":p5Function,"userStartAudio":p5Function,"getOutputVolume":p5Function,"outputVolume":p5Function,"sampleRate":p5Function,"freqToMidi":p5Function,"midiToFreq":p5Function,"soundFormats":p5Function,"saveSound":p5Function,"loadSound":p5Function,"createConvolver":p5Function,"setBPM":p5Function}; exports.p5FunctionKeywords = p5FunctionKeywords; exports.p5VariableKeywords = p5VariableKeywords; diff --git a/package-lock.json b/package-lock.json index 2b09fffe1e..ba004685bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "express-session": "^1.17.2", "final-form": "^4.20.2", "friendly-words": "^1.2.0", + "fuse.js": "^6.6.2", "htmlhint": "^0.15.1", "i18next": "^19.9.2", "i18next-http-backend": "^1.2.6", @@ -9397,6 +9398,15 @@ "util-deprecate": "^1.0.2" } }, + "node_modules/@storybook/ui/node_modules/fuse.js": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz", + "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/@storybook/ui/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -23652,12 +23662,11 @@ } }, "node_modules/fuse.js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz", - "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==", - "dev": true, + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", + "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==", "engines": { - "node": ">=6" + "node": ">=10" } }, "node_modules/gauge": { @@ -58356,6 +58365,12 @@ "util-deprecate": "^1.0.2" }, "dependencies": { + "fuse.js": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz", + "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==", + "dev": true + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -69531,10 +69546,9 @@ "dev": true }, "fuse.js": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.6.1.tgz", - "integrity": "sha512-hT9yh/tiinkmirKrlv4KWOjztdoZo1mx9Qh4KvWqC7isoXwdUY3PNWUxceF4/qO9R6riA2C29jdTOeQOIROjgw==", - "dev": true + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", + "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==" }, "gauge": { "version": "2.7.4", diff --git a/package.json b/package.json index 2a58c10246..bdf7d3ce1c 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "fetch-examples-gg:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-gg.bundle.js", "fetch-examples-ml5:prod": "cross-env NODE_ENV=production node ./dist/fetch-examples-ml5.bundle.js", "update-syntax-highlighting": "node ./server/scripts/update-syntax-highlighting.js", + "update-p5-hinter": "node ./server/scripts/update-p5-hinter.js", "heroku-postbuild": "touch .env; npm run build", "storybook": "start-storybook -p 6006", "build-storybook": "build-storybook" @@ -179,6 +180,7 @@ "express-session": "^1.17.2", "final-form": "^4.20.2", "friendly-words": "^1.2.0", + "fuse.js": "^6.6.2", "htmlhint": "^0.15.1", "i18next": "^19.9.2", "i18next-http-backend": "^1.2.6", diff --git a/server/models/user.js b/server/models/user.js index 6295f1b929..7c293aba72 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -73,7 +73,8 @@ const userSchema = new Schema( theme: { type: String, default: 'light' }, autorefresh: { type: Boolean, default: false }, language: { type: String, default: 'en-US' }, - autocloseBracketsQuotes: { type: Boolean, default: true } + autocloseBracketsQuotes: { type: Boolean, default: true }, + autocompleteHinter: { type: Boolean, default: true } }, totalSize: { type: Number, default: 0 }, cookieConsent: { diff --git a/server/scripts/update-p5-hinter.js b/server/scripts/update-p5-hinter.js new file mode 100644 index 0000000000..b563c8a8cc --- /dev/null +++ b/server/scripts/update-p5-hinter.js @@ -0,0 +1,58 @@ +const fs = require('fs'); +const process = require('process'); +const axios = require('axios'); + +// const getDescription = (d) => { +// return d.split('\n')[0].replace('

    ', ''); +// }; + +axios + .get('https://p5js.org/reference/data.json') + .then((response) => { + const { data } = response; + + const arr = data.classitems; + const p5Keywords = []; + + arr.forEach((obj) => { + if ( + obj.class === 'p5' && + obj.module !== 'Foundation' && + obj.name && + obj.itemtype + ) { + let type; + if (obj.itemtype === 'method') type = 'fun'; + else if (obj.itemtype === 'property') type = 'var'; + else type = 'attr'; + + p5Keywords.push({ + text: obj.name, + type + }); + } + }); + + const keywords = JSON.stringify(p5Keywords); + + let generatedCode = '/* eslint-disable */\n'; + generatedCode += + '/* generated: do not edit! helper file for hinter.' + + ' generated by update-p5-hinter script */\n'; + generatedCode += `exports.p5Hinter = ${keywords};\n`; + + fs.writeFile( + `${process.cwd()}/client/utils/p5-hinter.js`, + generatedCode, + (error) => { + if (error) { + console.log("Error!! Couldn't write to the file", error); + } else { + console.log('Hinter files updated successfully'); + } + } + ); + }) + .catch((err) => { + throw err; + }); diff --git a/translations/locales/en-US/translations.json b/translations/locales/en-US/translations.json index 66c2fe15fa..0d1b2979b3 100644 --- a/translations/locales/en-US/translations.json +++ b/translations/locales/en-US/translations.json @@ -155,6 +155,9 @@ "AutocloseBracketsQuotes": "Autoclose Brackets and Quotes", "AutocloseBracketsQuotesOnARIA": "autoclose brackets and quotes on", "AutocloseBracketsQuotesOffARIA": "autoclose brackets and quotes off", + "AutocompleteHinter": "Autocomplete Hinter", + "AutocompleteHinterOnARIA": "autocomplete hinter on", + "AutocompleteHinterOffARIA": "autocomplete hinter off", "WordWrap": "Word Wrap", "LineWrapOnARIA": "linewrap on", "LineWrapOffARIA": "linewrap off", @@ -574,6 +577,7 @@ "DarkTheme": "Dark", "HighContrastTheme": "High Contrast", "Autosave": "Autosave", + "AutocompleteHinter": "Autocomplete Hinter", "WordWrap": "Word Wrap", "LineNumbers": "Line numbers", "LintWarningSound": "Lint warning sound", diff --git a/translations/locales/zh-CN/translations.json b/translations/locales/zh-CN/translations.json index 3892b41ab8..509b1ff630 100644 --- a/translations/locales/zh-CN/translations.json +++ b/translations/locales/zh-CN/translations.json @@ -150,6 +150,9 @@ "AutocloseBracketsQuotes": "自动添加反括号和反引号", "AutocloseBracketsQuotesOnARIA": "打开自动添加反括号和反引号", "AutocloseBracketsQuotesOffARIA": "关闭自动添加反括号和反引号", + "AutocompleteHinter": "补全提示", + "AutocompleteHinterOnARIA": "打开补全提示", + "AutocompleteHinterOffARIA": "关闭补全提示", "WordWrap": "自动提行", "LineWrapOnARIA": "打开自动提行", "LineWrapOffARIA": "关闭自动提行", @@ -568,6 +571,7 @@ "DarkTheme": "暗黑", "HighContrastTheme": "高对比度", "Autosave": "自动保存", + "AutocompleteHinter": "补全提示", "WordWrap": "自动提行", "LineNumbers": "行号", "LintWarningSound": "纠码器警告音", diff --git a/translations/locales/zh-TW/translations.json b/translations/locales/zh-TW/translations.json index 75eb2d0e1f..a5318be06f 100644 --- a/translations/locales/zh-TW/translations.json +++ b/translations/locales/zh-TW/translations.json @@ -150,6 +150,9 @@ "AutocloseBracketsQuotes": "自動加上括號與引號", "AutocloseBracketsQuotesOnARIA": "自動加上括號與引號", "AutocloseBracketsQuotesOffARIA": "不自動加上括號與引號", + "AutocompleteHinter": "補全提示", + "AutocompleteHinterOnARIA": "加上補全提示", + "AutocompleteHinterOffARIA": "不加上補全提示", "WordWrap": "折行", "LineWrapOnARIA": "啟用自動折行", "LineWrapOffARIA": "停用自動折行", @@ -568,6 +571,7 @@ "DarkTheme": "暗色", "HighContrastTheme": "高對比", "Autosave": "自動儲存", + "AutocompleteHinter": "補全提示", "WordWrap": "折行", "LineNumbers": "行號", "LintWarningSound": "語法檢查警示聲",