@@ -0,0 +1,488 @@
/**
* Aptana Studio
* Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.editor.css.internal.text;

import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.aptana.core.IFilter;
import com.aptana.core.IMap;
import com.aptana.core.util.CollectionsUtil;
import com.aptana.core.util.FileUtil;
import com.aptana.core.util.StringUtil;
import com.aptana.core.util.URIUtil;
import com.aptana.editor.common.hover.TagStripperAndTypeBolder;
import com.aptana.editor.css.contentassist.model.ElementElement;
import com.aptana.editor.css.contentassist.model.ICSSMetadataElement;
import com.aptana.editor.css.contentassist.model.PropertyElement;
import com.aptana.editor.css.contentassist.model.PseudoClassElement;
import com.aptana.editor.css.contentassist.model.PseudoElementElement;
import com.aptana.editor.css.contentassist.model.SpecificationElement;
import com.aptana.editor.css.contentassist.model.UserAgentElement;

/**
* @author cwilliams
*/
public class CSSModelFormatter
{
/**
* This is intentionally NOT <br />
* because the Additional Info popup doesn't handle that properly right now.
*/
private static final String HTML_NEWLINE = "<br>"; //$NON-NLS-1$

/**
* For text hovers
*/
public static final CSSModelFormatter TEXT_HOVER = new CSSModelFormatter(true, Section.SIGNATURE,
Section.DESCRIPTION, Section.PLATFORMS, Section.REMARK, Section.EXAMPLE, Section.SPECIFICATIONS);

/**
* Used by UI label providers.
*/
public static final CSSModelFormatter LABEL = new CSSModelFormatter(false, Section.SIGNATURE);

/**
* For "additional info" popup from highlighted CA item.
*/
public static final CSSModelFormatter ADDITIONAL_INFO = new CSSModelFormatter(true, Section.SIGNATURE,
Section.DESCRIPTION, Section.PLATFORMS);

/**
* The list of sections to display in our output.
*/
private List<Section> fSections;

/**
* Use HTML tags in the output?
*/
private boolean useHTML;

private CSSModelFormatter(boolean useHTML, Section... sectionsToDisplay)
{
this.fSections = Arrays.asList(sectionsToDisplay);
this.useHTML = useHTML;
for (Section s : fSections)
{
s.useHTML = useHTML;
}
}

/**
* getDocumentDisplayName
*
* @param document
* @return
*/
public static String getDocumentDisplayName(String document)
{
String result = null;

if (document != null)
{
int index = document.lastIndexOf('/');

if (index != -1)
{
result = document.substring(index + 1);
}
else
{
result = document;
}

result = URIUtil.decodeURI(result);
}

return result;
}

/**
* Returns just the header, typically the signature plus optionally the locations
*
* @param element
* @return
*/
public String getHeader(ICSSMetadataElement element)
{
return getHeader(CollectionsUtil.newList(element));
}

/**
* Returns just the header, typically the signature plus optionally the locations
*
* @param elements
* @return
*/
public String getHeader(final Collection<ICSSMetadataElement> elements)
{
if (CollectionsUtil.isEmpty(elements))
{
return StringUtil.EMPTY;
}

List<String> stringParts = new ArrayList<String>(fSections.size() + 2);
if (useHTML)
{
stringParts.add(TagStripperAndTypeBolder.BOLD_OPEN_TAG);
}

List<Section> headerSections = CollectionsUtil.filter(fSections, new IFilter<Section>()
{
public boolean include(Section item)
{
return item.isHeader();
}
});
stringParts.addAll(CollectionsUtil.map(headerSections, new IMap<Section, String>()
{
public String map(Section s)
{
return s.generate(elements, null);
}
}));

if (useHTML)
{
stringParts.add(TagStripperAndTypeBolder.BOLD_CLOSE_TAG);
}
return StringUtil.concat(stringParts);
}

/**
* Returns just the documentation body.
*
* @param element
* @return
*/
public String getDocumentation(ICSSMetadataElement element)
{
return getDocumentation(CollectionsUtil.newList(element));
}

/**
* Returns just the documentation body.
*
* @param elements
* @return
*/
public String getDocumentation(final Collection<ICSSMetadataElement> elements)
{
List<Section> docSections = CollectionsUtil.filter(fSections, new IFilter<Section>()
{
public boolean include(Section s)
{
return !s.isHeader();
}
});

List<String> sectionStrings = CollectionsUtil.map(docSections, new IMap<Section, String>()
{
public String map(Section s)
{
return s.generate(elements, null);
}
});
return StringUtil.concat(sectionStrings);
}

protected String newline()
{
return useHTML ? HTML_NEWLINE : FileUtil.NEW_LINE;
}

private abstract static class Section
{
protected boolean useHTML;
private String name;

private Section(String name)
{
this.name = name;
}

public boolean isHeader()
{
return false;
}

private String newline()
{
return useHTML ? HTML_NEWLINE : FileUtil.NEW_LINE;
}

protected String addSection(String title, String value)
{
StringBuilder builder = new StringBuilder();
if (!StringUtil.isEmpty(value))
{
builder.append(newline()).append(newline());
if (useHTML)
{
builder.append(TagStripperAndTypeBolder.BOLD_OPEN_TAG);
}
builder.append(title);
if (useHTML)
{
builder.append(TagStripperAndTypeBolder.BOLD_CLOSE_TAG);
}
builder.append(newline());
builder.append(value.trim());
}
return builder.toString();
}

@Override
public String toString()
{
return name;
}

public abstract String generate(Collection<ICSSMetadataElement> properties, URI root);

/**
* Name, signature
*/
final static Section SIGNATURE = new Section("SIGNATURE") //$NON-NLS-1$
{
public boolean isHeader()
{
return true;
}

public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
ICSSMetadataElement element = elements.iterator().next();
return element.getName();
}
};

/**
* Single example
*/
final static Section EXAMPLE = new Section("EXAMPLE") //$NON-NLS-1$
{
@Override
public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
String example = getFirstExample(elements);
return addSection(Messages.CSSModelFormatter_ExampleSection, example);
}

private String getFirstExample(Collection<ICSSMetadataElement> elements)
{
for (ICSSMetadataElement element : elements)
{
String example = element.getExample();
if (!StringUtil.isEmpty(example))
{
return example;
}
}
return StringUtil.EMPTY;
}
};

