增加文件管理器,增加文件编辑

This commit is contained in:
2025-08-09 13:44:52 +08:00
parent 5e532ad83f
commit 1f13548039
13 changed files with 4384 additions and 10 deletions

View File

@@ -0,0 +1,219 @@
import * as monaco from 'monaco-editor';
import type { Linter } from 'eslint';
// 将 ESLint 诊断转换为 Monaco 标记
export function convertESLintToMonacoMarkers(
eslintMessages: Linter.LintMessage[],
_model: monaco.editor.ITextModel
): monaco.editor.IMarkerData[] {
return eslintMessages.map(message => {
return {
severity: message.severity === 2
? monaco.MarkerSeverity.Error
: monaco.MarkerSeverity.Warning,
startLineNumber: message.line || 1,
startColumn: message.column || 1,
endLineNumber: message.endLine || message.line || 1,
endColumn: message.endColumn || (message.column ? message.column + 1 : 1),
message: message.message,
source: message.ruleId || 'eslint',
};
});
}
// 实时语法检查配置
export interface RealtimeLintOptions {
enabled: boolean;
delay: number; // 延迟时间(毫秒)
showInlineErrors: boolean;
showErrorsInScrollbar: boolean;
showErrorsInMinimap: boolean;
}
export const defaultLintOptions: RealtimeLintOptions = {
enabled: true,
delay: 500,
showInlineErrors: true,
showErrorsInScrollbar: true,
showErrorsInMinimap: true,
};
// 配置实时语法检查
export function setupRealtimeLinting(
editor: monaco.editor.IStandaloneCodeEditor,
options: RealtimeLintOptions = defaultLintOptions
) {
if (!options.enabled) return;
let lintTimer: NodeJS.Timeout | null = null;
const performLinting = () => {
const model = editor.getModel();
if (!model) return;
const language = model.getLanguageId();
if (language !== 'typescript' && language !== 'javascript' &&
language !== 'typescriptreact' && language !== 'javascriptreact') {
return;
}
// 根据选项配置显示
if (options.showErrorsInScrollbar) {
editor.updateOptions({
overviewRulerLanes: 3,
});
}
if (options.showErrorsInMinimap) {
editor.updateOptions({
minimap: {
showSlider: 'always',
renderCharacters: false,
},
});
}
};
// 监听内容变化
editor.onDidChangeModelContent(() => {
if (lintTimer) {
clearTimeout(lintTimer);
}
lintTimer = setTimeout(() => {
performLinting();
}, options.delay);
});
// 初始检查
performLinting();
}
// 代码快速修复建议
export interface QuickFix {
title: string;
kind: string;
edit: monaco.languages.WorkspaceEdit;
}
// 注册代码操作提供器(快速修复)
export function registerCodeActionProvider() {
monaco.languages.registerCodeActionProvider(['typescript', 'javascript', 'typescriptreact', 'javascriptreact'], {
provideCodeActions: (model, _range, context, _token) => {
const actions: monaco.languages.CodeAction[] = [];
// 检查是否有错误标记
const markers = context.markers.filter(marker => marker.severity === monaco.MarkerSeverity.Error);
for (const marker of markers) {
// 未使用变量的快速修复
if (marker.code === '6133' || marker.message.includes('is declared but')) {
actions.push({
title: `Remove unused declaration`,
kind: 'quickfix',
diagnostics: [marker],
edit: {
edits: [{
resource: model.uri,
textEdit: {
range: {
startLineNumber: marker.startLineNumber,
startColumn: 1,
endLineNumber: marker.endLineNumber,
endColumn: model.getLineLength(marker.endLineNumber) + 1,
},
text: '',
},
versionId: undefined,
}],
},
isPreferred: true,
});
}
// 缺少导入的快速修复
if (marker.message.includes('Cannot find name')) {
const variableName = marker.message.match(/Cannot find name '([^']+)'/)?.[1];
if (variableName) {
actions.push({
title: `Import '${variableName}'`,
kind: 'quickfix',
diagnostics: [marker],
edit: {
edits: [{
resource: model.uri,
textEdit: {
range: {
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: 1,
},
text: `import { ${variableName} } from './${variableName.toLowerCase()}';\n`,
},
versionId: undefined,
}],
},
isPreferred: false,
});
}
}
// 类型错误的快速修复
if (marker.message.includes('Type') && marker.message.includes('is not assignable')) {
actions.push({
title: 'Add type assertion',
kind: 'quickfix',
diagnostics: [marker],
edit: {
edits: [{
resource: model.uri,
textEdit: {
range: {
startLineNumber: marker.startLineNumber,
startColumn: marker.startColumn,
endLineNumber: marker.endLineNumber,
endColumn: marker.endColumn,
},
text: `(${model.getValueInRange({
startLineNumber: marker.startLineNumber,
startColumn: marker.startColumn,
endLineNumber: marker.endLineNumber,
endColumn: marker.endColumn,
})} as any)`,
},
versionId: undefined,
}],
},
isPreferred: false,
});
}
}
// 添加格式化操作
actions.push({
title: 'Format Document',
kind: 'source.formatAll',
command: {
id: 'editor.action.formatDocument',
title: 'Format Document',
},
});
// 添加组织导入操作
actions.push({
title: 'Organize Imports',
kind: 'source.organizeImports',
command: {
id: 'editor.action.organizeImports',
title: 'Organize Imports',
},
});
return {
actions,
dispose: () => {},
};
},
});
}

