/******************************************************************************* * Copyright (c) 2007, 2017 Alphonse Van Assche 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: * Alphonse Van Assche - initial API and implementation *******************************************************************************/ package org.eclipse.linuxtools.internal.rpm.ui.editor; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.linuxtools.rpm.ui.editor.parser.Specfile; import org.eclipse.linuxtools.rpm.ui.editor.parser.SpecfileDefine; public class RpmMacroOccurrencesUpdater implements ISelectionChangedListener { private static final String ANNOTATION_TYPE = Activator.PLUGIN_ID + ".highlightannotation"; //$NON-NLS-1$ private final SpecfileEditor fEditor; private final List<Annotation> fOldAnnotations = new LinkedList<>(); /** * Creates a new instance on editor <code>specfileEditor</code>. * * @param specfileEditor * The editor to mark occurrences on. */ public RpmMacroOccurrencesUpdater(SpecfileEditor specfileEditor) { ((IPostSelectionProvider) specfileEditor.getSelectionProvider()).addPostSelectionChangedListener(this); fEditor = specfileEditor; } @Override public void selectionChanged(SelectionChangedEvent event) { update((ISourceViewer) event.getSource()); } /** * Updates the drawn annotations. * * @param viewer * The viewer to get the document and annotation model from */ public void update(ISourceViewer viewer) { try { IDocument document = viewer.getDocument(); IAnnotationModel model = viewer.getAnnotationModel(); if (document == null || model == null) { return; } removeOldAnnotations(model); String currentSelectedWord = getWordAtSelection(fEditor.getSelectionProvider().getSelection(), document); if (isMacro(currentSelectedWord)) { Specfile spec = fEditor.getSpecfile(); SpecfileDefine define = spec.getDefine(currentSelectedWord); String word = currentSelectedWord + ": "; //$NON-NLS-1$ if (define != null) { word += define.getStringValue(); } else { // If there's no such define we try to see if it corresponds // to // a Source or Patch declaration String retrivedValue = RPMUtils.getSourceOrPatchValue(spec, currentSelectedWord.toLowerCase()); if (retrivedValue != null) { word += retrivedValue; } else { // If it does not correspond to a Patch or Source macro, // try to find it // in the macro proposals list. retrivedValue = RPMUtils.getMacroValueFromMacroList(currentSelectedWord); if (retrivedValue != null) { word += retrivedValue; } } } createNewAnnotations(currentSelectedWord, word, document, model); } } catch (BadLocationException e) { SpecfileLog.logError(e); } } /** * Removes the previous set of annotations from the annotation model. * * @param model * the annotation model */ private void removeOldAnnotations(IAnnotationModel model) { for (Annotation annotation : fOldAnnotations) { model.removeAnnotation(annotation); } fOldAnnotations.clear(); } /** * Checks if <code>word</code> is an macro. * * @param word * the word to check * * @return <code>true</code> if <code>word</code> is an macro, * <code>false</code> otherwise */ private boolean isMacro(String word) { List<SpecfileDefine> defines = getMacros(); if (word.length() > 0) { for (SpecfileDefine define : defines) { if (containsWord(define, word)) { return true; } } if (Activator.getDefault().getRpmMacroList().getProposals("%" + word).size() > 0) {//$NON-NLS-1$ return true; } } return false; } /** * Retrieves the macros from the editor's specfile. * * @return the macros from the editor's specfile */ private List<SpecfileDefine> getMacros() { Specfile specfile = fEditor.getSpecfile(); if (specfile != null) { List<SpecfileDefine> macros = specfile.getDefines(); if (macros != null) { return macros; } } return new ArrayList<>(); } /** * Returns <code>true</code> if <code>macro</code> equals the word * <code>current</code>. * * @param macro * the <code>macro</code> to check * @param current * the word to look for * * @return <code>true</code> if <code>macro</code> contains the word * <code>current</code>,<code>false</code> if not */ private boolean containsWord(SpecfileDefine macro, String current) { return macro.getName().equalsIgnoreCase(current); } /** * Returns the word at the current selection / caret position. * * @param selection * the selection * @param document * the document * @return the currently selected text, or the word at the caret if the * selection has length 0 * @throws BadLocationException * if accessing the document fails */ private String getWordAtSelection(ISelection selection, IDocument document) throws BadLocationException { String word; if (selection instanceof ITextSelection) { ITextSelection ts = (ITextSelection) selection; int offset = ts.getOffset(); int end = offset + ts.getLength(); // non-empty selections if (end != offset) { word = ts.getText(); } else { while (offset > 0 && isDefineChar(document.getChar(offset - 1))) { offset--; } while (end < document.getLength() && isDefineChar(document.getChar(end))) { end++; } word = document.get(offset, end - offset); } } else { word = ""; //$NON-NLS-1$ } return word.toLowerCase(); } private boolean isDefineChar(char c) { return c != '{' && c != '}' && c != '?' && !Character.isWhitespace(c); } /** * Adds an annotation for every occurrence of <code>macro</code> in the * document. Also stores the created annotations in * <code>fOldAnnotations</code>. * * @param macro * the word to look for * @param document * the document * @param model * the annotation model */ private void createNewAnnotations(String macro, String hoverContent, IDocument document, IAnnotationModel model) { String content = document.get().toLowerCase(); int idx = content.indexOf(macro.toLowerCase()); while (idx != -1) { Annotation annotation = new Annotation(ANNOTATION_TYPE, false, hoverContent); Position position = new Position(idx, macro.length()); model.addAnnotation(annotation, position); fOldAnnotations.add(annotation); idx = content.indexOf(macro, idx + 1); } } public void dispose() { ((IPostSelectionProvider) fEditor.getSelectionProvider()).removePostSelectionChangedListener(this); } }