package ctagsinterface.main; import ise.plugin.nav.AutoJump; import java.io.File; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Vector; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.apache.lucene.document.Document; import org.apache.lucene.document.Fieldable; import org.gjt.sp.jedit.ActionSet; import org.gjt.sp.jedit.Buffer; import org.gjt.sp.jedit.EditBus; import org.gjt.sp.jedit.EditPlugin; import org.gjt.sp.jedit.View; import org.gjt.sp.jedit.jEdit; import org.gjt.sp.jedit.gui.DockableWindowManager; import org.gjt.sp.jedit.gui.StatusBar; import org.gjt.sp.jedit.io.VFSManager; import org.gjt.sp.jedit.msg.PositionChanging; import org.gjt.sp.jedit.textarea.JEditTextArea; import org.gjt.sp.util.Task; import org.gjt.sp.util.ThreadUtilities; import ctagsinterface.dockables.Progress; import ctagsinterface.dockables.TagList; import ctagsinterface.index.QueryDialog; import ctagsinterface.index.TagIndex; import ctagsinterface.index.TagIndex.DocHandler; import ctagsinterface.index.TagIndex.Origin; import ctagsinterface.index.TagIndex.OriginType; import ctagsinterface.jedit.BufferWatcher; import ctagsinterface.jedit.TagCompletion; import ctagsinterface.jedit.TagTooltip; import ctagsinterface.main.Parser.TagHandler; import ctagsinterface.options.ActionsOptionPane; import ctagsinterface.options.GeneralOptionPane; import ctagsinterface.options.ProjectsOptionPane; import ctagsinterface.projects.ProjectDependencies; import ctagsinterface.projects.ProjectWatcher; import org.gjt.sp.jedit.Macros; public class CtagsInterfacePlugin extends EditPlugin { private static final String MISC_ORIGIN_ID = "temp"; public static final String TAGS_UPDATED_BUFFER_PROP = "TagsUpdated"; private static final String DOCKABLE = "ctags-interface-tag-list"; static public final String OPTION = "options.CtagsInterface."; static public final String MESSAGE = "messages.CtagsInterface."; static public final String ACTION_SET = "Plugin: CtagsInterface - Actions"; private static final String PROGRESS = "ctags-interface-progress"; private static TagIndex index; private static BufferWatcher watcher; private static ProjectWatcher pvi; private static ActionSet actions; private static KindIconProvider iconProvider; private static Object bufferUpdateLock = new Object(); private static HashMap<String, Task> tasks = new HashMap<String, Task>(); private static LanguageMap langMap = new LanguageMap(); public void start() { index = new TagIndex(); watcher = new BufferWatcher(index); EditPlugin p = jEdit.getPlugin("projectviewer.ProjectPlugin",false); pvi = (p == null) ? null : new ProjectWatcher(); actions = new ActionSet(ACTION_SET); updateActions(); jEdit.addActionSet(actions); iconProvider = new KindIconProvider(); TagTooltip.start(); } public void stop() { TagTooltip.stop(); if (pvi != null) { pvi.stop(); pvi = null; } watcher.shutdown(); watcher = null; index.close(); index = null; } static public TagIndex getIndex() { return index; } static public ImageIcon getIcon(Tag tag) { return iconProvider.getIcon(tag); } static public Object getBufferUpdateLock() { return bufferUpdateLock; } static public Runner getRunner(Logger logger) { return new Runner(logger); } static public Parser getParser(Logger logger) { return new Parser(logger); } static public Logger getLogger(View view, String name) { Progress progress = getProgressDockable(view); return new Logger(name, progress); } static public LanguageMap getLangMap() { return langMap; } static public String getLanguage(View view) { LanguageMap langMap = getLangMap(); String language = langMap.getLanguage(view); return language; } static public void updateActions() { actions.removeAllActions(); QueryAction[] queries = ActionsOptionPane.loadActions(); for (int i = 0; i < queries.length; i++) actions.addAction(queries[i]); actions.initKeyBindings(); } static private class TagFileHandler implements TagHandler { private HashSet<String> files = new HashSet<String>(); private Origin origin; private String originsStr; private Boolean remove = false; public TagFileHandler(Origin origin) { this(origin, false); } public TagFileHandler(Origin origin, Boolean remove) { this.origin = origin; this.remove = remove; } public void processTag(Tag t) { String file = t.getFile(); if(remove) { index.deleteTag(t); return; } String originStr = this.origin != null ? origin.toString() : ""; if (! files.contains(file)) { // Add the new origin to the current list of origins, if not // yet included. originsStr = index.getOriginsOfFile(file); originsStr = index.appendOrigin(originsStr, originStr); index.deleteTagsFromSourceFile(file); files.add(file); } index.insertTag(t, originsStr); } } // Adds a temporary tag file to the DB // Existing tags from source files in the tag file are removed first. static public void addTagFile(View view, String tagFile) { if (tagFile == null || tagFile.length() == 0) return; if(!new File(tagFile).exists()) return; Logger logger = getLogger(view, "Adding tag file " + tagFile); Parser parser = getParser(logger); parser.parseTagFile(tagFile, new TagFileHandler( index.getOrigin(OriginType.TAGFILE, tagFile, true))); } // Action: Prompt for a temporary tag file to add to the DB static public void addTagFile(View view) { String tagFile = JOptionPane.showInputDialog("Tag file:"); addTagFile(view, tagFile); } // Action: Add temporary tag file to the DB static public void addTagFile(String tagFile) { addTagFile(jEdit.getActiveView(), tagFile); } // Remove tags from tag file from the DB static public void deleteTagsFromTagFile(View view, String tagFilePath) { if (tagFilePath == null || tagFilePath.length() == 0) return; if(!new File(tagFilePath).exists()) return; Logger logger = getLogger(view, "Removing tag file " + tagFilePath); index.deleteTagsOfOrigin(logger, index.getOrigin(OriginType.TAGFILE, tagFilePath, true)); } // Action: Add current file to the DB with origin static public void addCurrentFile(View view) { String path = view.getBuffer().getPath(); addFile(view, path); } static public void addFile(View view, String path) { addFile(view, path, index.getOrigin(TagIndex.OriginType.MISC, MISC_ORIGIN_ID, true)); } static public void addFile(View view, String path, Origin origin) { Logger logger = getLogger(view, "Adding source file " + path); TagFileHandler handler = new TagFileHandler(origin, false); tagSourceTree(logger, path, handler); } // Action: update current file in the DB, doesn't add origin static public void updateCurrentFile(View view) { String path = view.getBuffer().getPath(); updateFile(view, path); } static public void updateFile(View view, String path) { if (getIndex().getOriginsOfFile(path).isEmpty()) return; Logger logger = getLogger(view, "Adding source file " + path); TagFileHandler handler = new TagFileHandler(null, false); // null runs ctags with same origins tagSourceTree(logger, path, handler); } // Action: remove current file from the DB static public void deleteCurrentFile(View view) { String path = view.getBuffer().getPath(); deleteFile(view, path); } static public void deleteFile(View view, String path) { Logger logger = getLogger(view, "Removing source file " + path); index.deleteTagsFromSourceFileOfOrigin(logger, path); } static public void deleteAll(View view) { String msg = MESSAGE + "removeAll"; if( JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(view, jEdit.getProperty(msg), "Remove all tags", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE)) return; for(OriginType type : OriginType.values()) { Vector<String> origins = new Vector<String>(); getIndex().getOrigins(type, origins); for (int i = 0; i < origins.size(); i++) { String id = origins.get(i); Origin origin = getIndex().getOrigin(type, id, false); Logger logger = getLogger(view, "Deleting " + type.name + " " + id); deleteOrigin(logger, type, id, false); //getIndex().deleteTagsOfOrigin(logger, origin); //getIndex().deleteOriginOfOrigin(logger, origin); } } } public static void jumpToTags(final View view, List<Tag> tags) { if (tags == null || tags.size() == 0) { JOptionPane.showMessageDialog(view, "No tags found"); return; } if (tags.size() > 1) { view.getDockableWindowManager().showDockableWindow(DOCKABLE); JComponent c = view.getDockableWindowManager().getDockable(DOCKABLE); TagList tl = (TagList) c; tl.setTags(tags); return; } Tag t = tags.get(0); jumpToTag(view, t); } // Action: Add all projects to the database public static void tagAllProjects(View view) { if (pvi == null) { JOptionPane.showMessageDialog(view, "Project support disabled."); return; } Vector<String> allProjects = pvi.getProjects(); Vector<String> dbProjects = ProjectsOptionPane.getProjects(); for (int i = 0; i < allProjects.size(); i++) { String project = allProjects.get(i); Logger logger = getLogger(view, "Project " + project); if (! dbProjects.contains(project)) insertOrigin(logger, OriginType.PROJECT, project); } } // Action: Search for a tag containing a substring public static void searchTagBySubstring(final View view) { new QuickSearchTagDialog(view, QuickSearchTagDialog.Mode.SUBSTRING); } // Action: Search for a tag by prefix public static void searchTagByPrefix(final View view) { new QuickSearchTagDialog(view, QuickSearchTagDialog.Mode.PREFIX); } public static Vector<Tag> runScopedQuery(View view, String q, int maxResults) { if (GeneralOptionPane.getMatchLanguage()) q += " AND " + index.getLangNameQuery(getLanguage(view)); boolean projectScope = (pvi != null && (ProjectsOptionPane.getSearchActiveProjectOnly() || ProjectsOptionPane.getSearchActiveProjectFirst() || ProjectsOptionPane.getSearchActiveProjectAndDeps())); Vector<Tag> tags = new Vector<Tag>(); String project = projectScope ? pvi.getActiveProject(view) : null; if (project != null && projectScope) { Vector<Origin> origins = new Vector<Origin>(); if (ProjectsOptionPane.getSearchActiveProjectAndDeps()) { HashMap<String, Vector<String>> originsHash = ProjectDependencies.getDependencies(project); Vector<String> projects = originsHash.get(OriginType.PROJECT.name); if (projects == null) { projects = new Vector<String>(); originsHash.put(OriginType.PROJECT.name, projects); } projects.add(project); for (Entry<String, Vector<String>> origin: originsHash.entrySet()) { for (String s: origin.getValue()) { origins.add(index.getOrigin( OriginType.fromString(origin.getKey()), s, false)); } } index.runQueryInOrigins(q, origins, maxResults, tags); } else { origins.add(index.getOrigin(OriginType.PROJECT, project, false)); index.runQueryInOrigins(q, origins, maxResults, tags); if (ProjectsOptionPane.getSearchActiveProjectFirst() && tags.isEmpty()) { index.runQuery(q, maxResults, tags); } } } else index.runQuery(q, maxResults, tags); return tags; } public static Vector<Tag> runScopedQuery(View view, String q) { return runScopedQuery(view, q, TagIndex.MAX_RESULTS); } public static Vector<Tag> queryScopedTag(View view, String tag) { if (tag == null || tag.length() == 0) return null; String q = index.getTagNameQuery(tag); return runScopedQuery(view, q); } // Context menu action from FSB public static void addFromFSB(View view, String directory) { // FSB adds a trailing directory separator, that Ctags cannot handle if (directory.endsWith(File.separator)) directory = directory.substring(0, directory.length() - 1); Logger logger = CtagsInterfacePlugin.getLogger(jEdit.getActiveView(), OriginType.DIRECTORY.name + " " + directory); insertOrigin(logger, OriginType.DIRECTORY, directory); } // Action: Jump to the selected tag (or tag at caret). public static void jumpToTag(final View view) { String tag = getDestinationTag(view); if (tag == null || tag.length() == 0) { JOptionPane.showMessageDialog( view, "No tag selected nor identified at caret"); return; } Vector<Tag> tags = queryScopedTag(view, tag); if (tags == null) return; jumpToTags(view, tags); } // Actions: Offer code completion options from the DB public static void completeFromDb(final View view) { String prefix = getCompletionPrefix(view); if (prefix == null) return; TagCompletion.complete(view, prefix); } // Run a query on the database and display the results (for debugging) public static void runQuery(final View view) { String q = JOptionPane.showInputDialog("Enter query:"); if (q == null) return; System.err.println("-- Executing query: " + q); index.runQuery(q, 1000000, new DocHandler() { public void handle(Document doc) { String s = ""; for (Fieldable f: doc.getFields()) s += f.name() + ":" + f.stringValue() + " "; System.err.println(s); } }); } // Show a query dialog public static void showQueryDialog(final View view) { new QueryDialog(view); } // Returns the prefix for code completion public static String getCompletionPrefix(View view) { String tag = view.getTextArea().getSelectedText(); if (tag == null || tag.length() == 0) tag = getTagUpToCaret(view); return tag; } private static String getTagUpToCaret(View view) { JEditTextArea ta = view.getTextArea(); int line = ta.getCaretLine(); int index = ta.getCaretPosition() - ta.getLineStartOffset(line); String text = ta.getLineText(line); Pattern pat = Pattern.compile(GeneralOptionPane.getPattern()); Matcher m = pat.matcher(text); int end = -1; int start = -1; String selected = ""; while (end < index) { if (! m.find()) return null; end = m.end(); start = m.start(); selected = m.group(); } if (start > index || selected.length() == 0) return null; return selected.substring(0, selected.length() - (end - index)); } // Returns the tag to jump to: The selected tag or the one at the caret. static public String getDestinationTag(View view) { String tag = view.getTextArea().getSelectedText(); if (tag == null || tag.length() == 0) tag = getTagAtCaret(view); return tag; } // Returns the tag at the caret. static private String getTagAtCaret(View view) { JEditTextArea ta = view.getTextArea(); int line = ta.getCaretLine(); int index = ta.getCaretPosition() - ta.getLineStartOffset(line); return getTagAt(ta, line, index); } public static String getTagAt(JEditTextArea textArea, int line, int offsetInLine) { String text = textArea.getLineText(line); Pattern pat = Pattern.compile(GeneralOptionPane.getPattern()); Matcher m = pat.matcher(text); int end = -1; int start = -1; String selected = ""; while (end <= offsetInLine) { if (! m.find()) return null; end = m.end(); start = m.start(); selected = m.group(); } if (start > offsetInLine || selected.length() == 0) return null; return selected; } // Jumps to the specified location public static void jumpTo(final View view, final String file, final int line, final boolean sendAutoJumpStarted) { Runnable r = new Runnable() { public void run() { jumpToDirect(view, file, line, sendAutoJumpStarted); } }; if (SwingUtilities.isEventDispatchThread()) r.run(); else SwingUtilities.invokeLater(r); } private static void jumpToDirect(final View view, String file, final int line, boolean sendAutoJumpStarted) { final EditPlugin p = jEdit.getPlugin("ise.plugin.nav.NavigatorPlugin",false); if (sendAutoJumpStarted && (p != null)) AutoJumpSender.sendAutoJump(view, AutoJump.STARTED); Buffer b = view.getBuffer(); if (b == null || (! b.getPath().equals(file)) || (view.getTextArea().getCaretLine() != line - 1)) { EditBus.send(new PositionChanging(view.getEditPane())); } Buffer buffer = jEdit.openFile(view, file); if (buffer == null) { System.err.println("Unable to open: " + file); return; } VFSManager.runInAWTThread(new Runnable() { public void run() { try { view.getTextArea().setCaretPosition( view.getTextArea().getLineStartOffset(line - 1)); if (p != null) AutoJumpSender.sendAutoJump(view, AutoJump.ENDED); } catch (Exception e) { e.printStackTrace(); } } }); } // Jumps to the specified tag public static void jumpToTag(final View view, final Tag tag) { String file = tag.getFile(); if (file == null) return; int line = tag.getLine(); if (line < 1) return; final String path = tag.getFile(); // If "update on load" is used, the file tags will be updated when // the buffer is loaded, so delay the jump. Otherwise, jump now. if ((! GeneralOptionPane.getUpdateOnLoad()) || (jEdit.getBuffer(path) != null)) { jumpTo(view, path, tag.getLine(), true); return; } // Send the "AutoJump.STARTED" event before opening the new buffer final EditPlugin p = jEdit.getPlugin("ise.plugin.nav.NavigatorPlugin",false); if (p != null) AutoJumpSender.sendAutoJump(view, AutoJump.STARTED); final long time = System.currentTimeMillis(); final Buffer buffer = jEdit.openFile(view, path); if (buffer == null) { System.err.println("Unable to open: " + path); return; } Runnable delayJump = new Runnable() { public void run() { synchronized(bufferUpdateLock) { while (true) { Long l = (Long) buffer.getProperty(TAGS_UPDATED_BUFFER_PROP); if ((l != null) && (l.longValue() > time)) break; try { bufferUpdateLock.wait(); } catch (InterruptedException e) {} } } Tag updatedTag = getUpdatedTag(tag); jumpTo(view, path, updatedTag.getLine(), false); } }; Thread bgTask = new Thread(delayJump); bgTask.start(); } private static Tag getUpdatedTag(Tag tag) { Vector<Tag> tags = new Vector<Tag>(); index.getIdenticalTags(tag, tags); if ((tags == null) || tags.isEmpty()) return tag; return tags.get(0); } // Jumps to the specified location public static void jumpToOffset(final View view, String file, final int offset) { final EditPlugin p = jEdit.getPlugin("ise.plugin.nav.NavigatorPlugin",false); if (p != null) AutoJumpSender.sendAutoJump(view, AutoJump.STARTED); Buffer buffer = jEdit.openFile(view, file); if (buffer == null) { System.err.println("Unable to open: " + file); return; } VFSManager.runInAWTThread(new Runnable() { public void run() { view.getTextArea().setCaretPosition(offset); if (p != null) AutoJumpSender.sendAutoJump(view, AutoJump.ENDED); } }); } // Updates the given origins in the DB static public void updateOrigins(OriginType type, Vector<String> names) { // Remove obsolete origins Vector<String> current = new Vector<String>(); index.getOrigins(type, current); View view = jEdit.getActiveView(); for (int i = 0; i < current.size(); i++) { String name = current.get(i); if (! names.contains(name)) { Logger logger = getLogger(view, "Delete " + type.name + " " + name); deleteOrigin(logger, type, name); } } // Add new origins for (int i = 0; i < names.size(); i++) { String name = names.get(i); if (! current.contains(name)) { Logger logger = getLogger(view, type.name + " " + name); insertOrigin(logger, type, name); } } } // Refreshes the given origin in the DB static public void refreshOrigin(OriginType type, String name) { Origin origin = index.getOrigin(type, name, false); View view = jEdit.getActiveView(); Logger logger = getLogger(view, name); index.deleteTagsOfOrigin(logger, origin); tagOrigin(logger, origin); } public static void deleteOrigin(OriginType type, String name) { View view = jEdit.getActiveView(); Logger logger = getLogger(view, "Delete " + type.name + " " + name); deleteOrigin(logger, type, name); } // Deletes an origin with all associated data from the DB public static void deleteOrigin(final Logger logger, final OriginType type, final String name) { deleteOrigin(logger, type, name, true); } // Deletes an origin with all associated data from the DB public static void deleteOrigin(final Logger logger, final OriginType type, final String name, Boolean threaded) { if (!threaded) { deleteOrigin(logger, index.getOrigin(type, name, false)); return; } addWorkRequest(name, new Task() { public void _run() { deleteOrigin(logger, index.getOrigin(type, name, false)); } }); } private static void deleteOrigin(final Logger logger, final Origin origin) { index.deleteOrigin(logger, origin); if (pvi != null && origin.type == OriginType.PROJECT) pvi.updateWatchers(); } // Inserts a new origin to the DB, runs Ctags on it and adds the tags // to the DB. public static void insertOrigin(Logger logger, OriginType type, String name) { Origin origin = index.getOrigin(type, name, true); tagOrigin(logger, origin); if (pvi != null && type == OriginType.PROJECT) pvi.updateWatchers(); } // Runs Ctags on the specified origin and adds the tags to the DB. private static void tagOrigin(Logger logger, Origin origin) { TagFileHandler handler = new TagFileHandler(origin); switch (origin.type) { case PROJECT: tagProject(logger, origin.id, handler); break; case DIRECTORY: tagSourceTree(logger, origin.id, handler); break; case ARCHIVE: tagArchive(logger, origin.id, handler); break; } } private static void addWorkRequest(String originId, Task task) { Task current = tasks.get(originId); if (current != null) current.cancel(); tasks.put(originId, task); ThreadUtilities.runInBackground(task); } private static Progress getProgressDockable(View view) { DockableWindowManager dwm = view.getDockableWindowManager(); if (GeneralOptionPane.getShowProgress()) { dwm.showDockableWindow(PROGRESS); } Progress win = (Progress) dwm.getDockable(PROGRESS); /* * This is horrible. But getDockable() returns null if the * dockable window is not instantiated yet, even if it * really exists. */ if (win == null) { win = new Progress(view); } return win; } private static StatusBar getStatusBar() { View v = jEdit.getActiveView(); if (v == null) return null; return v.getStatus(); } private static void setStatusMessage(String msg) { StatusBar s = getStatusBar(); if (s == null) return; s.setMessage(msg); } private static void removeStatusMessage() { StatusBar s = getStatusBar(); if (s == null) return; s.setMessage(""); } private static void parseTagFile(Runner runner, Parser parser, String tagFile, TagHandler handler) { parser.parseTagFile(tagFile, handler); runner.releaseFile(tagFile); } /* Source file support */ public static void tagSourceFile(final String file) { setStatusMessage("Tagging file: " + file); final Object syncObject = new Object(); final boolean [] done = { false }; addWorkRequest(file, new Task() { public void _run() { // Get the file origins, to restore when updating the file. final String originsStr = index.getOriginsOfFile(file); index.deleteTagsFromSourceFile(file); Runner runner = getRunner(null); String tagFile = runner.runOnFile(file); TagHandler handler = new TagHandler() { public void processTag(Tag t) { index.insertTag(t, originsStr); } }; Parser parser = getParser(null); parseTagFile(runner, parser, tagFile, handler); synchronized (syncObject) { done[0] = true; syncObject.notifyAll(); } } }); synchronized (syncObject) { if (! done[0]) { try { syncObject.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } removeStatusMessage(); } /* Archive support */ public static void tagArchive(final Logger logger, final String archive, final TagHandler handler) { setStatusMessage("Tagging archive: " + archive); addWorkRequest(archive, new Task() { public void _run() { HashMap<String, String> localToVFS = new HashMap<String, String>(); Runner runner = getRunner(logger); String tagFile = runner.runOnArchive(archive, localToVFS); if (tagFile == null) return; Parser parser = getParser(logger); parser.setSourcePathMapping(localToVFS); parseTagFile(runner, parser, tagFile, handler); } }); removeStatusMessage(); } /* Source tree support */ // Runs Ctags on a source tree and add the tags and associated data to the DB public static void tagSourceTree(final Logger logger, final String tree, final TagHandler handler) { setStatusMessage("Tagging source tree: " + tree); addWorkRequest(tree, new Task() { public void _run() { Runner runner = getRunner(logger); String tagFile = runner.runOnTree(tree); Parser parser = getParser(logger); parseTagFile(runner, parser, tagFile, handler); } }); removeStatusMessage(); } /* Project support */ public static ProjectWatcher getProjectWatcher() { return pvi; } // Runs Ctags on a list of files and add the tags and associated data to the DB private static void tagFiles(Logger logger, Vector<String> files, TagHandler handler) { Runner runner = getRunner(logger); Parser parser = getParser(logger); String tagFile = runner.runOnFiles(files); parseTagFile(runner, parser, tagFile, handler); } // Runs Ctags on a project and inserts the tags and associated data to the DB public static void tagProject(final Logger logger, final String project, final TagHandler handler) { if (pvi == null) return; setStatusMessage("Tagging project: " + project); addWorkRequest(project, new Task() { public void _run() { Vector<String> files = pvi.getFiles(project); if (files == null) JOptionPane.showMessageDialog(jEdit.getActiveView(), "Cannot find project named '" + project + "'."); else tagFiles(logger, files, handler); } }); removeStatusMessage(); } public static void updateProject(String project, Vector<String> added, Vector<String> removed) { setStatusMessage("Updating project: " + project); Origin origin = index.getOrigin(OriginType.PROJECT, project, true); View view = jEdit.getActiveView(); Logger logger = getLogger(view, "Project " + project); if (origin != null && removed != null && (! removed.isEmpty())) index.deleteTagsOfOrigin(logger, origin); if ((added != null) && (! added.isEmpty())) { TagHandler handler = new TagFileHandler(origin); tagFiles(logger, added, handler); } removeStatusMessage(); } /* * Interface for other plugins */ public static Vector<Tag> queryTag(String tag) { Vector<Tag> tags = new Vector<Tag>(); index.queryTag(tag, tags); return tags; } public static Vector<Tag> query(String query, int maxResults) { Vector<Tag> tags = new Vector<Tag>(); index.queryTags(query, maxResults, tags); return tags; } public static String getTagNameQuery(String tag) { return index.getTagNameQuery(tag); } }