🚀 Monaco Editor 全攻略:自定义格式化、智能联想与 Vue 组件封装

一、Monaco Editor 自定义格式化

编辑器本身提供有JSON格式化的功能,但是想格式化JAVA等不是内置的语言可能需要自定义注册器进行格式化. 例如json

自带有format

sql就没有这个功能

现在领导给我安排了一个新的需求让我支持Java高亮,格式化Java及代码检测,我一个前端又不懂后端语言,给我好一顿挠头😖!

然后就开始了AI编程之旅,开始在各种AI中🔍答案。 废话不多说,直接上代码⌨️!

😜 其实最终只用到了这一段,用到了prettier,和Java的格式化插件完成的。

import prettier from "prettier/standalone";

import java from "prettier-plugin-java";

这个时候你会有疑问🤔,那我怎么格式化呢?编辑器中除了json没有format这个功能啊 😭。

放心,Monaco 提供了丰富的注册机和自定义的功能的,既然右键没出来Format,那咱自己写一个右键的功能不香么? 主要用到了addAction这个函数

ts 复制代码
/**
 *
 * @description 添加格式化prettier动作
 * @param editorRef 编辑器实例
 * @param cb 回调函数
 */

export function setPrettierAction(editorRef, language, cb) {
	editorRef?.addAction({
		id: "prettier",
		label: "Prettier",
		precondition: null,
		contextMenuGroupId: "navigation",
		async run(editor: { getValue: () => any; setValue: (arg0: any) => void }) {
			const currentValue = editor.getValue();
			const newValue = await formatString(currentValue, language);

			if (newValue !== currentValue) {
				cb(newValue);
			}
		}
	});
}

prettier功能出现了!!到目前为止格式化功能就完成了🤩。

二、Monaco Editor 代码联想功能配置

怎么做到和在客户端中能上下文联想,并且能出现自带方法呢?js类型是默认有的,下面讲怎么自定义 主要用到了registerCompletionItemProvider这个方法。

我是写了一个 suggestion函数来进行提示的,写了一些关键词,snippets等,👇提供了完整的代码示例!

格式化及联想,自定义代码段完整功能代码🍡

ts 复制代码
import * as monaco from "monaco-editor/esm/vs/editor/editor.api";
import prettier from "prettier/standalone";
import parserHtml from "prettier/parser-html";
import parseCss from "prettier/parser-postcss";
import parserBabel from "prettier/parser-babel";
import java from "prettier-plugin-java";

export const prettierConfig: any = {
	semi: false,
	singleQuote: true,
	printWidth: 120,
	trailingComma: "none",
	endOfLine: "auto",
	tabWidth: 2
};

const formatJavaScript = string =>
	prettier.format(string, {
		parser: "javascript",
		plugins: [parserBabel],
		trailingComma: "es5",
		...prettierConfig
	});
const formatJson = string =>
	prettier.format(string, {
		parser: "json",
		plugins: [parserBabel],
		trailingComma: "es5",
		...prettierConfig
	});

const formatHtml = string =>
	prettier.format(string, {
		parser: "html",
		plugins: [parserBabel, parserHtml],
		...prettierConfig
	});

const formatCss = string =>
	prettier.format(string, {
		parser: "css",
		plugins: [parseCss],
		...prettierConfig
	});

/**
 * @description 格式化代码工具
 * @created 2025/07/21 09:22:42
 * @param code 编辑器内容数据
 */

const formatCode = async (code: any) => {
	const formattedCode = await prettier.format(code, {
		parser: "java",
		plugins: [java]
	});

	return formattedCode;
};
const formatterMap = {
	json: formatJson,
	typescript: formatJavaScript,
	javascript: formatJavaScript,
	html: formatHtml,
	css: formatCss,
	java: formatCode
};

/**
 *
 * @description 自定义java语言代码补全
 * @created 2025/07/21 09:25:50
 */

export function setupJavaCodeCompletion() {
	monaco.languages.registerCompletionItemProvider("java", {
		triggerCharacters: [".", "("],
		provideCompletionItems: async (model, position) => {
			const word = model.getWordUntilPosition(position);
			const code = model.getValue();
			const range = {
				startLineNumber: position.lineNumber,
				endLineNumber: position.lineNumber,
				startColumn: word.startColumn,
				endColumn: word.endColumn
			};

			return { suggestions: getDefaultSuggestions(range) };
		}
	});
}

