Skip to content

Commit 3d674d7

Browse files
committed
Fix end indent, 'end' will auto indent
1 parent ae246ef commit 3d674d7

File tree

4 files changed

+407
-4
lines changed

4 files changed

+407
-4
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright (c) 2017. tangzx([email protected])
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.tang.intellij.lua.editor;
18+
19+
import com.intellij.codeInsight.editorActions.enter.EnterHandlerDelegateAdapter;
20+
import com.intellij.openapi.actionSystem.DataContext;
21+
import com.intellij.openapi.editor.Document;
22+
import com.intellij.openapi.editor.Editor;
23+
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
24+
import com.intellij.openapi.util.Ref;
25+
import com.intellij.openapi.util.TextRange;
26+
import com.intellij.psi.PsiDocumentManager;
27+
import com.intellij.psi.PsiElement;
28+
import com.intellij.psi.PsiFile;
29+
import com.tang.intellij.lua.lang.LuaLanguage;
30+
import com.tang.intellij.lua.psi.*;
31+
import org.jetbrains.annotations.NotNull;
32+
33+
/**
34+
* Handles Enter key press for Lua files, especially for 'end' keyword indentation
35+
*/
36+
public class LuaEnterHandlerDelegate extends EnterHandlerDelegateAdapter {
37+
38+
@Override
39+
public Result preprocessEnter(@NotNull PsiFile file,
40+
@NotNull Editor editor,
41+
@NotNull Ref<Integer> caretOffset,
42+
@NotNull Ref<Integer> caretAdvance,
43+
@NotNull DataContext dataContext,
44+
EditorActionHandler originalHandler) {
45+
if (!file.getLanguage().is(LuaLanguage.INSTANCE)) {
46+
return Result.Continue;
47+
}
48+
49+
int offset = caretOffset.get();
50+
if (offset <= 0) {
51+
return Result.Continue;
52+
}
53+
54+
Document document = editor.getDocument();
55+
PsiDocumentManager psiDocManager = PsiDocumentManager.getInstance(file.getProject());
56+
psiDocManager.commitDocument(document);
57+
58+
// 获取光标前的元素
59+
PsiElement element = file.findElementAt(offset - 1);
60+
if (element == null) {
61+
return Result.Continue;
62+
}
63+
64+
// 检查是否在 'end' 或 'until' 关键字之后按 Enter
65+
if (isAfterEndKeyword(element, document, offset)) {
66+
// 找到对应的开始关键字的缩进
67+
String targetIndent = findMatchingBlockIndent(element);
68+
if (targetIndent != null) {
69+
int currentLine = document.getLineNumber(offset);
70+
int lineStartOffset = document.getLineStartOffset(currentLine);
71+
int lineEndOffset = document.getLineEndOffset(currentLine);
72+
73+
// 获取当前行内容
74+
String lineText = document.getText(new TextRange(lineStartOffset, lineEndOffset));
75+
76+
// 提取当前行的缩进和关键字后面的内容
77+
String keyword = lineText.contains("end") ? "end" : "until";
78+
int keywordPos = lineText.indexOf(keyword);
79+
if (keywordPos >= 0) {
80+
String afterKeyword = lineText.substring(keywordPos + keyword.length()).trim();
81+
82+
// 替换整行为正确缩进的关键字
83+
document.replaceString(lineStartOffset, lineEndOffset,
84+
targetIndent + keyword + (afterKeyword.isEmpty() ? "" : " " + afterKeyword));
85+
86+
// 更新光标位置到关键字之后
87+
caretOffset.set(lineStartOffset + targetIndent.length() + keyword.length() +
88+
(afterKeyword.isEmpty() ? 0 : 1 + afterKeyword.length()));
89+
}
90+
}
91+
}
92+
93+
return Result.Continue;
94+
}
95+
96+
/**
97+
* 检查光标是否在 'end' 或 'until' 关键字之后
98+
*/
99+
private boolean isAfterEndKeyword(PsiElement element, Document document, int offset) {
100+
// 简单检查:查看当前行文本是否以 'end' 或 'until' 结尾
101+
int currentLine = document.getLineNumber(offset);
102+
int lineStartOffset = document.getLineStartOffset(currentLine);
103+
String lineText = document.getText(new TextRange(lineStartOffset, offset)).trim();
104+
105+
return lineText.equals("end") || lineText.equals("until");
106+
}
107+
108+
/**
109+
* 查找与 'end' 或 'until' 匹配的父级语句的缩进
110+
* 对于 local f = function() ... end, end 应该对齐到 local 而不是 function
111+
*/
112+
private String findMatchingBlockIndent(PsiElement endElement) {
113+
// 向上遍历 PSI 树,找到包含此 end 的块结构
114+
PsiElement current = endElement.getParent();
115+
int maxIterations = 50; // 防止无限循环
116+
int iterations = 0;
117+
118+
while (current != null && iterations++ < maxIterations) {
119+
// 找到块结构
120+
if (current instanceof LuaStatement ||
121+
current instanceof LuaFuncDef ||
122+
current instanceof LuaLocalFuncDef ||
123+
current instanceof LuaClassMethodDef ||
124+
current instanceof LuaLocalDef
125+
) {
126+
127+
// 获取包含该块的最外层语句
128+
PsiElement statement = current;
129+
PsiElement parent = current.getParent();
130+
131+
// 向上查找,找到最顶层的语句 (例如 LuaLocalDef 包含 LuaLocalFuncDef)
132+
while (parent != null && !(parent instanceof LuaPsiFile) && !(parent instanceof LuaBlock)) {
133+
if (parent instanceof LuaExprStat ||
134+
parent instanceof LuaLocalDef ||
135+
parent instanceof LuaAssignStat) {
136+
statement = parent;
137+
}
138+
parent = parent.getParent();
139+
}
140+
141+
// 获取语句的缩进
142+
int startOffset = statement.getTextRange().getStartOffset();
143+
Document document = PsiDocumentManager.getInstance(current.getProject())
144+
.getDocument(current.getContainingFile());
145+
if (document != null) {
146+
int lineNumber = document.getLineNumber(startOffset);
147+
int lineStartOffset = document.getLineStartOffset(lineNumber);
148+
String lineText = document.getText(new TextRange(lineStartOffset, startOffset));
149+
150+
// 提取缩进(空格和制表符)
151+
StringBuilder indent = new StringBuilder();
152+
for (char c : lineText.toCharArray()) {
153+
if (c == ' ' || c == '\t') {
154+
indent.append(c);
155+
} else {
156+
break;
157+
}
158+
}
159+
return indent.toString();
160+
}
161+
}
162+
163+
current = current.getParent();
164+
}
165+
166+
return null;
167+
}
168+
}

