一、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 组件封装
- 首先把Monaco中的重要方法封装成hooks方便使用和拓展
- 封装Vue template组件
- 页面中引入组件使用
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...