UniApp自定义代码编辑器:从0到1打造专业开发工具

在移动应用开发领域,代码编辑器一直是开发者们不可或缺的工具。随着鸿蒙系统(HarmonyOS)的崛起,为其量身打造一款流畅、高效的代码编辑器变得尤为重要。本文将深入探讨如何使用UniApp框架开发一个专业级的代码编辑器,并重点关注鸿蒙系统的适配与优化。

技术选型与架构设计

在开始开发之前,我们需要慎重考虑技术栈的选择。经过实践,我们选择了以下核心技术:

  1. UniApp框架:提供跨平台能力
  2. Monaco Editor:微软开源的代码编辑器核心
  3. Prism.js:轻量级的代码语法高亮库
  4. Web Worker:用于代码分析和格式化
  5. IndexedDB:本地文件存储

项目依赖配置

首先,让我们配置必要的项目依赖:

// package.json
{"dependencies": {"monaco-editor": "^0.45.0","prismjs": "^1.29.0","@types/prismjs": "^1.26.3","prettier": "^3.1.0","localforage": "^1.10.0"}
}

编辑器核心实现

1. 编辑器状态管理

首先,我们需要创建一个编辑器状态管理类来处理编辑器的核心状态:

// utils/EditorState.ts
export class EditorState {private static instance: EditorState;private currentFile: string | null = null;private isModified: boolean = false;private history: string[] = [];private historyIndex: number = -1;private constructor() {// 单例模式}static getInstance(): EditorState {if (!EditorState.instance) {EditorState.instance = new EditorState();}return EditorState.instance;}setCurrentFile(filename: string): void {this.currentFile = filename;this.isModified = false;this.history = [];this.historyIndex = -1;}markAsModified(): void {this.isModified = true;}addToHistory(content: string): void {this.history = this.history.slice(0, this.historyIndex + 1);this.history.push(content);this.historyIndex++;// 限制历史记录大小if (this.history.length > 50) {this.history.shift();this.historyIndex--;}}undo(): string | null {if (this.historyIndex > 0) {this.historyIndex--;return this.history[this.historyIndex];}return null;}redo(): string | null {if (this.historyIndex < this.history.length - 1) {this.historyIndex++;return this.history[this.historyIndex];}return null;}
}

2. 编辑器组件实现

创建主编辑器组件,实现核心编辑功能:

<!-- components/CodeEditor.vue -->
<template><view class="code-editor-container"><view class="editor-toolbar"><view class="toolbar-item" @tap="handleUndo"><text class="iconfont icon-undo"></text></view><view class="toolbar-item" @tap="handleRedo"><text class="iconfont icon-redo"></text></view><view class="toolbar-item" @tap="handleFormat"><text class="iconfont icon-format"></text></view></view><view class="editor-content":style="{ height: editorHeight + 'px' }"@touchstart="handleTouchStart"@touchmove="handleTouchMove"@touchend="handleTouchEnd"><view class="line-numbers"><text v-for="line in lineNumbers" :key="line"class="line-number">{{ line }}</text></view><textareaclass="code-area"v-model="code":style="textareaStyles"@input="handleInput"@scroll="handleScroll":show-confirm-bar="false":auto-height="false":cursor-spacing="20"></textarea></view></view>
</template><script lang="ts">
import { defineComponent, ref, computed, onMounted, watch } from 'vue';
import { EditorState } from '@/utils/EditorState';
import { formatCode } from '@/utils/CodeFormatter';
import { SyntaxHighlighter } from '@/utils/SyntaxHighlighter';export default defineComponent({name: 'CodeEditor',props: {initialCode: {type: String,default: ''},language: {type: String,default: 'javascript'}},setup(props) {const code = ref(props.initialCode);const editorHeight = ref(0);const scrollTop = ref(0);const editorState = EditorState.getInstance();const syntaxHighlighter = new SyntaxHighlighter();// 计算行号const lineNumbers = computed(() => {const lines = code.value.split('\n').length;return Array.from({ length: lines }, (_, i) => i + 1);});// 编辑器样式const textareaStyles = computed(() => ({'font-family': 'Monaco, Consolas, monospace','font-size': '14px','line-height': '1.5','tab-size': '2','padding-left': '4em','white-space': 'pre','overflow-x': 'auto'}));// 处理输入const handleInput = (event: any) => {const newContent = event.detail.value;code.value = newContent;editorState.markAsModified();editorState.addToHistory(newContent);// 触发语法高亮syntaxHighlighter.highlight(newContent, props.language);};// 撤销操作const handleUndo = () => {const previousContent = editorState.undo();if (previousContent !== null) {code.value = previousContent;}};// 重做操作const handleRedo = () => {const nextContent = editorState.redo();if (nextContent !== null) {code.value = nextContent;}};// 格式化代码const handleFormat = async () => {try {const formatted = await formatCode(code.value, props.language);code.value = formatted;editorState.markAsModified();editorState.addToHistory(formatted);} catch (error) {uni.showToast({title: '格式化失败',icon: 'none'});}};// 处理滚动同步const handleScroll = (event: any) => {scrollTop.value = event.detail.scrollTop;};onMounted(() => {const sysInfo = uni.getSystemInfoSync();editorHeight.value = sysInfo.windowHeight - 44; // 减去工具栏高度// 初始化编辑器状态editorState.addToHistory(props.initialCode);});return {code,editorHeight,lineNumbers,textareaStyles,handleInput,handleUndo,handleRedo,handleFormat,handleScroll};}
});
</script><style lang="scss">
.code-editor-container {display: flex;flex-direction: column;height: 100%;background-color: #1e1e1e;color: #d4d4d4;.editor-toolbar {display: flex;height: 44px;padding: 0 10px;background-color: #2d2d2d;border-bottom: 1px solid #3d3d3d;.toolbar-item {width: 44px;height: 44px;display: flex;align-items: center;justify-content: center;&:active {background-color: #3d3d3d;}}}.editor-content {flex: 1;display: flex;position: relative;overflow: hidden;.line-numbers {position: absolute;left: 0;top: 0;width: 3em;padding: 8px 0;background-color: #2d2d2d;text-align: right;user-select: none;.line-number {display: block;padding: 0 8px;color: #858585;font-size: 12px;line-height: 1.5;}}.code-area {flex: 1;padding: 8px 0;background-color: transparent;color: inherit;font-size: inherit;line-height: inherit;border: none;}}
}
</style>

3. 语法高亮实现

为了提供更好的代码编辑体验,我们需要实现实时语法高亮:

// utils/SyntaxHighlighter.ts
import * as Prism from 'prismjs';export class SyntaxHighlighter {private worker: Worker | null = null;private highlightQueue: Array<{code: string;language: string;resolve: (value: string) => void;}> = [];private isProcessing: boolean = false;constructor() {this.initializeWorker();}private initializeWorker(): void {// 鸿蒙平台特殊处理if (uni.getSystemInfoSync().platform === 'harmony') {const workerModule = uni.requireNativePlugin('worker');this.worker = workerModule.createWorker('highlighter.js');} else {this.worker = new Worker('/workers/highlighter.js');}this.worker.onmessage = (event) => {const { highlightedCode } = event.data;if (this.highlightQueue.length > 0) {const current = this.highlightQueue.shift();current?.resolve(highlightedCode);}this.processNextInQueue();};}private processNextInQueue(): void {if (this.highlightQueue.length === 0) {this.isProcessing = false;return;}this.isProcessing = true;const next = this.highlightQueue[0];this.worker?.postMessage({code: next.code,language: next.language});}highlight(code: string, language: string): Promise<string> {return new Promise((resolve) => {// 添加到队列this.highlightQueue.push({ code, language, resolve });// 如果没有正在处理的任务,开始处理if (!this.isProcessing) {this.processNextInQueue();}});}dispose(): void {this.worker?.terminate();this.worker = null;this.highlightQueue = [];this.isProcessing = false;}
}

4. 代码格式化工具

实现代码格式化功能:

// utils/CodeFormatter.ts
import prettier from 'prettier';
import parserBabel from 'prettier/parser-babel';
import parserTypescript from 'prettier/parser-typescript';
import parserHtml from 'prettier/parser-html';
import parserCss from 'prettier/parser-postcss';export async function formatCode(code: string, language: string): Promise<string> {const options = {parser: getParserForLanguage(language),plugins: [parserBabel, parserTypescript, parserHtml, parserCss],semi: true,singleQuote: true,tabWidth: 2,printWidth: 80,trailingComma: 'es5'};try {return await prettier.format(code, options);} catch (error) {console.error('格式化失败:', error);throw error;}
}function getParserForLanguage(language: string): string {switch (language.toLowerCase()) {case 'javascript':case 'js':return 'babel';case 'typescript':case 'ts':return 'typescript';case 'html':case 'vue':return 'html';case 'css':case 'scss':case 'less':return 'css';default:return 'babel';}
}

鸿蒙系统适配优化

在鸿蒙系统上运行代码编辑器需要特别注意以下几点:

1. 性能优化

// utils/HarmonyOptimizer.ts
export class HarmonyOptimizer {static setupPerformanceMonitor(): void {if (uni.getSystemInfoSync().platform === 'harmony') {const performance = uni.requireNativePlugin('performance');// 监控内存使用performance.startMonitoring({type: 'memory',callback: (data: any) => {if (data.usedMemory > data.totalMemory * 0.8) {this.performGC();}}});}}static performGC(): void {// 触发垃圾回收if (uni.getSystemInfoSync().platform === 'harmony') {const system = uni.requireNativePlugin('system');system.gc();}}static optimizeRendering(): void {if (uni.getSystemInfoSync().platform === 'harmony') {// 使用鸿蒙原生渲染引擎const render = uni.requireNativePlugin('render');render.setRenderMode({mode: 'hardware' // 启用硬件加速});}}
}

2. 输入法优化

// utils/InputMethodManager.ts
export class InputMethodManager {static adjustInputMethod(): void {if (uni.getSystemInfoSync().platform === 'harmony') {const input = uni.requireNativePlugin('input');// 设置输入法模式input.setInputMode({type: 'text',returnKeyType: 'done',options: {autoCapitalize: false,autoCorrect: false}});}}static handleKeyboardHeight(callback: (height: number) => void): void {uni.onKeyboardHeightChange((res) => {callback(res.height);});}
}

实际应用案例

下面是一个完整的代码编辑器页面示例:

<!-- pages/editor/index.vue -->
<template><view class="editor-page"><CodeEditor:initial-code="initialCode":language="currentLanguage"@change="handleCodeChange"/><view class="floating-menu" v-if="showMenu"><view class="menu-item" @tap="handleSave"><text class="iconfont icon-save"></text><text>保存</text></view><view class="menu-item" @tap="handleShare"><text class="iconfont icon-share"></text><text>分享</text></view></view></view>
</template><script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
import CodeEditor from '@/components/CodeEditor.vue';
import { HarmonyOptimizer } from '@/utils/HarmonyOptimizer';
import { InputMethodManager } from '@/utils/InputMethodManager';export default defineComponent({components: {CodeEditor},setup() {const initialCode = ref('');const currentLanguage = ref('javascript');const showMenu = ref(false);onMounted(() => {// 初始化优化HarmonyOptimizer.setupPerformanceMonitor();HarmonyOptimizer.optimizeRendering();InputMethodManager.adjustInputMethod();// 处理键盘高度变化InputMethodManager.handleKeyboardHeight((height) => {// 调整编辑器布局});});const handleCodeChange = (newCode: string) => {// 处理代码变更};const handleSave = async () => {try {// 实现保存逻辑await saveCode();uni.showToast({title: '保存成功',icon: 'success'});} catch (error) {uni.showToast({title: '保存失败',icon: 'none'});}};const handleShare = () => {// 实现分享逻辑};return {initialCode,currentLanguage,showMenu,handleCodeChange,handleSave,handleShare};}
});
</script><style lang="scss">
.editor-page {height: 100vh;position: relative;
}.floating-menu {position: fixed;right: 20px;bottom: 20px;background-color: #2d2d2d;border-radius: 8px;padding: 8px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);.menu-item {display: flex;align-items: center;padding: 8px 12px;text {margin-left: 8px;color: #d4d4d4;}&:active {background-color: #3d3d3d;}}
}
</style>

总结与展望

通过本文的实践,我们实现了一个功能完整的代码编辑器,并针对鸿蒙系统进行了深度优化。主要特点包括:

  1. 完整的编辑功能:语法高亮、代码格式化、撤销/重做等
  2. 鸿蒙系统特别优化:性能监控、输入法适配、渲染优化
  3. 良好的用户体验:流畅的输入响应、合理的布局设计

未来的优化方向:

  1. 支持更多编程语言和框架
  2. 实现代码自动补全
  3. 集成代码调试功能
  4. 优化大文件处理性能
  5. 增强与鸿蒙系统的集成度

随着鸿蒙生态的不断发展,相信会有更多优秀的开发工具在这个平台上涌现。我们也将持续关注新特性的支持情况,不断优化和改进我们的代码编辑器。