/******************************************************************************* * Copyright (c) 2000, 2007 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 *******************************************************************************/ package org.rubypeople.rdt.internal.ui.viewsupport; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.ui.model.IWorkbenchAdapter; import org.rubypeople.rdt.core.IField; import org.rubypeople.rdt.core.ILoadpathContainer; import org.rubypeople.rdt.core.ILoadpathEntry; import org.rubypeople.rdt.core.ILocalVariable; import org.rubypeople.rdt.core.IMember; import org.rubypeople.rdt.core.IMethod; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.ISourceFolder; import org.rubypeople.rdt.core.ISourceFolderRoot; import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.LoadpathContainerInitializer; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.internal.core.util.Messages; import org.rubypeople.rdt.internal.corext.util.RubyModelUtil; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.RubyUIMessages; import org.rubypeople.rdt.internal.ui.packageview.LoadPathContainer; import org.rubypeople.rdt.internal.ui.viewsupport.ColoredString.Style; import org.rubypeople.rdt.launching.RubyRuntime; import org.rubypeople.rdt.ui.RubyElementLabels; public class ColoredRubyElementLabels { public static final Style QUALIFIER_STYLE= new Style(ColoredViewersManager.QUALIFIER_COLOR_NAME); public static final Style COUNTER_STYLE= new Style(ColoredViewersManager.COUNTER_COLOR_NAME); public static final Style DECORATIONS_STYLE= new Style(ColoredViewersManager.DECORATIONS_COLOR_NAME); private static final Style APPENDED_TYPE_STYLE= DECORATIONS_STYLE; public final static long COLORIZE= 1L << 55; private final static long QUALIFIER_FLAGS= RubyElementLabels.P_COMPRESSED | RubyElementLabels.USE_RESOLVED; private static final boolean getFlag(long flags, long flag) { return (flags & flag) != 0; } /** * Returns the label of the given object. The object must be of type {@link IRubyElement} or adapt to {@link IWorkbenchAdapter}. The empty string is returned * if the element type is not known. * @param obj Object to get the label from. * @param flags The rendering flags * @return Returns the label or the empty string if the object type is not supported. */ public static ColoredString getTextLabel(Object obj, long flags) { if (obj instanceof IRubyElement) { return getElementLabel((IRubyElement) obj, flags); } else if (obj instanceof IResource) { return new ColoredString(((IResource) obj).getName()); } else if (obj instanceof LoadPathContainer) { LoadPathContainer container= (LoadPathContainer) obj; return getContainerEntryLabel(container.getLoadpathEntry().getPath(), container.getRubyProject()); } return new ColoredString(RubyElementLabels.getTextLabel(obj, flags)); } /** * Returns the label for a Ruby element with the flags as defined by this class. * @param element The element to render. * @param flags The rendering flags. * @return the label of the Ruby element */ public static ColoredString getElementLabel(IRubyElement element, long flags) { ColoredString result= new ColoredString(); getElementLabel(element, flags, result); return result; } /** * Returns the label for a Ruby element with the flags as defined by this class. * @param element The element to render. * @param flags The rendering flags. * @param result The buffer to append the resulting label to. */ public static void getElementLabel(IRubyElement element, long flags, ColoredString result) { int type= element.getElementType(); ISourceFolderRoot root= null; if (type != IRubyElement.RUBY_MODEL && type != IRubyElement.RUBY_PROJECT && type != IRubyElement.SOURCE_FOLDER_ROOT) root= RubyModelUtil.getSourceFolderRoot(element); if (root != null && getFlag(flags, RubyElementLabels.PREPEND_ROOT_PATH)) { getSourceFolderRootLabel(root, RubyElementLabels.ROOT_QUALIFIED, result); result.append(RubyElementLabels.CONCAT_STRING); } switch (type) { case IRubyElement.METHOD: getMethodLabel((IMethod) element, flags, result); break; case IRubyElement.FIELD: getFieldLabel((IField) element, flags, result); break; case IRubyElement.LOCAL_VARIABLE: getLocalVariableLabel((ILocalVariable) element, flags, result); break; case IRubyElement.TYPE: getTypeLabel((IType) element, flags, result); break; case IRubyElement.SCRIPT: getCompilationUnitLabel((IRubyScript) element, flags, result); break; case IRubyElement.SOURCE_FOLDER: getSourceFolderLabel((ISourceFolder) element, flags, result); break; case IRubyElement.SOURCE_FOLDER_ROOT: getSourceFolderRootLabel((ISourceFolderRoot) element, flags, result); break; case IRubyElement.IMPORT_CONTAINER: case IRubyElement.IMPORT_DECLARATION: getDeclarationLabel(element, flags, result); break; case IRubyElement.RUBY_PROJECT: case IRubyElement.RUBY_MODEL: result.append(element.getElementName()); break; default: result.append(element.getElementName()); } if (root != null && getFlag(flags, RubyElementLabels.APPEND_ROOT_PATH)) { int offset= result.length(); result.append(RubyElementLabels.CONCAT_STRING); getSourceFolderRootLabel(root, RubyElementLabels.ROOT_QUALIFIED, result); if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } /** * Appends the label for a method to a {@link ColoredString}. Considers the M_* flags. * @param method The element to render. * @param flags The rendering flags. Flags with names starting with 'M_' are considered. * @param result The buffer to append the resulting label to. */ public static void getMethodLabel(IMethod method, long flags, ColoredString result) { try { // qualification if (getFlag(flags, RubyElementLabels.M_FULLY_QUALIFIED)) { getTypeLabel(method.getDeclaringType(), RubyElementLabels.T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), result); result.append('.'); } result.append(method.getElementName()); // parameters result.append('('); if (getFlag(flags, RubyElementLabels.M_PARAMETER_NAMES)) { int nParams= 0; boolean renderVarargs= false; String[] names= null; if (getFlag(flags, RubyElementLabels.M_PARAMETER_NAMES) && method.exists()) { names= method.getParameterNames(); nParams= names.length; } for (int i= 0; i < nParams; i++) { if (i > 0) { result.append(RubyElementLabels.COMMA_STRING); } if (names != null) { result.append(names[i]); } } } result.append(')'); // category if (getFlag(flags, RubyElementLabels.M_CATEGORY) && method.exists()) getCategoryLabel(method, result); // post qualification if (getFlag(flags, RubyElementLabels.M_POST_QUALIFIED)) { int offset= result.length(); result.append(RubyElementLabels.CONCAT_STRING); getTypeLabel(method.getDeclaringType(), RubyElementLabels.T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), result); if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } catch (RubyModelException e) { RubyPlugin.log(e); // NotExistsException will not reach this point } } private static void getCategoryLabel(IMember member, ColoredString result) throws RubyModelException { // String[] categories= member.getCategories(); // if (categories.length > 0) { // ColoredString categoriesBuf= new ColoredString(); // for (int i= 0; i < categories.length; i++) { // if (i > 0) // categoriesBuf.append(RubyUIMessages.RubyElementLabels_category_separator_string); // categoriesBuf.append(categories[i]); // } // result.append(RubyElementLabels.CONCAT_STRING); // result.append(Messages.format(RubyUIMessages.RubyElementLabels_category , categoriesBuf.toString())); // } } /** * Appends the label for a field to a {@link ColoredString}. Considers the F_* flags. * @param field The element to render. * @param flags The rendering flags. Flags with names starting with 'F_' are considered. * @param result The buffer to append the resulting label to. */ public static void getFieldLabel(IField field, long flags, ColoredString result) { try { // qualification if (getFlag(flags, RubyElementLabels.F_FULLY_QUALIFIED)) { getTypeLabel(field.getDeclaringType(), RubyElementLabels.T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), result); result.append('.'); } result.append(field.getElementName()); // category if (getFlag(flags, RubyElementLabels.F_CATEGORY) && field.exists()) getCategoryLabel(field, result); // post qualification if (getFlag(flags, RubyElementLabels.F_POST_QUALIFIED)) { int offset= result.length(); result.append(RubyElementLabels.CONCAT_STRING); getTypeLabel(field.getDeclaringType(), RubyElementLabels.T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), result); if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } catch (RubyModelException e) { RubyPlugin.log(e); // NotExistsException will not reach this point } } /** * Appends the label for a local variable to a {@link ColoredString}. * @param localVariable The element to render. * @param flags The rendering flags. Flags with names starting with 'F_' are considered. * @param result The buffer to append the resulting label to. */ public static void getLocalVariableLabel(ILocalVariable localVariable, long flags, ColoredString result) { if (getFlag(flags, RubyElementLabels.F_FULLY_QUALIFIED)) { getElementLabel(localVariable.getParent(), RubyElementLabels.M_FULLY_QUALIFIED | RubyElementLabels.T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), result); result.append('.'); } result.append(localVariable.getElementName()); // post qualification if (getFlag(flags, RubyElementLabels.F_POST_QUALIFIED)) { result.append(RubyElementLabels.CONCAT_STRING); getElementLabel(localVariable.getParent(), RubyElementLabels.M_FULLY_QUALIFIED | RubyElementLabels.T_FILENAME_QUALIFIED | (flags & QUALIFIER_FLAGS), result); } } /** * Appends the label for a type to a {@link ColoredString}. Considers the T_* flags. * @param type The element to render. * @param flags The rendering flags. Flags with names starting with 'T_' are considered. * @param result The buffer to append the resulting label to. */ public static void getTypeLabel(IType type, long flags, ColoredString result) { if (getFlag(flags, RubyElementLabels.T_FILENAME_QUALIFIED)) { ISourceFolder folder = type.getSourceFolder(); if (!folder.isDefaultPackage()) { getSourceFolderLabel(folder, (flags & QUALIFIER_FLAGS), result); result.append('/'); } getCompilationUnitLabel(type.getRubyScript(), (flags & QUALIFIER_FLAGS), result); result.append(':'); } if (getFlag(flags, RubyElementLabels.T_FILENAME_QUALIFIED | RubyElementLabels.T_NAME_FULLY_QUALIFIED)) { IType declaringType= type.getDeclaringType(); if (declaringType != null) { getTypeLabel(declaringType, RubyElementLabels.T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), result); result.append("::"); } int parentType= type.getParent().getElementType(); if (parentType == IRubyElement.METHOD || parentType == IRubyElement.FIELD) { // anonymous or local getElementLabel(type.getParent(), 0, result); result.append('.'); } } String typeName= type.getElementName(); if (typeName.length() == 0) { // anonymous try { String supertypeName = type.getSuperclassName(); // String[] superInterfaceNames= type.getIncludedModuleNames(); // if (superInterfaceNames.length > 0) { // supertypeName= Signature.getSimpleName(superInterfaceNames[0]); // } else { // supertypeName= Signature.getSimpleName(type.getSuperclassName()); // } typeName= Messages.format(RubyUIMessages.RubyElementLabels_anonym_type , supertypeName); } catch (RubyModelException e) { //ignore typeName= RubyUIMessages.RubyElementLabels_anonym; } } result.append(typeName); // category if (getFlag(flags, RubyElementLabels.T_CATEGORY) && type.exists()) { try { getCategoryLabel(type, result); } catch (RubyModelException e) { // ignore } } // post qualification if (getFlag(flags, RubyElementLabels.T_POST_QUALIFIED)) { int offset= result.length(); result.append(RubyElementLabels.CONCAT_STRING); IType declaringType= type.getDeclaringType(); if (declaringType != null) { getTypeLabel(declaringType, RubyElementLabels.T_NAME_FULLY_QUALIFIED | (flags & QUALIFIER_FLAGS), result); int parentType= type.getParent().getElementType(); if (parentType == IRubyElement.METHOD || parentType == IRubyElement.FIELD) { // anonymous or local result.append('.'); getElementLabel(type.getParent(), 0, result); } ISourceFolder folder = type.getSourceFolder(); if (!folder.isDefaultPackage()) { getSourceFolderLabel(folder, flags & QUALIFIER_FLAGS, result); result.append('/'); } getCompilationUnitLabel(type.getRubyScript(), (flags & QUALIFIER_FLAGS), result); try { int other = type.getNameRange().getOffset(); result.append(", offset: " + other); } catch (RubyModelException e) { RubyPlugin.log(e); } } else { getSourceFolderLabel(type.getSourceFolder(), flags & QUALIFIER_FLAGS, result); } if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } /** * Appends the label for a import container, import or package declaration to a {@link ColoredString}. Considers the D_* flags. * @param declaration The element to render. * @param flags The rendering flags. Flags with names starting with 'D_' are considered. * @param result The buffer to append the resulting label to. */ public static void getDeclarationLabel(IRubyElement declaration, long flags, ColoredString result) { if (getFlag(flags, RubyElementLabels.D_QUALIFIED)) { IRubyElement openable= (IRubyElement) declaration.getOpenable(); if (openable != null) { result.append(getElementLabel(openable, RubyElementLabels.CF_QUALIFIED | RubyElementLabels.CU_QUALIFIED | (flags & QUALIFIER_FLAGS))); result.append('/'); } } if (declaration.getElementType() == IRubyElement.IMPORT_CONTAINER) { result.append(RubyUIMessages.RubyElementLabels_import_container); } else { result.append(declaration.getElementName()); } // post qualification if (getFlag(flags, RubyElementLabels.D_POST_QUALIFIED)) { int offset= result.length(); IRubyElement openable= (IRubyElement) declaration.getOpenable(); if (openable != null) { result.append(RubyElementLabels.CONCAT_STRING); result.append(getElementLabel(openable, RubyElementLabels.CF_QUALIFIED | RubyElementLabels.CU_QUALIFIED | (flags & QUALIFIER_FLAGS))); } if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } /** * Appends the label for a compilation unit to a {@link ColoredString}. Considers the CU_* flags. * @param cu The element to render. * @param flags The rendering flags. Flags with names starting with 'CU_' are considered. * @param result The buffer to append the resulting label to. */ public static void getCompilationUnitLabel(IRubyScript cu, long flags, ColoredString result) { if (getFlag(flags, RubyElementLabels.CU_QUALIFIED)) { ISourceFolder pack= (ISourceFolder) cu.getParent(); if (!pack.isDefaultPackage()) { getSourceFolderLabel(pack, (flags & QUALIFIER_FLAGS), result); result.append('.'); } } result.append(cu.getElementName()); if (getFlag(flags, RubyElementLabels.CU_POST_QUALIFIED)) { int offset= result.length(); result.append(RubyElementLabels.CONCAT_STRING); getSourceFolderLabel((ISourceFolder) cu.getParent(), flags & QUALIFIER_FLAGS, result); if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } /** * Appends the label for a package fragment to a {@link ColoredString}. Considers the P_* flags. * @param pack The element to render. * @param flags The rendering flags. Flags with names starting with P_' are considered. * @param result The buffer to append the resulting label to. */ public static void getSourceFolderLabel(ISourceFolder pack, long flags, ColoredString result) { if (getFlag(flags, RubyElementLabels.P_QUALIFIED)) { getSourceFolderRootLabel((ISourceFolderRoot) pack.getParent(), RubyElementLabels.ROOT_QUALIFIED, result); result.append('/'); } if (pack.isDefaultPackage()) { result.append(RubyElementLabels.DEFAULT_PACKAGE); } else if (getFlag(flags, RubyElementLabels.P_COMPRESSED)) { StringBuffer buf= new StringBuffer(); RubyElementLabels.getSourceFolderLabel(pack, RubyElementLabels.P_COMPRESSED, buf); result.append(buf.toString()); } else { result.append(pack.getElementName()); } if (getFlag(flags, RubyElementLabels.P_POST_QUALIFIED)) { int offset= result.length(); result.append(RubyElementLabels.CONCAT_STRING); getSourceFolderRootLabel((ISourceFolderRoot) pack.getParent(), RubyElementLabels.ROOT_QUALIFIED, result); if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } /** * Appends the label for a package fragment root to a {@link ColoredString}. Considers the ROOT_* flags. * @param root The element to render. * @param flags The rendering flags. Flags with names starting with ROOT_' are considered. * @param result The buffer to append the resulting label to. */ public static void getSourceFolderRootLabel(ISourceFolderRoot root, long flags, ColoredString result) { if (root.isArchive()) getArchiveLabel(root, flags, result); else getFolderLabel(root, flags, result); } private static void getArchiveLabel(ISourceFolderRoot root, long flags, ColoredString result) { // Handle variables different if (getFlag(flags, RubyElementLabels.ROOT_VARIABLE) && getVariableLabel(root, flags, result)) return; boolean external= root.isExternal(); if (external) getExternalArchiveLabel(root, flags, result); else getInternalArchiveLabel(root, flags, result); } private static boolean getVariableLabel(ISourceFolderRoot root, long flags, ColoredString result) { try { ILoadpathEntry rawEntry= root.getRawLoadpathEntry(); if (rawEntry != null && rawEntry.getEntryKind() == ILoadpathEntry.CPE_VARIABLE) { IPath path= rawEntry.getPath().makeRelative(); int offset= result.length(); if (getFlag(flags, RubyElementLabels.REFERENCED_ROOT_POST_QUALIFIED)) { int segements= path.segmentCount(); if (segements > 0) { result.append(path.segment(segements - 1)); if (segements > 1) { result.append(RubyElementLabels.CONCAT_STRING); result.append(path.removeLastSegments(1).toOSString()); } } else { result.append(path.toString()); } } else { result.append(path.toString()); } result.append(RubyElementLabels.CONCAT_STRING); if (root.isExternal()) result.append(root.getPath().toOSString()); else result.append(root.getPath().makeRelative().toString()); if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } return true; } } catch (RubyModelException e) { RubyPlugin.log(e); // problems with class path } return false; } private static void getExternalArchiveLabel(ISourceFolderRoot root, long flags, ColoredString result) { IPath path= root.getPath(); if (getFlag(flags, RubyElementLabels.REFERENCED_ROOT_POST_QUALIFIED)) { int segements= path.segmentCount(); if (segements > 0) { result.append(path.segment(segements - 1)); int offset= result.length(); if (segements > 1 || path.getDevice() != null) { result.append(RubyElementLabels.CONCAT_STRING); result.append(path.removeLastSegments(1).toOSString()); } if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } else { result.append(path.toOSString()); } } else { result.append(path.toOSString()); } } private static void getInternalArchiveLabel(ISourceFolderRoot root, long flags, ColoredString result) { IResource resource= root.getResource(); boolean rootQualified= getFlag(flags, RubyElementLabels.ROOT_QUALIFIED); boolean referencedQualified= getFlag(flags, RubyElementLabels.REFERENCED_ROOT_POST_QUALIFIED) && isReferenced(root); if (rootQualified) { result.append(root.getPath().makeRelative().toString()); } else { result.append(root.getElementName()); int offset= result.length(); if (referencedQualified) { result.append(RubyElementLabels.CONCAT_STRING); result.append(resource.getParent().getFullPath().makeRelative().toString()); } else if (getFlag(flags, RubyElementLabels.ROOT_POST_QUALIFIED)) { result.append(RubyElementLabels.CONCAT_STRING); result.append(root.getParent().getPath().makeRelative().toString()); } else { return; } if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } private static void getFolderLabel(ISourceFolderRoot root, long flags, ColoredString result) { IResource resource= root.getResource(); boolean rootQualified= getFlag(flags, RubyElementLabels.ROOT_QUALIFIED); boolean referencedQualified= getFlag(flags, RubyElementLabels.REFERENCED_ROOT_POST_QUALIFIED) && isReferenced(root); if (rootQualified) { result.append(root.getPath().makeRelative().toString()); } else { if (resource != null) { IPath projectRelativePath= resource.getProjectRelativePath(); if (projectRelativePath.segmentCount() == 0) { result.append(resource.getName()); referencedQualified= false; } else { result.append(projectRelativePath.toString()); } } else result.append(root.getElementName()); int offset= result.length(); if (referencedQualified) { result.append(RubyElementLabels.CONCAT_STRING); result.append(resource.getProject().getName()); } else if (getFlag(flags, RubyElementLabels.ROOT_POST_QUALIFIED)) { result.append(RubyElementLabels.CONCAT_STRING); result.append(root.getParent().getElementName()); } else { return; } if (getFlag(flags, COLORIZE)) { result.colorize(offset, result.length() - offset, QUALIFIER_STYLE); } } } /** * @param root * @return <code>true</code> if the given package fragment root is * referenced. This means it is owned by a different project but is referenced * by the root's parent. Returns <code>false</code> if the given root * doesn't have an underlying resource. */ private static boolean isReferenced(ISourceFolderRoot root) { IResource resource= root.getResource(); if (resource != null) { IProject jarProject= resource.getProject(); IProject container= root.getRubyProject().getProject(); return !container.equals(jarProject); } return false; } /** * Returns the label of a classpath container * @param containerPath The path of the container. * @param project The project the container is resolved in. * @return Returns the label of the classpath container */ public static ColoredString getContainerEntryLabel(IPath containerPath, IRubyProject project) { try { ILoadpathContainer container= RubyCore.getLoadpathContainer(containerPath, project); String description= null; if (container != null) { description= container.getDescription(); } if (description == null) { LoadpathContainerInitializer initializer= RubyCore.getLoadpathContainerInitializer(containerPath.segment(0)); if (initializer != null) { description= initializer.getDescription(containerPath, project); } } if (description != null) { ColoredString str= new ColoredString(description); if (containerPath.segmentCount() > 0 && RubyRuntime.RUBY_CONTAINER.equals(containerPath.segment(0))) { int index= description.indexOf('['); if (index != -1) { str.colorize(index, description.length() - index, DECORATIONS_STYLE); } } return str; } } catch (RubyModelException e) { // ignore } return new ColoredString(containerPath.toString()); } public static ColoredString decorateColoredString(ColoredString string, String decorated, Style color) { String label= string.getString(); int originalStart= decorated.indexOf(label); if (originalStart == -1) { return new ColoredString(decorated); // the decorator did something wild } if (originalStart > 0) { ColoredString newString= new ColoredString(decorated.substring(0, originalStart), color); newString.append(string); string= newString; } if (decorated.length() > originalStart + label.length()) { // decorator appended something return string.append(decorated.substring(originalStart + label.length()), color); } return string; // no change } }