/**
* Multiple examples
*/
final static Section EXAMPLES = new Section("EXAMPLES") //$NON-NLS-1$
{
@Override
public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
List<String> examples = new ArrayList<String>(elements.size());
for (ICSSMetadataElement element : elements)
{
examples.add(element.getExample());
}
examples = CollectionsUtil.filter(examples, new IFilter<String>()
{
public boolean include(String item)
{
return !StringUtil.isEmpty(item);
}
});
if (examples.size() == 1)
{
return addSection(Messages.CSSModelFormatter_ExampleSection, examples.get(0));
}

List<String> builder = new ArrayList<String>();
for (int i = 0; i < examples.size(); i++)
{
builder.add(addSection(MessageFormat.format(Messages.CSSModelFormatter_Example_Number, i + 1),
examples.get(i)));
}
return StringUtil.concat(builder);
}
};

/**
* Single remark
*/
final static Section REMARK = new Section("REMARK") //$NON-NLS-1$
{
@Override
public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
String remark = getFirstRemark(elements);
if (remark == null)
{
return StringUtil.EMPTY;
}
return addSection(Messages.CSSModelFormatter_RemarksSection, remark);
}

private String getFirstRemark(Collection<ICSSMetadataElement> elements)
{
for (ICSSMetadataElement element : elements)
{
String remark = null;
if (element instanceof ElementElement)
{
remark = ((ElementElement) element).getRemark();
}
else if (element instanceof PropertyElement)
{
remark = ((PropertyElement) element).getRemark();
}
if (!StringUtil.isEmpty(remark))
{
return remark;
}
}
return StringUtil.EMPTY;
}
};

/**
* Defining specs
*/
final static Section SPECIFICATIONS = new Section("SPECIFICATIONS") //$NON-NLS-1$
{
@Override
public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
Set<SpecificationElement> specs = new HashSet<SpecificationElement>();
for (ICSSMetadataElement element : elements)
{
if (element instanceof PropertyElement)
{
specs.addAll(((PropertyElement) element).getSpecifications());
}
else if (element instanceof PseudoClassElement)
{
specs.addAll(((PseudoClassElement) element).getSpecifications());
}
else if (element instanceof PseudoElementElement)
{
specs.addAll(((PseudoElementElement) element).getSpecifications());
}
}
if (CollectionsUtil.isEmpty(specs))
{
return StringUtil.EMPTY;
}
return addSection(Messages.CSSModelFormatter_SpecificationSection, getSpecificationsString(specs));
}

private String getSpecificationsString(Collection<SpecificationElement> specs)
{
List<String> strings = CollectionsUtil.map(specs, new IMap<SpecificationElement, String>()
{
public String map(SpecificationElement item)
{
StringBuilder b = new StringBuilder();
b.append(item.getName());
String version = item.getVersion();
if (!StringUtil.isEmpty(version))
{
b.append(": ").append(version); //$NON-NLS-1$
}
return b.toString();
}
});
return StringUtil.join(", ", strings); //$NON-NLS-1$
}
};

