/****************************************************************************** * Copyright (c) 2011 GitHub Inc. * 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: * Kevin Sawicki (GitHub Inc.) - initial API and implementation *****************************************************************************/ package org.eclipse.egit.ui.internal.commit; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.TreeSet; import org.eclipse.core.runtime.Path; import org.eclipse.egit.ui.UIUtils; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.CompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.swt.graphics.Image; /** * Commit proposal processor */ public abstract class CommitProposalProcessor implements IContentAssistProcessor { /** * Replace all non single space whitespace characters with a single space * * @param value * @return replaced string */ private static final String escapeWhitespace(String value) { final StringBuilder escaped = new StringBuilder(value); final int length = escaped.length(); for (int i = 0; i < length; i++) { char c = escaped.charAt(i); if (c != ' ' && Character.isWhitespace(c)) escaped.setCharAt(i, ' '); } return escaped.toString(); } private static final ICompletionProposal[] NO_PROPOSALS = new ICompletionProposal[0]; private final LocalResourceManager resourceManager = new LocalResourceManager( JFaceResources.getResources()); private class CommitFile implements Comparable<CommitFile> { private final String display; private final String full; public CommitFile(String display, String full) { this.display = display; this.full = full; } public boolean matches(String prefix) { return display.toLowerCase(Locale.US).startsWith(prefix); } @Override public int compareTo(CommitFile other) { return display.compareTo(other.display); } @Override public int hashCode() { return display.hashCode(); } @Override public boolean equals(Object other) { if (!(other instanceof CommitFile)) return false; return (this.compareTo((CommitFile) other) == 0); } public Image getImage() { return (Image) resourceManager.get(UIUtils.getEditorImage(full)); } public ICompletionProposal createProposal(int offset, int length) { return new CompletionProposal(display, offset, length, display.length(), getImage(), display, null, null); } } /** * Dispose of processor */ public void dispose() { resourceManager.dispose(); } private String getPrefix(ITextViewer viewer, int offset) throws BadLocationException { IDocument doc = viewer.getDocument(); if (doc == null || offset > doc.getLength()) return null; int length = 0; int start = offset - 1; while (start >= 0 && !Character.isWhitespace(doc.getChar(start))) { start--; length++; } return doc.get(start + 1, length); } @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { String prefix; try { prefix = getPrefix(viewer, offset); } catch (BadLocationException e) { return NO_PROPOSALS; } Collection<String> messages = computeMessageProposals(); Set<CommitFile> files = computeFileProposals(); List<ICompletionProposal> proposals = new ArrayList<>(); if (prefix != null && prefix.length() > 0) { int replacementLength = prefix.length(); int replacementOffset = offset - replacementLength; prefix = prefix.toLowerCase(Locale.US); for (CommitFile file : files) if (file.matches(prefix)) proposals.add(file.createProposal(replacementOffset, replacementLength)); for (String message : messages) if (message.startsWith(prefix)) proposals.add(new CompletionProposal(message, replacementOffset, replacementLength, message .length(), (Image) resourceManager .get(UIIcons.ELCL16_COMMENTS), escapeWhitespace(message), null, null)); } else { for (String message : messages) proposals.add(new CompletionProposal(message, offset, 0, message.length(), (Image) resourceManager .get(UIIcons.ELCL16_COMMENTS), escapeWhitespace(message), null, null)); for (CommitFile file : files) proposals.add(file.createProposal(offset, 0)); } return proposals.toArray(new ICompletionProposal[proposals.size()]); } /** * @return the file names which will be made available through content assist */ protected abstract Collection<String> computeFileNameProposals(); /** * @return the commit messages which will be made available through content assist */ protected abstract Collection<String> computeMessageProposals(); private Set<CommitFile> computeFileProposals() { Collection<String> paths = computeFileNameProposals(); Set<CommitFile> files = new TreeSet<>(); for (String path : paths) { String name = new Path(path).lastSegment(); if (name == null) continue; files.add(new CommitFile(name, name)); int lastDot = name.lastIndexOf('.'); if (lastDot > 0) files.add(new CommitFile(name.substring(0, lastDot), name)); } return files; } @Override public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { return null; } @Override public char[] getCompletionProposalAutoActivationCharacters() { return null; } @Override public char[] getContextInformationAutoActivationCharacters() { return null; } @Override public String getErrorMessage() { return null; } @Override public IContextInformationValidator getContextInformationValidator() { return null; } }