// :collapseFolds=1:folding=explicit: // // Date Tag Buddy v0.2 // - a BeanShell macro script for the jEdit text editor - // // {{{ What does it do? // Updates certain date tags in an HTML buffer (the current edit mode must be "html"): // 1. Updates the date value of a modification date tag:
Last modified: 01 Mar 2006 // 2. Updates the year in a copyright text: © Me 2006 // Updates all "includes" in an HTML buffer. An include is defined as two HTML comments where // the first one contains the path to a file that should be inserted between the comments: // // // The path can be relative, as in the example, or absolute. Included files may contain date tags. // // The date tag and "include" updates can be performed automatically when the buffer is saved. Therefore install // Ollie Rutherfurd's ActionHooks plugin (http://plugins.jedit.org/plugins/?ActionHooks) and set it up: // 1. Add the Date Tag Buddy macro to the "BufferUpdate.SAVED" event list. // 2. Configure the ActionHooks plugin to start up with JEdit. // This ensures that date tags and "includes" are automatically updated whenever the HTML buffer is saved. // // A modification tag is identified by and . A copyright text is identified by © and . // The macro assumes that the date value is located at the end of each tag. To change the default behavior of the // macro you can modify the constants at its end. // }}} // // Copyright (c) 2006. Ralf Kintrup [r.kintrup@web.de] // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with the jEdit program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // {{{ History // 2006-02-05 Created. Implemented modification date and copyright text // update functionality. // Checked for jEdit 4.2 and 4.3pre3 API. [rk] // 2006-03-15 Implemented "include" update functionality. // Checked for jEdit 4.3pre3 API. [rk] // }}} // {{{ imports import gnu.regexp.*; import java.text.SimpleDateFormat; // }}} // {{{ include methods // {{{ updateIncludes() method /** * Updates all "includes" within the buffer if they need to get updated. * An include is defined as two HTML comments where the first one contains * the path to a file that should be inserted between the comments: * * * The path can be relative as in the example or absolute. * * @return 'true' if the text area was modified, otherwise 'false'. */ boolean updateIncludes() { boolean modified = false; boolean modifiedInclude; // Construct a regular expression that filters out the file path and // the content between the HTML comments StringBuffer stbRegExp = new StringBuffer(100); stbRegExp.append(""); stbRegExp.append("([\\s\\S]*?)"); // Subexpression 2: the content between the two comments stbRegExp.append(""); RE regExp = new gnu.regexp.RE(stbRegExp.toString()); // Iterate through the regexp matches and insert the file contents. REMatch[] matchArray = regExp.getAllMatches(textArea.getText()); REMatch match; String matchFilePath; int count = matchArray.length; //Macros.message(view, "Found " + count + " includes."); // Debug for (int i = 0; i < count; i++) { match = matchArray[i]; matchFilePath = match.toString(1); //Macros.message(view, "Found include " + (i + 1) + " from " + match.getStartIndex(2) + " to " + match.getEndIndex(2) + ":\n" + match.toString(2)); // Debug modifiedInclude = insertFile(matchFilePath, match.getStartIndex(2), match.getEndIndex(2)); if (modifiedInclude) { modified = true; // The matches moved due to the insert. Rescan the buffer. matchArray = regExp.getAllMatches(textArea.getText()); } } return modified; } // }}} // {{{ insertFile() method /** * Inserts the content of the specified file to the buffer replacing the * provided buffer substring. The insert is only performed if the file * differs from the substring. If the file is already open in jEdit it * inserts the content of the open buffer, even if it was modified. * Part of the code is taken from Ollie Rutherfurds "Insert Selection" * macro. * * @param filePath relative or absolute path with the file name of the * file to include. * @param start the start position of the substring to replace. * @param end the end position of the substring to replace. * @return 'true' if the text area was modified, otherwise 'false'. */ boolean insertFile(String filePath, int start, int end) { boolean isDifferent = false; boolean closeBuffer = false; // Try to get the buffer in case it's already open. String absolutePath = MiscUtilities.constructPath(view.getBuffer().getDirectory(), filePath); Buffer buffer = jEdit.getBuffer(absolutePath); if (buffer == null) { // Read the file content into temporary buffer buffer = jEdit.openTemporary(view, null, filePath, false); closeBuffer = true; } try { if (buffer == null) { Macros.message(view, "Could not open temporary buffer:\n" + filePath + "\n\n"); return isDifferent; } // Continue after the buffer was fully loaded. while (!buffer.isLoaded()) { VFSManager.waitForRequests(); } // Compare the selected text with the file content. To prevent // an infite loop when date tags are used in the "includes" // together with the BufferUpdate.SAVED event, the date tags // need to be taken out of the equal comparison. String oldInclude = view.getTextArea().getText(start, end - start); String fileContent = buffer.getText(0, buffer.getLength()); if (!fileContent.equals("") && !equalsWithoutDateTags(oldInclude, fileContent)) { isDifferent = true; view.getTextArea().setSelectedText(new Selection.Range(start, end), fileContent); } if (fileContent.equals("")) { Macros.message(view, "Could not find include file or file is empty:\n" + filePath + "\n\n"); } } finally { if (closeBuffer && (buffer != null)) { setAccessibility(true); buffer.close(); buffer = null; setAccessibility(false); } } return isDifferent; } // }}} // {{{ equalsWithoutDateTags() method /** * Returns whether the passed two texts are equal when date tag values * in them are ignored. * * @param text1 first text to compare. * @param text1 second text to compare. * @return 'true' if the two texts are equal, otherwise 'false'. */ boolean equalsWithoutDateTags(String text1, String text2) { String newText1 = new String(text1); String newText2 = new String(text2); newText1 = deleteDateTagValue(newText1, MODIFICATION_DATE_START, MODIFICATION_DATE_END); newText1 = deleteDateTagValue(newText1, COPYRIGHT_START, COPYRIGHT_END); newText2 = deleteDateTagValue(newText2, MODIFICATION_DATE_START, MODIFICATION_DATE_END); newText2 = deleteDateTagValue(newText2, COPYRIGHT_START, COPYRIGHT_END); return newText1.equals(newText2); } // }}} // {{{ deleteDateTagValue() method /** * Deletes the tag value from the passed text. Only deletes the value of * the first occurrence of the tag. * * @param text the text to work on * @param tagBegin the opening tag * @param tagEnd the closing tag * @return a new String without the tag value. */ String deleteDateTagValue(String text, String tagBegin, String tagEnd) { String newText = new String(text); RE regExp = new gnu.regexp.RE(tagBegin + "([\\s\\S]*?)" + tagEnd); REMatchEnumeration matchEnum = regExp.getMatchEnumeration(text); // Delete the value of the first occurrence of the tag. if (matchEnum.hasMoreElements()) { REMatch match = matchEnum.nextMatch(); String matchValue = match.toString(1); newText = text.substring(0, match.getStartIndex(1)).concat(text.substring(match.getEndIndex(1))); } return newText; } // }}} // }}} // {{{ date tag methods // {{{ updateModificationDateTag() method /** * Updates the value of the first occurence of a modification date tag * in the passed text area. The tag value is set to the current date * according to the passed pattern and locale. The pattern must deliver * a date value with a fixed length for all possible dates. * * The method call updateModificationDateTag("", "", * "dd MMM yyyy", Locale.US, textArea) will update the date of the * following modification date tag: * Last modified: 7 Feb 2006 * * @param tagBegin the modification date begin tag * @param tagEnd the modification date end tag * @param pattern the pattern describing the date and time * format. See the SimpleDateFormat class for details. * @param locale the locale whose date format symbols should be used * @param textArea the text area to search * @return 'true' if the text area was modified, otherwise 'false'. */ boolean updateModificationDateTag(String tagBegin, String tagEnd, String pattern, Locale locale, JEditTextArea textArea) { String dateString = new SimpleDateFormat(pattern, locale).format(new Date()); return updateTag(tagBegin, tagEnd, dateString, textArea); } // }}} // {{{ updateCopyrightYear() method /** * Updates the value of the first occurence of a copyright tag in the * passed text area. The copyright tag value is set to the current year. * * The method call updateCopyrightTag("©", "") will update the * year in the following copyright text: * Copyright © Me 2006 * * @param tagBegin the copyright begin text * @param tagEnd the copyright end text * @param textArea the text area to search * @return 'true' if the text area was modified, otherwise 'false'. */ boolean updateCopyrightYear(String tagBegin, String tagEnd, JEditTextArea textArea) { String year = new SimpleDateFormat("yyyy", Locale.US).format(new Date()); return updateTag(tagBegin, tagEnd, year, textArea); } // }}} // {{{ updateTag() method /** * Searches for a begin and an end tag in the entire text area and * replaces the last characters of its value with the passed new value. * Only updates the value of the first occurence of the tag and only * modifies the text area if the new value differs from the old one. * * @param String tagBegin the begin tag * @param String tagEnd the end tag * @param String newValue the last characters of the new tag value * @param JEditTextArea textArea the text area to search * @return 'true' if the text area was modified, otherwise 'false'. */ boolean updateTag(String tagBegin, String tagEnd, String newValue, JEditTextArea textArea) { boolean isDifferent = false; // Create pattern to match a text with the length of the new value. StringBuffer valuePattern = new StringBuffer(""); int patternLength = newValue.length(); for (int i = 0; i < patternLength; i++) { valuePattern.append("[\\s\\S]"); } // Search for the tag in the current buffer. RE regExp = new gnu.regexp.RE("(" + tagBegin + ")([\\s\\S]*?)(" + valuePattern.toString() + ")(" + tagEnd + ")"); REMatchEnumeration matchEnum = regExp.getMatchEnumeration(textArea.getText()); if (matchEnum.hasMoreElements()) { REMatch match = matchEnum.nextMatch(); String matchStart = match.toString(1); String matchPrefix = match.toString(2); String matchValue = match.toString(3); String matchEnd = match.toString(4); String matchComplete = match.toString(); isDifferent = !matchValue.equals(newValue); if (isDifferent) { // Write start and end tag with the new value between the start // and the end of the match, replacing the old value. textArea.setSelection(new Selection.Range(match.getStartIndex(), match.getEndIndex())); textArea.setSelectedText(matchStart + matchPrefix + newValue + matchEnd); } } return isDifferent; } // }}} // }}} // {{{ displayHelp() method /** * Display a help dialog. */ void displayHelp() { StringBuffer helpMsg = new StringBuffer(""); helpMsg.append("DateTagBuddy Help\n"); helpMsg.append("(only displayed when the buffer is not in html mode and the ActionHooks plugin is disabled)\n"); helpMsg.append("\n"); helpMsg.append("Updates certain date tags in an HTML buffer (the edit mode must be ''html''):\n"); helpMsg.append("1. Updates the date value of a modification date tag: Last modified: 01 Mar 2006\n"); helpMsg.append("2. Updates the year in a copyright text: © Me 2006\n"); helpMsg.append("Updates all ''includes'' in an HTML buffer. An include is defined as two HTML comments where\n"); helpMsg.append("the first one contains the path to a file that should be inserted between the comments:\n"); helpMsg.append("\n"); helpMsg.append("\n"); helpMsg.append("The path can be relative, as in the example, or absolute. Include files may contain date tags.\n"); helpMsg.append("\n"); helpMsg.append("The date tag and ''include'' updates can be performed automatically when the buffer is saved. Therefore\n"); helpMsg.append("install Ollie Rutherfurd's ActionHooks plugin (http://plugins.jedit.org/plugins/?ActionHooks) and set it up:\n"); helpMsg.append("1. Add the Date Tag Buddy macro to the ''BufferUpdate.SAVED'' event list.\n"); helpMsg.append("2. Configure the ActionHooks plugin to start up with JEdit.\n"); helpMsg.append("This ensures that date tags and ''includes'' are automatically updated whenever the HTML buffer is saved. \n"); helpMsg.append("\n"); Macros.message(view, helpMsg.toString()); } // }}} // TO CHANGE THE MATCHED TAGS MODIFY THE CONSTANTS BELOW. final String MODIFICATION_DATE_START = ""; final String MODIFICATION_DATE_END = ""; final String MODIFICATION_DATE_PATTERN = "dd MMM yyyy"; // Must have a fixed width for all possible date values. final Locale MODIFICATION_DATE_LOCALE = Locale.US; // Choose between Java Locales for the date format. final String COPYRIGHT_START = "©"; final String COPYRIGHT_END = ""; // {{{ main Buffer buffer = editPane.getBuffer(); String mode = buffer.getMode().getName(); JEditTextArea textArea = editPane.getTextArea(); EditPlugin actionHooksPlugin = jEdit.getPlugin("actionhooks.ActionHooksPlugin"); // Display a help dialog if the macro is not used the right way. if ((!mode.equals("html") && actionHooksPlugin == null) || (!mode.equals("html") && !actionhooks.ActionHooksPlugin.getEnabled())) { displayHelp(); // Update tags only if the buffer is in HTML mode and if the text area is editable. } else if (mode.equals("html") && textArea.isEditable()) { // Save the caret position and the selection. int savedCaretPosition = textArea.getCaretPosition(); Selection[] savedSelection = textArea.getSelection(); // Update the buffer. boolean modifiedIncludes = updateIncludes(); boolean modifiedDateTag = updateModificationDateTag(MODIFICATION_DATE_START, MODIFICATION_DATE_END, MODIFICATION_DATE_PATTERN, MODIFICATION_DATE_LOCALE, textArea); boolean modifiedCopyright = updateCopyrightYear(COPYRIGHT_START, COPYRIGHT_END, textArea); // Restore the caret position and the selection. textArea.setCaretPosition(savedCaretPosition, false); textArea.setSelection(savedSelection); // Only save the buffer if something was changed in order to prevent an // infinite loop when hooked on the BufferUpdate.SAVED event with the // ActionHooks plugin. Save the buffer if the ActionHooks plugin is // enabled, assuming that the user added the BufferUpdate.SAVED event // hook. In all other cases let the user decide when to save the buffer. if ((modifiedIncludes || modifiedDateTag || modifiedCopyright) && (actionHooksPlugin != null) && (actionhooks.ActionHooksPlugin.getEnabled())) { buffer.save(view, null); } } // }}} /*