Skip to content

introducing context awareness to the autocomplete hinter in addition to context aware renaming, jump to definition and other refactoring changes (draft work for review) #3594

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

Draft
wants to merge 41 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a79c2f1
context-aware hinter code
kammeows Jun 5, 2025
3e84dd5
Merge pull request #1 from kammeows/Branch_Kamakshi
kammeows Jun 5, 2025
5fc1c64
Merge branch 'processing:develop' into develop
kammeows Jun 5, 2025
9c4bc52
context-aware hinter code
kammeows Jun 5, 2025
cf71058
parse code with babel and warn when blacklisted function focused
kammeows Jun 8, 2025
e8bf752
updated the hinter to suggest class methods
kammeows Jun 13, 2025
819b9cb
Merge pull request #2 from kammeows/Branch_Kamakshi
kammeows Jun 13, 2025
5559967
user-defined context-aware variables, functions & classes suggestion
kammeows Jun 25, 2025
2c132e6
Merge branch 'processing:develop' into develop
kammeows Jun 25, 2025
761657a
Merge pull request #3 from kammeows/Branch_Kamakshi
kammeows Jun 25, 2025
b0ec598
added listOfAllFunctions.json
kammeows Jun 28, 2025
87d89a5
Merge pull request #4 from kammeows/Branch_Kamakshi
kammeows Jun 28, 2025
d28914a
implement context aware renaming + fixed bug to show inline hint sugg…
kammeows Jul 9, 2025
b0a0feb
fixed autocomplete bug
kammeows Jul 9, 2025
5120dce
bug fix: ensure renaming doesnt happen with commented code and keywords
kammeows Jul 12, 2025
bdb7d86
minor changes to ensure all features work smoothly
kammeows Jul 16, 2025
e3c7f18
fixed a minor bug in autocomplete method suggestions
kammeows Jul 17, 2025
093dccc
Merge pull request #5 from kammeows/Branch_Kamakshi
kammeows Jul 17, 2025
ea39d0e
implement jump to definition basic functionality
kammeows Jul 22, 2025
a627fde
Merge branch 'Branch_Kamakshi' of https://github.com/kammeows/p5.js-w…
kammeows Jul 24, 2025
990712a
Merge branch 'develop' into Branch_Kamakshi
kammeows Jul 24, 2025
b006d5d
Merge pull request #8 from kammeows/Branch_Kamakshi
kammeows Jul 24, 2025
f94b2fb
remove console statements
kammeows Jul 24, 2025
a7229da
add descriptive names for files & add a readme for new changes
kammeows Jul 25, 2025
f30b884
fix autocomplete and renaming behavior for same var and func name
kammeows Jul 28, 2025
01454d0
fix autocomplete suggestions behavior for same function and variable …
kammeows Jul 29, 2025
a67e89d
fix renaming behavior with same function name in different contexts
kammeows Jul 29, 2025
ce4ae68
fix logic for correct behavior of jump to definition with same functi…
kammeows Jul 30, 2025
2415953
fix renaming behavior
kammeows Aug 9, 2025
1779145
implement screen reader behavior for context aware renaming and jump …
kammeows Aug 9, 2025
c50566f
fix local renaming bug
kammeows Aug 10, 2025
88a9c65
Merge branch 'develop' of https://github.com/processing/p5.js-web-edi…
kammeows Aug 10, 2025
9172902
Merge pull request #10 from kammeows/processing-develop
kammeows Aug 10, 2025
0665cc6
add jump to definition from one file to another file
kammeows Aug 11, 2025
293b49e
modify jump to definition to work according to runtime behavior
kammeows Aug 13, 2025
842c46b
renaming logic for classes
kammeows Aug 17, 2025
a0569d0
fixes on renaming logic for classes and methods along with other mino…
kammeows Aug 18, 2025
fdcd37a
final edits to clean the code
kammeows Aug 23, 2025
26442f4
Merge branch 'develop' into develop
kammeows Aug 23, 2025
ca26d11
Merge branch 'develop' of https://github.com/kammeows/p5.js-web-edito…
kammeows Aug 23, 2025
77465e9
update files to ensure consistency and fix test errors
kammeows Aug 23, 2025
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
83 changes: 83 additions & 0 deletions README_hinter_and_refactoring_changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# GSoC 2025: p5.js Autocomplete Hinter & Refactoring System
This readme elaborates on the core components of the context-aware autocomplete hinter, refactoring utilities, and supporting data structures developed as part of Google Summer of Code 2025. The goal is to enable smart context-aware autocompletion, jump-to-definition, and safe variable renaming.

