Skip to content

Autocomplete and Hinter #1873

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Jun 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e5bda75
add hinter and autocompletion for css and js
peilingjiang May 23, 2021
5314b71
change hinter options
peilingjiang May 23, 2021
9e5864d
Merge branch 'develop' into autocomplete
peilingjiang Jun 11, 2021
fd3f5de
rebase
peilingjiang Jun 15, 2021
cdcb6e2
change hinter options
peilingjiang May 23, 2021
1770d49
resolve
peilingjiang Jun 15, 2021
c12470d
resolve
peilingjiang Jun 15, 2021
430b61f
bump fuse.js
peilingjiang Jun 15, 2021
d994594
Merge branch 'develop' into autocomplete
peilingjiang Jun 30, 2021
d361074
Merge branch 'develop' into autocomplete
peilingjiang Jul 9, 2021
127efa8
Merge branch 'develop' into autocomplete
peilingjiang Jul 11, 2021
e272936
Merge branch 'develop' into autocomplete
peilingjiang Jul 13, 2021
26d881e
Merge branch 'develop' into autocomplete
peilingjiang Jul 15, 2021
538f313
Merge branch 'develop' into autocomplete
peilingjiang Jul 16, 2021
b416d84
Merge branch 'develop' into autocomplete
peilingjiang Jul 17, 2021
7d0e158
Merge branch 'develop' into autocomplete
peilingjiang Nov 6, 2021
3a36c64
resolve feedback issues
peilingjiang Nov 6, 2021
a969297
rename show hint component
peilingjiang Nov 6, 2021
36c9542
Merge branch 'develop' into autocomplete
catarak Nov 12, 2021
2fc7ad5
Update aria-labels for Autocomplete
catarak Nov 12, 2021
1b1c5c0
add tab to p5 link
peilingjiang Nov 13, 2021
411fa55
update tab function
peilingjiang Nov 13, 2021
013b669
rm console
peilingjiang Nov 13, 2021
9aba6d3
Merge branch 'develop' into autocomplete
peilingjiang Nov 16, 2021
f79f873
add key shortcut
peilingjiang Nov 17, 2021
2b9476d
increase readability
peilingjiang Nov 19, 2021
14630b0
Merge branch 'develop' into autocomplete
peilingjiang Nov 19, 2021
cec4bb7
Merge branch 'develop' into autocomplete
peilingjiang Dec 9, 2021
2133213
add to shortcut page
peilingjiang Dec 10, 2021
9ad64c8
Merge branch 'develop' into autocomplete
peilingjiang Mar 1, 2022
a8ffea7
Merge branch 'develop' into autocomplete
peilingjiang Apr 19, 2022
0b64f3a
Merge branch 'develop' into autocomplete
peilingjiang Apr 22, 2022
5878d1d
resolve package conflicts
peilingjiang May 25, 2023
1056400
update hinter utils for latest p5 updates
peilingjiang May 25, 2023
cddc1cd
add switch for desktop and mobile and update ui
peilingjiang May 25, 2023
044a230
make wider for long var names
peilingjiang May 25, 2023
02c276c
update tests
peilingjiang May 25, 2023
14bef00
update syntax highlighting
peilingjiang May 25, 2023
2ff74a0
rm unused lines
peilingjiang Jun 3, 2023
cf6b033
minor update
peilingjiang Jun 3, 2023
92ce962
minor update
peilingjiang Jun 3, 2023
58f38cd
update lock file
peilingjiang Jun 3, 2023
0f3507a
update lock file
peilingjiang Jun 3, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
21 changes: 20 additions & 1 deletion client/modules/IDE/actions/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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({
Expand Down
118 changes: 114 additions & 4 deletions client/modules/IDE/components/Editor.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -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[
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions client/modules/IDE/components/KeyboardShortcutModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ function KeyboardShortcutModal() {
</span>
<span>{t('KeyboardShortcuts.General.TurnOffAccessibleOutput')}</span>
</li>
<li className="keyboard-shortcut-item">
<span className="keyboard-shortcut__command">{'\u21E7'} + Right</span>
<span>Go to Reference for Selected Item in Hinter</span>
</li>
</ul>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -35,6 +36,7 @@ describe('<Preferences />', () => {
fontSize: 12,
autosave: false,
autocloseBracketsQuotes: false,
autocompleteHinter: false,
linewrap: false,
lineNumbers: false,
theme: 'contrast',
Expand All @@ -45,6 +47,7 @@ describe('<Preferences />', () => {
setFontSize: jest.fn(),
setAutosave: jest.fn(),
setAutocloseBracketsQuotes: jest.fn(),
setAutocompleteHinter: jest.fn(),
setLinewrap: jest.fn(),
setLineNumbers: jest.fn(),
setTheme: jest.fn(),
Expand Down Expand Up @@ -426,6 +429,28 @@ describe('<Preferences />', () => {
);
});

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;
Expand Down Expand Up @@ -481,6 +506,34 @@ describe('<Preferences />', () => {
});
});

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;
Expand All @@ -492,7 +545,7 @@ describe('<Preferences />', () => {
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
});
Expand Down Expand Up @@ -520,7 +573,7 @@ describe('<Preferences />', () => {
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
});
Expand Down
Loading