package com.redhat.ceylon.eclipse.code.complete; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationPresenter; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.editor.CeylonSourceViewer; class ParameterContextValidator implements IContextInformationValidator, IContextInformationPresenter { private int position; private IContextInformation information; private int currentParameter; private CeylonEditor editor; ParameterContextValidator(CeylonEditor editor) { this.editor = editor; } @Override public boolean updatePresentation(int brokenPosition, TextPresentation presentation) { String s = information.getInformationDisplayString(); presentation.clear(); if (this.position==-1) { presentation.addStyleRange(new StyleRange(0, s.length(), null, null, SWT.BOLD)); addItalics(presentation, s); return true; } int currentParameter = -1; CeylonSourceViewer viewer = editor.getCeylonSourceViewer(); int position = viewer.getSelectedRange().x; IDocument doc = viewer.getDocument(); try { boolean namedInvocation = doc.getChar(this.position)=='{'; if (!namedInvocation) Assert.isTrue(doc.getChar(this.position)=='('); // int paren = doc.get(this.position, position-this.position) // .indexOf(namedInvocation?'{':'('); // if (paren<0) { //TODO: is this really useful? // this.position = doc.get(0, position).lastIndexOf('('); // } currentParameter = getCharCount(doc, this.position+1, position, namedInvocation?";":",", "", true); } catch (BadLocationException x) { return false; } if (currentParameter != -1) { if (this.currentParameter == currentParameter) { return false; } } presentation.clear(); this.currentParameter = currentParameter; int[] commas = computeCommaPositions(s); if (commas.length - 2 < currentParameter) { presentation.addStyleRange(new StyleRange(0, s.length(), null, null, SWT.NORMAL)); addItalics(presentation, s); return true; } int start = commas[currentParameter] + 1; int end = commas[currentParameter + 1]; if (start > 0) { presentation.addStyleRange(new StyleRange(0, start, null, null, SWT.NORMAL)); } if (end > start) { presentation.addStyleRange(new StyleRange(start, end - start, null, null, SWT.BOLD)); } if (end < s.length()) { presentation.addStyleRange(new StyleRange(end, s.length() - end, null, null, SWT.NORMAL)); } addItalics(presentation, s); return true; } private void addItalics(TextPresentation presentation, String s) { Matcher m2 = p2.matcher(s); while (m2.find()) { presentation.mergeStyleRange(new StyleRange(m2.start(), m2.end()-m2.start(), null, null, SWT.ITALIC)); } // Matcher m1 = p1.matcher(s); // while (m1.find()) { // presentation.mergeStyleRange(new StyleRange(m1.start(), m1.end()-m1.start()+1, // typeColor, null)); // } } // final Pattern p1 = Pattern.compile("\\b\\p{javaUpperCase}\\w*\\b"); final Pattern p2 = Pattern.compile("\\b\\p{javaLowerCase}\\w*\\b"); // final Color typeColor = color(getCurrentTheme().getColorRegistry(), TYPES); @Override public void install(IContextInformation info, ITextViewer viewer, int documentPosition) { if (info instanceof ParameterContextInformation) { ParameterContextInformation pci = (ParameterContextInformation) info; this.position = pci.getArgumentListOffset(); } else if (info instanceof ParametersCompletionProposal.ParameterContextInformation) { ParametersCompletionProposal.ParameterContextInformation pci = (ParametersCompletionProposal.ParameterContextInformation) info; this.position = pci.getArgumentListOffset(); } else { this.position = -1; } Assert.isTrue(viewer==editor.getCeylonSourceViewer()); this.information = info; this.currentParameter= -1; } @Override public boolean isContextInformationValid(int brokenPosition) { if (editor.isInLinkedMode()) { Object linkedModeOwner = editor.getLinkedModeOwner(); if (linkedModeOwner instanceof InvocationCompletionProposal || linkedModeOwner instanceof ParametersCompletionProposal || linkedModeOwner instanceof com.redhat.ceylon.ide.common.completion.InvocationCompletionProposal || linkedModeOwner instanceof com.redhat.ceylon.ide.common.completion.RefinementCompletionProposal) { return true; } } try { CeylonSourceViewer viewer = editor.getCeylonSourceViewer(); int position = viewer.getSelectedRange().x; if (position < this.position) { return false; } IDocument document = viewer.getDocument(); IRegion line = document.getLineInformationOfOffset(this.position); if (position < line.getOffset() || position >= document.getLength()) { return false; } // System.out.println(document.get(this.position, position-this.position)); int semiCount = getCharCount(document, this.position, position, ";", "", true); int fenceCount = getCharCount(document, this.position, position, "{(", "})", false); return semiCount==0 && fenceCount>0; } catch (BadLocationException x) { return false; } } /*@Override public boolean isContextInformationValid(int offset) { IContextInformation[] infos= computeContextInformation(viewer, offset); if (infos != null && infos.length > 0) { for (int i= 0; i < infos.length; i++) if (information.equals(infos[i])) return true; } return false; }*/ private static final int NONE = 0; private static final int BRACKET = 1; private static final int BRACE = 2; private static final int PAREN = 3; private static final int ANGLE = 4; private static int getCharCount(IDocument document, final int start, final int end, String increments, String decrements, boolean considerNesting) throws BadLocationException { Assert.isTrue((increments.length() != 0 || decrements.length() != 0) && !increments.equals(decrements)); int nestingMode = NONE; int nestingLevel = 0; int charCount = 0; int offset = start; char prev = ' '; while (offset < end) { char curr = document.getChar(offset++); switch (curr) { case '/': if (offset < end) { char next = document.getChar(offset); if (next == '*') { // a comment starts, advance to the comment end offset= getCommentEnd(document, offset + 1, end); } else if (next == '/') { // '//'-comment: nothing to do anymore on this line int nextLine= document.getLineOfOffset(offset) + 1; if (nextLine == document.getNumberOfLines()) { offset= end; } else { offset= document.getLineOffset(nextLine); } } } break; case '*': if (offset < end) { char next= document.getChar(offset); if (next == '/') { // we have been in a comment: forget what we read before charCount= 0; ++ offset; } } break; case '"': case '\'': offset= getStringEnd(document, offset, end, curr); break; case '[': if (considerNesting) { if (nestingMode == BRACKET || nestingMode == NONE) { nestingMode= BRACKET; nestingLevel++; } break; } //$FALL-THROUGH$ case ']': if (considerNesting) { if (nestingMode == BRACKET) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } //$FALL-THROUGH$ case '(': if (considerNesting) { if (nestingMode == ANGLE) { // generics heuristic failed nestingMode=PAREN; nestingLevel= 1; } if (nestingMode == PAREN || nestingMode == NONE) { nestingMode= PAREN; nestingLevel++; } break; } //$FALL-THROUGH$ case ')': if (considerNesting) { if (nestingMode == PAREN) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } //$FALL-THROUGH$ case '{': if (considerNesting) { if (nestingMode == ANGLE) { // generics heuristic failed nestingMode=BRACE; nestingLevel= 1; } if (nestingMode == BRACE || nestingMode == NONE) { nestingMode= BRACE; nestingLevel++; } break; } //$FALL-THROUGH$ case '}': if (considerNesting) { if (nestingMode == BRACE) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } //$FALL-THROUGH$ case '<': if (considerNesting) { if (nestingMode == ANGLE || nestingMode == NONE /*&& checkGenericsHeuristic(document, offset - 1, start - 1)*/) { nestingMode= ANGLE; nestingLevel++; } break; } //$FALL-THROUGH$ case '>': if (considerNesting && prev != '=') { //check that it's not a fat arrow if (nestingMode == ANGLE) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } //$FALL-THROUGH$ default: if (nestingLevel==0) { if (increments.indexOf(curr) >= 0) { ++ charCount; } if (decrements.indexOf(curr) >= 0) { -- charCount; } } } } return charCount; } static int findCharCount(int count, IDocument document, final int start, final int end, String increments, String decrements, boolean considerNesting) throws BadLocationException { Assert.isTrue((increments.length() != 0 || decrements.length() != 0) && !increments.equals(decrements)); final int NONE= 0; final int BRACKET= 1; final int BRACE= 2; final int PAREN= 3; final int ANGLE= 4; int nestingMode= NONE; int nestingLevel= 0; int charCount= 0; int offset= start; boolean lastWasEquals = false; while (offset < end) { if (nestingLevel == 0) { if (count==charCount) { return offset-1; } } char curr= document.getChar(offset++); switch (curr) { case '/': if (offset < end) { char next= document.getChar(offset); if (next == '*') { // a comment starts, advance to the comment end offset= getCommentEnd(document, offset + 1, end); } else if (next == '/') { // '//'-comment: nothing to do anymore on this line int nextLine= document.getLineOfOffset(offset) + 1; if (nextLine == document.getNumberOfLines()) { offset= end; } else { offset= document.getLineOffset(nextLine); } } } break; case '*': if (offset < end) { char next= document.getChar(offset); if (next == '/') { // we have been in a comment: forget what we read before charCount= 0; ++ offset; } } break; case '"': case '\'': offset= getStringEnd(document, offset, end, curr); break; case '[': if (considerNesting) { if (nestingMode == BRACKET || nestingMode == NONE) { nestingMode= BRACKET; nestingLevel++; } break; } //$FALL-THROUGH$ case ']': if (considerNesting) { if (nestingMode == BRACKET) if (--nestingLevel == 0) { nestingMode= NONE; } break; } //$FALL-THROUGH$ case '(': if (considerNesting) { if (nestingMode == ANGLE) { // generics heuristic failed nestingMode=PAREN; nestingLevel= 1; } if (nestingMode == PAREN || nestingMode == NONE) { nestingMode= PAREN; nestingLevel++; } break; } //$FALL-THROUGH$ case ')': if (considerNesting) { if (nestingMode == 0) { return offset-1; } if (nestingMode == PAREN) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } //$FALL-THROUGH$ case '{': if (considerNesting) { if (nestingMode == ANGLE) { // generics heuristic failed nestingMode=BRACE; nestingLevel= 1; } if (nestingMode == BRACE || nestingMode == NONE) { nestingMode= BRACE; nestingLevel++; } break; } //$FALL-THROUGH$ case '}': if (considerNesting) { if (nestingMode == 0) { return offset-1; } if (nestingMode == BRACE) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } //$FALL-THROUGH$ case '<': if (considerNesting) { if (nestingMode == ANGLE || nestingMode == NONE /*&& checkGenericsHeuristic(document, offset - 1, start - 1)*/) { nestingMode= ANGLE; nestingLevel++; } break; } //$FALL-THROUGH$ case '>': if (!lastWasEquals) { if (nestingMode == 0) { return offset-1; } if (considerNesting) { if (nestingMode == ANGLE) { if (--nestingLevel == 0) { nestingMode= NONE; } } break; } } //$FALL-THROUGH$ default: if (nestingLevel == 0) { if (increments.indexOf(curr) >= 0) { ++ charCount; } if (decrements.indexOf(curr) >= 0) { -- charCount; } } } lastWasEquals = curr=='='; } return -1; } private static int[] computeCommaPositions(String code) { final int length= code.length(); int pos = 0; int angleLevel = 0; List<Integer> positions= new ArrayList<Integer>(); positions.add(new Integer(-1)); char prev = ' '; while (pos < length && pos != -1) { char ch = code.charAt(pos); switch (ch) { case ',': case ';': if (angleLevel == 0) { positions.add(new Integer(pos)); } break; case '<': case '(': case '{': case '[': angleLevel++; break; case '>': if (prev=='=') break; case ')': case '}': case ']': angleLevel--; break; // case '[': // pos= code.indexOf(']', pos); // break; default: break; } if (pos != -1) { pos++; } } positions.add(new Integer(length)); int[] fields= new int[positions.size()]; for (int i= 0; i < fields.length; i++) { fields[i]= positions.get(i).intValue(); } return fields; } private static int getCommentEnd(IDocument d, int pos, int end) throws BadLocationException { while (pos < end) { char curr= d.getChar(pos); pos++; if (curr == '*') { if (pos < end && d.getChar(pos) == '/') { return pos + 1; } } } return end; } private static int getStringEnd(IDocument d, int pos, int end, char ch) throws BadLocationException { while (pos < end) { char curr= d.getChar(pos); pos++; if (curr == '\\') { // ignore escaped characters pos++; } else if (curr == ch) { return pos; } } return end; } }