src/main/java/com/tang/intellij/lua/editor/LuaLineIndentProvider.java

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,18 @@ private boolean isWhitespaceOrComment(PsiElement element) {
135135
private String calculateIndentBasedOnPreviousLine(Editor editor, Project project, PsiElement element, String baseIndent) {
136136
String indentUnit = getIndentUnit(editor, project);
137137

138+
// 检查元素本身的类型
139+
IElementType elementType = element.getNode().getElementType();
140+
141+
// 如果前一行是 'end', 'until' 或 'elseif',返回到该块的基础缩进
142+
if (elementType == LuaTypes.END || elementType == LuaTypes.UNTIL) {
143+
// 找到对应的块结构并返回其基础缩进
144+
String blockIndent = findBlockBaseIndent(element);
145+
if (blockIndent != null) {
146+
return blockIndent;
147+
}
148+
}
149+
138150
// 检查是否是table相关的缩进
139151
String tableIndent = calculateTableIndent(element, baseIndent, indentUnit);
140152
if (tableIndent != null) {
@@ -155,9 +167,6 @@ private String calculateIndentBasedOnPreviousLine(Editor editor, Project project
155167
parent = parent.getParent();
156168
}
157169

158-
// 检查元素本身的类型
159-
IElementType elementType = element.getNode().getElementType();
160-
161170
// 需要增加缩进的情况
162171
if (elementType == LuaTypes.DO ||
163172
elementType == LuaTypes.THEN ||
@@ -176,6 +185,65 @@ private String calculateIndentBasedOnPreviousLine(Editor editor, Project project
176185
return baseIndent;
177186
}
178187

188+
/**
189+
* 查找块结构的基础缩进(即包含该块的外层缩进)
190+
*/
191+
private @Nullable String findBlockBaseIndent(PsiElement endElement) {
192+
// 向上遍历找到包含该 end 的块结构
193+
PsiElement current = endElement;
194+
while (current != null) {
195+
if (current instanceof LuaDoStat ||
196+
current instanceof LuaIfStat ||
197+
current instanceof LuaWhileStat ||
198+
current instanceof LuaForAStat ||
199+
current instanceof LuaForBStat ||
200+
current instanceof LuaRepeatStat ||
201+
current instanceof LuaFuncDef ||
202+
current instanceof LuaLocalFuncDef ||
203+
current instanceof LuaClassMethodDef) {
204+
205+
// 找到块的父元素
206+
PsiElement parent = current.getParent();
207+
if (parent != null) {
208+
// 如果父元素是 LuaBlock,继续向上找
209+
while (parent instanceof LuaBlock) {
210+
parent = parent.getParent();
211+
}
212+
213+
// 获取父元素所在行的缩进
214+
if (parent != null) {
215+
int startOffset = parent.getTextRange().getStartOffset();
216+
Document document = PsiDocumentManager.getInstance(current.getProject())
217+
.getDocument(current.getContainingFile());
218+
if (document != null) {
219+
int lineNumber = document.getLineNumber(startOffset);
220+
int lineStartOffset = document.getLineStartOffset(lineNumber);
221+
String lineText = document.getText(new TextRange(lineStartOffset, startOffset));
222+
223+
// 提取缩进
224+
StringBuilder indent = new StringBuilder();
225+
for (char c : lineText.toCharArray()) {
226+
if (c == ' ' || c == '\t') {
227+
indent.append(c);
228+
} else {
229+
break;
230+
}
231+
}
232+
return indent.toString();
233+
}
234+
}
235+
}
236+
237+
// 如果没有父元素或父元素是文件,返回空缩进
238+
return "";
239+
}
240+
241+
current = current.getParent();
242+
}
243+
244+
return null;
245+
}
246+
179247
private boolean shouldIncreaseIndent(PsiElement element) {
180248
return element instanceof LuaIfStat ||
181249
element instanceof LuaWhileStat ||

0 commit comments

Comments
 (0)