Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 41 additions & 0 deletions .github/workflows/update-p5-hinter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Update p5 hinter

# Keeps autocomplete and syntax highlighting in sync with the p5.js reference
# by regenerating client/utils/p5-hinter-v1.js and p5-hinter-v2.js.
on:
# Mondays 15:30 UTC
schedule:
- cron: '30 15 * * 1'
workflow_dispatch: {}

permissions:
contents: write
pull-requests: write

jobs:
update:
if: github.repository == 'processing/p5.js-web-editor'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
ref: develop
- name: Use Node.js
uses: actions/setup-node@v7
with:
node-version: '18.20.x'
- run: npm ci
- run: npm run update-p5-hinter
- name: Open pull request if the reference changed
uses: peter-evans/create-pull-request@v8
with:
branch: automated/update-p5-hinter
base: develop
add-paths: client/utils/p5-hinter-*.js
commit-message: Update p5 hinter from the p5.js reference
title: Update p5 hinter from the p5.js reference
body: |
Automated update from `npm run update-p5-hinter`, which regenerates the autocomplete and syntax highlighting keywords from the p5.js reference ([v1](https://v1.p5js.org/reference/data.json), [v2](https://p5js.org/reference/data.json)).

Created by the [Update p5 hinter](../actions/workflows/update-p5-hinter.yml) workflow.
delete-branch: true
60 changes: 37 additions & 23 deletions client/modules/IDE/components/Editor/utils/p5JavaScript.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ import { p5HinterV1 } from '../../../../../utils/p5-hinter-v1';
import { p5HinterV2 } from '../../../../../utils/p5-hinter-v2';
import { completionPreview } from './completionPreview';
import contextAwareHinter from '../../../../../utils/contextAwareHinter';
import {
p5FunctionKeywords,
p5VariableKeywords
} from '../../../../../utils/p5-keywords';

const p5Functions = new Set(Object.keys(p5FunctionKeywords));
const p5Variables = new Set(Object.keys(p5VariableKeywords));
// The hinter files are generated from the p5.js reference for each major
// version, so the highlighted keywords always match the sketch's version.
function getKeywords(hints) {
const functions = new Set();
const variables = new Set();
hints.forEach(({ label, type }) => {
if (type === 'method') {
functions.add(label);
} else if (type === 'variable' || type === 'constant') {
variables.add(label);
}
});
return { functions, variables };
}

const p5FunctionMark = Decoration.mark({ class: 'cm-p5-function' });
const p5VariableMark = Decoration.mark({ class: 'cm-p5-variable' });

// Used to add highlighting to the p5-specific keywords.
function buildHighlightDecorations(view) {
function buildHighlightDecorations(view, { functions, variables }) {
const builder = new RangeSetBuilder();
view.visibleRanges.forEach(({ from, to }) => {
syntaxTree(view.state).iterate({
Expand All @@ -29,9 +37,9 @@ function buildHighlightDecorations(view) {
const isDefinition = node.name === 'VariableDefinition';
if (!isVariable && !isDefinition) return;
const name = view.state.doc.sliceString(node.from, node.to);
if (p5Functions.has(name)) {
if (functions.has(name)) {
builder.add(node.from, node.to, p5FunctionMark);
} else if (p5Variables.has(name)) {
} else if (variables.has(name)) {
builder.add(node.from, node.to, p5VariableMark);
}
}
Expand All @@ -40,25 +48,31 @@ function buildHighlightDecorations(view) {
return builder.finish();
}

const p5Highlight = ViewPlugin.fromClass(
class {
constructor(view) {
this.decorations = buildHighlightDecorations(view);
}
function p5Highlight(keywords) {
return ViewPlugin.fromClass(
class {
constructor(view) {
this.decorations = buildHighlightDecorations(view, keywords);
}

update(update) {
if (update.docChanged || update.viewportChanged) {
this.decorations = buildHighlightDecorations(update.view);
update(update) {
if (update.docChanged || update.viewportChanged) {
this.decorations = buildHighlightDecorations(update.view, keywords);
}
}
}
},
{ decorations: (v) => v.decorations }
);
},
{ decorations: (v) => v.decorations }
);
}

const p5HighlightV1 = p5Highlight(getKeywords(p5HinterV1));
const p5HighlightV2 = p5Highlight(getKeywords(p5HinterV2));

export function p5JavaScript(p5Version) {
const jsLang = javascript();

const hints = p5Version?.startsWith('2.') ? p5HinterV2 : p5HinterV1;
const isV2 = p5Version?.startsWith('2.');
const hints = isV2 ? p5HinterV2 : p5HinterV1;

function addCompletions(context) {
const word = context.matchBefore(/\w*/);
Expand All @@ -78,6 +92,6 @@ export function p5JavaScript(p5Version) {
autocomplete: addCompletions
}),
completionPreview(),
p5Highlight
isV2 ? p5HighlightV2 : p5HighlightV1
]);
}
55 changes: 55 additions & 0 deletions client/modules/IDE/components/Editor/utils/p5JavaScript.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @jest-environment jsdom
*/
import { EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { p5JavaScript } from './p5JavaScript';

const doc = [
'ellipse(0, 0, 10);',
'curveVertex(0, 0);',
'smoothstep(0, 1, 0.5);',
'colorMode(OKLCH);',
'let x = mouseX;',
'myFunction();'
].join('\n');

function getHighlighted(p5Version) {
const parent = document.createElement('div');
document.body.appendChild(parent);
const view = new EditorView({
state: EditorState.create({
doc,
extensions: [p5JavaScript(p5Version)]
}),
parent
});
const functions = [...parent.querySelectorAll('.cm-p5-function')].map(
(el) => el.textContent
);
const variables = [...parent.querySelectorAll('.cm-p5-variable')].map(
(el) => el.textContent
);
view.destroy();
parent.remove();
return { functions, variables };
}

describe('p5JavaScript syntax highlighting', () => {
it('highlights p5.js v1 keywords for a v1 sketch', () => {
const { functions, variables } = getHighlighted('1.11.13');
expect(functions).toEqual(['ellipse', 'curveVertex', 'colorMode']);
expect(variables).toEqual(['mouseX']);
});

it('highlights p5.js v2 keywords for a v2 sketch', () => {
const { functions, variables } = getHighlighted('2.0.5');
expect(functions).toEqual(['ellipse', 'smoothstep', 'colorMode']);
expect(variables).toEqual(['OKLCH', 'mouseX']);
});

it('falls back to v1 keywords when the version is unknown', () => {
const { functions } = getHighlighted(undefined);
expect(functions).toEqual(['ellipse', 'curveVertex', 'colorMode']);
});
});
2 changes: 1 addition & 1 deletion client/utils/p5-hinter-v2.js

Large diffs are not rendered by default.

Loading