/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Sergey Prigogin (Google) *******************************************************************************/ package org.eclipse.cdt.ui.tests.text; import junit.framework.TestCase; import junit.framework.TestSuite; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.hyperlink.IHyperlink; import org.eclipse.cdt.core.dom.IPDOMManager; import org.eclipse.cdt.core.dom.ast.IASTTranslationUnit; import org.eclipse.cdt.core.model.ICContainer; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.model.ILanguage; import org.eclipse.cdt.core.model.IWorkingCopy; import org.eclipse.cdt.core.testplugin.CProjectHelper; import org.eclipse.cdt.ui.CUIPlugin; import org.eclipse.cdt.ui.IWorkingCopyManager; import org.eclipse.cdt.ui.testplugin.EditorTestHelper; import org.eclipse.cdt.internal.core.model.ASTCache.ASTRunnable; import org.eclipse.cdt.internal.ui.editor.ASTProvider; import org.eclipse.cdt.internal.ui.editor.CEditor; import org.eclipse.cdt.internal.ui.editor.CElementHyperlinkDetector; /** * This test just checks that hyperlinks are created in the right * places. It does not test that the hyperlinks actually take you * to the right place. * * @author Mike Kucera */ public class HyperlinkTest extends TestCase { private static final String CPP_FILE_NAME_1 = "hyperlink_test_cpp.cpp"; private static final String CPP_FILE_NAME_2 = "hyperlink_test_cpp_without_ast.cpp"; private static final String CPP_CODE = "#include <stdio.h> \n" + "#define SOMEMACRO macro_token1 macro_token2 \n" + "// COMMENT there should not be links inside of comments \n" + "class Point { \n" + " public: \n" + " Point(); \n" + " ~Point(); \n" + " void set(int x, int y); \n" + " int getX(); \n" + " int getY(); \n" + " Point operator+(Point); \n" + " private: \n" + " int x, y; \n" + "}; \n" + "#define SOMEMACROFUNCTION(a) function(a) \n" + "int test(Point p) { \n" + " char* str = \"STRING LITERAL\"; \n" + " p + p;" + " int arg;" + " SOMEMACROFUNCTION(arg);" + "} \n"; private static final String C_FILE_NAME_1 = "hyperlink_test_c_1.c"; private static final String C_CODE_1 = "int main() { \n" + " int class = 99; \n" + "}"; private static final String C_FILE_NAME_2 = "hyperlink_test_c_2.c"; private static final String C_CODE_2 = "#ifdef NOTDEF\n" + " int nothere = 99; \n" + "#else\n" + " int itworks = 100; \n" + "#endif\n"; private ICProject project; private CEditor editor; public static TestSuite suite() { return new TestSuite(HyperlinkTest.class); } private void setUpEditor(String fileName, String code) throws Exception { setUpEditor(fileName, code, true); } private void setUpEditor(String fileName, String code, boolean buildAST) throws Exception { super.setUp(); project= CProjectHelper.createCCProject(super.getName(), "unused", IPDOMManager.ID_NO_INDEXER); ICContainer cContainer= CProjectHelper.addCContainer(project, "src"); IFile file= EditorTestHelper.createFile((IContainer) cContainer.getResource(), fileName, code, new NullProgressMonitor()); assertNotNull(file); assertTrue(file.exists()); editor = (CEditor) EditorTestHelper.openInEditor(file, true); EditorTestHelper.joinReconciler(EditorTestHelper.getSourceViewer(editor), 10, 1000, 10); if (buildAST) { // Since CElementHyperlinkDetector doesn't wait for an AST to be created, // we trigger AST creation ahead of time. IWorkingCopyManager manager = CUIPlugin.getDefault().getWorkingCopyManager(); IWorkingCopy workingCopy = manager.getWorkingCopy(editor.getEditorInput()); IStatus status= ASTProvider.getASTProvider().runOnAST(workingCopy, ASTProvider.WAIT_IF_OPEN, null, new ASTRunnable() { public IStatus runOnAST(ILanguage lang, IASTTranslationUnit ast) { return Status.OK_STATUS; } }); } } @Override protected void tearDown() throws Exception { EditorTestHelper.closeEditor(editor); CProjectHelper.delete(project); } private IHyperlink[] getHyperlinks(int mouseOffset) { CElementHyperlinkDetector detector = new CElementHyperlinkDetector(); detector.setContext(editor); IRegion region = new Region(mouseOffset, 0); return detector.detectHyperlinks(EditorTestHelper.getSourceViewer(editor), region, false); } private void assertHyperlink(int mouseOffset, int linkStartOffset, int linkLength) { IHyperlink[] links = getHyperlinks(mouseOffset); assertNotNull(links); assertEquals(1, links.length); IRegion hyperlinkRegion = links[0].getHyperlinkRegion(); assertEquals(linkStartOffset, hyperlinkRegion.getOffset()); assertEquals(linkLength, hyperlinkRegion.getLength()); } private void assertNotHyperlink(int mouseOffset) { IHyperlink[] links = getHyperlinks(mouseOffset); assertNull(links); } public void testHyperlinksCpp() throws Exception { // entire include highlighted setUpEditor(CPP_FILE_NAME_1, CPP_CODE, true); assertHyperlink(CPP_CODE.indexOf("#include") + 2, 0, "#include <stdio.h>".length()); assertHyperlink(CPP_CODE.indexOf("<stdio.h>") + 2, 0, "#include <stdio.h>".length()); assertHyperlink(CPP_CODE.indexOf("<stdio.h>") + "<stdio.h".length(), 0, "#include <stdio.h>".length()); // hovering over the whitespace inside an include still results in a hyperlink assertHyperlink(CPP_CODE.indexOf("<stdio.h>") - 1, 0, "#include <stdio.h>".length()); // no hyperlinks in macro bodies assertNotHyperlink(CPP_CODE.indexOf("#define") + 1); assertHyperlink(CPP_CODE.indexOf("SOMEMACRO"), CPP_CODE.indexOf("SOMEMACRO"), "SOMEMACRO".length()); // see bug 259015 assertHyperlink(CPP_CODE.indexOf("macro_token1"), CPP_CODE.indexOf("macro_token1"), "macro_token1".length()); assertHyperlink(CPP_CODE.indexOf("macro_token2"), CPP_CODE.indexOf("macro_token2"), "macro_token2".length()); // see bug 344604 assertHyperlink(CPP_CODE.indexOf("arg);"), CPP_CODE.indexOf("arg);"), "arg".length()); // no hyperlinks for comments assertNotHyperlink(CPP_CODE.indexOf("//") + 1); assertNotHyperlink(CPP_CODE.indexOf("COMMENT") + 1); // no hyperlinks for keywords assertNotHyperlink(CPP_CODE.indexOf("class") + 1); assertNotHyperlink(CPP_CODE.indexOf("public") + 1); assertNotHyperlink(CPP_CODE.indexOf("private") + 1); assertNotHyperlink(CPP_CODE.indexOf("int x") + 1); assertNotHyperlink(CPP_CODE.indexOf("char") + 1); assertNotHyperlink(CPP_CODE.indexOf("void") + 1); // no hyperlinks for punctuation assertNotHyperlink(CPP_CODE.indexOf("{")); assertNotHyperlink(CPP_CODE.indexOf("}")); assertNotHyperlink(CPP_CODE.indexOf("(" + 1)); assertNotHyperlink(CPP_CODE.indexOf(")")); assertNotHyperlink(CPP_CODE.indexOf(":")); assertNotHyperlink(CPP_CODE.indexOf(";")); // no hyperlinks inside strings assertNotHyperlink(CPP_CODE.indexOf("STRING") + 1); assertNotHyperlink(CPP_CODE.indexOf("STRING") + 6); assertNotHyperlink(CPP_CODE.indexOf("LITERAL") + 1); assertHyperlink(CPP_CODE.indexOf("Point {") + 1, CPP_CODE.indexOf("Point {"), "Point".length()); assertHyperlink(CPP_CODE.indexOf("Point()") + 1, CPP_CODE.indexOf("Point()"), "Point".length()); assertHyperlink(CPP_CODE.indexOf("~Point()") + 1, CPP_CODE.indexOf("~Point()"), "~Point".length()); assertHyperlink(CPP_CODE.indexOf("set(") + 1, CPP_CODE.indexOf("set("), "set".length()); assertHyperlink(CPP_CODE.indexOf("getX()") + 1, CPP_CODE.indexOf("getX()"), "getX".length()); assertHyperlink(CPP_CODE.indexOf("getY()") + 1, CPP_CODE.indexOf("getY()"), "getY".length()); // hyperlinks for overloaded operators assertHyperlink(CPP_CODE.indexOf("+ p"), CPP_CODE.indexOf("+ p"), 1); } public void testHyperlinksCppWithoutAST() throws Exception { // entire include highlighted setUpEditor(CPP_FILE_NAME_2, CPP_CODE, false); assertHyperlink(CPP_CODE.indexOf("#include") + 2, 0, "#include <stdio.h>".length()); assertHyperlink(CPP_CODE.indexOf("<stdio.h>") + 2, 0, "#include <stdio.h>".length()); assertHyperlink(CPP_CODE.indexOf("<stdio.h>") + "<stdio.h".length(), 0, "#include <stdio.h>".length()); // hovering over the whitespace inside an include still results in a hyperlink assertHyperlink(CPP_CODE.indexOf("<stdio.h>") - 1, 0, "#include <stdio.h>".length()); // no hyperlinks in macro bodies assertNotHyperlink(CPP_CODE.indexOf("#define") + 1); assertHyperlink(CPP_CODE.indexOf("SOMEMACRO"), CPP_CODE.indexOf("SOMEMACRO"), "SOMEMACRO".length()); // see bug 259015 assertHyperlink(CPP_CODE.indexOf("macro_token1"), CPP_CODE.indexOf("macro_token1"), "macro_token1".length()); assertHyperlink(CPP_CODE.indexOf("macro_token2"), CPP_CODE.indexOf("macro_token2"), "macro_token2".length()); // see bug 344604 assertHyperlink(CPP_CODE.indexOf("arg);"), CPP_CODE.indexOf("arg);"), "arg".length()); // no hyperlinks for comments assertNotHyperlink(CPP_CODE.indexOf("//") + 1); assertNotHyperlink(CPP_CODE.indexOf("COMMENT") + 1); // no hyperlinks for keywords assertNotHyperlink(CPP_CODE.indexOf("class") + 1); assertNotHyperlink(CPP_CODE.indexOf("public") + 1); assertNotHyperlink(CPP_CODE.indexOf("private") + 1); assertNotHyperlink(CPP_CODE.indexOf("int x") + 1); assertNotHyperlink(CPP_CODE.indexOf("char") + 1); assertNotHyperlink(CPP_CODE.indexOf("void") + 1); // no hyperlinks for punctuation assertNotHyperlink(CPP_CODE.indexOf("{")); assertNotHyperlink(CPP_CODE.indexOf("}")); assertNotHyperlink(CPP_CODE.indexOf("(" + 1)); assertNotHyperlink(CPP_CODE.indexOf(")")); assertNotHyperlink(CPP_CODE.indexOf(":")); assertNotHyperlink(CPP_CODE.indexOf(";")); // no hyperlinks inside strings assertNotHyperlink(CPP_CODE.indexOf("STRING") + 1); assertNotHyperlink(CPP_CODE.indexOf("STRING") + 6); assertNotHyperlink(CPP_CODE.indexOf("LITERAL") + 1); assertHyperlink(CPP_CODE.indexOf("Point {") + 1, CPP_CODE.indexOf("Point {"), "Point".length()); assertHyperlink(CPP_CODE.indexOf("Point()") + 1, CPP_CODE.indexOf("Point()"), "Point".length()); // No-AST hyperlink detection doesn't know that tilde is part of the destructor name. assertHyperlink(CPP_CODE.indexOf("~Point()") + 1, CPP_CODE.indexOf("~Point()") + 1, "Point".length()); assertHyperlink(CPP_CODE.indexOf("set(") + 1, CPP_CODE.indexOf("set("), "set".length()); assertHyperlink(CPP_CODE.indexOf("getX()") + 1, CPP_CODE.indexOf("getX()"), "getX".length()); assertHyperlink(CPP_CODE.indexOf("getY()") + 1, CPP_CODE.indexOf("getY()"), "getY".length()); // Overloaded operators are not hyperlinked when AST is not available. //assertHyperlink(CPP_CODE.indexOf("+ p"), CPP_CODE.indexOf("+ p"), 1); } public void testHyperlinksCKeywords() throws Exception { setUpEditor(C_FILE_NAME_1, C_CODE_1, true); // 'class' is not a keyword in C, it should be hyperlinked assertHyperlink(C_CODE_1.indexOf("class") + 1, C_CODE_1.indexOf("class"), "class".length()); // no hyperlinks for numeric literals assertNotHyperlink(C_CODE_1.indexOf("99") + 1); } public void testHyperlinksInactiveCode() throws Exception { setUpEditor(C_FILE_NAME_2, C_CODE_2, true); assertNotHyperlink(C_CODE_2.indexOf("#ifdef") + 2); assertNotHyperlink(C_CODE_2.indexOf("#else") + 2); assertNotHyperlink(C_CODE_2.indexOf("#endif") + 2); // see bug 259015 // assertNotHyperlink(C_CODE_2.indexOf("nothere") + 1); assertHyperlink(C_CODE_2.indexOf("itworks") + 1, C_CODE_2.indexOf("itworks"), "itworks".length()); } }