From a5ab66d6cdd57e62e4ad46a756fea3810885658f Mon Sep 17 00:00:00 2001
From: doodlewind <7312949+doodlewind@users.noreply.github.com>
Date: Fri, 11 Apr 2025 04:28:51 +0000
Subject: [PATCH] feat(editor): add basic code support in turbo renderer
(#11619)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This PR adds basic support for code block:
[Screen Recording 2025-04-10 at 8.13.26 PM.mov (uploaded via Graphite)
](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/5d749979-f7f1-4e4d-ba5b-bc4ba29f8b83.mov)
---
blocksuite/affine/blocks/code/package.json | 4 +-
blocksuite/affine/blocks/code/src/index.ts | 2 +
.../code/src/turbo/code-layout-handler.ts | 71 +++++++++++++++++++
.../code/src/turbo/code-painter.worker.ts | 53 ++++++++++++++
blocksuite/affine/blocks/code/tsconfig.json | 1 +
.../extensions/turbo-painter.worker.ts | 2 +
.../blocksuite/extensions/turbo-renderer.ts | 2 +
tools/utils/src/workspace.gen.ts | 1 +
yarn.lock | 25 +++----
9 files changed, 148 insertions(+), 13 deletions(-)
create mode 100644 blocksuite/affine/blocks/code/src/turbo/code-layout-handler.ts
create mode 100644 blocksuite/affine/blocks/code/src/turbo/code-painter.worker.ts
diff --git a/blocksuite/affine/blocks/code/package.json b/blocksuite/affine/blocks/code/package.json
index 5af3375d3d..833a10f7db 100644
--- a/blocksuite/affine/blocks/code/package.json
+++ b/blocksuite/affine/blocks/code/package.json
@@ -11,6 +11,7 @@
"license": "MIT",
"dependencies": {
"@blocksuite/affine-components": "workspace:*",
+ "@blocksuite/affine-gfx-turbo-renderer": "workspace:*",
"@blocksuite/affine-inline-latex": "workspace:*",
"@blocksuite/affine-inline-link": "workspace:*",
"@blocksuite/affine-inline-preset": "workspace:*",
@@ -35,7 +36,8 @@
},
"exports": {
".": "./src/index.ts",
- "./effects": "./src/effects.ts"
+ "./effects": "./src/effects.ts",
+ "./turbo-painter": "./src/turbo/code-painter.worker.ts"
},
"files": [
"src",
diff --git a/blocksuite/affine/blocks/code/src/index.ts b/blocksuite/affine/blocks/code/src/index.ts
index dc06c61a67..4de4cf592b 100644
--- a/blocksuite/affine/blocks/code/src/index.ts
+++ b/blocksuite/affine/blocks/code/src/index.ts
@@ -3,3 +3,5 @@ export * from './code-block';
export * from './code-block-config';
export * from './code-block-spec';
export * from './code-toolbar';
+export * from './turbo/code-layout-handler';
+export * from './turbo/code-painter.worker';
diff --git a/blocksuite/affine/blocks/code/src/turbo/code-layout-handler.ts b/blocksuite/affine/blocks/code/src/turbo/code-layout-handler.ts
new file mode 100644
index 0000000000..3a43ba2b85
--- /dev/null
+++ b/blocksuite/affine/blocks/code/src/turbo/code-layout-handler.ts
@@ -0,0 +1,71 @@
+import type { Rect } from '@blocksuite/affine-gfx-turbo-renderer';
+import {
+ BlockLayoutHandlerExtension,
+ BlockLayoutHandlersIdentifier,
+} from '@blocksuite/affine-gfx-turbo-renderer';
+import type { Container } from '@blocksuite/global/di';
+import type { EditorHost, GfxBlockComponent } from '@blocksuite/std';
+import { clientToModelCoord, type ViewportRecord } from '@blocksuite/std/gfx';
+import type { BlockModel } from '@blocksuite/store';
+
+import type { CodeLayout } from './code-painter.worker';
+
+export class CodeLayoutHandlerExtension extends BlockLayoutHandlerExtension {
+ readonly blockType = 'affine:code';
+
+ static override setup(di: Container) {
+ di.addImpl(
+ BlockLayoutHandlersIdentifier('code'),
+ CodeLayoutHandlerExtension
+ );
+ }
+
+ override queryLayout(
+ model: BlockModel,
+ host: EditorHost,
+ viewportRecord: ViewportRecord
+ ): CodeLayout | null {
+ const component = host.std.view.getBlock(model.id) as GfxBlockComponent;
+ if (!component) return null;
+
+ const codeBlockElement = component.querySelector(
+ '.affine-code-block-container'
+ );
+ if (!codeBlockElement) return null;
+
+ const { zoom, viewScale } = viewportRecord;
+ const codeLayout: CodeLayout = {
+ type: 'affine:code',
+ blockId: model.id,
+ rect: { x: 0, y: 0, w: 0, h: 0 },
+ };
+
+ // Get the bounding rect of the code block
+ const clientRect = codeBlockElement.getBoundingClientRect();
+ if (!clientRect) return null;
+
+ // Convert client coordinates to model coordinates
+ const [modelX, modelY] = clientToModelCoord(viewportRecord, [
+ clientRect.x,
+ clientRect.y,
+ ]);
+
+ codeLayout.rect = {
+ x: modelX,
+ y: modelY,
+ w: clientRect.width / zoom / viewScale,
+ h: clientRect.height / zoom / viewScale,
+ };
+
+ return codeLayout;
+ }
+
+ calculateBound(layout: CodeLayout) {
+ const rect: Rect = layout.rect;
+
+ return {
+ rect,
+ subRects: [rect],
+ };
+ }
+}
diff --git a/blocksuite/affine/blocks/code/src/turbo/code-painter.worker.ts b/blocksuite/affine/blocks/code/src/turbo/code-painter.worker.ts
new file mode 100644
index 0000000000..9b929a3152
--- /dev/null
+++ b/blocksuite/affine/blocks/code/src/turbo/code-painter.worker.ts
@@ -0,0 +1,53 @@
+import type {
+ BlockLayout,
+ BlockLayoutPainter,
+ WorkerToHostMessage,
+} from '@blocksuite/affine-gfx-turbo-renderer';
+import { BlockLayoutPainterExtension } from '@blocksuite/affine-gfx-turbo-renderer/painter';
+
+export interface CodeLayout extends BlockLayout {
+ type: 'affine:code';
+}
+
+function isCodeLayout(layout: BlockLayout): layout is CodeLayout {
+ return layout.type === 'affine:code';
+}
+
+class CodeLayoutPainter implements BlockLayoutPainter {
+ paint(
+ ctx: OffscreenCanvasRenderingContext2D,
+ layout: BlockLayout,
+ layoutBaseX: number,
+ layoutBaseY: number
+ ): void {
+ if (!isCodeLayout(layout)) {
+ const message: WorkerToHostMessage = {
+ type: 'paintError',
+ error: 'Invalid layout format',
+ blockType: 'affine:code',
+ };
+ self.postMessage(message);
+ return;
+ }
+
+ // Get the layout dimensions
+ const x = layout.rect.x - layoutBaseX;
+ const y = layout.rect.y - layoutBaseY;
+ const width = layout.rect.w;
+ const height = layout.rect.h;
+
+ // Simple white rectangle for now
+ ctx.fillStyle = 'white';
+ ctx.fillRect(x, y, width, height);
+
+ // Add a border to visualize the code block
+ ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
+ ctx.lineWidth = 1;
+ ctx.strokeRect(x, y, width, height);
+ }
+}
+
+export const CodeLayoutPainterExtension = BlockLayoutPainterExtension(
+ 'affine:code',
+ CodeLayoutPainter
+);
diff --git a/blocksuite/affine/blocks/code/tsconfig.json b/blocksuite/affine/blocks/code/tsconfig.json
index e5046f026e..0132fbfea1 100644
--- a/blocksuite/affine/blocks/code/tsconfig.json
+++ b/blocksuite/affine/blocks/code/tsconfig.json
@@ -8,6 +8,7 @@
"include": ["./src"],
"references": [
{ "path": "../../components" },
+ { "path": "../../gfx/turbo-renderer" },
{ "path": "../../inlines/latex" },
{ "path": "../../inlines/link" },
{ "path": "../../inlines/preset" },
diff --git a/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts b/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts
index d8767fd39d..4ee5a1dd0b 100644
--- a/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts
+++ b/packages/frontend/core/src/blocksuite/extensions/turbo-painter.worker.ts
@@ -1,3 +1,4 @@
+import { CodeLayoutPainterExtension } from '@blocksuite/affine/blocks/code';
import { ListLayoutPainterExtension } from '@blocksuite/affine/blocks/list';
import { NoteLayoutPainterExtension } from '@blocksuite/affine/blocks/note';
import { ParagraphLayoutPainterExtension } from '@blocksuite/affine/blocks/paragraph';
@@ -7,4 +8,5 @@ new ViewportLayoutPainter([
ParagraphLayoutPainterExtension,
ListLayoutPainterExtension,
NoteLayoutPainterExtension,
+ CodeLayoutPainterExtension,
]);
diff --git a/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts b/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts
index 70478278ca..4b27de2669 100644
--- a/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts
+++ b/packages/frontend/core/src/blocksuite/extensions/turbo-renderer.ts
@@ -1,4 +1,5 @@
import { getWorkerUrl } from '@affine/env/worker';
+import { CodeLayoutHandlerExtension } from '@blocksuite/affine/blocks/code';
import { ListLayoutHandlerExtension } from '@blocksuite/affine/blocks/list';
import { NoteLayoutHandlerExtension } from '@blocksuite/affine/blocks/note';
import { ParagraphLayoutHandlerExtension } from '@blocksuite/affine/blocks/paragraph';
@@ -17,6 +18,7 @@ export function patchTurboRendererExtension() {
ParagraphLayoutHandlerExtension,
ListLayoutHandlerExtension,
NoteLayoutHandlerExtension,
+ CodeLayoutHandlerExtension,
TurboRendererConfigFactory({
options: {
zoomThreshold: 1,
diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts
index 54fdca6484..b3bdb73ec4 100644
--- a/tools/utils/src/workspace.gen.ts
+++ b/tools/utils/src/workspace.gen.ts
@@ -111,6 +111,7 @@ export const PackageList = [
name: '@blocksuite/affine-block-code',
workspaceDependencies: [
'blocksuite/affine/components',
+ 'blocksuite/affine/gfx/turbo-renderer',
'blocksuite/affine/inlines/latex',
'blocksuite/affine/inlines/link',
'blocksuite/affine/inlines/preset',
diff --git a/yarn.lock b/yarn.lock
index 962f910057..3bb06d344d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2450,6 +2450,7 @@ __metadata:
resolution: "@blocksuite/affine-block-code@workspace:blocksuite/affine/blocks/code"
dependencies:
"@blocksuite/affine-components": "workspace:*"
+ "@blocksuite/affine-gfx-turbo-renderer": "workspace:*"
"@blocksuite/affine-inline-latex": "workspace:*"
"@blocksuite/affine-inline-link": "workspace:*"
"@blocksuite/affine-inline-preset": "workspace:*"
@@ -20555,13 +20556,13 @@ __metadata:
linkType: hard
"eslint-config-prettier@npm:^10.0.0":
- version: 10.1.1
- resolution: "eslint-config-prettier@npm:10.1.1"
+ version: 10.1.2
+ resolution: "eslint-config-prettier@npm:10.1.2"
peerDependencies:
eslint: ">=7.0.0"
bin:
eslint-config-prettier: bin/cli.js
- checksum: 10/e78e195a4f19e0de9bf655648bb3433877d6a5368537f1b1049976b74180844a00dd7c1ba3144e3da6e8b6864f8dcdfcda6c7338a537883fcf5b212ef6dcd0e0
+ checksum: 10/7b096cbb75ff57cee933451e9c8bd2926688bc603a7d74c3d89b2bd57324cb0346c7e95ac24b17ef2dd2050bb870602c032368f11bf57c2962210418a99caf3f
languageName: node
linkType: hard
@@ -22992,8 +22993,8 @@ __metadata:
linkType: hard
"http-proxy-middleware@npm:^2.0.7":
- version: 2.0.8
- resolution: "http-proxy-middleware@npm:2.0.8"
+ version: 2.0.9
+ resolution: "http-proxy-middleware@npm:2.0.9"
dependencies:
"@types/http-proxy": "npm:^1.17.8"
http-proxy: "npm:^1.18.1"
@@ -23005,13 +23006,13 @@ __metadata:
peerDependenciesMeta:
"@types/express":
optional: true
- checksum: 10/77f54a73ca97fc453d535d9dee702392348505909105bf8868ddcc799903e553bfe5180aaec6e022bf23685072df6b0f91a0884331c2c3245ccf61ad1647fa27
+ checksum: 10/4ece416a91d52e96f8136c5f4abfbf7ac2f39becbad21fa8b158a12d7e7d8f808287ff1ae342b903fd1f15f2249dee87fabc09e1f0e73106b83331c496d67660
languageName: node
linkType: hard
"http-proxy-middleware@npm:^3.0.3":
- version: 3.0.4
- resolution: "http-proxy-middleware@npm:3.0.4"
+ version: 3.0.5
+ resolution: "http-proxy-middleware@npm:3.0.5"
dependencies:
"@types/http-proxy": "npm:^1.17.15"
debug: "npm:^4.3.6"
@@ -23019,7 +23020,7 @@ __metadata:
is-glob: "npm:^4.0.3"
is-plain-object: "npm:^5.0.0"
micromatch: "npm:^4.0.8"
- checksum: 10/3e0f4ae61b62ba14ac6821f5208b5590ac5ec4a1a0cfbd5e643ca7b75540dabab2d83b81ad046bdf06365ba15cb7e237aaa2836398922f6777ee82f90393d704
+ checksum: 10/83c1956be6451a5f4a2f3c7b3d84085dbd47e1efb5bb684c1ed668a6606c18c7c07be823b0dbba1326955b64cf88de2672492940b0b48d140215fbdb06105c9a
languageName: node
linkType: hard
@@ -32930,9 +32931,9 @@ __metadata:
linkType: hard
"undici@npm:^7.1.0":
- version: 7.7.0
- resolution: "undici@npm:7.7.0"
- checksum: 10/629861f30411f43b276163a9aa3f6b74b70a6fb911f3389220524bfb149003771447ec6779f3d49f8a9cf54f9aa09b5b5c1b35521c25344df48e4f4aae1e21aa
+ version: 7.8.0
+ resolution: "undici@npm:7.8.0"
+ checksum: 10/bbd1fd9e63f0842196fc0210ffb36c29eed2841f1499e149718b96e52e195afe3ccaaa2da76aae4253cd5cc58a26b20836f7a98db11f335b594009603cc72eec
languageName: node
linkType: hard