Edit workspace files directly on web (#2270)

* feat(files): edit workspace files on web

Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge.

* feat(panels): surface and protect modified tabs

Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently.

* fix(tests): use portable fake timeout handle

* fix(files): harden editor conflict handling

Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface.

* fix(files): close editor concurrency gaps

Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads.

* test(files): expect read revision metadata
This commit is contained in:
Mohamed Boudra
2026-07-21 04:41:19 +08:00
committed by GitHub
parent 9292f58896
commit 4bda2dfea9
69 changed files with 3678 additions and 241 deletions
+48 -40
View File
@@ -1,4 +1,4 @@
import { StreamLanguage } from "@codemirror/language";
import { defineLanguageFacet, Language, StreamLanguage } from "@codemirror/language";
import { dart } from "@codemirror/legacy-modes/mode/clike";
import { swift } from "@codemirror/legacy-modes/mode/swift";
import { parser as jsParser } from "@lezer/javascript";
@@ -18,65 +18,73 @@ import { csharpLanguage } from "@replit/codemirror-lang-csharp";
import { parser as elixirParser } from "lezer-elixir";
import type { Parser } from "@lezer/common";
const parsersByExtension: Record<string, Parser> = {
function language(parser: Parser): Language {
return new Language(defineLanguageFacet(), parser);
}
const languagesByExtension: Record<string, Language> = {
// JavaScript/TypeScript
js: jsParser,
jsx: jsParser.configure({ dialect: "jsx" }),
ts: jsParser.configure({ dialect: "ts" }),
tsx: jsParser.configure({ dialect: "ts jsx" }),
mjs: jsParser,
cjs: jsParser,
js: language(jsParser),
jsx: language(jsParser.configure({ dialect: "jsx" })),
ts: language(jsParser.configure({ dialect: "ts" })),
tsx: language(jsParser.configure({ dialect: "ts jsx" })),
mjs: language(jsParser),
cjs: language(jsParser),
// C / C++ / Objective-C
c: cppParser,
h: cppParser,
cc: cppParser,
cpp: cppParser,
cxx: cppParser,
hpp: cppParser,
hxx: cppParser,
m: cppParser,
mm: cppParser,
c: language(cppParser),
h: language(cppParser),
cc: language(cppParser),
cpp: language(cppParser),
cxx: language(cppParser),
hpp: language(cppParser),
hxx: language(cppParser),
m: language(cppParser),
mm: language(cppParser),
// JSON
json: jsonParser,
json: language(jsonParser),
// CSS
css: cssParser,
scss: cssParser,
css: language(cssParser),
scss: language(cssParser),
// HTML
html: htmlParser,
htm: htmlParser,
html: language(htmlParser),
htm: language(htmlParser),
// XML
xml: xmlParser,
xml: language(xmlParser),
// Java
java: javaParser,
java: language(javaParser),
// Python
py: pythonParser,
py: language(pythonParser),
// Go
go: goParser,
go: language(goParser),
// PHP
php: phpParser,
php: language(phpParser),
// YAML
yaml: yamlParser,
yml: yamlParser,
yaml: language(yamlParser),
yml: language(yamlParser),
// Rust
rs: rustParser,
rs: language(rustParser),
// Swift
swift: StreamLanguage.define(swift).parser,
swift: StreamLanguage.define(swift),
// Dart
dart: StreamLanguage.define(dart).parser,
dart: StreamLanguage.define(dart),
// C#
cs: csharpLanguage.parser,
cs: csharpLanguage,
// Elixir
ex: elixirParser,
exs: elixirParser,
ex: language(elixirParser),
exs: language(elixirParser),
// Markdown
md: markdownParser,
mdx: markdownParser,
md: language(markdownParser),
mdx: language(markdownParser),
};
export function getParserForFile(filename: string): Parser | null {
export function getLanguageForFile(filename: string): Language | null {
const ext = filename.split(".").pop()?.toLowerCase();
if (!ext) return null;
return parsersByExtension[ext] ?? null;
return languagesByExtension[ext] ?? null;
}
export function getParserForFile(filename: string): Parser | null {
return getLanguageForFile(filename)?.parser ?? null;
}
export function isLanguageSupported(filename: string): boolean {
@@ -84,5 +92,5 @@ export function isLanguageSupported(filename: string): boolean {
}
export function getSupportedExtensions(): string[] {
return Object.keys(parsersByExtension);
return Object.keys(languagesByExtension);
}