package org.pixelgaffer.turnierserver.codr; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import javafx.scene.control.Tab; import javafx.scene.layout.BorderPane; import javax.xml.parsers.ParserConfigurationException; import net.lingala.zip4j.core.ZipFile; import net.lingala.zip4j.exception.ZipException; import org.apache.commons.io.FileUtils; import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.LineNumberFactory; import org.pixelgaffer.katepartparser.EmptyStyle; import org.pixelgaffer.katepartparser.Style; import org.pixelgaffer.katepartparser.Styles; import org.pixelgaffer.katepartparser.SyntaxFileResolver; import org.pixelgaffer.katepartparser.SyntaxParser; import org.pixelgaffer.turnierserver.codr.utilities.ErrorLog; import org.pixelgaffer.turnierserver.codr.utilities.Paths; import org.xml.sax.SAXException; /** * Managet die Darstellung des Code-Editors. * * @author Dominic */ public class CodeEditor { public static void writeSyntax () throws ParserConfigurationException, SAXException { File syntaxFolder = new File(Paths.syntaxFolder()); if (syntaxFolder.exists()) return; try { syntaxFolder.mkdirs(); PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File(syntaxFolder, "_fallback.xml")), UTF_8)); out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); out.println("<!DOCTYPE language SYSTEM \"language.dtd\">"); out.println("<language name=\"fallback\" section=\"\" extensions=\"*\" mimetype=\"\" priority=\"-100\">"); out.println(" <highlighting>"); out.println(" <contexts>"); out.println(" <context name=\"cdata\" attribute=\"normal\" lineEndContext=\"#stay\">"); out.println(" </context>"); out.println(" </contexts>"); out.println(" <itemDatas>"); out.println(" <itemData name=\"normal\" defStyleNum=\"dsNormal\" />"); out.println(" </itemDatas>"); out.println(" </highlighting>"); out.println("</language>"); out.close(); File tmp = File.createTempFile("syntax", ".zip"); tmp.deleteOnExit(); FileUtils.copyURLToFile(CodeEditor.class.getResource("syntax.zip"), tmp); ZipFile zipFile = new ZipFile(tmp); zipFile.extractAll(syntaxFolder.getAbsolutePath()); p = new Properties(); for (String filename : syntaxFolder.list()) { if (filename.equals("_fallback.xml")) continue; SyntaxParser parser = new SyntaxParser(new File(syntaxFolder, filename), resolver, new EmptyStyle()); p.put("names." + parser.getName(), filename); if (parser.isHidden()) continue; for (String extension : parser.getExtensions()) { extension = extension.trim(); if (p.containsKey("extensions." + extension)) { if (Integer.parseInt(p.getProperty("extensions." + extension + ".priority")) > parser.getPriority()) continue; } p.put("extensions." + extension, filename); p.put("extensions." + extension + ".priority", Integer.toString(parser.getPriority())); } } p.store(new FileOutputStream(new File(syntaxFolder, "index.prop")), "Enthält alle Syntax-Dateien sortiert nach den Dateienden und den Namen"); } catch (ZipException | IOException e) { ErrorLog.write("Konnte Syntax nicht schreiben: " + e); syntaxFolder.delete(); } } private static Properties p = null; private static final SyntaxFileResolver resolver = (name) -> new File(Paths.syntaxFolder(), p.getProperty("names." + name, "_fallback.xml")); private static Properties props () throws IOException { if (p == null) { p = new Properties(); p.load(new FileInputStream(new File(Paths.syntaxFolder(), "index.prop"))); } return p; } private String savedText = ""; private File document; public boolean loaded = false; private CodeArea codeArea; private SyntaxParser parser; /** * Initialisiert den CodeEditor mit der zu zeigenden Datei * * @param doc */ public CodeEditor (File doc) { document = doc; codeArea = new CodeArea(); codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea)); // codeArea.setStyle("-fx-text-fill: white"); Style style = new EmptyStyle(); try { style = MainApp.cStart.btTheme.isSelected() ? Styles.getStyle("VibrantInk") : Styles.getStyle("Eclipse"); } catch (IOException e1) { e1.printStackTrace(); } try { for (Object o : props().keySet()) { String extension = (String)o; if (!extension.startsWith("extensions.") || extension.endsWith(".priority")) continue; extension = extension.substring("extensions.".length()); extension = extension.replace(".", "\\."); extension = extension.replace("*", ".*"); Pattern pattern = Pattern.compile(extension, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(doc.getName()); if (matcher.matches()) { parser = new SyntaxParser(new File(Paths.syntaxFolder(), (String)props().get(o)), resolver, style); System.out.println("Loading " + parser.getName() + " for file " + doc); break; } } } catch (SAXException | ParserConfigurationException | IOException e) { ErrorLog.write("Fehler beim Laden des SyntaxParser: " + e); e.printStackTrace(); } if (parser == null) { try { parser = new SyntaxParser(new File(Paths.syntaxFolder(), "_fallback.xml"), resolver, style); } catch (ParserConfigurationException | IOException | SAXException e) { ErrorLog.write("Fehler beim Laden des SyntaxParser: " + e); e.printStackTrace(); } } if (parser != null) { codeArea.setId("codeArea"); try { codeArea.getStylesheets().add(parser.generateStylesheet("codeArea").toExternalForm()); } catch (IOException e) { ErrorLog.write("Fehler beim Laden eines generierten StyleSheets: " + e); e.printStackTrace(); } codeArea.textProperty().addListener( (obs, oldText, newText) -> codeArea.setStyleSpans(0, parser.computeHighlighting(newText))); } load(); } /** * Überprüft, ob der angezeigte Text mit seiner gespeicherten Datei * übereinstimmt * * @return true, wenn savedText != text */ public boolean hasChanged () { if (loaded) return !savedText.equals(getCode()); else return false; } public String getCode () { return codeArea.getText(); } public void setCode (String t) { codeArea.replaceText(0, codeArea.getText().length() - 1, t); } /** * Erstellt ein Tab, das in die Tab-Leiste eingefügt werden kann * * @return das erstellte Tab */ public Tab getView () { BorderPane pane = new BorderPane(codeArea); return new Tab(document.getName(), pane); } /** * Lädt den Inhalt der Datei in die StringProperty text */ public void load () { try { setCode(FileUtils.readFileToString(document)); savedText = getCode(); loaded = true; } catch (IOException e) { ErrorLog.write("Quellcode konnte nicht gelesen werden: " + e); } } /** * Speichert den Inhalt der StringProperty text in die Datei * * @return false wenn nichts geändert wurde, ansonsten true */ public boolean save () { if (!hasChanged()) return false; forceSave(); return true; } /** * Speichert den Inhalt der StringProperty text in eine Datei. Dabei wird * nicht Überprüft, ob sich der Inhalt verändert hat. */ public void forceSave () { try { FileWriter writer = new FileWriter(document, false); writer.write(getCode()); writer.close(); savedText = getCode(); } catch (IOException e) { ErrorLog.write("Quellcode konnte nicht bearbeitet werden"); } } }