




KDevelop 是 KDE 社区出品的开源集成开发环境,支持 C/C++、Python、JavaScript、HTML/CSS、PHP、Java 等 30 余种编程语言,提供多标签编辑器、文件树管理、全局搜索、符号大纲、查找导航等功能。本文记录将其基于 Electron 壳方案适配到鸿蒙 PC 平台的完整流程。
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
欢迎在 PC 社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_kdevelop
KDevelop 的核心功能是代码编辑与项目管理。与 VS Code 等大型编辑器相比,它更加轻量,聚焦于多语言代码编辑、文件树管理和符号导航,适合在鸿蒙 PC 平台上快速浏览和编辑代码。
| 层级 | 职责 | 技术实现 | | --- | --- | --- | | 主进程 | 窗口管理 + 文件系统 IPC | Electron BrowserWindow + Node.js fs/path | | IPC 通道 | 进程间通信(11 个通道) | ipcMain.handle / ipcRenderer.invoke | | 渲染进程 | 多标签编辑器 + 文件树 + 搜索 | CodeMirror 5.65.16 + 原生 DOM | | 样式层 | 深色主题 | Catppuccin Mocha 配色方案 |
鸿蒙 Electron 适配层存在三大兼容约束,必须在开发中严格遵守:
ohos_hap/
├── electron-apps/
│ └── KDevelop/
│ ├── main.js
│ ├── renderer.js
│ ├── index.html
│ ├── package.json
│ └── styles/
│ └── kdevelop.css
├── web_engine/
│ └── src/main/resources/
│ └── resfile/resources/app/
└── build-profile.json5
文件 :electron-apps/KDevelop/main.js
KDevelop 的核心是文件操作。在鸿蒙平台上,渲染进程无法直接使用 Node.js 的文件系统模块,必须通过 IPC 通道委托主进程完成。主进程注册 11 个 IPC 通道,覆盖文件对话框、文件读写、目录操作和文件搜索:
var
electron
=
require
(
'electron'
)
;
var
app
=
electron
.
app
;
var
BrowserWindow
=
electron
.
BrowserWindow
;
var
screen
=
electron
.
screen
;
var
ipcMain
=
electron
.
ipcMain
;
var
dialog
=
electron
.
dialog
;
var
path
=
require
(
'path'
)
;
var
fs
=
require
(
'fs'
)
;
app
.
disableHardwareAcceleration
(
)
;
var
mainWindow
=
null
;
function
createWindow
(
)
{
try
{
var
display
=
screen
.
getPrimaryDisplay
(
)
;
mainWindow
=
new
BrowserWindow
(
{
width
:
Math
.
floor
(
display
.
workAreaSize
.
width
*
0.92
)
,
height
:
Math
.
floor
(
display
.
workAreaSize
.
height
*
0.88
)
,
frame
:
true
,
transparent
:
false
,
resizable
:
true
,
webPreferences
:
{
nodeIntegration
:
true
,
contextIsolation
:
false
}
}
)
;
mainWindow
.
loadFile
(
'index.html'
)
;
}
catch
(
e
)
{
console
.
warn
(
'[KDevelop] 创建窗口失败:'
,
e
.
message
)
;
}
}
app
.
whenReady
(
)
.
then
(
function
(
)
{
createWindow
(
)
;
}
)
;
app
.
on
(
'window-all-closed'
,
function
(
)
{
app
.
quit
(
)
;
}
)
;
文件对话框 IPC 通道注册(3 个通道):
ipcMain
.
handle
(
'dialog:openFile'
,
function
(
)
{
return
dialog
.
showOpenDialog
(
mainWindow
,
{
properties
:
[
'openFile'
]
,
filters
:
[
{
name
:
'所有文件'
,
extensions
:
[
'*'
]
}
]
}
)
;
}
)
;
ipcMain
.
handle
(
'dialog:openFolder'
,
function
(
)
{
return
dialog
.
showOpenDialog
(
mainWindow
,
{
properties
:
[
'openDirectory'
]
}
)
;
}
)
;
ipcMain
.
handle
(
'dialog:saveFile'
,
function
(
)
{
return
dialog
.
showSaveDialog
(
mainWindow
,
{
filters
:
[
{
name
:
'所有文件'
,
extensions
:
[
'*'
]
}
]
}
)
;
}
)
;
文件读写与目录操作 IPC 通道注册(7 个通道):
ipcMain
.
handle
(
'file:read'
,
function
(
event
,
filePath
)
{
try
{
var
content
=
fs
.
readFileSync
(
filePath
,
'utf-8'
)
;
return
{
success
:
true
,
content
:
content
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
}
;
}
}
)
;
ipcMain
.
handle
(
'file:write'
,
function
(
event
,
filePath
,
content
)
{
try
{
fs
.
writeFileSync
(
filePath
,
content
,
'utf-8'
)
;
return
{
success
:
true
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
}
;
}
}
)
;
ipcMain
.
handle
(
'dir:read'
,
function
(
event
,
dirPath
)
{
try
{
var
entries
=
fs
.
readdirSync
(
dirPath
,
{
withFileTypes
:
true
}
)
;
var
items
=
[
]
;
for
(
var
i
=
0
;
i
<
entries
.
length
;
i
++
)
{
var
entry
=
entries
[
i
]
;
if
(
entry
.
name
.
startsWith
(
'.'
)
||
entry
.
name
===
'node_modules'
)
continue
;
items
.
push
(
{
name
:
entry
.
name
,
path
:
path
.
join
(
dirPath
,
entry
.
name
)
,
isDirectory
:
entry
.
isDirectory
(
)
}
)
;
}
items
.
sort
(
function
(
a
,
b
)
{
if
(
a
.
isDirectory
!==
b
.
isDirectory
)
return
a
.
isDirectory
?
-
1
:
1
;
return
a
.
name
.
localeCompare
(
b
.
name
)
;
}
)
;
return
{
success
:
true
,
items
:
items
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
}
;
}
}
)
;
ipcMain
.
handle
(
'file:create'
,
function
(
event
,
filePath
)
{
try
{
fs
.
writeFileSync
(
filePath
,
''
,
'utf-8'
)
;
return
{
success
:
true
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
}
;
}
}
)
;
ipcMain
.
handle
(
'dir:create'
,
function
(
event
,
dirPath
)
{
try
{
fs
.
mkdirSync
(
dirPath
,
{
recursive
:
true
}
)
;
return
{
success
:
true
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
}
;
}
}
)
;
ipcMain
.
handle
(
'file:delete'
,
function
(
event
,
filePath
)
{
try
{
var
stat
=
fs
.
statSync
(
filePath
)
;
if
(
stat
.
isDirectory
(
)
)
{
fs
.
rmdirSync
(
filePath
,
{
recursive
:
true
}
)
;
}
else
{
fs
.
unlinkSync
(
filePath
)
;
}
return
{
success
:
true
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
}
;
}
}
)
;
文件搜索 IPC 通道注册(1 个通道,支持正则匹配,最大深度 5 层,最多 200 条结果):
ipcMain
.
handle
(
'file:search'
,
function
(
event
,
dirPath
,
pattern
)
{
try
{
var
results
=
[
]
;
var
regex
;
try
{
regex
=
new
RegExp
(
pattern
,
'i'
)
;
}
catch
(
re
)
{
return
{
success
:
false
,
error
:
'无效的正则表达式: '
+
re
.
message
,
results
:
[
]
}
;
}
function
walk
(
dir
,
depth
)
{
if
(
depth
>
5
||
results
.
length
>
200
)
return
;
var
entries
=
fs
.
readdirSync
(
dir
,
{
withFileTypes
:
true
}
)
;
for
(
var
i
=
0
;
i
<
entries
.
length
;
i
++
)
{
if
(
entries
[
i
]
.
name
.
startsWith
(
'.'
)
||
entries
[
i
]
.
name
===
'node_modules'
)
continue
;
var
fullPath
=
path
.
join
(
dir
,
entries
[
i
]
.
name
)
;
if
(
regex
.
test
(
entries
[
i
]
.
name
)
)
{
results
.
push
(
{
name
:
entries
[
i
]
.
name
,
path
:
fullPath
,
isDirectory
:
entries
[
i
]
.
isDirectory
(
)
}
)
;
}
if
(
entries
[
i
]
.
isDirectory
(
)
)
{
walk
(
fullPath
,
depth
+
1
)
;
}
}
}
walk
(
dirPath
,
0
)
;
return
{
success
:
true
,
results
:
results
}
;
}
catch
(
e
)
{
return
{
success
:
false
,
error
:
e
.
message
,
results
:
[
]
}
;
}
}
)
;
文件 :electron-apps/KDevelop/index.html
KDevelop 采用经典 IDE 布局:菜单栏 + 工具栏 + 左侧工具选择器 + 侧边栏 + 编辑器区域 + 状态栏。工具选择器使用垂直按钮组切换文件树、搜索、符号三个视图,这是 KDE 风格的关键设计:
<
!
--
主体区域
--
>
<
div
class
=
"main"
>
<
!
--
左侧工具选择器(KDevelop 风格)
--
>
<
div
class
=
"tool-selector"
id
=
"toolSelector"
>
<
div
class
=
"tool-btn-v active"
data
-
tool
=
"files"
title
=
"文件/项目"
>
<
span
class
=
"tool-icon"
>
📁
<
/
span
>
<
/
div
>
<
div
class
=
"tool-btn-v"
data
-
tool
=
"search"
title
=
"全局搜索"
>
<
span
class
=
"tool-icon"
>
🔍
<
/
span
>
<
/
div
>
<
div
class
=
"tool-btn-v"
data
-
tool
=
"symbols"
title
=
"符号/大纲"
>
<
span
class
=
"tool-icon"
>
𝑓
<
/
span
>
<
/
div
>
<
/
div
>
<
!
--
侧边栏面板
--
>
<
div
class
=
"sidebar"
id
=
"sidebar"
>
<
!
--
文件树视图
--
>
<
div
class
=
"tool-view active"
id
=
"viewFiles"
>
<
div
class
=
"sidebar-header"
>
<
span
class
=
"sidebar-title"
>
项目文件
<
/
span
>
<
div
class
=
"sidebar-actions"
>
<
button
class
=
"sidebar-btn"
id
=
"btnNewFileTree"
title
=
"新建文件"
>
📄
<
/
button
>
<
button
class
=
"sidebar-btn"
id
=
"btnNewFolderTree"
title
=
"新建文件夹"
>
📁
<
/
button
>
<
button
class
=
"sidebar-btn"
id
=
"btnRefreshTree"
title
=
"刷新"
>
🔄
<
/
button
>
<
/
div
>
<
/
div
>
<
div
class
=
"sidebar-content"
id
=
"fileTree"
>
<
div
class
=
"sidebar-empty"
>
<
p
>
未打开文件夹
<
/
p
>
<
button id
=
"btnOpenFolderEmpty"
class
=
"btn-primary"
>
打开文件夹
<
/
button
>
<
/
div
>
<
/
div
>
<
/
div
>
<
!
--
搜索视图
--
>
<
div
class
=
"tool-view"
id
=
"viewSearch"
>
<
div
class
=
"sidebar-header"
>
<
span
class
=
"sidebar-title"
>
全局搜索
<
/
span
>
<
/
div
>
<
div
class
=
"sidebar-content"
>
<
div
class
=
"search-bar"
>
<
input type
=
"text"
id
=
"searchInput"
class
=
"search-input"
placeholder
=
"搜索文件名..."
>
<
button id
=
"btnSearch"
class
=
"btn-search"
>
🔍
<
/
button
>
<
/
div
>
<
div id
=
"searchResults"
class
=
"search-results"
>
<
div
class
=
"sidebar-empty-hint"
>
输入关键词搜索文件
<
/
div
>
<
/
div
>
<
/
div
>
<
/
div
>
<
!
--
符号
/
大纲视图
--
>
<
div
class
=
"tool-view"
id
=
"viewSymbols"
>
<
div
class
=
"sidebar-header"
>
<
span
class
=
"sidebar-title"
>
符号
/
大纲
<
/
span
>
<
/
div
>
<
div
class
=
"sidebar-content"
>
<
div id
=
"symbolList"
class
=
"symbol-list"
>
<
div
class
=
"sidebar-empty-hint"
>
打开文件后显示函数和类列表
<
/
div
>
<
/
div
>
<
/
div
>
<
/
div
>
<
/
div
>
<
!
--
侧边栏拖动条
--
>
<
div
class
=
"sidebar-resizer"
id
=
"sidebarResizer"
>
<
/
div
>
<
!
--
编辑器区域
--
>
<
div
class
=
"editor-area"
>
<
!
--
标签栏
--
>
<
div
class
=
"tab-bar"
id
=
"tabBar"
>
<
div
class
=
"tab-bar-empty"
>
欢迎使用 KDevelop
<
/
div
>
<
/
div
>
<
!
--
编辑器容器
--
>
<
div
class
=
"editor-container"
id
=
"editorContainer"
>
<
div
class
=
"welcome-screen"
id
=
"welcomeScreen"
>
<
div
class
=
"welcome-logo"
>
KDevelop
<
/
div
>
<
div
class
=
"welcome-subtitle"
>
KDE
集成开发环境
<
/
div
>
<
div
class
=
"welcome-actions"
>
<
button
class
=
"welcome-btn"
id
=
"btnWelcomeOpen"
>
📂 打开文件夹
<
/
button
>
<
button
class
=
"welcome-btn"
id
=
"btnWelcomeNew"
>
📄 新建文件
<
/
button
>
<
/
div
>
<
div
class
=
"welcome-info"
>
<
p
>
支持语言:
C
/
C
++
、Python、JavaScript、
HTML
/
CSS
、
PHP
、Java
<
/
p
>
<
p
>
快捷键:Ctrl
+
N
新建
|
Ctrl
+
O
打开
|
Ctrl
+
S
保存
|
Ctrl
+
F
查找
<
/
p
>
<
/
div
>
<
/
div
>
<
/
div
>
<
/
div
>
<
/
div
>
CodeMirror CDN 加载 9 个语言模式和 8 个功能插件:
<
!
--
CodeMirror
--
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/htmlmixed/htmlmixed.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/xml/xml.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/javascript/javascript.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/css/css.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/python/python.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/clike/clike.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/php/php.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/markdown/markdown.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/shell/shell.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/edit/closebrackets.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/edit/closetag.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/fold/foldcode.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/fold/foldgutter.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/fold/brace-fold.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/fold/xml-fold.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/search/searchcursor.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/addon/selection/active-line.min.js"
>
<
/
script
>
文件 :electron-apps/KDevelop/renderer.js
渲染进程的核心逻辑包括:多标签编辑器、文件树管理、全局搜索、符号提取、查找栏和侧边栏拖动。
多标签编辑器 :每个标签保存完整的 CodeMirror 实例和元数据,切换时显示/隐藏对应编辑器容器:
function
createTab
(
name
,
filePath
,
content
,
lang
)
{
var
id
=
++
state
.
tabIdCounter
;
var
tab
=
{
id
:
id
,
name
:
name
,
path
:
filePath
,
modified
:
false
,
lang
:
lang
}
;
var
container
=
document
.
createElement
(
'div'
)
;
container
.
id
=
'editor-'
+
id
;
container
.
style
.
display
=
'none'
;
document
.
getElementById
(
'editorContainer'
)
.
appendChild
(
container
)
;
tab
.
editor
=
CodeMirror
(
container
,
{
value
:
content
||
''
,
mode
:
getModeForLang
(
lang
)
,
theme
:
'material-darker'
,
lineNumbers
:
true
,
autoCloseBrackets
:
true
,
autoCloseTags
:
true
,
styleActiveLine
:
true
,
foldGutter
:
true
,
gutters
:
[
'CodeMirror-linenumbers'
,
'CodeMirror-foldgutter'
]
,
tabSize
:
4
,
indentWithTabs
:
true
}
)
;
tab
.
editor
.
on
(
'change'
,
function
(
)
{
if
(
!
tab
.
modified
)
{
tab
.
modified
=
true
;
updateTabUI
(
tab
)
;
}
}
)
;
tab
.
editor
.
on
(
'cursorActivity'
,
function
(
)
{
if
(
state
.
activeTabId
===
id
)
updateStatusBar
(
)
;
}
)
;
state
.
tabs
.
push
(
tab
)
;
renderTabBar
(
)
;
var
ws
=
document
.
getElementById
(
'welcomeScreen'
)
;
if
(
ws
)
ws
.
style
.
display
=
'none'
;
return
tab
;
}
function
switchToTab
(
id
)
{
state
.
activeTabId
=
id
;
state
.
tabs
.
forEach
(
function
(
tab
)
{
var
el
=
document
.
getElementById
(
'editor-'
+
tab
.
id
)
;
if
(
el
)
el
.
style
.
display
=
tab
.
id
===
id
?
''
:
'none'
;
}
)
;
var
tab
=
getActiveTab
(
)
;
if
(
tab
)
{
tab
.
editor
.
refresh
(
)
;
tab
.
editor
.
focus
(
)
;
}
renderTabBar
(
)
;
updateStatusBar
(
)
;
}
语言检测 :支持 30 余种文件扩展名,映射到 11 种 CodeMirror 语言模式:
function
detectLanguage
(
filename
)
{
var
ext
=
filename
.
split
(
'.'
)
.
pop
(
)
.
toLowerCase
(
)
;
var
map
=
{
'js'
:
'javascript'
,
'mjs'
:
'javascript'
,
'cjs'
:
'javascript'
,
'ts'
:
'javascript'
,
'tsx'
:
'javascript'
,
'html'
:
'html'
,
'htm'
:
'html'
,
'xml'
:
'xml'
,
'svg'
:
'xml'
,
'css'
:
'css'
,
'scss'
:
'css'
,
'sass'
:
'css'
,
'less'
:
'css'
,
'py'
:
'python'
,
'pyw'
:
'python'
,
'cpp'
:
'c++'
,
'cxx'
:
'c++'
,
'cc'
:
'c++'
,
'c'
:
'c++'
,
'h'
:
'c++'
,
'hpp'
:
'c++'
,
'java'
:
'java'
,
'kt'
:
'java'
,
'kts'
:
'java'
,
'php'
:
'php'
,
'md'
:
'markdown'
,
'markdown'
:
'markdown'
,
'sh'
:
'shell'
,
'bash'
:
'shell'
,
'zsh'
:
'shell'
,
'json'
:
'javascript'
,
'yaml'
:
'text'
,
'yml'
:
'text'
,
'txt'
:
'text'
,
'log'
:
'text'
}
;
return
map
[
ext
]
||
'text'
;
}
function
getModeForLang
(
lang
)
{
var
map
=
{
'javascript'
:
'javascript'
,
'html'
:
'htmlmixed'
,
'xml'
:
'xml'
,
'css'
:
'css'
,
'python'
:
'python'
,
'c++'
:
'text/x-c++src'
,
'java'
:
'text/x-java'
,
'php'
:
'php'
,
'markdown'
:
'markdown'
,
'shell'
:
'shell'
,
'text'
:
'text'
}
;
return
map
[
lang
]
||
'text'
;
}
符号提取 :从当前编辑器内容提取函数、类和变量定义,显示在符号视图中,点击跳转到对应行:
function
extractSymbols
(
)
{
var
tab
=
getActiveTab
(
)
;
var
container
=
document
.
getElementById
(
'symbolList'
)
;
if
(
!
tab
||
!
tab
.
editor
)
{
container
.
innerHTML
=
'<div class="sidebar-empty-hint">打开文件后显示函数和类列表</div>'
;
return
;
}
var
symbols
=
[
]
;
var
lines
=
tab
.
editor
.
getValue
(
)
.
split
(
'\n'
)
;
for
(
var
i
=
0
;
i
<
lines
.
length
;
i
++
)
{
var
line
=
lines
[
i
]
;
var
fnMatch
=
line
.
match
(
/
(?:function\s+(\w+))|(?:const\s+(\w+)\s*=\s*(?:function|\())|(?:def\s+(\w+))|(?:async\s+function\s+(\w+))
/
)
;
if
(
fnMatch
)
{
var
name
=
fnMatch
[
1
]
||
fnMatch
[
2
]
||
fnMatch
[
3
]
||
fnMatch
[
4
]
;
symbols
.
push
(
{
name
:
name
,
kind
:
'fn'
,
line
:
i
+
1
}
)
;
}
var
classMatch
=
line
.
match
(
/
(?:class\s+(\w+))
/
)
;
if
(
classMatch
)
{
symbols
.
push
(
{
name
:
classMatch
[
1
]
,
kind
:
'class'
,
line
:
i
+
1
}
)
;
}
var
varMatch
=
line
.
match
(
/
(?:var|let|const)\s+(\w+)\s*=
/
)
;
if
(
varMatch
&&
!
fnMatch
)
{
symbols
.
push
(
{
name
:
varMatch
[
1
]
,
kind
:
'var'
,
line
:
i
+
1
}
)
;
}
}
container
.
innerHTML
=
''
;
if
(
symbols
.
length
===
0
)
{
container
.
innerHTML
=
'<div class="sidebar-empty-hint">未检测到符号</div>'
;
return
;
}
symbols
.
forEach
(
function
(
sym
)
{
var
el
=
document
.
createElement
(
'div'
)
;
el
.
className
=
'symbol-item'
;
el
.
innerHTML
=
'<span class="symbol-kind '
+
sym
.
kind
+
'">'
+
sym
.
kind
.
toUpperCase
(
)
+
'</span><span>'
+
sym
.
name
+
'</span><span style="color:var(--overlay0);font-size:11px;margin-left:auto">:'
+
sym
.
line
+
'</span>'
;
el
.
addEventListener
(
'click'
,
function
(
)
{
tab
.
editor
.
setCursor
(
sym
.
line
-
1
,
0
)
;
tab
.
editor
.
focus
(
)
;
}
)
;
container
.
appendChild
(
el
)
;
}
)
;
}
文件 :electron-apps/KDevelop/renderer.js
文件树采用懒加载模式,点击文件夹时才读取子目录内容,避免一次性加载大项目:
function
renderTreeItems
(
parent
,
items
,
basePath
)
{
items
.
forEach
(
function
(
item
)
{
if
(
item
.
isDirectory
)
{
var
folderEl
=
document
.
createElement
(
'div'
)
;
folderEl
.
className
=
'tree-item'
;
folderEl
.
innerHTML
=
'<span class="tree-toggle">▶</span><span class="tree-icon">📁</span><span class="tree-name">'
+
item
.
name
+
'</span>'
;
folderEl
.
addEventListener
(
'click'
,
function
(
e
)
{
e
.
stopPropagation
(
)
;
var
children
=
folderEl
.
nextElementSibling
;
var
toggle
=
folderEl
.
querySelector
(
'.tree-toggle'
)
;
if
(
children
&&
children
.
classList
.
contains
(
'tree-children'
)
)
{
children
.
classList
.
toggle
(
'collapsed'
)
;
toggle
.
textContent
=
children
.
classList
.
contains
(
'collapsed'
)
?
'▶'
:
'▼'
;
}
else
{
toggle
.
textContent
=
'▼'
;
folderEl
.
querySelector
(
'.tree-icon'
)
.
textContent
=
'📂'
;
ipc
(
'dir:read'
,
item
.
path
)
.
then
(
function
(
res
)
{
if
(
res
.
success
)
{
var
childContainer
=
document
.
createElement
(
'div'
)
;
childContainer
.
className
=
'tree-children'
;
renderTreeItems
(
childContainer
,
res
.
items
,
item
.
path
)
;
folderEl
.
parentNode
.
insertBefore
(
childContainer
,
folderEl
.
nextSibling
)
;
}
}
)
;
}
}
)
;
folderEl
.
addEventListener
(
'contextmenu'
,
function
(
e
)
{
e
.
preventDefault
(
)
;
e
.
stopPropagation
(
)
;
state
.
contextTarget
=
item
;
showContextMenu
(
e
.
clientX
,
e
.
clientY
)
;
}
)
;
parent
.
appendChild
(
folderEl
)
;
}
else
{
var
fileEl
=
document
.
createElement
(
'div'
)
;
fileEl
.
className
=
'tree-item'
;
fileEl
.
innerHTML
=
'<span class="tree-icon" style="margin-left:16px">'
+
getFileIcon
(
item
.
name
)
+
'</span><span class="tree-name">'
+
item
.
name
+
'</span>'
;
fileEl
.
addEventListener
(
'click'
,
function
(
e
)
{
e
.
stopPropagation
(
)
;
document
.
querySelectorAll
(
'.tree-item.active'
)
.
forEach
(
function
(
a
)
{
a
.
classList
.
remove
(
'active'
)
;
}
)
;
fileEl
.
classList
.
add
(
'active'
)
;
openFileByPath
(
item
.
path
)
;
}
)
;
fileEl
.
addEventListener
(
'contextmenu'
,
function
(
e
)
{
e
.
preventDefault
(
)
;
e
.
stopPropagation
(
)
;
state
.
contextTarget
=
item
;
showContextMenu
(
e
.
clientX
,
e
.
clientY
)
;
}
)
;
parent
.
appendChild
(
fileEl
)
;
}
}
)
;
}
侧边栏拖动条通过 mousedown/mousemove/mouseup 三事件实现宽度调整:
function
initSidebarResizer
(
)
{
var
resizer
=
document
.
getElementById
(
'sidebarResizer'
)
;
var
sidebar
=
document
.
getElementById
(
'sidebar'
)
;
var
startX
,
startW
;
resizer
.
addEventListener
(
'mousedown'
,
function
(
e
)
{
startX
=
e
.
clientX
;
startW
=
sidebar
.
offsetWidth
;
document
.
addEventListener
(
'mousemove'
,
onMove
)
;
document
.
addEventListener
(
'mouseup'
,
onUp
)
;
e
.
preventDefault
(
)
;
}
)
;
function
onMove
(
e
)
{
var
w
=
startW
+
(
e
.
clientX
-
startX
)
;
if
(
w
>
150
&&
w
<
600
)
sidebar
.
style
.
width
=
w
+
'px'
;
}
function
onUp
(
)
{
document
.
removeEventListener
(
'mousemove'
,
onMove
)
;
document
.
removeEventListener
(
'mouseup'
,
onUp
)
;
}
}
文件 :electron-apps/KDevelop/styles/kdevelop.css
KDevelop 采用 Catppuccin Mocha 配色方案,20 个标准 CSS 变量定义完整的深色主题:
:
root
{
--
base
:
#1e1e2e
;
--
mantle
:
#
181825
;
--
crust
:
#11111b
;
--
surface0
:
#
313244
;
--
surface1
:
#45475a
;
--
surface2
:
#585b70
;
--
text
:
#cdd6f4
;
--
text
-
muted
:
#a6adc8
;
--
overlay0
:
#6c7086
;
--
blue
:
#89b4fa
;
--
green
:
#a6e3a1
;
--
red
:
#f38ba8
;
--
yellow
:
#f9e2af
;
--
mauve
:
#cba6f7
;
--
peach
:
#fab387
;
--
teal
:
#94e2d5
;
--
sky
:
#89dceb
;
--
pink
:
#f5c2e7
;
--
border
:
#45475a
;
--
border
-
light
:
#585b70
;
--
menubar
-
height
:
28px
;
--
toolbar
-
height
:
36px
;
--
statusbar
-
height
:
24px
;
--
tool
-
selector
-
width
:
40px
;
--
sidebar
-
width
:
260px
;
--
font
-
mono
:
'Consolas'
,
'Monaco'
,
'Courier New'
,
monospace
;
--
font
-
ui
:
-
apple
-
system
,
BlinkMacSystemFont
,
'Segoe UI'
,
sans
-
serif
;
}
工具选择器激活状态配色(KDevelop 风格左侧蓝色指示条):
.
tool
-
btn
-
v
{
width
:
34px
;
height
:
34px
;
display
:
flex
;
align
-
items
:
center
;
justify
-
content
:
center
;
cursor
:
pointer
;
border
-
radius
:
6px
;
font
-
size
:
16px
;
color
:
var
(
--
overlay0
)
;
transition
:
background
0
.
15s
,
color
0
.
15s
;
}
.
tool
-
btn
-
v
:
hover
{
background
:
var
(
--
surface0
)
;
color
:
var
(
--
text
)
;
}
.
tool
-
btn
-
v
.
active
{
background
:
var
(
--
surface1
)
;
color
:
var
(
--
blue
)
;
border
-
left
:
3px solid
var
(
--
blue
)
;
border
-
radius
:
0
6px 6px
0
;
}
符号类型徽章配色(函数蓝色、类紫色、变量青色):
.
symbol
-
kind
{
font
-
size
:
10px
;
padding
:
1px 4px
;
border
-
radius
:
3px
;
font
-
weight
:
600
;
}
.
symbol
-
kind
.
fn
{
background
:
rgba
(
137
,
180
,
250
,
0.2
)
;
color
:
var
(
--
blue
)
;
}
.
symbol
-
kind
.
class
{
background
:
rgba
(
203
,
166
,
247
,
0.2
)
;
color
:
var
(
--
mauve
)
;
}
.
symbol
-
kind
.
var
{
background
:
rgba
(
148
,
226
,
213
,
0.2
)
;
color
:
var
(
--
teal
)
;
}
将 Electron 应用文件复制到鸿蒙 web_engine 模块的部署目录:
$src
=
"electron-apps\KDevelop"
$dest
=
"web_engine\src\main\resources\resfile\resources\app"
Copy
-
Item
"$src\main.js"
"$dest\main.js"
-
Force
Copy
-
Item
"$src\renderer.js"
"$dest\renderer.js"
-
Force
Copy
-
Item
"$src\index.html"
"$dest\index.html"
-
Force
Copy
-
Item
"$src\package.json"
"$dest\package.json"
-
Force
Copy
-
Item
"$src\styles\kdevelop.css"
"$dest\styles\kdevelop.css"
-
Force



Q1:为什么文件操作要通过 IPC 而不是直接在渲染进程操作?
问题现象:渲染进程能否直接使用 Node.js 的 fs 模块读写文件?
根本原因:虽然 KDevelop 开启了 nodeIntegration: true,但鸿蒙 Electron 适配层对渲染进程的 Node.js 能力有限制,文件系统操作必须通过主进程 IPC 完成
解决方案:主进程注册 11 个 IPC 通道,渲染进程通过 ipcRenderer.invoke 调用
function
ipc
(
channel
)
{
var
args
=
Array
.
prototype
.
slice
.
call
(
arguments
,
1
)
;
return
ipcRenderer
.
invoke
.
apply
(
ipcRenderer
,
[
channel
]
.
concat
(
args
)
)
;
}
ipc
(
'file:read'
,
filePath
)
.
then
(
function
(
result
)
{
if
(
result
.
success
)
{
var
tab
=
createTab
(
name
,
filePath
,
result
.
content
,
lang
)
;
switchToTab
(
tab
.
id
)
;
}
}
)
;
Q2:CodeMirror 为什么选择 5.x 而不是 6.x?
问题现象:CodeMirror 6 是最新版本,为什么不用?
分析:CodeMirror 6 采用模块化架构,需要打包工具(webpack/rollup)支持,而鸿蒙 Electron 壳应用使用纯 HTML 加载方式,无法使用打包工具
结论:CodeMirror 5.x 支持纯 CDN 加载,通过 script 标签直接引入,无需打包工具,适合鸿蒙平台
<
!
--
CodeMirror
5
.
x
CDN
加载方式
--
>
<
link rel
=
"stylesheet"
href
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.css"
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/lib/codemirror.min.js"
>
<
/
script
>
<
script src
=
"https://cdn.jsdelivr.net/npm/codemirror@5.65.16/mode/javascript/javascript.min.js"
>
<
/
script
>
问题现象:KDevelop 左侧的工具选择器为什么不用水平标签页?
根本原因:IDE 布局中侧边栏宽度有限,水平标签页文字会被截断,垂直按钮组使用图标更节省空间
解决方案:使用 40px 宽的垂直按钮栏,3 个图标按钮切换文件树、搜索、符号视图
<
!
--
工具选择器(KDevelop 风格)
--
>
<
div
class
=
"tool-selector"
id
=
"toolSelector"
>
<
div
class
=
"tool-btn-v active"
data
-
tool
=
"files"
title
=
"文件/项目"
>
<
span
class
=
"tool-icon"
>
📁
<
/
span
>
<
/
div
>
<
div
class
=
"tool-btn-v"
data
-
tool
=
"search"
title
=
"全局搜索"
>
<
span
class
=
"tool-icon"
>
🔍
<
/
span
>
<
/
div
>
<
div
class
=
"tool-btn-v"
data
-
tool
=
"symbols"
title
=
"符号/大纲"
>
<
span
class
=
"tool-icon"
>
𝑓
<
/
span
>
<
/
div
>
<
/
div
>
问题现象:打开包含数千个文件的项目时,文件树是否会一次性全部加载?
分析:如果一次性加载所有文件和目录,会导致 IPC 传输大量数据,渲染进程 DOM 节点过多也会卡顿
解决方案:采用懒加载模式,首次只显示根目录,点击文件夹时才读取子目录
folderEl
.
addEventListener
(
'click'
,
function
(
e
)
{
e
.
stopPropagation
(
)
;
var
children
=
folderEl
.
nextElementSibling
;
var
toggle
=
folderEl
.
querySelector
(
'.tree-toggle'
)
;
if
(
children
&&
children
.
classList
.
contains
(
'tree-children'
)
)
{
children
.
classList
.
toggle
(
'collapsed'
)
;
toggle
.
textContent
=
children
.
classList
.
contains
(
'collapsed'
)
?
'▶'
:
'▼'
;
}
else
{
toggle
.
textContent
=
'▼'
;
ipc
(
'dir:read'
,
item
.
path
)
.
then
(
function
(
res
)
{
if
(
res
.
success
)
{
var
childContainer
=
document
.
createElement
(
'div'
)
;
childContainer
.
className
=
'tree-children'
;
renderTreeItems
(
childContainer
,
res
.
items
,
item
.
path
)
;
folderEl
.
parentNode
.
insertBefore
(
childContainer
,
folderEl
.
nextSibling
)
;
}
}
)
;
}
}
)
;
function
initKeyboardShortcuts
(
)
{
document
.
addEventListener
(
'keydown'
,
function
(
e
)
{
var
ctrl
=
e
.
ctrlKey
||
e
.
metaKey
;
if
(
ctrl
&&
e
.
key
===
'n'
)
{
e
.
preventDefault
(
)
;
newFile
(
)
;
}
else
if
(
ctrl
&&
e
.
key
===
'o'
&&
e
.
shiftKey
)
{
e
.
preventDefault
(
)
;
openFolder
(
)
;
}
else
if
(
ctrl
&&
e
.
key
===
'o'
)
{
e
.
preventDefault
(
)
;
openFile
(
)
;
}
else
if
(
ctrl
&&
e
.
key
===
's'
&&
e
.
shiftKey
)
{
e
.
preventDefault
(
)
;
saveFileAs
(
)
;
}
else
if
(
ctrl
&&
e
.
key
===
's'
)
{
e
.
preventDefault
(
)
;
saveCurrentFile
(
)
;
}
else
if
(
ctrl
&&
e
.
key
===
'w'
)
{
e
.
preventDefault
(
)
;
closeActiveTab
(
)
;
}
else
if
(
ctrl
&&
e
.
key
===
'f'
)
{
e
.
preventDefault
(
)
;
toggleFindBar
(
true
)
;
}
else
if
(
e
.
key
===
'Escape'
)
{
toggleFindBar
(
false
)
;
}
}
)
;
}
问题现象:修改了 electron-apps/KDevelop/ 下的文件,但构建后没有生效
根本原因:文件没有同步到鸿蒙 web_engine 模块的部署目录
解决方案:使用 PowerShell 脚本同步文件
$src
=
"electron-apps\KDevelop"
$dest
=
"web_engine\src\main\resources\resfile\resources\app"
Copy
-
Item
"$src\main.js"
"$dest\main.js"
-
Force
Copy
-
Item
"$src\renderer.js"
"$dest\renderer.js"
-
Force
Copy
-
Item
"$src\index.html"
"$dest\index.html"
-
Force
Copy
-
Item
"$src\package.json"
"$dest\package.json"
-
Force
Copy
-
Item
"$src\styles\kdevelop.css"
"$dest\styles\kdevelop.css"
-
Force
部署路径:web_engine/src/main/resources/resfile/resources/app/
注意:每次修改代码后都需要同步,否则构建的 HAP 包不会包含最新代码。















转载声明:本文为转载整理,原文版权归原作者及原站点所有。
原文来源:CSDN HarmonyOS
原创地址:https://blog.csdn.net/COLLINSXU/article/details/165006717
原文作者:程序山海;如有侵权,请联系管理员删除。