Skip to content

Commit 5ccfe5e

Browse files
committed
add: 缩进
1 parent 574fca6 commit 5ccfe5e

5 files changed

Lines changed: 170 additions & 190 deletions

File tree

src/main/kotlin/com/company/plugin/language/ZyEnterHandler.kt

Lines changed: 92 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,16 @@ import com.intellij.psi.PsiFile
1010
import com.intellij.openapi.util.TextRange
1111
import com.intellij.application.options.CodeStyle
1212
import com.intellij.openapi.util.Ref
13-
import com.intellij.openapi.util.Key
1413
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
1514
import com.intellij.openapi.diagnostic.Logger
1615

1716
/**
1817
* ZY 回车换行缩进处理器
19-
* 在 .zy 文件中按下回车后,继承上一行的缩进(空格/制表符原样保留),保持作用域内缩进一致
18+
* 重构版本:简化逻辑,直接响应,准确处理缩进
2019
*/
2120
class ZyEnterHandler : EnterHandlerDelegateAdapter() {
2221

2322
companion object {
24-
// 标记本次回车已由 preprocessEnter 处理,避免 postProcessEnter 再次插入缩进导致光标错位
25-
private val ENTER_HANDLED_KEY: Key<Boolean> = Key.create("zy.enter.handled")
2623
private val LOG: Logger = Logger.getInstance(ZyEnterHandler::class.java)
2724
}
2825

@@ -34,149 +31,111 @@ class ZyEnterHandler : EnterHandlerDelegateAdapter() {
3431
dataContext: DataContext,
3532
originalHandler: EditorActionHandler?
3633
): EnterHandlerDelegate.Result {
34+
// 只处理.zy文件
3735
if (!file.name.endsWith(".zy")) {
3836
return EnterHandlerDelegate.Result.Continue
3937
}
4038

4139
val document = editor.document
4240
val offset = caretOffsetRef.get()
43-
if (offset <= 0) return EnterHandlerDelegate.Result.Continue
44-
45-
val text = document.charsSequence
46-
// 向后寻找下一个非空白字符
47-
fun nextNonWs(from: Int): Char {
48-
var i = from
49-
while (i < text.length) {
50-
val ch = text[i]
51-
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') return ch
52-
i++
41+
42+
// 获取当前行信息
43+
val currentLine = document.getLineNumber(offset)
44+
val currentLineStart = document.getLineStartOffset(currentLine)
45+
val currentLineEnd = document.getLineEndOffset(currentLine)
46+
val currentLineText = document.getText(TextRange(currentLineStart, currentLineEnd))
47+
48+
// 获取光标前后的字符
49+
val beforeCursor = if (offset > currentLineStart) document.getText(TextRange(offset - 1, offset)) else ""
50+
val afterCursor = if (offset < currentLineEnd) document.getText(TextRange(offset, offset + 1)) else ""
51+
52+
// 计算当前行的基础缩进
53+
val baseIndent = getLineIndent(currentLineText)
54+
55+
// 获取缩进设置
56+
val indentOptions = CodeStyle.getSettings(file).getIndentOptions(file.fileType)
57+
val indentUnit = if (indentOptions.USE_TAB_CHARACTER) "\t" else " ".repeat(indentOptions.INDENT_SIZE)
58+
59+
var newIndent = baseIndent
60+
var extraNewlines = ""
61+
62+
// 处理特殊情况
63+
when {
64+
// 情况1:在成对括号之间(如 {|}、[|]、(|))
65+
isPairBrackets(beforeCursor, afterCursor) -> {
66+
newIndent = baseIndent + indentUnit
67+
extraNewlines = "\n" + baseIndent
5368
}
54-
return '\u0000'
55-
}
56-
// 向前寻找上一个非空白字符
57-
fun prevNonWs(before: Int): Char {
58-
var i = before - 1
59-
while (i >= 0) {
60-
val ch = text[i]
61-
if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') return ch
62-
i--
69+
// 情况2:在开括号后面
70+
isOpenBracket(beforeCursor) -> {
71+
newIndent = baseIndent + indentUnit
6372
}
64-
return '\u0000'
65-
}
66-
val prevChar = prevNonWs(offset)
67-
val nextChar = nextNonWs(offset)
68-
69-
// 情况1:位于成对括号之间的回车,插入两行并正确缩进({}、[]、())
70-
if ((prevChar == '{' && nextChar == '}') || (prevChar == '[' && nextChar == ']') || (prevChar == '(' && nextChar == ')')) {
71-
val line = document.getLineNumber(offset)
72-
val lineStart = document.getLineStartOffset(line)
73-
val baseIndentText = document.getText(TextRange(lineStart, offset - 1))
74-
val leadingIndent = buildString {
75-
for (ch in baseIndentText) {
76-
if (ch == ' ' || ch == '\t') append(ch) else break
73+
// 情况3:在闭括号前面
74+
isCloseBracket(afterCursor) -> {
75+
newIndent = if (baseIndent.length >= indentUnit.length) {
76+
baseIndent.substring(0, baseIndent.length - indentUnit.length)
77+
} else {
78+
""
7779
}
7880
}
79-
val indentOptions = CodeStyle.getSettings(file).getIndentOptions(file.fileType)
80-
val unitIndent = if (indentOptions.USE_TAB_CHARACTER) "\t" else " ".repeat(indentOptions.INDENT_SIZE)
81-
val innerIndent = leadingIndent + unitIndent
82-
83-
val insertion = "\n" + innerIndent + "\n" + leadingIndent
84-
document.insertString(offset, insertion)
85-
// 直接设置光标位置到中间行缩进末尾,避免依赖 caretAdvanceRef 在 Stop 模式下可能无效
86-
editor.caretModel.moveToOffset(offset + 1 + innerIndent.length)
87-
editor.putUserData(ENTER_HANDLED_KEY, true)
88-
return EnterHandlerDelegate.Result.Stop
89-
}
90-
91-
// 情况2:紧随开括号后的回车,当前行直接 +1 级缩进(适用于 { [ ())
92-
if (prevChar == '{' || prevChar == '[' || prevChar == '(') {
93-
val line = document.getLineNumber(offset)
94-
val lineStart = document.getLineStartOffset(line)
95-
val baseIndentText = document.getText(TextRange(lineStart, lineStart + (document.getLineEndOffset(line) - lineStart).coerceAtLeast(0))).let { lineText ->
96-
// 仅提取行首空白作为基底缩进
97-
buildString {
98-
for (ch in lineText) {
99-
if (ch == ' ' || ch == '\t') append(ch) else break
100-
}
81+
// 情况4:普通换行,继承当前行缩进
82+
else -> {
83+
// 检查当前行最后的非空白字符
84+
val trimmedLine = currentLineText.trimEnd()
85+
if (trimmedLine.endsWith("{") || trimmedLine.endsWith("[") || trimmedLine.endsWith("(")) {
86+
newIndent = baseIndent + indentUnit
10187
}
10288
}
103-
val leadingIndent = baseIndentText
104-
val indentOptions = CodeStyle.getSettings(file).getIndentOptions(file.fileType)
105-
val unitIndent = if (indentOptions.USE_TAB_CHARACTER) "\t" else " ".repeat(indentOptions.INDENT_SIZE)
106-
val innerIndent = leadingIndent + unitIndent
107-
val insertion = "\n" + innerIndent
108-
document.insertString(offset, insertion)
109-
caretAdvanceRef.set(1 + innerIndent.length)
110-
editor.putUserData(ENTER_HANDLED_KEY, true)
111-
return EnterHandlerDelegate.Result.Stop
11289
}
113-
114-
return EnterHandlerDelegate.Result.Continue
90+
91+
// 插入换行和缩进
92+
val insertion = "\n" + newIndent + extraNewlines
93+
document.insertString(offset, insertion)
94+
95+
// 设置光标位置
96+
val newCaretPosition = offset + 1 + newIndent.length
97+
editor.caretModel.moveToOffset(newCaretPosition)
98+
99+
LOG.debug("Enter processed: baseIndent='$baseIndent', newIndent='$newIndent', caretPos=$newCaretPosition")
100+
101+
return EnterHandlerDelegate.Result.Stop
115102
}
116-
117-
override fun postProcessEnter(file: PsiFile, editor: Editor, dataContext: DataContext): EnterHandlerDelegate.Result {
118-
if (!file.name.endsWith(".zy")) return EnterHandlerDelegate.Result.Continue
119-
120-
// 如果 preprocessEnter 已处理本次回车,则跳过后续缩进调整,避免重复缩进导致光标位置错误
121-
if (editor.getUserData(ENTER_HANDLED_KEY) == true) {
122-
editor.putUserData(ENTER_HANDLED_KEY, false)
123-
return EnterHandlerDelegate.Result.Continue
124-
}
125-
126-
val document: Document = editor.document
127-
val caretOffset = editor.caretModel.offset
128-
val currentLine = document.getLineNumber(caretOffset)
129-
if (currentLine <= 0) return EnterHandlerDelegate.Result.Continue
130-
131-
val prevLine = currentLine - 1
132-
val prevStart = document.getLineStartOffset(prevLine)
133-
val prevEnd = document.getLineEndOffset(prevLine)
134-
val prevText = document.getText(TextRange(prevStart, prevEnd))
135-
136-
// 计算上一行前导空白(保留空格/Tab 原样)
137-
val leadingIndent = buildString {
138-
for (ch in prevText) {
139-
if (ch == ' ' || ch == '\t') append(ch) else break
103+
104+
/**
105+
* 获取行的前导缩进
106+
*/
107+
private fun getLineIndent(lineText: String): String {
108+
val indent = StringBuilder()
109+
for (char in lineText) {
110+
if (char == ' ' || char == '\t') {
111+
indent.append(char)
112+
} else {
113+
break
140114
}
141115
}
142-
143-
// 根据上一行结尾和当前行起始,调整缩进层级
144-
val trimmedPrev = prevText.trimEnd()
145-
// 仅当上一行最后一个非空白字符是开括号时才增加一级
146-
fun lastNonWsChar(s: String): Char? {
147-
for (i in s.length - 1 downTo 0) {
148-
val ch = s[i]
149-
if (ch != ' ' && ch != '\t') return ch
150-
}
151-
return null
152-
}
153-
val lastCh = lastNonWsChar(trimmedPrev)
154-
val prevEndsWithOpen = lastCh == '{' || lastCh == '[' || lastCh == '('
155-
156-
// 若当前行以闭合符开头,则减少一级缩进
157-
val currLineStart = document.getLineStartOffset(currentLine)
158-
val currLineEnd = document.getLineEndOffset(currentLine)
159-
val currHeadText = if (currLineStart < currLineEnd) document.getText(TextRange(currLineStart, (currLineStart + 3).coerceAtMost(currLineEnd))) else ""
160-
val currStartsWithClose = currHeadText.startsWith("}") || currHeadText.startsWith(")") || currHeadText.startsWith("]")
161-
162-
// 依据项目代码风格设置确定缩进单位(Tab 或 N 个空格)
163-
val indentOptions = CodeStyle.getSettings(file).getIndentOptions(file.fileType)
164-
val unitIndent = if (indentOptions.USE_TAB_CHARACTER) "\t" else " ".repeat(indentOptions.INDENT_SIZE)
165-
val baseIndent = leadingIndent
166-
val finalIndent = buildString {
167-
append(baseIndent)
168-
if (prevEndsWithOpen) append(unitIndent)
169-
if (currStartsWithClose && length >= unitIndent.length) delete(length - unitIndent.length, length)
170-
}
171-
172-
if (finalIndent.isNotEmpty()) {
173-
document.insertString(caretOffset, finalIndent)
174-
editor.caretModel.moveToOffset(caretOffset + finalIndent.length)
175-
return EnterHandlerDelegate.Result.DefaultSkipIndent
176-
}
177-
178-
return EnterHandlerDelegate.Result.Continue
116+
return indent.toString()
179117
}
180-
}
181-
182-
118+
119+
/**
120+
* 检查是否在成对括号之间
121+
*/
122+
private fun isPairBrackets(before: String, after: String): Boolean {
123+
return (before == "{" && after == "}") ||
124+
(before == "[" && after == "]") ||
125+
(before == "(" && after == ")")
126+
}
127+
128+
/**
129+
* 检查是否是开括号
130+
*/
131+
private fun isOpenBracket(char: String): Boolean {
132+
return char == "{" || char == "[" || char == "("
133+
}
134+
135+
/**
136+
* 检查是否是闭括号
137+
*/
138+
private fun isCloseBracket(char: String): Boolean {
139+
return char == "}" || char == "]" || char == ")"
140+
}
141+
}

src/main/kotlin/com/company/plugin/lsp/ZyDocumentSyncComponent.kt

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,38 @@ class ZyDocumentSyncComponent : ProjectActivity {
1919
}
2020

2121
override suspend fun execute(project: Project) {
22-
// 添加全局文档监听器
23-
val documentListener = object : DocumentListener {
24-
override fun documentChanged(event: DocumentEvent) {
25-
handleDocumentChange(project, event)
22+
try {
23+
// 检查IDE是否已完全初始化到COMPONENTS_LOADED状态
24+
while (!com.intellij.diagnostic.LoadingState.COMPONENTS_LOADED.isOccurred) {
25+
LOG.debug("Waiting for COMPONENTS_LOADED state for document sync...")
26+
kotlinx.coroutines.delay(100)
27+
}
28+
29+
// 额外延迟确保所有组件稳定
30+
kotlinx.coroutines.delay(500)
31+
32+
// 检查应用程序状态
33+
val app = com.intellij.openapi.application.ApplicationManager.getApplication()
34+
if (app == null || app.isDisposed || project.isDisposed) {
35+
LOG.warn("Application or project not ready for document sync")
36+
return
2637
}
38+
39+
// 添加全局文档监听器
40+
val documentListener = object : DocumentListener {
41+
override fun documentChanged(event: DocumentEvent) {
42+
handleDocumentChange(project, event)
43+
}
44+
}
45+
46+
// 使用 EditorFactory 添加全局监听器
47+
com.intellij.openapi.editor.EditorFactory.getInstance()
48+
.eventMulticaster.addDocumentListener(documentListener, project)
49+
50+
LOG.info("ZY document sync component initialized for project: ${project.name}")
51+
} catch (e: Exception) {
52+
LOG.error("Error initializing document sync component for project: ${project.name}", e)
2753
}
28-
29-
// 使用 EditorFactory 添加全局监听器
30-
com.intellij.openapi.editor.EditorFactory.getInstance()
31-
.eventMulticaster.addDocumentListener(documentListener, project)
32-
33-
LOG.info("ZY document sync component initialized for project: ${project.name}")
3454
}
3555

3656
private fun handleDocumentChange(project: Project, event: DocumentEvent) {

src/main/kotlin/com/company/plugin/lsp/ZyLspClient.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ class ZyLspClient(private val project: Project) : LanguageClient {
5858
*/
5959
fun startServer(): Boolean {
6060
try {
61+
// 检查IDE是否已达到COMPONENTS_LOADED状态
62+
if (!com.intellij.diagnostic.LoadingState.COMPONENTS_LOADED.isOccurred) {
63+
LOG.warn("IDE not fully loaded (COMPONENTS_LOADED), cannot start LSP server")
64+
return false
65+
}
66+
6167
// 检查应用程序是否已完全初始化
6268
val app = com.intellij.openapi.application.ApplicationManager.getApplication()
6369
if (app == null || app.isDisposed) {
@@ -201,6 +207,13 @@ class ZyLspClient(private val project: Project) : LanguageClient {
201207
return future
202208
}
203209

210+
/**
211+
* 检查文档是否已经在LSP中打开
212+
*/
213+
fun isDocumentOpen(uri: String): Boolean {
214+
return openedDocuments.contains(uri)
215+
}
216+
204217
/**
205218
* 确保 LSP 端已打开并同步指定文档(最小实现:仅 didOpen 一次)
206219
*/

src/main/kotlin/com/company/plugin/lsp/ZyProjectStartupActivity.kt

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,36 +21,44 @@ class ZyProjectStartupActivity : ProjectActivity {
2121
try {
2222
LOG.info("ZyProjectStartupActivity executed for project: ${project.name}")
2323

24-
// 大幅延迟,确保IDE组件完全加载并达到COMPONENTS_LOADED状态
25-
delay(3000)
26-
27-
// 检查应用程序状态
24+
// 检查IDE是否已完全初始化到COMPONENTS_LOADED状态
2825
val app = ApplicationManager.getApplication()
2926
if (app == null || app.isDisposed) {
30-
LOG.warn("Application not fully initialized, skipping LSP startup")
27+
LOG.warn("Application not ready, skipping LSP startup")
3128
return
3229
}
3330

31+
// 等待IDE完全加载
32+
while (!com.intellij.diagnostic.LoadingState.COMPONENTS_LOADED.isOccurred) {
33+
LOG.debug("Waiting for COMPONENTS_LOADED state...")
34+
delay(100)
35+
}
36+
37+
// 额外延迟确保所有组件稳定
38+
delay(1000)
39+
3440
// 检查项目是否包含 .zy 文件
3541
if (hasZyFiles(project)) {
3642
LOG.info("Found .zy files in project: ${project.name}, starting LSP service")
3743

38-
// 再次延迟一下,确保所有组件都已准备好
39-
delay(1000)
40-
4144
// 在EDT线程中安全地启动LSP服务
4245
ApplicationManager.getApplication().invokeLater {
4346
try {
47+
if (project.isDisposed) {
48+
LOG.debug("Project disposed, skipping LSP startup")
49+
return@invokeLater
50+
}
51+
4452
val lspService = project.getService(ZyLspService::class.java)
45-
if (lspService != null && !project.isDisposed) {
53+
if (lspService != null) {
4654
val started = lspService.startLspService()
4755
if (started) {
4856
LOG.info("LSP service started successfully for project: ${project.name}")
4957
} else {
5058
LOG.warn("Failed to start LSP service for project: ${project.name}")
5159
}
5260
} else {
53-
LOG.error("ZyLspService not found or project disposed for project: ${project.name}")
61+
LOG.error("ZyLspService not found for project: ${project.name}")
5462
}
5563
} catch (e: Exception) {
5664
LOG.error("Error starting LSP service for project: ${project.name}", e)

0 commit comments

Comments
 (0)