# Project Overview

## Autocomplete Hinter Context-Aware Functionality
The following files and modules work together to make the p5.js autocomplete hinter context-aware:

### p5CodeAstAnalyzer.js
Purpose: Parses user-written p5.js code using Babel and extracts structural information:

- Maps variable names to p5 class instances
- Tracks declared variables in each function or global scope
- Detects user-defined functions and their parameters
- Collects info about user-defined classes, constructor-assigned properties, and methods

Key Output Maps:

- variableToP5ClassMap: Maps variable names (e.g., col) to their p5.js class type (e.g., p5.Color)
- scopeToDeclaredVarsMap: Maps function names or global scope to variables declared in them
- userDefinedFunctionMetadata: Metadata about custom functions (params, type, etc.)
- userDefinedClassMetadata: Metadata for user-defined classes (methods, constructor properties)

### context-aware-hinter.js
Purpose: Provides code autocompletion hints based on:

- Current cursor context (draw, setup, etc.)
- p5CodeAstAnalyzer output
- p5 class method definitions
- Variable/function scope and visibility
- Scope-specific blacklist/whitelist logic

Features:

- Dot-autocompletion (e.g., col. shows methods of p5.Color)
- Scope-sensitive variable/function suggestions
- Ranks hints by type and scope relevance

### getContext.js
Purpose: Get the context of the cursor, i.e. inside what function is the cursor in

## Context-Aware Renaming Functionality
The following files ensure context-aware renaming when a variable or user-defined function is selected and the F2 button is clicked

### rename-variable.js
Purpose: Safely renames a variable in the user's code editor by:

- Analyzing AST to find all matching identifiers
- Ensuring replacement only occurs within the same lexical scope
- Performing in-place replacement using CodeMirror APIs

### showRenameDialog.jsx
Purpose: Opens either a dialog box to get the new variable name or a temporary box to show that the word selected cannot be renamed

## Jump to Definition
The following file allows user to jump to the definition for variables or parameters when a word is ctrl-clicked.

### jumptodefinition.js
Purpose: Implements “jump to definition” for variables or parameters in the editor.

How It Works:

- Uses AST + scope map to locate the definition site of a variable
- Supports both VariableDeclarator and FunctionDeclaration/params
- Moves the editor cursor to the source location of the definition

## Supporting Data Files
### p5-instance-methods-and-creators.json
Purpose: Maps p5.js classes to:

- Methods used to instantiate them (createMethods)
- Methods available on those instances (methods)

### p5-scope-function-access-map.json
Purpose: Defines which p5.js functions are allowed or disallowed inside functions like setup, draw, preload, etc.

### p5-reference-functions.json
Purpose: A flat list of all available p5.js functions.

Used to:

- Differentiate between built-in and user-defined functions
- Filter out redefinitions or incorrect hints

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep the README as a separate GitHub Gist, and not include it in the final PR to be merged.

2 changes: 2 additions & 0 deletions client/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ render(
</Provider>,
document.getElementById('root')
);

export default store;
105 changes: 74 additions & 31 deletions client/modules/IDE/components/Editor/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ import { EditorContainer, EditorHolder } from './MobileEditor';
import { FolderIcon } from '../../../../common/icons';
import IconButton from '../../../../common/IconButton';

import contextAwareHinter from '../contextAwareHinter';
import showRenameDialog from '../showRenameDialog';
import { handleRename } from '../rename-variable';
import { jumpToDefinition } from '../jump-to-definition';
import { ensureAriaLiveRegion } from '../../utils/ScreenReaderHelper';

emmet(CodeMirror);

