Skip to content

Commit

Permalink
Python Quality of Life Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
ddimaria committed Mar 7, 2024
1 parent 95f61f4 commit 6dc1857
Show file tree
Hide file tree
Showing 68 changed files with 10,506 additions and 5,833 deletions.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
VITE_AUTH0_DOMAIN=
VITE_AUTH0_CLIENT_ID=
VITE_AUTH0_AUDIENCE=
VITE_AUTH0_ISSUER=
VITE_QUADRATIC_API_URL=http://localhost:8000
18 changes: 17 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ cargo install cargo-watch

## Local Environment Setup

First, copy over the example file:

```shell
cp .env.example .env.local
```

Enter the missing Auth0 values and save.

Now that dependencies are installed, all you need to do is run `node dev` to
bring up the all services. Invoke `node run --help` for information on how
to use this script, as you can use it to watch individual (or groups of)
Expand Down Expand Up @@ -92,7 +100,15 @@ docker-compose build quadratic-multiplayer

You can also develop Quadratic without using docker

* Set up .env in quadratic-client, quadratic-api, quadratic-multiplayer, and quadratic-files. (todo: better description of how to do this)
* Set up .env in quadratic-client, quadratic-api, quadratic-multiplayer, and quadratic-files.

```shell
cp .env.example .env.local
cp quadratic-client/.env_example quadratic-client/.env.local
cp quadratic-api/.env_example quadratic-api/.env
cp quadratic-multiplayer/.env.example quadratic-multiplayer/.env
cp quadratic-files/.env.example quadratic-files/.env
```

#### Installing PostgreSQL

Expand Down
8,635 changes: 3,460 additions & 5,175 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions quadratic-api/scripts/migrateFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,4 @@ migrateFiles()
console.log('Disconnected from database.');
console.log('Finished migrating files.');
});

18 changes: 14 additions & 4 deletions quadratic-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@
"tailwind-merge": "^2.0.0",
"tailwindcss-animate": "^1.0.7",
"ua-parser-js": "^1.0.37",
"uuid": "^9.0.0"
"uuid": "^9.0.0",
"vscode": "npm:@codingame/monaco-vscode-api@>=2.0.0 <2.1.0",
"vscode-jsonrpc": "^8.2.0",
"vscode-languageclient": "^9.0.1",
"vscode-languageserver-protocol": "^3.17.5"
},
"main": "public/electron.js",
"scripts": {
Expand Down Expand Up @@ -112,6 +116,7 @@
"@types/react-dom": "^18.2.7",
"@types/uuid": "^9.0.1",
"@vitejs/plugin-react": "^4.2.0",
"@vitest/web-worker": "^1.3.1",
"autoprefixer": "^10.4.16",
"electron": "^23.0.0",
"electron-is-dev": "^2.0.0",
Expand All @@ -122,18 +127,23 @@
"happy-dom": "^13.0.0",
"jest-environment-jsdom": "^29.7.0",
"msdf-bmfont-xml": "^2.7.0",
"msw": "^2.2.2",
"node-fetch": "^3.3.2",
"postcss": "^8.4.31",
"prettier-plugin-tailwindcss": "^0.4.1",
"pyodide": "^0.24.1",
"pyodide": "^0.25.0",
"sass": "^1.69.5",
"serve": "^14.2.0",
"tailwindcss": "^3.3.5",
"vite": "^5.0.0",
"vite": "^5.1.2",
"vite-plugin-checker": "^0.6.2",
"vite-plugin-top-level-await": "^1.4.1",
"vite-plugin-wasm": "^3.3.0",
"vite-tsconfig-paths": "^4.2.1",
"vitest": "^1.1.3"
"vitest": "^1.3.1",
"vscode-languageserver-types": "^3.17.5"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "^4.12.0"
}
}
3 changes: 3 additions & 0 deletions quadratic-client/public/pyright.worker.js

Large diffs are not rendered by default.