// 辅助函数:生成默认补全建议
function getDefaultSuggestions(range) {
	const keywords = [
		"public",
		"private",
		"protected",
		"static",
		"void",
		"int",
		"String",
		"boolean",
		"char",
		"double",
		"float",
		"if",
		"else",
		"for",
		"while",
		"return",
		"class",
		"interface",
		"extends"
	];

	const commonClasses = ["ArrayList", "HashMap", "System", "String", "Integer", "Boolean", "List", "Map", "Set", "Collections"];

	const snippets = [
		{
			label: "System.out.println",
			kind: monaco.languages.CompletionItemKind.Method,
			insertText: "System.out.println(${1:text});",
			insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
			documentation: "Prints a string to the console"
		},
		{
			label: "public static void main",
			kind: monaco.languages.CompletionItemKind.Function,
			insertText: "public static void main(String[] args) {\n\t${1:statement};\n}",
			insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
			documentation: "Main method entry point"
		},
		{
			label: "if-else",
			kind: monaco.languages.CompletionItemKind.Snippet,
			insertText: ["if (${1:condition}) {", "\t${2:statement}", "} else {", "\t${3:statement}", "}"].join("\n"),
			documentation: "Conditional statement"
		},
		{
			label: "for-i",
			kind: monaco.languages.CompletionItemKind.Snippet,
			insertText: ["for (int ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) {", "\t${3:statement}", "}"].join("\n"),
			documentation: "For loop with index"
		}
	];

	return [
		...keywords.map(keyword => ({
			label: keyword,
			kind: monaco.languages.CompletionItemKind.Keyword,
			insertText: keyword,
			range
		})),
		...commonClasses.map(cls => ({
			label: cls,
			kind: monaco.languages.CompletionItemKind.Class,
			insertText: cls,
			range
		})),
		...snippets.map(snippet => ({ ...snippet, range }))
	];
}

export const formatString = (str, language) => {
	const formatter = formatterMap[language] || formatJson;
	let result = str;
	try {
		result = formatter(str);
	} catch (error) {
		const printer = console;
		printer.log(error);
	}

	return result;
};
/**
 *
 * @description 添加格式化prettier动作
 * @param editorRef 编辑器实例
 * @param cb 回调函数
 */

export function setPrettierAction(editorRef, language, cb) {
	editorRef?.addAction({
		id: "prettier",
		label: "Prettier",
		precondition: null,
		contextMenuGroupId: "navigation",
		async run(editor: { getValue: () => any; setValue: (arg0: any) => void }) {
			const currentValue = editor.getValue();
			const newValue = await formatString(currentValue, language);

			if (newValue !== currentValue) {
				cb(newValue);
			}
		}
	});
}

三、Vue 组件封装

  1. 首先把Monaco中的重要方法封装成hooks方便使用和拓展
  2. 封装Vue template组件
  3. 页面中引入组件使用

useMonacoEditor Hook

ts 复制代码
import * as monaco from "monaco-editor";
import { ref, nextTick, onBeforeUnmount } from "vue";

export const languageEnums = monaco.languages
	.getLanguages()
	.map(item => {
		return { label: item.id, value: item.id };
	})
	.sort((a, b) => a.label.localeCompare(b.label));

export default function useMonacoEditor({ language = "json", theme = "vs" }) {
	// 编辑器示例
	let monacoEditor: monaco.editor.IStandaloneCodeEditor | null = null;
	// 目标元素
	const monacoEditorRef = ref<HTMLElement | null>(null);

	// 创建实例
	function createEditor(editorOption: monaco.editor.IStandaloneEditorConstructionOptions = {}) {
		if (!monacoEditorRef.value) return;
		monacoEditor = monaco.editor.create(monacoEditorRef.value, {
			// 初始模型
			model: monaco.editor.createModel("", language),
			// 是否启用预览图
			minimap: { enabled: false },
			// 圆角
			roundedSelection: true,
			// 主题
			theme: theme,
			// 主键
			multiCursorModifier: "ctrlCmd",
			// 滚动条
			scrollbar: {
				verticalScrollbarSize: 8,
				horizontalScrollbarSize: 8
			},
			// 行号
			lineNumbers: "on",
			// tab大小
			tabSize: 2,
			//字体大小
			fontSize: 14,
			// 控制编辑器在用户键入、粘贴、移动或缩进行时是否应自动调整缩进
			autoIndent: "advanced",
			// 自动布局
			automaticLayout: true,
			...editorOption
		});
		return monacoEditor;
	}

	// 格式化
	async function formatDoc() {
		await monacoEditor?.getAction("editor.action.formatDocument")?.run();
	}

	// 数据更新
	function updateVal(val: string) {
		nextTick(() => {
			if (getOption(monaco.editor.EditorOption.readOnly)) {
				updateOptions({ readOnly: false });
			}
			monacoEditor?.setValue(val);
			setTimeout(async () => {
				await formatDoc();
			}, 10);
		});
	}

	// 配置更新
	function updateOptions(opt: monaco.editor.IStandaloneEditorConstructionOptions) {
		monacoEditor?.updateOptions(opt);
	}
	// 修改语言
	function updateLanguage(language: string) {
		const model = monacoEditor?.getModel();
		if (model) {
			monaco.editor.setModelLanguage(model, language);
		}
	}
	// 获取配置
	function getOption(name: monaco.editor.EditorOption) {
		return monacoEditor?.getOption(name);
	}

	// 获取实例
	function getEditor() {
		return monacoEditor;
	}

	// 页面离开 销毁
	onBeforeUnmount(() => {
		if (monacoEditor) {
			monacoEditor.dispose();
		}
	});

	return {
		monacoEditorRef,
		createEditor,
		getEditor,
		updateVal,
		updateOptions,
		updateLanguage,
		getOption,
		formatDoc
	};
}

