PageRenderTime 55ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/bundles/plugins-trunk/RubyPlugin/src/org/jedit/ruby/structure/AutoIndentAndInsertEnd.java

#
Java | 553 lines | 389 code | 69 blank | 95 comment | 116 complexity | c33e1e38b10de597532cc753f7fdf129 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1. /*
  2. * AutoIndentAndInsertEnd.java - auto indents and inserts end keyword
  3. *
  4. * Copyright 2005 Robert McKinnon
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. */
  20. package org.jedit.ruby.structure;
  21. import org.gjt.sp.jedit.View;
  22. import org.gjt.sp.jedit.Buffer;
  23. import org.gjt.sp.jedit.textarea.JEditTextArea;
  24. import org.jedit.ruby.RubyPlugin;
  25. import org.jedit.ruby.utils.RegularExpression;
  26. import java.util.regex.MatchResult;
  27. /**
  28. * <p>This action is intended to be executed each time the enter key is pressed.
  29. * Add the 'ENTER' key shortcut via:<ul><li>
  30. * Utilities -> Global Options -> Shortcuts -> Edit Shortcuts: RubyPlugin
  31. * </li></ul>
  32. * </p>
  33. * <p/>
  34. * Currently auto inserts and indents <code>end</code> after the following patterns:<pre>
  35. * if x
  36. * for x
  37. * while x
  38. * until x
  39. * unless x
  40. * def x
  41. * case x
  42. * class x
  43. * module x
  44. * begin
  45. * loop do
  46. * y do |z|
  47. * </pre>
  48. * It also:<ul>
  49. * <li>auto-aligns <code>else</code> and <code>end</code> keywords</li>
  50. * <li>removes empty brackets () when you press enter inside of them</li>
  51. * </ul>
  52. * </p>
  53. *
  54. * @author robmckinnon at users.sourceforge.net
  55. */
  56. public final class AutoIndentAndInsertEnd {
  57. private static final AutoIndentAndInsertEnd instance = new AutoIndentAndInsertEnd();
  58. private JEditTextArea area;
  59. /**
  60. * Singleton private constructor
  61. */
  62. private AutoIndentAndInsertEnd() {
  63. }
  64. public static void performIndent(View view) {
  65. instance.innerPerformIndent(view);
  66. }
  67. private void innerPerformIndent(View view) {
  68. area = view.getTextArea();
  69. Buffer buffer = view.getBuffer();
  70. buffer.writeLock();
  71. buffer.beginCompoundEdit();
  72. try {
  73. doIndent();
  74. } finally {
  75. if (buffer.insideCompoundEdit()) {
  76. buffer.endCompoundEdit();
  77. }
  78. buffer.writeUnlock();
  79. }
  80. }
  81. private void doIndent() {
  82. area.removeTrailingWhiteSpace();
  83. int row = area.getCaretLine();
  84. String line = area.getLineText(row);
  85. String trimLine = line.trim();
  86. int caretPosition = area.getCaretPosition() - area.getLineStartOffset(row);
  87. boolean openingBrace = line.indexOf("{") != -1 && line.indexOf("}") == -1;
  88. if (caretPosition != line.length() || openingBrace) {
  89. if (CommentRegExp.instance.isMatch(line)) {
  90. handleComment(line, row);
  91. } else {
  92. boolean oneCharFromEnd = caretPosition == line.length() - 1;
  93. boolean insideEmptyParenthesis = oneCharFromEnd && caretPosition != 0 && line.charAt(caretPosition - 1) == '(' && line.charAt(caretPosition) == ')';
  94. if (insideEmptyParenthesis) {
  95. area.selectLine();
  96. area.setSelectedText(line.substring(0, line.length() - 2));
  97. }
  98. area.insertEnterAndIndent();
  99. }
  100. } else if (trimLine.startsWith("else") || trimLine.startsWith("elsif")) {
  101. handleElse(trimLine, row);
  102. } else {
  103. // String text = area.getText();
  104. // int offset = area.getCaretPosition();
  105. // if (text.indexOf('\n', offset) == text.lastIndexOf('\n', offset)) {
  106. // area.setText(text + '\n');
  107. // }
  108. handleInsertEnter(trimLine, row, line);
  109. }
  110. }
  111. private void handleInsertEnter(String trimLine, int row, String line) {
  112. boolean matchesConditionalAssignment = TrailingConditionRegExp.instance.isMatch(line);
  113. boolean matchesDo = DoRegExp.instance.isMatch(line) && !isDoInComment(line);
  114. boolean matchesSyntax = MatchRegExp.instance.isMatch(line);
  115. boolean ignore = IgnoreRegExp.instance.isMatch(line);
  116. if (matchesConditionalAssignment) {
  117. handleInsertEnd(TrailingConditionRegExp.instance, line);
  118. } else if (!ignore && matchesDo) {
  119. handleInsertEnd(DoRegExp.instance, line);
  120. } else if (!ignore && matchesSyntax) {
  121. handleInsertEnd(MatchRegExp.instance, line);
  122. } else if (EndRegExp.instance.isMatch(trimLine)) {
  123. handleEnd(trimLine, row);
  124. } else if (CommentRegExp.instance.isMatch(trimLine)) {
  125. handleComment(line, row);
  126. } else {
  127. area.insertEnterAndIndent();
  128. while(line.trim().length() == 0 && row > 0) {
  129. row--;
  130. line = area.getLineText(row);
  131. }
  132. line = line.trim();
  133. if(line.endsWith("end")
  134. && line.substring(0, line.length()-3).trim().endsWith(";")
  135. && line.indexOf("#") == -1) {
  136. area.shiftIndentLeft();
  137. }
  138. }
  139. }
  140. private void handleElse(String trimLine, int row) {
  141. area.insertEnterAndIndent();
  142. if (row > 0) {
  143. int index = row;
  144. while (index > 0) {
  145. index--;
  146. String line = area.getLineText(index);
  147. if (TrailingConditionRegExp.instance.isMatch(line) && line.indexOf("elsif") == -1) {
  148. indentAfterTrailingConditionalAssignment(line, trimLine);
  149. break;
  150. } else if(IfElsifRegExp.instance.isMatch(line)) {
  151. MatchResult matches = IfElsifRegExp.instance.firstMatch(line);
  152. String indent = matches.group(1);
  153. reIndent(trimLine, indent);
  154. area.selectLine();
  155. area.setSelectedText(indent + area.getSelectedText().trim());
  156. area.shiftIndentRight();
  157. break;
  158. }
  159. }
  160. }
  161. }
  162. private void indentAfterTrailingConditionalAssignment(String line, String trimLine) {
  163. String indent = TrailingConditionRegExp.instance.indent(line);
  164. reIndent(trimLine, indent);
  165. area.selectLine();
  166. area.setSelectedText(indent + area.getSelectedText().trim());
  167. area.shiftIndentRight();
  168. MatchResult matches = TrailingConditionRegExp.instance.firstMatch(line);
  169. if (matches.group(3).startsWith("case")) {
  170. area.goToPrevLine(false);
  171. area.shiftIndentRight();
  172. area.goToNextLine(false);
  173. area.shiftIndentRight();
  174. }
  175. }
  176. private void reIndent(String trimLine, String indent) {
  177. area.goToPrevLine(false);
  178. area.selectLine();
  179. area.setSelectedText(indent + trimLine);
  180. area.goToNextLine(false);
  181. }
  182. private void handleComment(String line, int row) {
  183. area.insertEnterAndIndent();
  184. if (row > 0) {
  185. int index = row;
  186. while (index > 0) {
  187. line = area.getLineText(index);
  188. index--;
  189. if (CommentRegExp.instance.isMatch(line)) {
  190. MatchResult matches = CommentRegExp.instance.firstMatch(line);
  191. String hashes = matches.group(2);
  192. if (hashes.equals("##")) {
  193. String indent = matches.group(1);
  194. area.selectLine();
  195. String text = area.getSelectedText() == null ? "" : area.getSelectedText();
  196. text = text.trim();
  197. area.setSelectedText(indent + "# " + text);
  198. area.goToEndOfLine(false);
  199. break;
  200. }
  201. } else {
  202. break;
  203. }
  204. }
  205. }
  206. }
  207. private void handleEnd(String trimLine, int row) {
  208. area.insertEnterAndIndent();
  209. if (row > 0) {
  210. int index = row;
  211. int endCount = 0;
  212. while (index > 0) {
  213. index--;
  214. String line = area.getLineText(index);
  215. if (EndRegExp.instance.isMatch(line)) {
  216. endCount++;
  217. } else if (!IgnoreRegExp.instance.isMatch(line) || TrailingConditionRegExp.instance.isMatch(line)) {
  218. boolean notElse = line.indexOf("elsif") == -1 && line.indexOf("else") == -1;
  219. boolean isDoStatement = DoRegExp.instance.isMatch(line) && !isDoInComment(line);
  220. boolean isSyntaxStatement = MatchRegExp.instance.isMatch(line) && notElse;
  221. boolean isTrailingCondition = TrailingConditionRegExp.instance.isMatch(line);
  222. // Macros.message(area, line + " "
  223. // + isDoStatement + " " + isSyntaxStatement + " " + isTrailingCondition);
  224. if (isDoStatement || isSyntaxStatement || isTrailingCondition) {
  225. if (endCount > 0) {
  226. endCount--;
  227. } else {
  228. InsertRegularExpression re = isDoStatement ? DoRegExp.instance : MatchRegExp.instance;
  229. re = isTrailingCondition ? TrailingConditionRegExp.instance : re;
  230. String indent = re.indent(line);
  231. reIndent(trimLine, indent);
  232. MatchResult matches = re.firstMatch(line);
  233. area.selectLine();
  234. area.setSelectedText(matches.group(1));
  235. break;
  236. }
  237. }
  238. }
  239. }
  240. }
  241. }
  242. private static boolean isDoInComment(String line) {
  243. boolean inComment = false;
  244. int commentIndex = line.indexOf("#");
  245. int paramIndex = line.indexOf("#{");
  246. boolean hasHash = commentIndex != -1;
  247. boolean hashNotParamStart = commentIndex != paramIndex;
  248. if (hasHash && hashNotParamStart) {
  249. int doIndex = line.indexOf(" do ");
  250. if (doIndex > commentIndex) {
  251. inComment = true;
  252. }
  253. }
  254. return inComment;
  255. }
  256. private void handleInsertEnd(InsertRegularExpression re, String line) {
  257. String indent = re.indent(line);
  258. area.insertEnterAndIndent();
  259. area.selectLine();
  260. area.setSelectedText(indent + "end");
  261. if (endsNotBalanced()) {
  262. area.deleteLine();
  263. }
  264. area.goToPrevLine(false);
  265. area.goToEndOfWhiteSpace(false);
  266. area.insertEnterAndIndent();
  267. area.selectLine();
  268. String text = area.getSelectedText() != null ? area.getSelectedText().trim() : "";
  269. area.setSelectedText(indent + text);
  270. if(!line.endsWith("end")) {
  271. area.shiftIndentRight();
  272. }
  273. }
  274. private boolean endsNotBalanced() {
  275. String line;
  276. int count = area.getLineCount();
  277. int balancedCount = 0;
  278. // StringBuffer buffer = new StringBuffer("");
  279. boolean isString = false;
  280. for (int i = 0; i < count; i++) {
  281. line = area.getLineText(i).trim();
  282. if (hasEndKeyword(line)) {
  283. int endCount = getEndCount(line);
  284. while (endCount != 0) {
  285. balancedCount--;
  286. // buffer.append(balancedCount).append("");
  287. // for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? " " : ""));
  288. // buffer.append(line).append("\n");
  289. endCount--;
  290. }
  291. }
  292. if (line.indexOf("<<-EOF") != -1) {
  293. isString = true;
  294. } else if (line.indexOf("EOF") != -1) {
  295. isString = false;
  296. }
  297. if (!isString) {
  298. boolean isDoMatch = DoRegExp.instance.isMatch(line);
  299. boolean doInComment = isDoInComment(line);
  300. // if(line.indexOf("File.open") != -1) {
  301. // buffer.append("do: ").append(isDoMatch).append(", in comment: ").append(doInComment).append(',').append(line).append('\n');
  302. // }
  303. boolean isDoStatement = isDoMatch && !doInComment;
  304. boolean ignore = IgnoreRegExp.instance.isMatch(line);
  305. boolean conditionalAssignment = TrailingConditionRegExp.instance.isMatch(line);
  306. if (conditionalAssignment || (!ignore && (isDoStatement || MatchRegExp.instance.isMatch(line)))) {
  307. boolean openingBrace = line.indexOf("{") != -1 && line.indexOf("}") == -1;
  308. boolean elsif = line.indexOf("elsif") != -1;
  309. if (!openingBrace && !elsif) {
  310. balancedCount++;
  311. // buffer.append(balancedCount).append("");
  312. // for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? " " : ""));
  313. // buffer.append(line).append("\n");
  314. int moduleIndex = line.indexOf("module");
  315. while(moduleIndex != -1) {
  316. moduleIndex = line.indexOf("module", moduleIndex+5);
  317. if(moduleIndex != -1) {
  318. balancedCount++;
  319. // buffer.append(balancedCount).append("");
  320. // for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? " " : ""));
  321. // buffer.append(line).append("\n");
  322. }
  323. }
  324. moduleIndex = line.indexOf("module");
  325. if(moduleIndex != -1) {
  326. int classIndex = line.indexOf("class", moduleIndex+5);
  327. if(classIndex != -1) {
  328. balancedCount++;
  329. }
  330. }
  331. int classIndex = line.indexOf("class");
  332. // buffer.append("balance: ").append(balancedCount).append("\n");
  333. // buffer.append("classIndex: ").append(classIndex).append("\n");
  334. if (classIndex != -1) {
  335. int defIndex = line.indexOf("def", classIndex+4);
  336. // buffer.append("defIndex: ").append(defIndex).append("\n");
  337. while(defIndex != -1) {
  338. balancedCount++;
  339. // buffer.append(balancedCount).append("");
  340. // for (int j = 0; j < balancedCount; buffer.append(j++ > -1 ? " " : ""));
  341. // buffer.append(line).append("\n");
  342. defIndex = line.indexOf("def", defIndex+2);
  343. }
  344. }
  345. }
  346. }
  347. }
  348. }
  349. // RubyPlugin.log(buffer.toString(), AutoIndentAndInsertEnd.class);
  350. boolean endsNotBalanced = balancedCount < 0;
  351. RubyPlugin.log("Ends " + (endsNotBalanced ? "not " : "") + "balanced: " + balancedCount, AutoIndentAndInsertEnd.class);
  352. return endsNotBalanced;
  353. }
  354. public static int getEndCount(String line) {
  355. // return SimpleEndRegExp.instance.getAllMatches(line).length;
  356. return SimpleEndRegExp.instance.allMatchResults(line);
  357. }
  358. public static boolean hasEndKeyword(String line) {
  359. return EnhancedEndRegExp.instance.isMatch(line) ||
  360. EnhancedEndRegExp2.instance.isMatch(line) ||
  361. (line.trim().endsWith("end") && line.indexOf("#") == -1);
  362. }
  363. public static abstract class InsertRegularExpression extends RegularExpression {
  364. String indent(String line) {
  365. return firstMatch(line).group(1);
  366. }
  367. }
  368. private static abstract class IndentRegularExpression extends InsertRegularExpression {
  369. final String indent(String line) {
  370. MatchResult match = instance().firstMatch(line);
  371. StringBuffer indent = new StringBuffer(match.group(1));
  372. if(extraIndent(line)) {
  373. for (int i = 0; i < match.group(2).length(); i++) {
  374. indent.append(" ");
  375. }
  376. }
  377. return indent.toString();
  378. }
  379. protected abstract RegularExpression instance();
  380. boolean extraIndent(String line) {
  381. return true;
  382. }
  383. }
  384. /**
  385. * matches other syntax that requires end
  386. */
  387. public static final class MatchRegExp extends IndentRegularExpression {
  388. public static final InsertRegularExpression instance = new MatchRegExp();
  389. private static final String indent = "(\\s*)";
  390. private static final String leadingText = "([^#]*)";
  391. private static final String trailingSpace = "\\s*";
  392. public static final String expression = indent + leadingText
  393. + "("
  394. + "((if|for|while|until|unless|def|case|class|module)((?:\\s|\\()\\s*\\S+)+)"
  395. + "|"
  396. + "(begin|loop[ ]do|do)"
  397. + ")" + trailingSpace;
  398. protected final String getPattern() {
  399. return expression;
  400. }
  401. protected final RegularExpression instance() {
  402. return instance;
  403. }
  404. final boolean extraIndent(String line) {
  405. return line.indexOf("begin") == -1 && line.indexOf("do") == -1;
  406. }
  407. }
  408. /**
  409. * matches lines to ignore
  410. */
  411. public static final class IgnoreRegExp extends InsertRegularExpression {
  412. public static final RegularExpression instance = new IgnoreRegExp();
  413. protected final String getPattern() {
  414. return "((.*)(" +
  415. // "([[:graph:]]\\s+(if|unless)(\\s+\\S+)+)" +
  416. "([^ \\t\\n\\r\\f\\v]\\s+(if|unless)(\\s+\\S+)+)" +
  417. ")\\s*)" +
  418. "|" +
  419. "([^\"]*(\"|')[^\"]*" +
  420. "(if|for|while|until|unless|def|case|class|module|do|begin|loop[ ]do)" +
  421. "[^\"]*(\"|')[^\"]*)";
  422. }
  423. }
  424. /**
  425. * matches x.y do |z| expressions
  426. */
  427. private static final class DoRegExp extends InsertRegularExpression {
  428. private static final InsertRegularExpression instance = new DoRegExp();
  429. protected final String getPattern() {
  430. return "(\\s*)(\\S+\\s+)+do\\s+\\|+[^\\|]*\\|\\s*";
  431. }
  432. }
  433. private static final class SimpleEndRegExp extends InsertRegularExpression {
  434. private static final RegularExpression instance = new SimpleEndRegExp();
  435. protected final String getPattern() {
  436. return "end";
  437. }
  438. }
  439. private static final class EndRegExp extends InsertRegularExpression {
  440. private static final RegularExpression instance = new EndRegExp();
  441. protected final String getPattern() {
  442. return "[^#]*end\\s*";
  443. }
  444. }
  445. private static final class EnhancedEndRegExp extends InsertRegularExpression {
  446. private static final RegularExpression instance = new EnhancedEndRegExp();
  447. protected final String getPattern() {
  448. return "^end(\\s*|(\\s+.*))";
  449. }
  450. }
  451. private static final class EnhancedEndRegExp2 extends InsertRegularExpression {
  452. private static final RegularExpression instance = new EnhancedEndRegExp();
  453. protected final String getPattern() {
  454. return "[^#]*\\s+end(\\s*|(\\s+.*))";
  455. }
  456. }
  457. private static final class CommentRegExp extends InsertRegularExpression {
  458. private static final RegularExpression instance = new CommentRegExp();
  459. protected final String getPattern() {
  460. return "(\\s*)(##?)(.*)";
  461. }
  462. }
  463. public static final class TrailingConditionRegExp extends IndentRegularExpression {
  464. public static final InsertRegularExpression instance = new TrailingConditionRegExp();
  465. protected final String getPattern() {
  466. return "(\\s*)([^#]*=\\s*)(((if)|(unless)|(case)).*)";
  467. }
  468. protected final RegularExpression instance() {
  469. return instance;
  470. }
  471. }
  472. private static final class IfElsifRegExp extends InsertRegularExpression {
  473. private static final RegularExpression instance = new IfElsifRegExp();
  474. protected final String getPattern() {
  475. return "(\\s*)((if)|(elsif))(.*)";
  476. }
  477. }
  478. // private static class WhenRegExp extends RegularExpression {
  479. // private static final RE instance = new WhenRegExp();
  480. // protected String getPattern() {
  481. // return "(\\s*)(when)(.*)";
  482. // }
  483. // }
  484. }