Skip to content

Commit 8d0b000

Browse files
committed
Merge branch 'main' into xcode26
2 parents 90eee51 + d22b8e8 commit 8d0b000

File tree

11 files changed

+237
-107
lines changed

11 files changed

+237
-107
lines changed

bun.lock

Lines changed: 99 additions & 93 deletions
Large diffs are not rendered by default.

src-tauri/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ serde = { version = "1", features = ["derive"] }
1616
serde_json = "1"
1717
tauri-plugin-shell = "2.3.0"
1818
tauri-plugin-fs = { version = "2.4.2", features= ["watch"] }
19-
tauri-plugin-dialog = "2.3.3"
19+
tauri-plugin-dialog = "2.4.0"
2020
tauri-plugin-store = "2.4.0"
2121
tauri-plugin-opener = "2.5.0"
2222
tauri-plugin-os = "2.3.1"

src/components/Tiles/CommandConsole.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Convert from "ansi-to-html";
55
import { Virtuoso } from "react-virtuoso";
66
import { useIDE } from "../../utilities/IDEContext";
77
import { escapeHtml } from "./Console";
8+
import { useParams } from "react-router";
89

910
const convert = new Convert();
1011

@@ -13,6 +14,12 @@ export default function CommandConsole() {
1314
const listenerAdded = useRef(false);
1415
const unlisten = useRef<() => void>(() => {});
1516

17+
const { path } = useParams<"path">();
18+
19+
useEffect(() => {
20+
setConsoleLines([]);
21+
}, [path]);
22+
1623
useEffect(() => {
1724
if (!listenerAdded.current) {
1825
(async () => {

src/components/Tiles/Console.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import "./Console.css";
33
import { listen } from "@tauri-apps/api/event";
44
import Convert from "ansi-to-html";
55
import { Virtuoso } from "react-virtuoso";
6+
import { useParams } from "react-router";
67

78
const convert = new Convert();
89

@@ -25,6 +26,11 @@ export default function Console({
2526
const [consoleLines, setConsoleLines] = useState<string[]>([]);
2627
const listenerAdded = useRef(false);
2728
const unlisten = useRef<() => void>(() => {});
29+
const { path } = useParams<"path">();
30+
31+
useEffect(() => {
32+
setConsoleLines([]);
33+
}, [path]);
2834

2935
useEffect(() => {
3036
if (!listenerAdded.current) {

src/components/Tiles/Editor.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
padding-bottom: 1px;
1818
position: relative;
1919
width: 100%;
20+
overflow-x: hidden;
2021
}
2122

2223
.tab-wrapper {
2324
display: flex;
2425
align-items: center;
2526
position: relative;
27+
flex-shrink: 0;
2628
}
2729

2830
.tabsContainer .Mui-selected::after {

src/components/Tiles/Editor.tsx

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import "@codingame/monaco-vscode-swift-default-extension";
1515
import "@codingame/monaco-vscode-theme-defaults-default-extension";
1616
import { platform } from "@tauri-apps/plugin-os";
1717
import { TabLike } from "../TabLike";
18+
import { useStore } from "../../utilities/StoreContext";
19+
import { useParams } from "react-router";
20+
import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
1821

1922
export type WorkerLoader = () => Worker;
2023
const workerLoaders: Partial<Record<string, WorkerLoader>> = {
@@ -47,7 +50,7 @@ export interface EditorProps {
4750
openFiles: string[];
4851
setOpenFiles: Dispatch<SetStateAction<string[]>>;
4952
focusedFile: string | null;
50-
setSaveFile: Dispatch<SetStateAction<(() => void) | null>>;
53+
setSaveFile: Dispatch<SetStateAction<(() => Promise<void>) | null>>;
5154
setUndo: Dispatch<SetStateAction<(() => void) | null>>;
5255
setRedo: Dispatch<SetStateAction<(() => void) | null>>;
5356
openNewFile: (file: string) => void;
@@ -101,8 +104,69 @@ export default ({
101104
const openNewFileRef = useRef(openNewFile);
102105
const selectionOverrideRef = useRef<ITextEditorOptions | null>(null);
103106
const hasInitializedRef = useRef(false);
107+
const scrollStates = useRef<{
108+
[key: string]: [number, number];
109+
}>({});
110+
const [hoveredOnBtn, setHoveredOnBtn] = useState<number | null>(null);
111+
const [formatOnSave] = useStore<boolean>("sourcekit/format", true);
104112

105-
let [hoveredOnBtn, setHoveredOnBtn] = useState<number | null>(null);
113+
const { path: filePath } = useParams<"path">();
114+
const hasAttemptedToReadOpenFiles = useRef<string | null>(null);
115+
116+
useEffect(() => {
117+
(async () => {
118+
if (!filePath || hasAttemptedToReadOpenFiles.current === filePath) return;
119+
const savePath = await path.join(
120+
filePath,
121+
".crosscode",
122+
"openFiles.json"
123+
);
124+
try {
125+
let text = await readTextFile(savePath);
126+
if (!text) return;
127+
let data = JSON.parse(text) as { files: string[]; focused: number };
128+
// make sure that types are as expected
129+
if (
130+
!data ||
131+
typeof data !== "object" ||
132+
!Array.isArray(data.files) ||
133+
typeof data.focused !== "number"
134+
)
135+
return;
136+
data.files = data.files.filter((file) => typeof file === "string");
137+
if (data.files.length === 0) return;
138+
if (data.focused < 0 || data.focused >= data.files.length) {
139+
data.focused = 0;
140+
}
141+
setOpenFiles(data.files);
142+
setTimeout(() => {
143+
setFocused(data.focused);
144+
}, 100);
145+
} catch (e) {
146+
void e;
147+
} finally {
148+
hasAttemptedToReadOpenFiles.current = filePath;
149+
}
150+
})();
151+
}, [filePath, setOpenFiles, setFocused]);
152+
153+
useEffect(() => {
154+
(async () => {
155+
if (!filePath || hasAttemptedToReadOpenFiles.current !== filePath) return;
156+
const savePath = await path.join(
157+
filePath,
158+
".crosscode",
159+
"openFiles.json"
160+
);
161+
let data = {
162+
files: openFiles,
163+
focused: focused ?? 0,
164+
};
165+
writeTextFile(savePath, JSON.stringify(data)).catch((err) => {
166+
console.error("Error writing openFiles.json:", err);
167+
});
168+
})();
169+
}, [openFiles, filePath, focused]);
106170

107171
useEffect(() => {
108172
globalEditorServiceCallbacks.currentTabsRef = currentTabsRef;
@@ -229,6 +293,18 @@ export default ({
229293
};
230294
}, [initialized]);
231295

296+
useEffect(() => {
297+
if (editor === null) return;
298+
let listener = editor.onDidScrollChange((e) => {
299+
if (focused !== undefined) {
300+
scrollStates.current[tabs[focused].file] = [e.scrollTop, e.scrollLeft];
301+
}
302+
});
303+
return () => {
304+
listener.dispose();
305+
};
306+
}, [editor, focused, tabs]);
307+
232308
useEffect(() => {
233309
if (!editor || !initialized) return;
234310
monaco.editor.setTheme(mode === "dark" ? "vs-dark" : "vs");
@@ -376,10 +452,22 @@ export default ({
376452
}
377453
});
378454
});
379-
setSaveFile(() => modelRef.object.save.bind(modelRef.object));
455+
456+
setSaveFile(() => async () => {
457+
if (formatOnSave) {
458+
await editor.getAction("editor.action.formatDocument")?.run();
459+
}
460+
await modelRef.object.save();
461+
});
380462

381463
editor.setModel(modelRef.object.textEditorModel);
382464

465+
if (scrollStates.current && scrollStates.current[filePath]) {
466+
const [scrollTop, scrollLeft] = scrollStates.current[filePath];
467+
editor.setScrollTop(scrollTop);
468+
editor.setScrollLeft(scrollLeft);
469+
}
470+
383471
// I don't love doing it like this but it seems to improve consistency over just running it directly
384472
requestAnimationFrame(() => {
385473
if (
@@ -409,7 +497,7 @@ export default ({
409497
});
410498
};
411499
switchFile();
412-
}, [focused, editor, tabs]);
500+
}, [focused, editor, tabs, formatOnSave]);
413501

414502
return (
415503
<div className={"editor"}>
@@ -418,6 +506,12 @@ export default ({
418506
onDragOver={handleContainerDragOver}
419507
onDragLeave={handleDragLeave}
420508
onDrop={handleDrop}
509+
onWheel={(e) => {
510+
if (e.deltaY !== 0) {
511+
e.currentTarget.scrollLeft += e.deltaY;
512+
e.preventDefault();
513+
}
514+
}}
421515
>
422516
{tabs.map((tab, index) => {
423517
let unsaved = unsavedFiles.includes(tab.file);

src/components/Tiles/FilteredConsole.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Convert from "ansi-to-html";
55
import { Virtuoso } from "react-virtuoso";
66
import { escapeHtml } from "./Console";
77
import React from "react";
8+
import { useParams } from "react-router";
89

910
const convert = new Convert();
1011

@@ -31,6 +32,12 @@ export default React.forwardRef<FilteredConsoleHandle, FilteredConsoleProps>(
3132
},
3233
}));
3334

35+
const { path } = useParams<"path">();
36+
37+
useEffect(() => {
38+
setConsoleLines([]);
39+
}, [path]);
40+
3441
useEffect(() => {
3542
if (!listenerAdded.current) {
3643
(async () => {

src/pages/IDE.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default () => {
4242
const { storeInitialized, store } = useContext(StoreContext);
4343
const [openFile, setOpenFile] = useState<string | null>(null);
4444
const [openFiles, setOpenFiles] = useState<string[]>([]);
45-
const [saveFile, setSaveFile] = useState<(() => void) | null>(null);
45+
const [saveFile, setSaveFile] = useState<(() => Promise<void>) | null>(null);
4646
const [undo, setUndo] = useState<(() => void) | null>(null);
4747
const [redo, setRedo] = useState<(() => void) | null>(null);
4848
const [theme] = useStore<"light" | "dark">("appearance/theme", "dark");
@@ -73,7 +73,9 @@ export default () => {
7373
throw new Error("Path parameter is required in IDE component");
7474
}
7575

76-
const [callbacks, setCallbacks] = useState<Record<string, () => void>>({});
76+
const [callbacks, setCallbacks] = useState<
77+
Record<string, (() => void) | (() => Promise<void>)>
78+
>({});
7779
const navigate = useNavigate();
7880
const [projectValidation, setProjectValidation] =
7981
useState<ProjectValidation | null>(null);
@@ -188,7 +190,7 @@ export default () => {
188190

189191
useEffect(() => {
190192
setCallbacks({
191-
save: saveFile ?? (() => {}),
193+
save: saveFile ?? (async () => {}),
192194
openFolderDialog,
193195
newProject: () => navigate("/new"),
194196
welcomePage: () => navigate("/"),

src/preferences/pages/sourcekit.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@ import { createPreferencePage, createItems } from "../helpers";
22

33
export const sourceKitPage = createPreferencePage(
44
"sourcekit",
5-
"SourceKit LSP",
5+
"Language Features",
66
[
77
createItems.checkbox(
88
"startup",
99
"Auto-Launch SourceKit",
1010
"Automatically start sourcekit-lsp when you open a project",
1111
true
1212
),
13+
createItems.checkbox(
14+
"format",
15+
"Format on save",
16+
"Automatically format your code when you save",
17+
true
18+
),
1319
],
1420
{
15-
description: "Customize the look and feel of the application",
16-
category: "general",
21+
description: "Configure SourceKit-LSP and other language features",
22+
category: "swift",
1723
}
1824
);

0 commit comments

Comments
 (0)