Binary file not shown.
1 change: 1 addition & 0 deletions quadratic-client/src/grid/computations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const CellEvaluationResultSchema = z.object({
success: z.boolean(),
std_out: z.string().optional(),
std_err: z.string().optional(),
output_type: z.string().or(z.null()).or(z.undefined()),
output_value: z.string().or(z.null()).or(z.undefined()),
cells_accessed: z.tuple([z.number(), z.number()]).array(),
array_output: ArrayOutputSchema,
Expand Down
4 changes: 2 additions & 2 deletions quadratic-client/src/helpers/parseEditorPythonCell.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ParseFormulaReturnType } from './formulaNotation';

const CELL = /\(\s*(-?\d+\s*,\s*-?\d+\s*)\)/;
const SIMPLE_CELL = new RegExp(`cell${CELL.source}`, 'g');
const MULTICURSOR_CELL = new RegExp(`cells\\(\\s*${CELL.source}\\s*,\\s*${CELL.source}\\s*\\)`, 'g');
const SIMPLE_CELL = new RegExp(`[cell|c|getCell]${CELL.source}`, 'g');
const MULTICURSOR_CELL = new RegExp(`[cells|getCells]\\(\\s*${CELL.source}\\s*,\\s*${CELL.source}\\s*\\)`, 'g');

export function parsePython(modelContent: string) {
let matches: RegExpExecArray | null;
Expand Down
12 changes: 12 additions & 0 deletions quadratic-client/src/ui/menus/CodeEditor/CodeEditor.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.codeEditorReturnHighlight {
/* font-weight: medium; */
}

.codeEditorReturnLineDecoration::after {
content: '\23CE';
color: black;
font-family: OpenSans;
position: absolute;
left: 5px;
transform: scaleX(-1);
}
40 changes: 38 additions & 2 deletions quadratic-client/src/ui/menus/CodeEditor/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
import { pythonStateAtom } from '@/atoms/pythonStateAtom';
import { Coordinate } from '@/gridGL/types/size';
import { multiplayer } from '@/multiplayer/multiplayer';
import { Pos } from '@/quadratic-core/types';
import { ComputedPythonReturnType } from '@/web-workers/pythonWebWorker/pythonTypes';
import mixpanel from 'mixpanel-browser';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Diagnostic } from 'vscode-languageserver-types';
import { hasPermissionToEditFile } from '../../../actions';
import { editorInteractionStateAtom } from '../../../atoms/editorInteractionStateAtom';
import { grid } from '../../../grid/controller/Grid';
import { pixiApp } from '../../../gridGL/pixiApp/PixiApp';
import { focusGrid } from '../../../helpers/focusGrid';
import { pythonWebWorker } from '../../../web-workers/pythonWebWorker/python';
import './CodeEditor.css';
import { CodeEditorBody } from './CodeEditorBody';
import { CodeEditorHeader } from './CodeEditorHeader';
import { Console } from './Console';
Expand All @@ -36,6 +40,8 @@ export const CodeEditor = () => {
const [consoleHeight, setConsoleHeight] = useState<number>(200);
const [showSaveChangesAlert, setShowSaveChangesAlert] = useState(false);
const [editorContent, setEditorContent] = useState<string | undefined>(codeString);
const [codeEditorReturn, setCodeEditorReturn] = useState<ComputedPythonReturnType | undefined>(undefined);
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([]);

const cellLocation = useMemo(() => {
return {
Expand Down Expand Up @@ -88,12 +94,13 @@ export const CodeEditor = () => {
editorInteractionState.selectedCell.x,
editorInteractionState.selectedCell.y
);

if (codeCell) {
setCodeString(codeCell.code_string);
setOut({ stdOut: codeCell.std_out ?? undefined, stdErr: codeCell.std_err ?? undefined });
if (updateEditorContent) setEditorContent(codeCell.code_string);
setEvaluationResult(codeCell.evaluation_result);
setSpillError(codeCell.spill_error?.map((c) => ({ x: Number(c.x), y: Number(c.y) })));
setSpillError(codeCell.spill_error?.map((c: Pos) => ({ x: Number(c.x), y: Number(c.y) } as Coordinate)));
} else {
setCodeString('');
if (updateEditorContent) setEditorContent('');
Expand All @@ -118,6 +125,23 @@ export const CodeEditor = () => {
};
}, [updateCodeCell]);

// listen for python-inspect-results in order to display python inspection results
useEffect(() => {
const updateType = (e: Event) => setCodeEditorReturn((e as CustomEvent).detail);
window.addEventListener('python-inspect-results', updateType);
return () => {
window.removeEventListener('python-inspect-results', updateType);
};
}, [updateCodeCell]);

useEffect(() => {
const updateDiagnostics = (e: Event) => setDiagnostics((e as CustomEvent).detail.diagnostics);
window.addEventListener('python-diagnostics', updateDiagnostics);
return () => {
window.removeEventListener('python-diagnostics', updateDiagnostics);
};
}, [updateCodeCell]);

useEffect(() => {
mixpanel.track('[CodeEditor].opened', { type: editorMode });
multiplayer.sendCellEdit('', 0, true);
Expand Down Expand Up @@ -154,16 +178,20 @@ export const CodeEditor = () => {

const saveAndRunCell = async () => {
const language = editorInteractionState.mode;

if (language === undefined)
throw new Error(`Language ${editorInteractionState.mode} not supported in CodeEditor#saveAndRunCell`);

grid.setCodeCellValue({
sheetId: cellLocation.sheetId,
x: cellLocation.x,
y: cellLocation.y,
codeString: editorContent ?? '',
language,
});

setCodeString(editorContent ?? '');

mixpanel.track('[CodeEditor].cellRun', {
type: editorMode,
});
Expand Down Expand Up @@ -280,7 +308,13 @@ export const CodeEditor = () => {
cancelPython={cancelPython}
closeEditor={() => closeEditor(false)}
/>
<CodeEditorBody editorContent={editorContent} setEditorContent={setEditorContent} closeEditor={closeEditor} />
<CodeEditorBody
editorContent={editorContent}
setEditorContent={setEditorContent}
closeEditor={closeEditor}
codeEditorReturn={codeEditorReturn}
diagnostics={diagnostics}
/>
<ResizeControl setState={setConsoleHeight} position="TOP" />

{/* Console Wrapper */}
Expand All @@ -301,6 +335,8 @@ export const CodeEditor = () => {
editorContent={editorContent}
evaluationResult={evaluationResult}
spillError={spillError}
hasUnsavedChanges={unsaved}
codeEditorReturn={codeEditorReturn}
/>
)}
</div>
Expand Down
42 changes: 36 additions & 6 deletions quadratic-client/src/ui/menus/CodeEditor/CodeEditorBody.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,58 @@
import { ComputedPythonReturnType } from '@/web-workers/pythonWebWorker/pythonTypes';
import Editor, { Monaco } from '@monaco-editor/react';
import monaco from 'monaco-editor';
import monaco, { editor } from 'monaco-editor';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useRecoilValue } from 'recoil';
import { Diagnostic } from 'vscode-languageserver-types';
import { hasPermissionToEditFile } from '../../../actions';
import { editorInteractionStateAtom } from '../../../atoms/editorInteractionStateAtom';
import { provideCompletionItems, provideHover } from '../../../quadratic-core/quadratic_core';
import { pyrightWorker, uri } from '../../../web-workers/pythonLanguageServer/worker';
import { CodeEditorPlaceholder } from './CodeEditorPlaceholder';
import { FormulaLanguageConfig, FormulaTokenizerConfig } from './FormulaLanguageModel';
import { provideCompletionItems as provideCompletionItemsPython } from './PythonLanguageModel';
import {
provideCompletionItems as provideCompletionItemsPython,
provideHover as provideHoverPython,
provideSignatureHelp as provideSignatureHelpPython,
} from './PythonLanguageModel';
import { QuadraticEditorTheme } from './quadraticEditorTheme';
import { useEditorCellHighlights } from './useEditorCellHighlights';
// import { useEditorDiagnostics } from './useEditorDiagnostics';
import { useEditorOnSelectionChange } from './useEditorOnSelectionChange';
import { useEditorReturn } from './useEditorReturn';

// todo: fix types

interface Props {
editorContent: string | undefined;
setEditorContent: (value: string | undefined) => void;
closeEditor: (skipSaveCheck: boolean) => void;
codeEditorReturn?: ComputedPythonReturnType;
diagnostics?: Diagnostic[];
}

export const CodeEditorBody = (props: Props) => {
const { editorContent, setEditorContent, closeEditor } = props;
const { editorContent, setEditorContent, closeEditor, codeEditorReturn } = props;

const editorInteractionState = useRecoilValue(editorInteractionStateAtom);
const language = editorInteractionState.mode;
const readOnly = !hasPermissionToEditFile(editorInteractionState.permissions);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
const monacoRef = useRef<Monaco | null>(null);

const [didMount, setDidMount] = useState(false);
const [isValidRef, setIsValidRef] = useState(false);

const language = editorInteractionState.mode;

useEditorCellHighlights(isValidRef, editorRef, monacoRef, language);
useEditorOnSelectionChange(isValidRef, editorRef, monacoRef, language);
useEditorReturn(isValidRef, editorRef, monacoRef, language, codeEditorReturn);

// TODO(ddimaria): leave this as we're looking to add this back in once improved
// useEditorDiagnostics(isValidRef, editorRef, monacoRef, language, diagnostics);

// TODO(ddimaria): this looks like a better pattern than the current one for
// the language model, consider moving to this
// useLanguageServer(isValidRef, editorRef, monacoRef, language);

useEffect(() => {
if (editorInteractionState.showCodeEditor) {
Expand Down Expand Up @@ -67,6 +85,17 @@ export const CodeEditorBody = (props: Props) => {
monaco.languages.register({ id: 'python' });
monaco.languages.registerCompletionItemProvider('python', {
provideCompletionItems: provideCompletionItemsPython,
triggerCharacters: ['.', '[', '"', "'"],
});
monaco.languages.registerSignatureHelpProvider('python', {
provideSignatureHelp: provideSignatureHelpPython,
signatureHelpTriggerCharacters: ['(', ','],
});
monaco.languages.registerHoverProvider('python', { provideHover: provideHoverPython });

// load the document in the python language server
pyrightWorker?.openDocument({
textDocument: { text: editorRef.current?.getValue() ?? '', uri, languageId: 'python' },
});

setDidMount(true);
Expand Down Expand Up @@ -104,6 +133,7 @@ export const CodeEditorBody = (props: Props) => {
onChange={setEditorContent}
onMount={onMount}
options={{
theme: 'light',
readOnly,
minimap: { enabled: true },
overviewRulerLanes: 0,
Expand Down
Loading

0 comments on commit 6dc1857

Please sign in to comment.