window.JSHINT = JSHINT;
Expand Down Expand Up @@ -109,6 +115,7 @@ class Editor extends React.Component {

componentDidMount() {
this.beep = new Audio(beepUrl);
ensureAriaLiveRegion();
// this.widgets = [];
this._cm = CodeMirror(this.codemirrorContainer, {
theme: `p5-${this.props.theme}`,
Expand Down Expand Up @@ -154,6 +161,16 @@ class Editor extends React.Component {

delete this._cm.options.lint.options.errors;

this._cm.getWrapperElement().addEventListener('click', (e) => {
const isMac = /Mac/.test(navigator.platform);
const isCtrlClick = isMac ? e.metaKey : e.ctrlKey;

if (isCtrlClick) {
const pos = this._cm.coordsChar({ left: e.clientX, top: e.clientY });
jumpToDefinition.call(this, pos);
}
});

const replaceCommand =
metaKey === 'Ctrl' ? `${metaKey}-H` : `${metaKey}-Option-F`;
this._cm.setOption('extraKeys', {
Expand All @@ -172,6 +189,7 @@ class Editor extends React.Component {
[`Shift-${metaKey}-E`]: (cm) => {
cm.getInputField().blur();
},
F2: (cm) => this.renameVariable(cm),
[`Shift-Tab`]: false,
[`${metaKey}-Enter`]: () => null,
[`Shift-${metaKey}-Enter`]: () => null,
Expand Down Expand Up @@ -209,7 +227,13 @@ class Editor extends React.Component {
}

this._cm.on('keydown', (_cm, e) => {
// Show hint
if (
((e.ctrlKey || e.metaKey) && e.key === 'v') ||
e.ctrlKey ||
e.altKey
) {
return;
}
const mode = this._cm.getOption('mode');
if (/^[a-z]$/i.test(e.key) && (mode === 'css' || mode === 'javascript')) {
this.showHint(_cm);
Expand Down Expand Up @@ -395,12 +419,15 @@ class Editor extends React.Component {
}

showHint(_cm) {
if (!_cm) return;

if (!this.props.autocompleteHinter) {
CodeMirror.showHint(_cm, () => {}, {});
return;
}

let focusedLinkElement = null;

const setFocusedLinkElement = (set) => {
if (set && !focusedLinkElement) {
const activeItemLink = document.querySelector(
Expand All @@ -415,6 +442,7 @@ class Editor extends React.Component {
}
}
};

const removeFocusedLinkElement = () => {
if (focusedLinkElement) {
focusedLinkElement.classList.remove('focused-hint-link');
Expand All @@ -437,12 +465,8 @@ class Editor extends React.Component {
);
if (activeItemLink) activeItemLink.click();
},
Right: (cm, e) => {
setFocusedLinkElement(true);
},
Left: (cm, e) => {
removeFocusedLinkElement();
},
Right: (cm, e) => setFocusedLinkElement(true),
Left: (cm, e) => removeFocusedLinkElement(),
Up: (cm, e) => {
const onLink = removeFocusedLinkElement();
e.moveFocus(-1);
Expand All @@ -461,30 +485,28 @@ class Editor extends React.Component {
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)
.filter((h) => h.item.text[0] === token.string[0]);

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);
}
const triggerHints = () => {
if (_cm.options.mode === 'javascript') {
CodeMirror.showHint(
_cm,
() => {
const c = _cm.getCursor();
const token = _cm.getTokenAt(c);
const hints = contextAwareHinter(_cm, { hinter: this.hinter });
return {
list: hints,
from: CodeMirror.Pos(c.line, token.start),
to: CodeMirror.Pos(c.line, c.ch)
};
},
hintOptions
);
} else if (_cm.options.mode === 'css') {
CodeMirror.showHint(_cm, CodeMirror.hint.css, hintOptions);
}
};

setTimeout(triggerHints, 0);
}

showReplace() {
Expand Down Expand Up @@ -522,6 +544,27 @@ class Editor extends React.Component {
}
}

renameVariable(cm) {
const cursorCoords = cm.cursorCoords(true, 'page');
const selection = cm.getSelection();
const pos = cm.getCursor(); // or selection start
const token = cm.getTokenAt(pos);
const tokenType = token.type;
if (!selection) {
return;
}

const sel = cm.listSelections()[0];
const fromPos =
CodeMirror.cmpPos(sel.anchor, sel.head) <= 0 ? sel.anchor : sel.head;

showRenameDialog(tokenType, cursorCoords, selection, (newName) => {
if (newName && newName.trim() !== '' && newName !== selection) {
handleRename(fromPos, selection, newName, cm);
}
});
}

initializeDocuments(files) {
this._docs = {};
files.forEach((file) => {
Expand Down
Loading
Loading