Skip to content

Commit a5a4a01

Browse files
committed
Merge branch 'main' into treeviewv3
2 parents c56dbc6 + b4d9e56 commit a5a4a01

File tree

14 files changed

+562
-175
lines changed

14 files changed

+562
-175
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ If you don't have VS Code installed yet, download it from [here](https://code.vi
2424
From within VS Code,
2525

2626
1. Click on the Extensions icon on the left sidebar or the `View: Show Extensions` command (`Ctrl+Shift+X`).
27-
2. Search for "Source Academy" and install it.
27+
2. Search for "Source Academy" and click "Install". When prompted for `Do you trust the publisher "Source Academy"?` click `Trust Publisher & Install`.
2828

2929
We'd recommend also installing the [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens) extension
3030
to show diagnostic messages more prominently.
3131

32+
Source Academy will store your code in the `.sourceacademy` folder of your home directory by default, which you may change in the settings.
33+
If asked, `Do you trust the authors of the files in this folder?`, do click on `Yes, I trust the authors`.
34+
3235
## Getting started
3336

3437
_If you don't have an existing Source Academy account, you can try out our public frontend. See section [Changing the frontend](#changing-the-frontend)._

language-configuration.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
],
2626
"folding": {
2727
"markers": {
28-
"start": "// PREPEND -- DO NOT EDIT",
29-
"end": "// END PREPEND"
28+
"start": "// PREPEND",
29+
"end": "// END PREPEND -- DO NOT EDIT PREPEND"
3030
}
3131
},
3232
"wordPattern": "[_$a-zA-Z][_$a-zA-Z0-9]*",

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
"@types/lodash": "^4.17.9",
142142
"@types/node": "^22.5.4",
143143
"@types/react": "^19.0.12",
144+
"@types/showdown": "^2.0.6",
144145
"@types/vscode": "^1.93.0",
145146
"@vscode/vsce": "^3.2.2",
146147
"esbuild": "^0.25.0",
@@ -158,6 +159,7 @@
158159
"lodash": "^4.17.21",
159160
"react": "^19.0.0",
160161
"react-dom": "^19.0.0",
162+
"showdown": "^2.1.0",
161163
"vscode-languageclient": "^9.0.1"
162164
},
163165
"packageManager": "yarn@1.22.22+sha1.ac34549e6aa8e7ead463a7407e1c7390f61a6610"

