New Code Node Proposal #688
Replies: 6 comments 16 replies
-
I like this, especially hooking into the highlighting library so that we don't have to worry about supporting each language at a library (or It might also be good to look into different diffing strategies. Maybe we can even hook into const myCode = createRef<Code>();
myCode().parser(python);
// Set the initial code value
myCode().edit("print('Hello, world!')");
// Add the code for the function definition over 2 seconds
yield* myCode().edit("main()
def main():
print('Hello, world!')
", 2); If I want more control over what that transition looks like, I can use the more low-level editing functions. Another proposal: it'd be good to have some sort of API for getting the bounding boxes of With regard to the decoration API, here's what I was looking at for the individual decoration interface. I don't think that this needs to be the solution there, but I did a dive into what some of the decorations would need and this is what I landed on: export interface CodeDecoration {
/**
* The range that this decoration should affect.
*/
range: CodeRange[] | SignalValue<CodeRange[]>;
/**
* If this decoration handles drawing the text itself. Multiple decorations of this type on the
* same range are mutually exclusive.
*/
needsToDrawText: boolean;
/**
* Text is drawn at 0;
* Anything that needs to manipulate the context or draw before text should be negative.
* Anything that needs to draw on top of text should be positive.
*/
priority: number;
/**
* Draw the decoration on the text, or if needsToDrawText is set, draws the text.
*/
draw(
node: CodeBlock,
position: Vector2,
progress: number,
char: string,
lineHeight: number,
width: number,
context: CanvasRenderingContext2D,
): void;
/**
* Called on negative-priority decorations in inverse order once the text has been drawn.
*/
cleanUp(
node: CodeBlock,
position: Vector2,
char: string,
lineHeight: number,
width: number,
context: CanvasRenderingContext2D,
): void;
} |
Beta Was this translation helpful? Give feedback.
-
The reason for using |
Beta Was this translation helpful? Give feedback.
-
Using numbers to identify locations will make changes to the in-video code difficult. Imagine if you have 30+ code transitions and you decide to add a line. You'd have to visit all the modifications and edit the line number to fix their locations. I think the API should allow for string matching. Something like insert(['func(', ')'], 'param')
replace('param', 'arg')
remove('arg')
// Or, for extra context
replace(['func(', 'param', ')'], 'arg')
remove(['func(', 'arg', ')']) If the target string is not found or is found to be "ambiguous" (multiple matches) you throw an error. This has a few advantages for the developer.
|
Beta Was this translation helpful? Give feedback.
-
Eventually you should steal const calls = CODE`
someCode();
otherCode();
`; It would have its indentation removed. Then if you inserted it into CODE`
function () {
${calls}
}
`; the indentation on every line would be replaced with the indentation of I don't know how well this lines up with your proposal, but it was a huge improvement whenever I added it to code-fns. The indentation was corrected whenever you started the string with a new line. So you wouldn't correct CODE`function () {
${calls}
}
`; |
Beta Was this translation helpful? Give feedback.
-
Just as a note - I would reference this issue here It would be good if emojis are supported. Thank you all for the great tool. |
Beta Was this translation helpful? Give feedback.
-
Out of curiosity, why does the developer need to think about I understand wanting / providing an interface for manual control (and I'm not suggesting to remove that), but my guess is often people don't need that level of control. Using Imagine: const source = Code.createSignal(`\
interface Color {
alpha: number;
}`);
yield* source.edit(0.6)`\
interface Color {
red: number;
blue: number;
green: number;
}`) And that's it. It knows the current state and knows how to perform the necessary operation. (could be either Another example would be: yield* source.edit(0.6)`\
interface Color {
red: number;
blue: number;
green: number;
alpha: number;
}`) It performs the You could allow customizing that with a high level api too in a number of ways (animate / delay per line, etc.). A code snippet to demonstrate the idea (which doesn't work in the existing API, but paints a picture): import { diffWords } from "diff";
class CodeToBeModified {
constructor(public code: string) {
this.code = code;
}
set(value: string) {
const before = this.code;
this.code = value;
return this.buildEdit(before, this.code);
}
/**
* Build up a list of the changes and non-changed strings
* by executing `diffWords` and iterating through to find
* every operation / piece.
*/
buildEdit(before: string, after: string): string[] {
const diffOut = diffWords(before, after);
const out = [];
for (let i = 0; i < diffOut.length; i++) {
if (diffOut[i].added && !diffOut[i+1].removed) {
out.push(insert(diffOut[i].value));
} else if (diffOut[i].removed && diffOut[i-1].added) {
out.push(edit(diffOut[i].value, diffOut[i-1].value));
} else if (diffOut[i].removed) {
out.push(remove(diffOut[i].value));
} else {
out.push(diffOut[i].value);
}
}
return out;
}
} Which would require something like: const code = CodeToBeModified(`\
interface Color {
red: number;
blue: number;
green: number;
alpha: number;
}`)
yield* source.edit(0.6, code.set(`\
interface Color {
red: number;
blue: number;
green: number;
}`)) Which would obviously be better with proper abstraction. |
Beta Was this translation helpful? Give feedback.
-
The following is a proposal for a new
Code
node that's meant to eventually replace the currentCodeBlock
.For the sake of simplicity, the proposal uses the present tense, but none of this has actually been implemented.
Nothing is set in stone and feedback is most welcome.
Generic Tag Function
A new generic tag function -
CODE
- is used to declare and store source code.It operates in a similar way to
code-fns
tags with two major differences:First-Class Signal Support
Fragments of code can be represented by a new dedicated signal created as follows:
The value of this signal can be provided as either a string or the array returned by
CODE
:It's possible to nest signal values inside of template strings.
This includes the new code signals:
As well as standard functions:
The contents of a
Code
node are also represented by a code signal.The following is a valid example:
Modifying Code Signals
append
prepend
insert
Inserts the code at a given position.
modify
Replaces the code at a given range.
The first argument is a range meaning that selection helper functions could also be used here.
remove
Removes the given range.
bulk
Takes an array of code modifications and applies them at the same time.
Default tween
The default signal tween animates from the current code to the new one using a diff algorithm:
Modifying
Code
NodesAll of the above methods are available in the
Code
node itself:On top of that, the node provides the traditional
edit
API:Putting it all Together
The following is a complete example of using code signals together with the
Code
node:Full UTF8 support + Ligatures
The following types of characters should just work:
CJK characters
(Example font: "Noto Sans Traditional Chinese"):
Code ligatures
(Example font: "JetBrains Mono"):
Emojis:
Highlighting
The
Code
node usesLezer
for parsing and highlighting the text.It's the same parser powering CodeMirror 6. The
@codemirror/legacy-modes
package is used to provide support for all the languages supported by CodeMirror 5.Specifying the Language
The language used by the
Code
node needs to be explicitly imported and passed to the node.This ensures that all required languages are known at compile time and tree shaking can do its job.
For the most popular languages,
@motion-canvas/2d
provides proxies, making it as easy to use as possible:More niche languages can still be used, either by directly importing the corresponding Lezer parser:
Or, using the legacy mode:
Styles
Styles are created using the same
HighlightStyle
mechanism CodeMirror uses. This makes it possible to use preexisting CodeMirror themes and easily define custom ones. Styles are passed directly to theCode
node:Decorations
The
Code
node provides a flexible API for creating custom decorations that can be applied at specific text ranges.These can be used to implement things like selections, error highlights, and underlines. A detailed discussion about this feature can be found in #258.
Automatic selection
The old
CodeBlock
automatically selected newly inserted text to make it stand out.In the new implementation, whether new insertions and edits are selected should be controlled by a separate property:
Release Strategy
The
Code
node will be introduced as a separate class fromCodeBlock
, exported from@motion-canvas/2d/components
.The idea is to let us slowly experiment and work on the new API while keeping
CodeBlock
around as the recommended solution.Once
Code
is ready, the originalCodeBlock
node will be marked as deprecated and scheduled to be removed inv4
.Beta Was this translation helpful? Give feedback.
All reactions