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

# · Java · 221 lines · 147 code · 41 blank · 33 comment · 21 complexity · a892cb84d7ede9fc1e18f2bb41a96d77 MD5 · raw file

  1. /*
  2. * RubySideKickParser.java - Side Kick Parser for Ruby
  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 sidekick.SideKickParser;
  22. import sidekick.SideKickParsedData;
  23. import sidekick.SideKickCompletion;
  24. import org.gjt.sp.jedit.Buffer;
  25. import org.gjt.sp.jedit.EditPane;
  26. import org.jruby.lexer.yacc.ISourcePosition;
  27. import org.jedit.ruby.ast.Member;
  28. import org.jedit.ruby.parser.RubyParser;
  29. import org.jedit.ruby.ast.RubyMembers;
  30. import org.jedit.ruby.RubyPlugin;
  31. import org.jedit.ruby.utils.EditorView;
  32. import org.jedit.ruby.completion.CodeCompletor;
  33. import org.jedit.ruby.completion.RubyCompletion;
  34. import org.jedit.ruby.completion.CodeAnalyzer;
  35. import errorlist.DefaultErrorSource;
  36. import errorlist.ErrorSource;
  37. import javax.swing.tree.DefaultMutableTreeNode;
  38. /**
  39. * @author robmckinnon at users.sourceforge.net
  40. */
  41. public final class RubySideKickParser extends SideKickParser {
  42. private static final ErrorSource.Error[] EMPTY_ERROR_LIST = new ErrorSource.Error[0];
  43. private static DefaultErrorSource errorSource;
  44. private final RubyTokenHandler tokenHandler;
  45. public RubySideKickParser() {
  46. super("ruby");
  47. tokenHandler = new RubyTokenHandler();
  48. }
  49. public final boolean supportsCompletion() {
  50. return true;
  51. }
  52. public final boolean canHandleBackspace() {
  53. return true;
  54. }
  55. public static ErrorSource.Error[] getErrors() {
  56. ErrorSource.Error[] errors = errorSource.getAllErrors();
  57. return errors != null ? errors : EMPTY_ERROR_LIST;
  58. }
  59. public final SideKickParsedData parse(final Buffer buffer, final DefaultErrorSource errorSource) {
  60. String text = buffer.getText(0, buffer.getLength());
  61. RubySideKickParser.errorSource = errorSource;
  62. RubyParser.WarningListener listener = new RubySideKickWarningListener(errorSource);
  63. SideKickParsedData data = new SideKickParsedData(buffer.getName());
  64. RubyMembers members = RubyParser.getMembers(text, buffer.getPath(), listener, false);
  65. if (!members.containsErrors()) {
  66. addNodes(data.root, members.getMembers(), buffer);
  67. } else if (RubyParser.hasLastGoodMembers(buffer)) {
  68. members = RubyParser.getLastGoodMembers(buffer);
  69. addNodes(data.root, members.combineMembersAndProblems(0), buffer);
  70. } else {
  71. addNodes(data.root, members.getProblems(), buffer);
  72. }
  73. return data;
  74. }
  75. /**
  76. * True if caret is at a method insertion
  77. * point, else false.
  78. * This means that if the user types a dot or
  79. * equivalent, this method will return true
  80. * permitting completion to automatically
  81. * occur after a delay. In other cases
  82. * there won't be automatic completion
  83. * after a delay, the user will have to
  84. * manually hit the completion shortcut.
  85. */
  86. public boolean canCompleteAnywhere() {
  87. EditorView view = RubyPlugin.getActiveView();
  88. return CodeAnalyzer.isDotInsertionPoint(view) || RubyCompletion.continueCompleting();
  89. }
  90. public final SideKickCompletion complete(EditPane editPane, int caret) {
  91. Buffer buffer = editPane.getBuffer();
  92. RubyToken syntaxType = tokenHandler.getTokenAtCaret(buffer, caret);
  93. RubyCompletion completion = null;
  94. if (!ignore(syntaxType)) {
  95. CodeCompletor completor = new CodeCompletor(RubyPlugin.getActiveView());
  96. if (completor.isDotInsertionPoint()) {
  97. completion = completor.getDotCompletion();
  98. } else if (completor.hasCompletion()) {
  99. completion = completor.getCompletion();
  100. } else {
  101. completion = completor.getEmptyCompletion();
  102. clearLastCompletion();
  103. }
  104. } else {
  105. clearLastCompletion();
  106. }
  107. return completion;
  108. }
  109. private static void clearLastCompletion() {
  110. CodeAnalyzer.setLastReturnTypes(null);
  111. CodeAnalyzer.setLastCompleted(null);
  112. }
  113. private boolean ignore(RubyToken token) {
  114. if (token.isComment() || token.isLiteral()) {
  115. RubyPlugin.log("ignoring: " + token, getClass());
  116. return true;
  117. } else {
  118. RubyPlugin.log("not ignoring: " + token, getClass());
  119. return false;
  120. }
  121. }
  122. private void addWarning(String message, ISourcePosition position, DefaultErrorSource errorSource) {
  123. addToErrorList(ErrorSource.WARNING, position, errorSource, message);
  124. }
  125. private void addError(String message, ISourcePosition position, DefaultErrorSource errorSource) {
  126. addToErrorList(ErrorSource.ERROR, position, errorSource, message);
  127. }
  128. private void addToErrorList(int type, ISourcePosition position, DefaultErrorSource errorSource, String message) {
  129. int line = position == null ? 0 : position.getEndLine();
  130. String file = position == null ? null : position.getFile();
  131. int startOffset = RubyPlugin.getStartOffset(line);
  132. int nonSpaceStartOffset = RubyPlugin.getNonSpaceStartOffset(line);
  133. int endOffset = RubyPlugin.getEndOffset(line);
  134. int startOffsetInLine = nonSpaceStartOffset - startOffset;
  135. int endOffsetInLine = endOffset - startOffset;
  136. RubyPlugin.log("start " + startOffsetInLine + ", end " + endOffsetInLine, getClass());
  137. errorSource.addError(type, file, line, startOffsetInLine, endOffsetInLine, message);
  138. }
  139. private void addNodes(DefaultMutableTreeNode parentNode, Member[] members, Buffer buffer) {
  140. if (members != null) {
  141. for (Member member : members) {
  142. MemberNode node = new MemberNode(member);
  143. node.setStart(buffer.createPosition(Math.min(buffer.getLength(), member.getStartOffset())));
  144. node.setEnd(buffer.createPosition(Math.min(buffer.getLength(), member.getEndOffset())));
  145. DefaultMutableTreeNode treeNode = node.createTreeNode();
  146. if (member.hasChildMembers()) {
  147. Member[] childMembers = member.getChildMembers();
  148. addNodes(treeNode, childMembers, buffer);
  149. }
  150. parentNode.add(treeNode);
  151. }
  152. }
  153. }
  154. private final class RubySideKickWarningListener implements RubyParser.WarningListener {
  155. private final DefaultErrorSource errorSource;
  156. public RubySideKickWarningListener(DefaultErrorSource errorSource) {
  157. this.errorSource = errorSource;
  158. }
  159. public boolean isVerbose() {
  160. return false;
  161. }
  162. public final void warn(ISourcePosition position, String message) {
  163. addWarning(message, position, errorSource);
  164. }
  165. public final void warn(String message) {
  166. addWarning(message, null, errorSource);
  167. }
  168. public final void warning(ISourcePosition position, String message) {
  169. addWarning(message, position, errorSource);
  170. }
  171. public final void warning(String message) {
  172. addWarning(message, null, errorSource);
  173. }
  174. public final void error(ISourcePosition position, String message) {
  175. addError(message, position, errorSource);
  176. }
  177. public final void clear() {
  178. }
  179. }
  180. }