diff --git a/package-lock.json b/package-lock.json index 1b166289df..5248a21516 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1006,8 +1006,23 @@ "@types/lodash": { "version": "4.14.123", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz", - "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==", - "dev": true + "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==" + }, + "@types/lodash.assignin": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@types/lodash.assignin/-/lodash.assignin-4.2.6.tgz", + "integrity": "sha512-kO9C2Oq0X8yehLu0o689SwR+wy+m4IQZg2TxRBXNkmpd0WY/GYEV+tTqrWRu2jt69eDOaVMJxna6QnDQ/g1TSg==", + "requires": { + "@types/lodash": "*" + } + }, + "@types/lodash.clonedeep": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.6.tgz", + "integrity": "sha512-cE1jYr2dEg1wBImvXlNtp0xDoS79rfEdGozQVgliDZj1uERH4k+rmEMTudP9b4VQ8O6nRb5gPqft0QzEQGMQgA==", + "requires": { + "@types/lodash": "*" + } }, "@types/lz-string": { "version": "1.3.33", @@ -10315,16 +10330,19 @@ "integrity": "sha512-hm2nYpDrwoO/OzBhdcqs/XGT6XjSuSSCVEpia+Kl2J6x4CYt5hISlVL/AYU1khoDXv0AQVgxtdJySb9gjAn56Q==" }, "js-slang": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/js-slang/-/js-slang-0.4.19.tgz", - "integrity": "sha512-ttBnzYNJ64c9BFG0MVZAOXk2YSlUOk33WIVcwwQm75izDLbRAOabP3oWL7igy6BQkLTa1DHHwV4KNus2uwoc2w==", + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/js-slang/-/js-slang-0.4.23.tgz", + "integrity": "sha512-Q/Kv+IHzC2PyLKlSUPVC+SeSbhKoqsEMHoTiyFmxS7/ZO47jiajtnCtZFmjGhSz1l9VHMl2uNq1rx3qHz5wfWA==", "requires": { "@types/estree": "0.0.39", + "@types/lodash.assignin": "^4.2.6", + "@types/lodash.clonedeep": "^4.5.6", "acorn": "^6.4.1", "acorn-loose": "^7.0.0", "acorn-walk": "^7.0.0", "astring": "^1.3.1", "jest-html-reporter": "^2.8.2", + "lodash": "^4.17.13", "node-getopt": "^0.3.2", "source-map": "^0.7.3" }, diff --git a/package.json b/package.json index 03a100b7c0..cb25a811bb 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "draft-js": "^0.10.5", "flexboxgrid": "^6.3.1", "flexboxgrid-helpers": "^1.1.3", - "js-slang": "^0.4.20", + "js-slang": "^0.4.23", "jwt-decode": "^2.2.0", "lodash": "^4.17.13", "lz-string": "^1.4.4", diff --git a/src/actions/actionTypes.ts b/src/actions/actionTypes.ts index 1329731088..98f087fd4c 100755 --- a/src/actions/actionTypes.ts +++ b/src/actions/actionTypes.ts @@ -40,6 +40,7 @@ export const END_CLEAR_CONTEXT = 'END_CLEAR_CONTEXT'; export const ENSURE_LIBRARIES_LOADED = 'ENSURE_LIBRARIES_LOADED'; export const EVAL_EDITOR = 'EVAL_EDITOR'; export const EVAL_REPL = 'EVAL_REPL'; +export const PROMPT_AUTOCOMPLETE = 'PROMPT_AUTOCOMPLETE'; // For interpreting code blocks silently (e.g. prepend) BEFORE the test case is run export const EVAL_SILENT = 'EVAL_SILENT'; export const EVAL_TESTCASE = 'EVAL_TESTCASE'; diff --git a/src/actions/workspaces.ts b/src/actions/workspaces.ts index b97f179ffd..e8ed4676aa 100755 --- a/src/actions/workspaces.ts +++ b/src/actions/workspaces.ts @@ -209,3 +209,16 @@ export const updateHasUnsavedChanges = ( workspaceLocation, hasUnsavedChanges }); + +export const promptAutocomplete = ( + workspaceLocation: WorkspaceLocation, + row: number, + column: number, + callback: any // TODO: define a type for this +) => + action(actionTypes.PROMPT_AUTOCOMPLETE, { + workspaceLocation, + row, + column, + callback + }); diff --git a/src/components/Playground.tsx b/src/components/Playground.tsx index 21442dc84f..fae7958ec9 100755 --- a/src/components/Playground.tsx +++ b/src/components/Playground.tsx @@ -104,6 +104,7 @@ export interface IDispatchProps { handleDebuggerResume: () => void; handleDebuggerReset: () => void; handleToggleEditorAutorun: () => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; } type PlaygroundState = { @@ -272,6 +273,7 @@ class Playground extends React.Component { handleDeclarationNavigate: this.props.handleDeclarationNavigate, handleEditorEval: this.props.handleEditorEval, handleEditorValueChange: this.props.handleEditorValueChange, + handlePromptAutocomplete: this.props.handlePromptAutocomplete, handleFinishInvite: this.props.handleFinishInvite, sharedbAceInitValue: this.props.sharedbAceInitValue, sharedbAceIsInviting: this.props.sharedbAceIsInviting, diff --git a/src/components/__tests__/Playground.tsx b/src/components/__tests__/Playground.tsx index 05897cd15e..98f279e917 100755 --- a/src/components/__tests__/Playground.tsx +++ b/src/components/__tests__/Playground.tsx @@ -54,7 +54,8 @@ const baseProps = { handleUsingSubst: (usingSubst: boolean) => {}, handleDebuggerPause: () => {}, handleDebuggerResume: () => {}, - handleDebuggerReset: () => {} + handleDebuggerReset: () => {}, + handlePromptAutocomplete: (row: number, col: number, callback: any) => {} }; const testValueProps: IPlaygroundProps = { diff --git a/src/components/academy/grading/GradingWorkspace.tsx b/src/components/academy/grading/GradingWorkspace.tsx index b040c112aa..1c51d27790 100755 --- a/src/components/academy/grading/GradingWorkspace.tsx +++ b/src/components/academy/grading/GradingWorkspace.tsx @@ -88,6 +88,7 @@ export type DispatchProps = { handleDebuggerReset: () => void; handleUpdateCurrentSubmissionId: (submissionId: number, questionId: number) => void; handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; }; class GradingWorkspace extends React.Component { @@ -179,6 +180,7 @@ class GradingWorkspace extends React.Component { highlightedLines: this.props.highlightedLines, newCursorPosition: this.props.newCursorPosition, handleEditorUpdateBreakpoints: this.props.handleEditorUpdateBreakpoints, + handlePromptAutocomplete: this.props.handlePromptAutocomplete, isEditorAutorun: false } : undefined, diff --git a/src/components/assessment/AssessmentWorkspace.tsx b/src/components/assessment/AssessmentWorkspace.tsx index 171a694c59..327b6b6cfe 100755 --- a/src/components/assessment/AssessmentWorkspace.tsx +++ b/src/components/assessment/AssessmentWorkspace.tsx @@ -108,6 +108,7 @@ export type DispatchProps = { handleDebuggerPause: () => void; handleDebuggerResume: () => void; handleDebuggerReset: () => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; }; class AssessmentWorkspace extends React.Component< @@ -248,6 +249,7 @@ class AssessmentWorkspace extends React.Component< highlightedLines: this.props.highlightedLines, newCursorPosition: this.props.newCursorPosition, handleEditorUpdateBreakpoints: this.props.handleEditorUpdateBreakpoints, + handlePromptAutocomplete: this.props.handlePromptAutocomplete, isEditorAutorun: false } : undefined, diff --git a/src/components/assessment/__tests__/AssessmentWorkspace.tsx b/src/components/assessment/__tests__/AssessmentWorkspace.tsx index 860176b816..2b521dc8ca 100644 --- a/src/components/assessment/__tests__/AssessmentWorkspace.tsx +++ b/src/components/assessment/__tests__/AssessmentWorkspace.tsx @@ -45,6 +45,7 @@ const defaultProps: AssessmentWorkspaceProps = { handleDebuggerPause: () => {}, handleDebuggerResume: () => {}, handleDebuggerReset: () => {}, + handlePromptAutocomplete: (row: number, col: number, callback: any) => {}, isRunning: false, isDebugging: false, enableDebugging: false, diff --git a/src/components/missionControl/EditingWorkspace.tsx b/src/components/missionControl/EditingWorkspace.tsx index eafe25a33b..e0c5e5d799 100644 --- a/src/components/missionControl/EditingWorkspace.tsx +++ b/src/components/missionControl/EditingWorkspace.tsx @@ -102,6 +102,7 @@ export type DispatchProps = { handleDebuggerReset: () => void; handleUpdateCurrentAssessmentId: (assessmentId: number, questionId: number) => void; handleUpdateHasUnsavedChanges: (hasUnsavedChanges: boolean) => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; }; interface IState { @@ -182,6 +183,7 @@ class AssessmentWorkspace extends React.Component void; handleEditorHeightChange: (height: number) => void; handleEditorValueChange: (val: string) => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; handleEditorWidthChange: (widthChange: number) => void; handleEditorUpdateBreakpoints: (breakpoints: string[]) => void; handleExternalSelect: (externalLibraryName: ExternalLibraryName) => void; @@ -234,6 +235,7 @@ class Sourcecast extends React.Component { }; const sourcecastControlbarProps: ISourcecastControlbarProps = { handleEditorValueChange: this.props.handleEditorValueChange, + handlePromptAutocomplete: this.props.handlePromptAutocomplete, handleSetCodeDeltasToApply: this.props.handleSetCodeDeltasToApply, handleSetEditorReadonly: this.props.handleSetEditorReadonly, handleSetInputToApply: this.props.handleSetInputToApply, diff --git a/src/components/sourcecast/SourcecastControlbar.tsx b/src/components/sourcecast/SourcecastControlbar.tsx index 9c2981f894..ba8d725eaf 100755 --- a/src/components/sourcecast/SourcecastControlbar.tsx +++ b/src/components/sourcecast/SourcecastControlbar.tsx @@ -204,6 +204,7 @@ export interface ISourcecastControlbarProps { playbackStatus: PlaybackStatus; handleChapterSelect: (chapter: number) => void; handleExternalSelect: (name: ExternalLibraryName) => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; } export interface ISourcecastControlbarState { diff --git a/src/components/workspace/Editor.tsx b/src/components/workspace/Editor.tsx index fc637722ae..1ef8af73fa 100755 --- a/src/components/workspace/Editor.tsx +++ b/src/components/workspace/Editor.tsx @@ -3,6 +3,7 @@ import AceEditor, { IAnnotation } from 'react-ace'; import { HotKeys } from 'react-hotkeys'; import sharedbAce from 'sharedb-ace'; +import { require as acequire } from 'ace-builds'; import 'ace-builds/src-noconflict/ext-language_tools'; import 'ace-builds/src-noconflict/ext-searchbox'; import { createContext, getAllOccurrencesInScope, getScope } from 'js-slang'; @@ -35,6 +36,7 @@ export interface IEditorProps { handleEditorValueChange: (newCode: string) => void; handleEditorUpdateBreakpoints: (breakpoints: string[]) => void; handleFinishInvite?: () => void; + handlePromptAutocomplete: (row: number, col: number, callback: any) => void; handleSetWebsocketStatus?: (websocketStatus: number) => void; handleUpdateHasUnsavedChanges?: (hasUnsavedChanges: boolean) => void; } @@ -44,12 +46,23 @@ export interface IPosition { column: number; } +// This interface is actually unused but ace poorly documents this feature so +// we leave this here for reference. +export interface IAutocompletionResult { + caption: string; + value: string; + meta?: string; + docHTML?: string; + score?: number; +} + class Editor extends React.PureComponent { public ShareAce: any; public AceEditor: React.RefObject; private markerIds: number[]; private onChangeMethod: (newCode: string) => void; private onValidateMethod: (annotations: IAnnotation[]) => void; + private completer: {}; constructor(props: IEditorProps) { super(props); @@ -68,6 +81,13 @@ class Editor extends React.PureComponent { this.props.handleEditorEval(); } }; + + this.completer = { + getCompletions: (editor: any, session: any, pos: any, prefix: any, callback: any) => { + // console.log(pos); // Cursor col is insertion location i.e. last char col + 1 + this.props.handlePromptAutocomplete(pos.row + 1, pos.column, callback); + } + }; } public getBreakpoints() { @@ -117,6 +137,9 @@ class Editor extends React.PureComponent { // Change all info annotations to error annotations session.on('changeAnnotation', this.handleAnnotationChange(session)); + // Start autocompletion + acequire('ace/ext/language_tools').setCompleters([this.completer]); + // Has session ID if (this.props.editorSessionId !== '') { this.handleStartCollabEditing(editor); @@ -222,6 +245,8 @@ class Editor extends React.PureComponent { value={this.props.editorValue} width="100%" setOptions={{ + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, fontFamily: "'Inconsolata', 'Consolas', monospace" }} /> diff --git a/src/components/workspace/__tests__/Editor.tsx b/src/components/workspace/__tests__/Editor.tsx index 47cf7876a7..67070cd7a8 100755 --- a/src/components/workspace/__tests__/Editor.tsx +++ b/src/components/workspace/__tests__/Editor.tsx @@ -23,7 +23,8 @@ test('Editor renders correctly', () => { handleEditorUpdateBreakpoints: breakpoints => {}, handleFinishInvite: () => {}, handleSetWebsocketStatus: websocketStatus => {}, - handleUpdateHasUnsavedChanges: hasUnsavedChanges => {} + handleUpdateHasUnsavedChanges: hasUnsavedChanges => {}, + handlePromptAutocomplete: (row: number, col: number, callback: any) => {} }; const app = ; const tree = shallow(app); diff --git a/src/containers/ApplicationContainer.ts b/src/containers/ApplicationContainer.ts index 6cb33013d0..62ebd646c3 100644 --- a/src/containers/ApplicationContainer.ts +++ b/src/containers/ApplicationContainer.ts @@ -6,6 +6,7 @@ import { changeExecTime, ensureLibrariesLoaded, externalLibrarySelect, + promptAutocomplete, WorkspaceLocations } from '../actions/workspaces'; import Application, { IDispatchProps, IStateProps } from '../components/Application'; @@ -47,6 +48,8 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Di workspaceLocation ), handleEditorValueChange: (val: string) => updateEditorValue(val, workspaceLocation), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(workspaceLocation, row, col, callback), handleEditorUpdateBreakpoints: (breakpoints: string[]) => setEditorBreakpoint(breakpoints, workspaceLocation), handleEnsureLibrariesLoaded: ensureLibrariesLoaded, diff --git a/src/containers/PlaygroundContainer.ts b/src/containers/PlaygroundContainer.ts index 5d2826db87..ccc17de0a4 100755 --- a/src/containers/PlaygroundContainer.ts +++ b/src/containers/PlaygroundContainer.ts @@ -23,6 +23,7 @@ import { initInvite, invalidEditorSessionId, navigateToDeclaration, + promptAutocomplete, setEditorBreakpoint, setEditorSessionId, setWebsocketStatus, @@ -104,7 +105,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Di handleUsingSubst: (usingSubst: boolean) => toggleUsingSubst(usingSubst), handleDebuggerPause: () => beginDebuggerPause(workspaceLocation), handleDebuggerResume: () => debuggerResume(workspaceLocation), - handleDebuggerReset: () => debuggerReset(workspaceLocation) + handleDebuggerReset: () => debuggerReset(workspaceLocation), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(workspaceLocation, row, col, callback) }, dispatch ); diff --git a/src/containers/academy/grading/GradingWorkspaceContainer.ts b/src/containers/academy/grading/GradingWorkspaceContainer.ts index 9ba7d3305d..80c327e802 100644 --- a/src/containers/academy/grading/GradingWorkspaceContainer.ts +++ b/src/containers/academy/grading/GradingWorkspaceContainer.ts @@ -18,6 +18,7 @@ import { evalTestcase, fetchGrading, navigateToDeclaration, + promptAutocomplete, setEditorBreakpoint, updateActiveTab, updateEditorValue, @@ -101,7 +102,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis updateHasUnsavedChanges(workspaceLocation, unsavedChanges), handleDebuggerPause: () => beginDebuggerPause(workspaceLocation), handleDebuggerResume: () => debuggerResume(workspaceLocation), - handleDebuggerReset: () => debuggerReset(workspaceLocation) + handleDebuggerReset: () => debuggerReset(workspaceLocation), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(workspaceLocation, row, col, callback) }, dispatch ); diff --git a/src/containers/assessment/AssessmentWorkspaceContainer.ts b/src/containers/assessment/AssessmentWorkspaceContainer.ts index f8cdcb30e3..5324c4f1dc 100755 --- a/src/containers/assessment/AssessmentWorkspaceContainer.ts +++ b/src/containers/assessment/AssessmentWorkspaceContainer.ts @@ -19,6 +19,7 @@ import { evalTestcase, fetchAssessment, navigateToDeclaration, + promptAutocomplete, setEditorBreakpoint, submitAnswer, updateActiveTab, @@ -102,7 +103,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleUpdateCurrentAssessmentId: updateCurrentAssessmentId, handleDebuggerPause: () => beginDebuggerPause(workspaceLocation), handleDebuggerResume: () => debuggerResume(workspaceLocation), - handleDebuggerReset: () => debuggerReset(workspaceLocation) + handleDebuggerReset: () => debuggerReset(workspaceLocation), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(workspaceLocation, row, col, callback) }, dispatch ); diff --git a/src/containers/missionControl/EditingWorkspaceContainer.ts b/src/containers/missionControl/EditingWorkspaceContainer.ts index 595d259fb0..1c9302dcc5 100644 --- a/src/containers/missionControl/EditingWorkspaceContainer.ts +++ b/src/containers/missionControl/EditingWorkspaceContainer.ts @@ -18,6 +18,7 @@ import { evalRepl, evalTestcase, navigateToDeclaration, + promptAutocomplete, setEditorBreakpoint, submitAnswer, updateEditorValue, @@ -95,7 +96,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Dis handleUpdateCurrentAssessmentId: updateCurrentAssessmentId, handleDebuggerPause: () => beginDebuggerPause(workspaceLocation), handleDebuggerResume: () => debuggerResume(workspaceLocation), - handleDebuggerReset: () => debuggerReset(workspaceLocation) + handleDebuggerReset: () => debuggerReset(workspaceLocation), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(workspaceLocation, row, col, callback) }, dispatch ); diff --git a/src/containers/sourcecast/SourcecastContainer.ts b/src/containers/sourcecast/SourcecastContainer.ts index 81e32941c6..e9756fb939 100755 --- a/src/containers/sourcecast/SourcecastContainer.ts +++ b/src/containers/sourcecast/SourcecastContainer.ts @@ -18,6 +18,7 @@ import { externalLibrarySelect, fetchSourcecastIndex, navigateToDeclaration, + promptAutocomplete, setCodeDeltasToApply, setEditorBreakpoint, setEditorReadonly, @@ -112,7 +113,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Di handleToggleEditorAutorun: () => toggleEditorAutorun(location), handleDebuggerPause: () => beginDebuggerPause(location), handleDebuggerResume: () => debuggerResume(location), - handleDebuggerReset: () => debuggerReset(location) + handleDebuggerReset: () => debuggerReset(location), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(location, row, col, callback) }, dispatch ); diff --git a/src/containers/sourcecast/SourcereelContainer.ts b/src/containers/sourcecast/SourcereelContainer.ts index 21cd9d2d1b..db5ab9220c 100755 --- a/src/containers/sourcecast/SourcereelContainer.ts +++ b/src/containers/sourcecast/SourcereelContainer.ts @@ -19,6 +19,7 @@ import { externalLibrarySelect, fetchSourcecastIndex, navigateToDeclaration, + promptAutocomplete, recordInit, recordInput, saveSourcecastData, @@ -108,7 +109,9 @@ const mapDispatchToProps: MapDispatchToProps = (dispatch: Di handleToggleEditorAutorun: () => toggleEditorAutorun(location), handleDebuggerPause: () => beginDebuggerPause(location), handleDebuggerResume: () => debuggerResume(location), - handleDebuggerReset: () => debuggerReset(location) + handleDebuggerReset: () => debuggerReset(location), + handlePromptAutocomplete: (row: number, col: number, callback: any) => + promptAutocomplete(location, row, col, callback) }, dispatch ); diff --git a/src/reducers/documentation.ts b/src/reducers/documentation.ts new file mode 100644 index 0000000000..7604cc28ff --- /dev/null +++ b/src/reducers/documentation.ts @@ -0,0 +1,55 @@ +import { SourceDocumentation } from 'js-slang'; +import { externalLibraries } from './externalLibraries'; + +const externalLibrariesDocumentation = {}; + +const MAX_CAPTION_LENGTH = 25; + +function shortenCaption(name: string): string { + if (name.length <= MAX_CAPTION_LENGTH) { + return name; + } + + return (name = name.substring(0, MAX_CAPTION_LENGTH - 3) + '...'); +} + +for (const [lib, names] of externalLibraries) { + const libDocs = names.map((name: string) => { + if (name in SourceDocumentation.ext_lib) { + return { + caption: shortenCaption(name), + value: name, + meta: SourceDocumentation.ext_lib[name].meta, + docHTML: SourceDocumentation.ext_lib[name].description + }; + } else { + return { + caption: shortenCaption(name), + value: name, + meta: 'const' + }; + } + }); + + externalLibrariesDocumentation[lib] = libDocs; +} + +const builtinDocumentation = {}; + +Object.entries(SourceDocumentation.builtins).forEach((chapterDoc: any) => { + const [chapter, docs] = chapterDoc; + builtinDocumentation[chapter] = Object.entries(docs).map((entry: any) => { + const [name, info] = entry; + return { + caption: shortenCaption(name), + value: name, + meta: info.meta, + docHTML: info.description + }; + }); +}); + +export const Documentation = { + builtins: builtinDocumentation, + externalLibraries: externalLibrariesDocumentation +}; diff --git a/src/sagas/workspaces.ts b/src/sagas/workspaces.ts index c8974fade2..b4b64eae7b 100644 --- a/src/sagas/workspaces.ts +++ b/src/sagas/workspaces.ts @@ -1,4 +1,5 @@ import { Context, findDeclaration, interrupt, resume, runInContext } from 'js-slang'; +import { getNames } from 'js-slang'; import { InterruptedError } from 'js-slang/dist/errors/errors'; import { manualToggleDebugger } from 'js-slang/dist/stdlib/inspector'; import { random } from 'lodash'; @@ -13,6 +14,7 @@ import { TestcaseType, TestcaseTypes } from '../components/assessment/assessmentShape'; +import { Documentation } from '../reducers/documentation'; import { externalLibraries } from '../reducers/externalLibraries'; import { IPlaygroundState, IState, IWorkspaceState, SideContentType } from '../reducers/states'; import { showSuccessMessage, showWarningMessage } from '../utils/notification'; @@ -96,6 +98,61 @@ export default function* workspaceSaga(): SagaIterator { yield* evalCode(value, context, execTime, workspaceLocation, actionTypes.EVAL_EDITOR); }); + yield takeEvery(actionTypes.PROMPT_AUTOCOMPLETE, function*( + action: ReturnType + ) { + const workspaceLocation = action.payload.workspaceLocation; + + context = yield select( + (state: IState) => (state.workspaces[workspaceLocation] as IWorkspaceState).context + ); + + const code: string = yield select((state: IState) => { + const prependCode = (state.workspaces[workspaceLocation] as IWorkspaceState).editorPrepend; + const editorCode = (state.workspaces[workspaceLocation] as IWorkspaceState).editorValue!; + return [prependCode, editorCode] as [string, string]; + }); + const [prepend, editorValue] = code; + + // Deal with prepended code + let autocompleteCode; + let prependLength = 0; + if (!prepend) { + autocompleteCode = editorValue; + } else { + prependLength = prepend.split('\n').length; + autocompleteCode = prepend + '\n' + editorValue; + } + + const editorNames: any = yield call( + getNames, + autocompleteCode, + action.payload.row + prependLength, + action.payload.column + ); + + const editorSuggestions = editorNames.map((name: any) => ({ + caption: name.name, + value: name.name, + meta: name.meta, + score: 1000 // Prioritize suggestions from code + })); + + const builtinSuggestions = Documentation.builtins[context.chapter] || []; + + const extLib = yield select( + (state: IState) => (state.workspaces[workspaceLocation] as IWorkspaceState).externalLibrary + ); + + const extLibSuggestions = Documentation.externalLibraries[extLib] || []; + + yield call( + action.payload.callback, + null, + editorSuggestions.concat(builtinSuggestions, extLibSuggestions) + ); + }); + yield takeEvery(actionTypes.TOGGLE_EDITOR_AUTORUN, function*( action: ReturnType ) {