scripts/lsp.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async function main() {
2222

2323
async function downloadLsp() {
2424
const outputFolder = getOutputDir();
25-
const version = "0.1.8";
25+
const version = "0.1.9";
2626
const lspFilename = "source-lsp.js";
2727
const url = `https://github.com/source-academy/source-lsp/releases/download/v${version}/${lspFilename}`;
2828
// const url = `https://github.com/source-academy/source-lsp/releases/latest/download/${lspFilename}`;

src/commands/evalEditor.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import * as vscode from "vscode";
22
import Messages from "../utils/messages";
3-
import { activeEditor, sendToFrontendWrapped } from "./showPanel";
3+
import { sendToFrontendWrapped } from "./showPanel";
4+
import { MessageHandler } from "../utils/messageHandler";
5+
6+
let messageHandler = MessageHandler.getInstance();
47

58
export async function evalEditor(context: vscode.ExtensionContext) {
6-
if (!activeEditor) {
9+
if (!messageHandler.activeEditor) {
710
vscode.window.showErrorMessage(
811
"Cannot evaluate code when there is no active editor!",
912
);
1013
return;
1114
}
12-
const message = Messages.EvalEditor(activeEditor.workspaceLocation);
15+
const message = Messages.EvalEditor(
16+
messageHandler.activeEditor.workspaceLocation,
17+
);
1318
sendToFrontendWrapped(message);
1419
}

src/commands/showPanel.tsx

Lines changed: 12 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2,110 +2,16 @@ import * as vscode from "vscode";
22
// Allow using JSX within this file by overriding our own createElement function
33
import React from "../utils/FakeReact";
44

5-
import Messages, {
6-
MessageType,
7-
MessageTypeNames,
8-
sendToFrontend,
9-
} from "../utils/messages";
5+
import { MessageType, sendToFrontend } from "../utils/messages";
106
import { LANGUAGES } from "../utils/languages";
117
import { setWebviewContent } from "../utils/webview";
128
import config from "../utils/config";
13-
import { Editor } from "../utils/editor";
149
import { FRONTEND_ELEMENT_ID } from "../constants";
15-
import { client, SOURCE_ACADEMY_ICON_URI } from "../extension";
1610
import _ from "lodash";
17-
import { treeDataProvider } from "../treeview";
11+
import { MessageHandler } from "../utils/messageHandler";
12+
import { SOURCE_ACADEMY_ICON_URI } from "../extension";
1813

19-
let panel: vscode.WebviewPanel | null = null;
20-
// This needs to be a reference to active
21-
// TODO: Fix this ugly code!
22-
export let activeEditor: Editor | null = null;
23-
24-
const messageQueue: MessageType[] = [];
25-
let handling = false;
26-
27-
// TODO: Remove panel and handling message logic out of the commands/ directory
28-
29-
async function handleMessage(
30-
context: vscode.ExtensionContext,
31-
message: MessageType,
32-
) {
33-
messageQueue.push(message);
34-
if (handling) {
35-
return;
36-
}
37-
handling = true;
38-
39-
while (messageQueue.length > 0) {
40-
const message = messageQueue.shift()!;
41-
console.log(`${Date.now()} Beginning handleMessage: ${message.type}`);
42-
switch (message.type) {
43-
case MessageTypeNames.ExtensionPing:
44-
sendToFrontend(panel, Messages.ExtensionPong(null));
45-
break;
46-
case MessageTypeNames.NewEditor:
47-
activeEditor = await Editor.create(
48-
message.workspaceLocation,
49-
message.assessmentName,
50-
message.questionId,
51-
message.prepend,
52-
message.initialCode,
53-
);
54-
activeEditor.uri;
55-
const info = context.globalState.get("info") ?? {};
56-
if (activeEditor.uri) {
57-
// TODO: Write our own wrapper to set nested keys easily, removing lodash
58-
// @ts-ignore
59-
_.set(info, `["${activeEditor.uri}"].chapter`, message.chapter ?? 1);
60-
// TODO: message.prepend can be undefined in runtime, investigate
61-
const nPrependLines =
62-
message.prepend && message.prepend !== ""
63-
? message.prepend.split("\n").length + 2 // account for start/end markers
64-
: 0;
65-
_.set(info, `["${activeEditor.uri}"].prepend`, nPrependLines);
66-
context.globalState.update("info", info);
67-
client.sendRequest("source/publishInfo", info);
68-
}
69-
70-
panel?.reveal(vscode.ViewColumn.Two);
71-
console.log(
72-
`EXTENSION: NewEditor: activeEditor set to ${activeEditor.assessmentName}_${activeEditor.questionId}`,
73-
);
74-
activeEditor.onChange((editor) => {
75-
const workspaceLocation = editor.workspaceLocation;
76-
const code = editor.getText();
77-
if (!code) {
78-
return;
79-
}
80-
if (editor !== activeEditor) {
81-
console.log(
82-
`EXTENSION: Editor ${editor.assessmentName}_${editor.questionId} is no longer active, skipping onChange`,
83-
);
84-
}
85-
const message = Messages.Text(workspaceLocation, code);
86-
console.log(`Sending message: ${JSON.stringify(message)}`);
87-
sendToFrontend(panel, message);
88-
});
89-
break;
90-
// case MessageTypeNames.Text:
91-
// if (!activeEditor) {
92-
// console.log("ERROR: activeEditor is not set");
93-
// break;
94-
// }
95-
// activeEditor.replace(message.code, "Text");
96-
// break;
97-
case MessageTypeNames.NotifyAssessmentsOverview:
98-
const { assessmentOverviews, courseId } = message;
99-
context.globalState.update("assessmentOverviews", assessmentOverviews);
100-
context.globalState.update("courseId", courseId);
101-
treeDataProvider.refresh();
102-
break;
103-
}
104-
console.log(`${Date.now()} Finish handleMessage: ${message.type}`);
105-
}
106-
107-
handling = false;
108-
}
14+
let messageHandler = MessageHandler.getInstance();
10915

11016
export async function showPanel(
11117
context: vscode.ExtensionContext,
@@ -119,7 +25,7 @@ export async function showPanel(
11925
// Get a reference to the active editor (before the focus is switched to our newly created webview)
12026
// firstEditor = vscode.window.activeTextEditor!;
12127

122-
panel = vscode.window.createWebviewPanel(
28+
messageHandler.panel = vscode.window.createWebviewPanel(
12329
"source-academy-panel",
12430
"Source Academy",
12531
vscode.ViewColumn.Beside,
@@ -129,18 +35,17 @@ export async function showPanel(
12935
},
13036
);
13137

132-
panel.webview.onDidReceiveMessage(
133-
(message: MessageType) => handleMessage(context, message),
38+
messageHandler.panel.webview.onDidReceiveMessage(
39+
(message: MessageType) => messageHandler.handleMessage(context, message),
13440
undefined,
13541
context.subscriptions,
13642
);
13743

13844
const iframeUrl = new URL(route ?? "/playground", config.frontendBaseUrl)
13945
.href;
14046

141-
// equivalent to panel.webview.html = ...
14247
setWebviewContent(
143-
panel,
48+
messageHandler.panel,
14449
context,
14550
// NOTE: This is not React code, but our FakeReact!
14651
<div
@@ -160,16 +65,16 @@ export async function showPanel(
16065
</div>,
16166
);
16267

163-
panel.iconPath = SOURCE_ACADEMY_ICON_URI;
68+
messageHandler.panel.iconPath = SOURCE_ACADEMY_ICON_URI;
16469
}
16570

166-
// TODO: Move this to a util file
16771
export async function sendToFrontendWrapped(message: MessageType) {
72+
sendToFrontend(messageHandler.panel, message);
16873
// TODO: This returning of status code shouldn't be necessary after refactor
169-
if (!panel) {
74+
if (!messageHandler.panel) {
17075
console.error("ERROR: panel is not set");
17176
return false;
17277
}
173-
sendToFrontend(panel, message);
78+
sendToFrontend(messageHandler.panel, message);
17479
return true;
17580
}

src/utils/editor.ts

Lines changed: 50 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
1+
// TODO: Move this file to src/features/editor/editor.ts
12
import * as vscode from "vscode";
23

34
import config from "../utils/config";
45
import Messages, { VscWorkspaceLocation } from "./messages";
56
import path from "path";
67
import { sendToFrontendWrapped } from "../commands/showPanel";
78
import { canonicaliseLocation } from "./misc";
9+
import { codeAddPrepend, codeRemovePrepend } from "./editorUtils";
810

911
export class Editor {
1012
editor?: vscode.TextEditor;
1113
workspaceLocation: VscWorkspaceLocation;
1214
assessmentName: string;
1315
questionId: number;
16+
assessmentType: string | null = null;
1417
onChangeCallback?: (editor: Editor) => void;
1518
code: string | null = null;
1619
uri: string | null = null;
@@ -26,6 +29,7 @@ export class Editor {
2629
) {
2730
this.workspaceLocation = workspaceLocation;
2831
this.assessmentName = assessmentName;
32+
this.assessmentType = this.assessmentType;
2933
this.questionId = questionId;
3034
}
3135

@@ -60,58 +64,53 @@ export class Editor {
6064
const uri = vscode.Uri.file(filePath);
6165
self.uri = uri.toString();
6266

63-
const contents =
64-
prepend !== ""
65-
? [
66-
"// PREPEND -- DO NOT EDIT",
67-
prepend,
68-
"// END PREPEND",
69-
initialCode,
70-
].join("\n")
71-
: initialCode;
72-
73-
await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)).then(
74-
(value) => {
75-
if (value.toString() !== contents) {
76-
self.log(
77-
"EXTENSION: Conflict detected between local and remote, prompting user to choose one",
67+
const contents = codeAddPrepend(initialCode, prepend);
68+
69+
await vscode.workspace.fs
70+
.readFile(vscode.Uri.file(filePath))
71+
.then((value) => value.toString())
72+
.then(
73+
(localCode) => {
74+
if (localCode !== contents) {
75+
self.log(
76+
"EXTENSION: Conflict detected between local and remote, prompting user to choose one",
77+
);
78+
vscode.window
79+
.showInformationMessage(
80+
[
81+
"The local file differs from the version on the Source Academy servers.",
82+
"Discard the local file and use the one on the server?",
83+
].join(" "),
84+
{ modal: true },
85+
"Yes",
86+
)
87+
.then(async (answer) => {
88+
// By default the code displayed is the local one
89+
if (answer === "Yes") {
90+
self.log("EXTENSION: Saving program from server to file");
91+
await vscode.workspace.fs.writeFile(
92+
uri,
93+
new TextEncoder().encode(contents),
94+
);
95+
} else if (answer === undefined) {
96+
// Modal cancelled
97+
const message = Messages.Text(
98+
self.workspaceLocation,
99+
codeRemovePrepend(localCode),
100+
);
101+
sendToFrontendWrapped(message);
102+
}
103+
});
104+
}
105+
},
106+
async () => {
107+
self.log(`Opening file failed, creating at ${filePath}`);
108+
await vscode.workspace.fs.writeFile(
109+
uri,
110+
new TextEncoder().encode(contents),
78111
);
79-
vscode.window
80-
.showInformationMessage(
81-
[
82-
"The local file differs from the version on the Source Academy servers.",
83-
"Discard the local file and use the one on the server?",
84-
].join(" "),
85-
{ modal: true },
86-
"Yes",
87-
)
88-
.then(async (answer) => {
89-
// By default the code displayed is the local one
90-
if (answer === "Yes") {
91-
self.log("EXTENSION: Saving program from server to file");
92-
await vscode.workspace.fs.writeFile(
93-
uri,
94-
new TextEncoder().encode(contents),
95-
);
96-
} else if (answer === undefined) {
97-
// Modal cancelled
98-
const message = Messages.Text(
99-
self.workspaceLocation,
100-
value.toString(),
101-
);
102-
sendToFrontendWrapped(message);
103-
}
104-
});
105-
}
106-
},
107-
async () => {
108-
self.log(`Opening file failed, creating at ${filePath}`);
109-
await vscode.workspace.fs.writeFile(
110-
uri,
111-
new TextEncoder().encode(contents),
112-
);
113-
},
114-
);
112+
},
113+
);
115114

116115
const editor = await vscode.window.showTextDocument(uri, {
117116
preview: false,

0 commit comments

Comments
 (0)