tempalte组件中引入hooks

js 复制代码
<template>
	<div ref="monacoEditorRef" :style="monacoEditorStyle"></div>
</template>
<script setup lang="ts">
import { useMonacoEditor } from "@/hooks";
import { onMounted, computed, watch, ref } from "vue";

const props = withDefaults(
	defineProps<{
		width?: string | number;
		height?: string | number;
		language?: string;
		theme?: string;
		editorOption?: Object;
		modelValue: string;
	}>(),
	{
		width: "100%",
		height: "100%",
		theme: "vs",
		language: "json",
		editorOption: () => ({}),
		modelValue: ""
	}
);

const emits = defineEmits<{
	(e: "blue"): void;
	(e: "update:modelValue", val: string): void;
}>();

const monacoEditorStyle = computed(() => {
	return {
		width: typeof props.width === "string" ? props.width : props.width + "px",
		height: typeof props.height === "string" ? props.height : props.height + "px"
	};
});
const monacoEditorInstance = ref<any>(null);
const { monacoEditorRef, createEditor, updateVal, updateOptions, getEditor, updateLanguage } = useMonacoEditor({
	language: props.language,
	theme: props.theme
});

onMounted(() => {
	const monacoEditor = createEditor(props.editorOption);
	monacoEditorInstance.value = monacoEditor;
	updateMonacoVal(props.modelValue);
	monacoEditor?.onDidChangeModelContent(() => {
		emits("update:modelValue", monacoEditor!.getValue());
	});
	monacoEditor?.onDidBlurEditorText(() => {
		emits("blue");
	});
});

watch(
	() => props.modelValue,
	() => {
		updateMonacoVal(props.modelValue);
	}
);
watch(
	() => props.language,
	() => {
		updateLanguage(props.language || "javascript");
	}
);
function updateMonacoVal(val: string) {
	if (val !== getEditor()?.getValue()) {
		updateVal(val);
	}
}

defineExpose({
	monacoEditorRef,
	monacoEditorInstance,
	createEditor,
	updateVal,
	updateOptions,
	getEditor,
	updateLanguage,
	updateMonacoVal
});
</script>
js 复制代码
<VueMonacoEditor v-model="xxx" theme="vs-dark" language="json" />

四、参考资料

4.1 Monaco Editor 官方文档

microsoft.github.io/monaco-edit...

格式化思路参考(低代码平台)

github.com/opentiny/ti...

相关推荐
opbr13 分钟前
🚀 告别传统枚举的痛点!TypeScript 中的 constructEnum 让你的代码更优雅 ✨
前端·javascript
鬼鬼_静若为竹13 分钟前
今天在增加了攻击小马的功能
前端
遂心_15 分钟前
React性能优化实战:掌握memo、useCallback与useMemo的精妙用法
前端·javascript·react.js
CodePlayer15 分钟前
《学会提问》读后感
前端·程序员
是晓晓吖16 分钟前
源网站数据采集方案之解析DOM(五)
前端
FogLetter17 分钟前
CSS模块化:告别样式冲突,拥抱组件化开发
前端·react.js
angelQ17 分钟前
针对"@antfu/eslint-config": "^4.17.0"最新版本使用报错Unexpected token 'with'的解决方法
前端·eslint
小飞机噗噗噗18 分钟前
记录js使用原生 XMLHttpRequest方法 封装http请求,并提供使用实例
前端·javascript·react.js
用户25191624271119 分钟前
Canvas之绘制图形后续
前端·javascript·canvas
CodePlayer25 分钟前
《有限与无限的游戏》——读后感
前端·稀土