/* * ModeProvider.java - An edit mode provider. * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2003 Slava Pestov * * 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.syntax; //{{{ Imports import org.gjt.sp.jedit.Mode; import org.gjt.sp.util.IOUtilities; import org.gjt.sp.util.Log; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.XMLReaderFactory; import java.io.*; import java.nio.file.Files; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.LinkedHashMap; import java.util.regex.*; import javax.swing.JOptionPane; //}}} /** * This class works like a singleton, the instance is initialized by jEdit. * * @author Matthieu Casanova * @version $Id: Buffer.java 8190 2006-12-07 07:58:34Z kpouer $ * @since jEdit 4.3pre10 */ public class ModeProvider { public static ModeProvider instance = new ModeProvider(); private final LinkedHashMap<String, Mode> modes = new LinkedHashMap<String, Mode>(220); //{{{ removeAll() method public void removeAll() { modes.clear(); } //}}} //{{{ removeMode() method /** * Will only remove user modes. * @return true if the mode was removed, false otherwise. */ public boolean removeMode(String name) throws IOException { Mode mode = modes.get(name); if (mode.isUserMode()) { // check that this mode is in the map Mode oldMode = modes.remove(name); if (oldMode == null) return false; // delete mode file from disk and remove the entry from the catalog file. // Actually, just rename the mode file by adding "_unused" to the end of the file name // and comment out the line in the catalog file. This way it is possible to undo // these changes manually without too much work. String modeFilename = (String)mode.getProperty("file"); File modeFile = new File(modeFilename); if (modeFile.exists()) { Path path = FileSystems.getDefault().getPath(modeFilename); Files.move(path, path.resolveSibling(modeFilename + "_unused"), StandardCopyOption.REPLACE_EXISTING); } // The mode file may not be present and still referenced in the catalog, so carry on. // delete entry from mode catalog, catalog is in the same directory as the mode file File catalogFile = new File(modeFile.getParent(),"catalog"); if (catalogFile.exists()) { StringBuilder contents = new StringBuilder(); try { // read in the catalog file BufferedReader br = new BufferedReader(new FileReader(catalogFile)); String line = null; while((line = br.readLine()) != null) { contents.append(line).append('\n'); } br.close(); } catch(IOException ioe) { // unable to read the catalog file modes.put(oldMode.getName(), oldMode); throw ioe; } if (contents.length() == 0) { // empty catalog file, how did that happen? modes.put(oldMode.getName(), oldMode); return false; } // remove the catalog entry for this mode Pattern p = Pattern.compile("(?m)(^\\s*[<]MODE.*?NAME=\"" + name + "\".*?[>])"); Matcher m = p.matcher(contents); String newContents = m.replaceFirst("<!--$1-->"); try { // rewrite the catalog file BufferedWriter bw = new BufferedWriter(new FileWriter(catalogFile)); bw.write(newContents, 0, newContents.length()); bw.flush(); bw.close(); } catch(IOException ioe) { // unable to write the catalog file modes.put(oldMode.getName(), oldMode); throw ioe; } } } return true; } //}}} //{{{ getMode() method /** * Returns the edit mode with the specified name. * @param name The edit mode * @since jEdit 4.3pre10 */ public Mode getMode(String name) { return modes.get(name); } //}}} //{{{ getModeForFile() method /** * Get the appropriate mode that must be used for the file * @param filename the filename * @param firstLine the first line of the file * @return the edit mode, or null if no mode match the file * @since jEdit 4.3pre12 */ public Mode getModeForFile(String filename, String firstLine) { return getModeForFile(null, filename, firstLine); } //}}} //{{{ getModeForFile() method /** * Get the appropriate mode that must be used for the file * @param filepath the filepath, can be {@code null} * @param filename the filename, can be {@code null} * @param firstLine the first line of the file * @return the edit mode, or null if no mode match the file * @since jEdit 4.5pre1 */ public Mode getModeForFile(String filepath, String filename, String firstLine) { if (filepath != null && filepath.endsWith(".gz")) filepath = filepath.substring(0, filepath.length() - 3); if (filename != null && filename.endsWith(".gz")) filename = filename.substring(0, filename.length() - 3); List<Mode> acceptable = new ArrayList<Mode>(1); for(Mode mode : modes.values()) { if(mode.accept(filepath, filename, firstLine)) { acceptable.add(mode); } } if (acceptable.size() == 1) { return acceptable.get(0); } if (acceptable.size() > 1) { // The check should be in reverse order so that // modes from the user catalog get checked first! Collections.reverse(acceptable); // the very most acceptable mode is one whose file // name doesn't only match the file name as regular // expression but which is identical for (Mode mode : acceptable) { if (mode.acceptIdentical(filepath, filename)) { return mode; } } // most acceptable is a mode that matches both the // filepath and the first line glob for (Mode mode : acceptable) { if (mode.acceptFile(filepath, filename) && mode.acceptFirstLine(firstLine)) { return mode; } } // next best is filepath match, there could be multiple matches, // need to choose the best one List<Mode> filepathMatch = new ArrayList<Mode>(); for (Mode mode : acceptable) { if (mode.acceptFile(filepath, filename)) { filepathMatch.add(mode); } } if (filepathMatch.size() == 1) { return filepathMatch.get(0); } else if (filepathMatch.size() > 1) { // return the one with the longest glob pattern since that one // is most likely to be more specific and hence the best choice Mode longest = filepathMatch.get(0); for (Mode mode : filepathMatch) { if (((String)mode.getProperty("filenameGlob")).length() > ((String)longest.getProperty("filenameGlob")).length()) { longest = mode; } } return longest; } // all acceptable choices are by first line glob, and // they all match, so just return the first one. return acceptable.get(0); } // no matching mode found for this file return null; } //}}} //{{{ getModes() method /** * Returns an array of installed edit modes. * @since jEdit 4.3pre10 */ public Mode[] getModes() { return modes.values().toArray(new Mode[modes.size()]); } //}}} //{{{ addMode() method /** * Do not call this method. It is only public so that classes * in the org.gjt.sp.jedit.syntax package can access it. * @since jEdit 4.3pre10 * @see org.gjt.sp.jedit.jEdit#reloadModes reloadModes * @param mode The edit mode */ public void addMode(Mode mode) { String name = mode.getName(); // The removal makes the "insertion order" in modes // (LinkedHashMap) follow the order of addMode() calls. modes.remove(name); modes.put(name, mode); } //}}} //{{{ addUserMode() method /** * Do not call this method. It is only public so that classes * in the org.gjt.sp.jedit.syntax package can access it. * @since jEdit 4.3pre10 * @see org.gjt.sp.jedit.jEdit#reloadModes reloadModes * @param mode The edit mode */ public void addUserMode(Mode mode, Path target) throws IOException { mode.setUserMode(true); String name = mode.getName(); String modeFile = (String)mode.getProperty("file"); String filenameGlob = (String)mode.getProperty("filenameGlob"); String firstLineGlob = (String)mode.getProperty("firstlineGlob"); // copy mode file to user mode directory Path source = FileSystems.getDefault().getPath(modeFile); Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); // add entry to mode catalog, catalog is in the same directory as the mode file File catalogFile = new File(target.toFile().getParent(),"catalog"); if (catalogFile.exists()) { try { // read in the catalog file BufferedReader br = new BufferedReader(new FileReader(catalogFile)); String line = null; StringBuilder contents = new StringBuilder(); while((line = br.readLine()) != null) { contents.append(line).append('\n'); } br.close(); // remove any existing catalog entry for this mode Pattern p = Pattern.compile("(?m)(^\\s*[<]MODE.*?NAME=\"" + name + "\".*?[>])"); Matcher m = p.matcher(contents); String newContents = m.replaceFirst("<!--$1-->"); // insert the catalog entry for this mode p = Pattern.compile("(?m)(</MODES>)"); m = p.matcher(contents); StringBuilder modeLine = new StringBuilder("\t<MODE NAME=\""); modeLine.append(name).append("\" FILE=\"").append(target.toFile().getName()).append("\""); modeLine.append(filenameGlob == null || filenameGlob.isEmpty() ? "" : " FILE_NAME_GLOB=\"" + filenameGlob + "\""); modeLine.append(firstLineGlob == null || firstLineGlob.isEmpty() ? "" : " FIRST_LINE_GLOB=\"" + firstLineGlob + "\""); modeLine.append("/>"); newContents = m.replaceFirst(modeLine + "\n$1" ); // rewrite the catalog file BufferedWriter bw = new BufferedWriter(new FileWriter(catalogFile)); bw.write(newContents, 0, newContents.length()); bw.flush(); bw.close(); } catch(Exception e) { // ignored } } addMode(mode); loadMode(mode); } //}}} //{{{ loadMode() method public void loadMode(Mode mode, XModeHandler xmh) { String fileName = (String)mode.getProperty("file"); Log.log(Log.NOTICE,this,"Loading edit mode " + fileName); XMLReader parser; try { parser = XMLReaderFactory.createXMLReader(); } catch (SAXException saxe) { Log.log(Log.ERROR, this, saxe); return; } mode.setTokenMarker(xmh.getTokenMarker()); InputStream grammar; try { grammar = new BufferedInputStream( new FileInputStream(fileName)); } catch (FileNotFoundException e1) { InputStream resource = ModeProvider.class.getResourceAsStream(fileName); if (resource == null) error(fileName, e1); grammar = new BufferedInputStream(resource); } try { InputSource isrc = new InputSource(grammar); isrc.setSystemId("jedit.jar"); parser.setContentHandler(xmh); parser.setDTDHandler(xmh); parser.setEntityResolver(xmh); parser.setErrorHandler(xmh); parser.parse(isrc); mode.setProperties(xmh.getModeProperties()); } catch (Throwable e) { error(fileName, e); } finally { IOUtilities.closeQuietly(grammar); } } //}}} //{{{ loadMode() method public void loadMode(Mode mode) { XModeHandler xmh = new XModeHandler(mode.getName()) { @Override public void error(String what, Object subst) { Log.log(Log.ERROR, this, subst); } @Override public TokenMarker getTokenMarker(String modeName) { Mode mode = getMode(modeName); if(mode == null) return null; else return mode.getTokenMarker(); } }; loadMode(mode, xmh); } //}}} //{{{ error() method protected void error(String file, Throwable e) { Log.log(Log.ERROR, this, e); } //}}} }