/**
*
*/
final static Section DESCRIPTION = new Section("DESCRIPTION") //$NON-NLS-1$
{
private TagStripperAndTypeBolder stripAndBold = new TagStripperAndTypeBolder();

@Override
public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
Set<String> descriptions = new HashSet<String>();
for (ICSSMetadataElement element : elements)
{
// strip p elements and bold any items that look like open tags with dotted local names
stripAndBold.setUseHTML(useHTML);
String desc = stripAndBold.searchAndReplace(element.getDescription());

if (!StringUtil.isEmpty(desc))
{
descriptions.add(desc);
}
}

if (CollectionsUtil.isEmpty(descriptions))
{
return Messages.CSSModelFormatter_NoDescription;
}
return StringUtil.join(", ", descriptions); //$NON-NLS-1$
}
};

/**
* User agents and versions
*/
final static Section PLATFORMS = new Section("PLATFORMS") //$NON-NLS-1$
{
@Override
public String generate(Collection<ICSSMetadataElement> elements, URI root)
{
Set<UserAgentElement> userAgents = new HashSet<UserAgentElement>();
for (ICSSMetadataElement element : elements)
{
userAgents.addAll(element.getUserAgents());
}
return addSection(Messages.CSSModelFormatter_SupportedPlatforms, getPlatforms(userAgents));
}

private String getPlatforms(Collection<UserAgentElement> userAgents)
{
List<String> strings = CollectionsUtil.map(userAgents, new IMap<UserAgentElement, String>()
{
public String map(UserAgentElement item)
{
StringBuilder b = new StringBuilder();
b.append(item.getPlatform());
String version = item.getVersion();
if (!StringUtil.isEmpty(version))
{
b.append(": ").append(version); //$NON-NLS-1$
}
return b.toString();
}
});
return StringUtil.join(", ", strings); //$NON-NLS-1$
}
};
}
}
@@ -0,0 +1,33 @@
/**
* Aptana Studio
* Copyright (c) 2005-2012 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.editor.css.internal.text;

import org.eclipse.osgi.util.NLS;

public class Messages extends NLS
{
private static final String BUNDLE_NAME = "com.aptana.editor.css.internal.text.messages"; //$NON-NLS-1$

public static String CSSModelFormatter_Example_Number;

public static String CSSModelFormatter_ExampleSection;
public static String CSSModelFormatter_NoDescription;
public static String CSSModelFormatter_RemarksSection;
public static String CSSModelFormatter_SpecificationSection;
public static String CSSModelFormatter_SupportedPlatforms;

static
{
// initialize resource bundle
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
}

private Messages()
{
}
}
@@ -0,0 +1,6 @@
CSSModelFormatter_Example_Number=Example {0}
CSSModelFormatter_ExampleSection=Example
CSSModelFormatter_NoDescription=No description available.
CSSModelFormatter_RemarksSection=Remarks
CSSModelFormatter_SpecificationSection=Specification
CSSModelFormatter_SupportedPlatforms=Supported Platforms
@@ -30,6 +30,7 @@
import com.aptana.editor.css.contentassist.CSSIndexQueryHelper;
import com.aptana.editor.css.contentassist.model.ElementElement;
import com.aptana.editor.css.contentassist.model.PropertyElement;
import com.aptana.editor.css.internal.text.CSSModelFormatter;
import com.aptana.editor.css.parsing.ast.CSSDeclarationNode;
import com.aptana.editor.css.parsing.ast.CSSFunctionNode;
import com.aptana.editor.css.parsing.ast.CSSNode;
@@ -60,6 +61,8 @@ public class CSSTextHover extends CommonTextHover implements ITextHover, ITextHo

private Object info;

private String fHeader;

/*
* (non-Javadoc)
* @see com.aptana.editor.common.hover.AbstractDocumentationHover#getHeader(java.lang.Object,
@@ -72,7 +75,7 @@ public String getHeader(Object element, IEditorPart editorPart, IRegion hoverReg
{
return Messages.CSSTextHover_cssColorHeaderText;
}
return null;
return fHeader;
}

/*
@@ -85,7 +88,7 @@ public String getDocumentation(Object element, IEditorPart editorPart, IRegion h
{
if (info instanceof String)
{
return info.toString();
return (String) info;
}
else if (info instanceof RGB)
{
@@ -176,6 +179,8 @@ public IRegion getHoverRegion(ITextViewer textViewer, int offset)
{
// assume no hover region
IRegion result = null;
info = null;
fHeader = null;

// grab document's parse model
IParseNode ast = getAST(textViewer, offset);
@@ -250,7 +255,9 @@ else if (parent instanceof CSSFunctionNode)
if (property != null)
{
result = new Region(cssNode.getStartingOffset(), propertyName.length());
info = property.getDescription();

fHeader = CSSModelFormatter.TEXT_HOVER.getHeader(property);
info = CSSModelFormatter.TEXT_HOVER.getDocumentation(property);
}
}
break;