412
src/lib/monaco-config.ts Normal file
View File

@@ -0,0 +1,412 @@
import * as monaco from 'monaco-editor';
import { registerCodeActionProvider } from './eslint-integration';
// TypeScript 默认编译选项
export const defaultCompilerOptions: monaco.languages.typescript.CompilerOptions = {
target: monaco.languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.ESNext,
noEmit: true,
esModuleInterop: true,
jsx: monaco.languages.typescript.JsxEmit.React,
reactNamespace: 'React',
allowJs: true,
typeRoots: ['node_modules/@types'],
lib: ['es2020', 'dom', 'dom.iterable', 'esnext'],
strict: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
resolveJsonModule: true,
isolatedModules: true,
noUnusedLocals: true,
noUnusedParameters: true,
noImplicitReturns: true,
noFallthroughCasesInSwitch: true,
};
// JavaScript 默认编译选项
export const jsCompilerOptions: monaco.languages.typescript.CompilerOptions = {
...defaultCompilerOptions,
strict: false,
noUnusedLocals: false,
noUnusedParameters: false,
checkJs: true,
allowJs: true,
};
// 配置 TypeScript 语言服务
export function configureMonacoTypescript() {
// 配置 TypeScript 默认选项
monaco.languages.typescript.typescriptDefaults.setCompilerOptions(defaultCompilerOptions);
// 配置 JavaScript 默认选项
monaco.languages.typescript.javascriptDefaults.setCompilerOptions(jsCompilerOptions);
// 设置诊断选项
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
noSuggestionDiagnostics: false,
});
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
noSuggestionDiagnostics: false,
});
// 启用格式化选项
monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true);
monaco.languages.typescript.javascriptDefaults.setEagerModelSync(true);
}
// 添加常用类型定义
export async function addTypeDefinitions() {
const typeDefs = [
{
name: '@types/react',
content: `
declare module "react" {
export interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
type: T;
props: P;
key: Key | null;
}
export type FC<P = {}> = FunctionComponent<P>;
export interface FunctionComponent<P = {}> {
(props: P, context?: any): ReactElement<any, any> | null;
}
export function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
export function useEffect(effect: EffectCallback, deps?: DependencyList): void;
export function useCallback<T extends Function>(callback: T, deps: DependencyList): T;
export function useMemo<T>(factory: () => T, deps: DependencyList): T;
export function useRef<T>(initialValue: T): MutableRefObject<T>;
}
`
},
{
name: '@types/node',
content: `
declare module "fs" {
export function readFileSync(path: string, encoding?: string): string | Buffer;
export function writeFileSync(path: string, data: string | Buffer): void;
}
declare module "path" {
export function join(...paths: string[]): string;
export function resolve(...paths: string[]): string;
}
`
}
];
for (const typeDef of typeDefs) {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
typeDef.content,
`file:///node_modules/${typeDef.name}/index.d.ts`
);
monaco.languages.typescript.javascriptDefaults.addExtraLib(
typeDef.content,
`file:///node_modules/${typeDef.name}/index.d.ts`
);
}
}
// 注册自定义主题
export function registerCustomThemes() {
// VS Code Dark+ 主题
monaco.editor.defineTheme('vs-dark-plus', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6A9955' },
{ token: 'keyword', foreground: '569CD6' },
{ token: 'string', foreground: 'CE9178' },
{ token: 'number', foreground: 'B5CEA8' },
{ token: 'type', foreground: '4EC9B0' },
{ token: 'class', foreground: '4EC9B0' },
{ token: 'function', foreground: 'DCDCAA' },
{ token: 'variable', foreground: '9CDCFE' },
{ token: 'constant', foreground: '4FC1FF' },
{ token: 'parameter', foreground: '9CDCFE' },
{ token: 'property', foreground: '9CDCFE' },
{ token: 'regexp', foreground: 'D16969' },
{ token: 'operator', foreground: 'D4D4D4' },
{ token: 'namespace', foreground: '4EC9B0' },
{ token: 'type.identifier', foreground: '4EC9B0' },
{ token: 'tag', foreground: '569CD6' },
{ token: 'attribute.name', foreground: '9CDCFE' },
{ token: 'attribute.value', foreground: 'CE9178' },
],
colors: {
'editor.background': '#1E1E1E',
'editor.foreground': '#D4D4D4',
'editorLineNumber.foreground': '#858585',
'editorCursor.foreground': '#AEAFAD',
'editor.selectionBackground': '#264F78',
'editor.inactiveSelectionBackground': '#3A3D41',
'editorIndentGuide.background': '#404040',
'editorIndentGuide.activeBackground': '#707070',
'editor.wordHighlightBackground': '#515C6A',
'editor.wordHighlightStrongBackground': '#515C6A',
'editorError.foreground': '#F48771',
'editorWarning.foreground': '#CCA700',
'editorInfo.foreground': '#75BEFF',
}
});
}
// 配置 JSON 语言
export function configureJsonLanguage() {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'http://json-schema.org/draft-07/schema#',
fileMatch: ['*.json'],
schema: {
type: 'object',
properties: {},
additionalProperties: true
}
},
{
uri: 'http://json.schemastore.org/package',
fileMatch: ['package.json'],
schema: {
type: 'object',
properties: {
name: { type: 'string' },
version: { type: 'string' },
dependencies: { type: 'object' },
devDependencies: { type: 'object' },
scripts: { type: 'object' }
}
}
},
{
uri: 'http://json.schemastore.org/tsconfig',
fileMatch: ['tsconfig.json', 'tsconfig.*.json'],
schema: {
type: 'object',
properties: {
compilerOptions: { type: 'object' },
include: { type: 'array' },
exclude: { type: 'array' }
}
}
}
],
allowComments: true,
trailingCommas: 'warning'
});
}
// 添加代码片段
export function registerSnippets() {
// TypeScript/JavaScript 代码片段
monaco.languages.registerCompletionItemProvider(['typescript', 'javascript', 'typescriptreact', 'javascriptreact'], {
provideCompletionItems: (model, position) => {
const word = model.getWordUntilPosition(position);
const range = {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
startColumn: word.startColumn,
endColumn: word.endColumn
};
const suggestions = [
{
label: 'log',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'console.log(${1:message});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Console log statement',
range
},
{
label: 'useState',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: 'const [${1:state}, set${1/(.*)/${1:/capitalize}/}] = useState(${2:initialValue});',
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'React useState hook',
range
},
{
label: 'useEffect',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'useEffect(() => {',
'\t${1:// Effect logic}',
'\treturn () => {',
'\t\t${2:// Cleanup}',
'\t};',
'}, [${3:dependencies}]);'
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'React useEffect hook',
range
},
{
label: 'component',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'const ${1:ComponentName}: React.FC = () => {',
'\treturn (',
'\t\t<div>',
'\t\t\t${2:content}',
'\t\t</div>',
'\t);',
'};',
'',
'export default ${1:ComponentName};'
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'React functional component',
range
},
{
label: 'async',
kind: monaco.languages.CompletionItemKind.Snippet,
insertText: [
'async function ${1:functionName}() {',
'\ttry {',
'\t\tconst result = await ${2:promise};',
'\t\t${3:// Handle result}',
'\t} catch (error) {',
'\t\tconsole.error(error);',
'\t}',
'}'
].join('\n'),
insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
documentation: 'Async function with try-catch',
range
}
];
return { suggestions };
}
});
}
// 配置语言特性
export function configureLanguageFeatures() {
// 配置 HTML 标签自动闭合
monaco.languages.registerOnTypeFormattingEditProvider(['html', 'xml', 'javascriptreact', 'typescriptreact'], {
autoFormatTriggerCharacters: ['>'],
provideOnTypeFormattingEdits: (model, position, ch) => {
if (ch === '>') {
const lineContent = model.getLineContent(position.lineNumber);
const beforeCursor = lineContent.substring(0, position.column - 1);
// 检查是否是开始标签
const tagMatch = beforeCursor.match(/<(\w+)(?:\s+[^>]*)?>/);
if (tagMatch) {
const tagName = tagMatch[1];
// 自闭合标签列表
const selfClosingTags = ['img', 'br', 'hr', 'input', 'meta', 'link'];
if (!selfClosingTags.includes(tagName.toLowerCase())) {
return [{
range: {
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: position.lineNumber,
endColumn: position.column
},
text: `</${tagName}>`
}];
}
}
}
return [];
}
});
// 配置括号自动配对
monaco.languages.setLanguageConfiguration('typescript', {
autoClosingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
{ open: '`', close: '`' },
{ open: '<', close: '>' },
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
{ open: '`', close: '`' },
{ open: '<', close: '>' },
],
});
}
// 初始化 Monaco Editor 配置
export async function initializeMonaco() {
// 配置 TypeScript/JavaScript
configureMonacoTypescript();
// 添加类型定义
await addTypeDefinitions();
// 注册自定义主题
registerCustomThemes();
// 配置 JSON
configureJsonLanguage();
// 注册代码片段
registerSnippets();
// 配置语言特性
configureLanguageFeatures();
// 注册代码操作提供器(快速修复)
registerCodeActionProvider();
}
// 格式化文档
export function formatDocument(editor: monaco.editor.IStandaloneCodeEditor) {
editor.getAction('editor.action.formatDocument')?.run();
}
// 添加错误标记
export function addErrorMarkers(
editor: monaco.editor.IStandaloneCodeEditor,
errors: Array<{
line: number;
column: number;
message: string;
severity: 'error' | 'warning' | 'info';
}>
) {
const model = editor.getModel();
if (!model) return;
const markers = errors.map(error => ({
severity: error.severity === 'error'
? monaco.MarkerSeverity.Error
: error.severity === 'warning'
? monaco.MarkerSeverity.Warning
: monaco.MarkerSeverity.Info,
startLineNumber: error.line,
startColumn: error.column,
endLineNumber: error.line,
endColumn: error.column + 1,
message: error.message,
}));
monaco.editor.setModelMarkers(model, 'owner', markers);
}
// 清除错误标记
export function clearErrorMarkers(editor: monaco.editor.IStandaloneCodeEditor) {
const model = editor.getModel();
if (model) {
monaco.editor.setModelMarkers(model, 'owner', []);
}
}