Bundle-ManifestVersion: 2
Bundle-Name: Silk icon set
Bundle-SymbolicName: com.famfamfam.silk
-Bundle-Version: 1.3
+Bundle-Version: 1.3.0
Bundle-Vendor: Semantum Oy
output.. = bin/\r
bin.includes = META-INF/,\\r
.,\\r
- bin/msijni.dll,\\r
- bin/msijni64.dll,\\r
+ msijni.dll,\\r
+ msijni64.dll,\\r
VC90.2008.SP1.KB2467174.redist.x64.exe,\\r
VC90.2008.SP1.KB2467174.redist.x86.exe\r
src.includes = native/\r
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Export-Package: org.simantics.db.procore.ui
Import-Package:
- org.osgi.framework;version="1.3.0",
+ org.eclipse.core.runtime,
org.eclipse.jface.dialogs,
org.eclipse.jface.operation,
- org.eclipse.core.runtime,
org.eclipse.swt.widgets,
- org.eclipse.ui,
+ org.osgi.framework;version="1.3.0",
org.simantics.db,
- org.simantics.db.exception,
org.simantics.db.common.utils,
+ org.simantics.db.exception,
org.simantics.db.server;visibility:=reexport
Require-Bundle: org.simantics.db.procore;bundle-version="1.2.1"
\r
import org.eclipse.swt.widgets.Display;\r
import org.eclipse.swt.widgets.Shell;\r
-import org.eclipse.ui.IWorkbench;\r
-import org.eclipse.ui.IWorkbenchWindow;\r
import org.simantics.db.DatabaseUserAgent;\r
import org.simantics.db.exception.InternalException;\r
import org.simantics.db.procore.ProCoreDriver;\r
\r
public final class ProCoreUserAgent implements DatabaseUserAgent {\r
- private static Shell getShell(IWorkbench workbench) {\r
- IWorkbenchWindow wbw = workbench.getActiveWorkbenchWindow();\r
- Shell shell = null;\r
- if (null != wbw) {\r
- shell = wbw.getShell();\r
- } else {\r
- Display d = getDisplay();\r
- if (d == null)\r
- return null;\r
- shell = d.getActiveShell();\r
- if (null == shell) {\r
- Shell[] shells = d.getShells();\r
- if (null != shells && shells.length > 0)\r
- shell = shells[0];\r
- }\r
- }\r
- return shell;\r
+ private static Shell getShell() {\r
+ Shell shell = null;\r
+ Display d = getDisplay();\r
+ if (d == null)\r
+ return null;\r
+ shell = d.getActiveShell();\r
+ if (null == shell) {\r
+ Shell[] shells = d.getShells();\r
+ if (null != shells && shells.length > 0)\r
+ shell = shells[0];\r
+ }\r
+ return shell;\r
}\r
private static Display getDisplay() {\r
- Display d = Display.getCurrent();\r
- if (d == null)\r
- d = Display.getDefault();\r
- return d;\r
+ Display d = Display.getCurrent();\r
+ if (d == null)\r
+ d = Display.getDefault();\r
+ return d;\r
}\r
- private IWorkbench workbench;\r
- public ProCoreUserAgent(IWorkbench workbench) {\r
- this.workbench = workbench;\r
- }\r
- private Shell getShell() {\r
- return getShell(workbench);\r
- }\r
- @Override\r
- public boolean handleStart(InternalException exception) {\r
- Shell shell = getShell();\r
- if (null == shell)\r
- return false; // no can do\r
- try {\r
- return Auxiliary.handleStart(shell, exception);\r
- } catch (InternalException e) {\r
- return false; // no could do\r
- }\r
- }\r
- \r
- @Override\r
- public String getId() {\r
- return ProCoreDriver.ProCoreDriverName;\r
- }\r
-}
\ No newline at end of file
+ @Override\r
+ public boolean handleStart(InternalException exception) {\r
+ Shell shell = getShell();\r
+ if (null == shell)\r
+ return false; // no can do\r
+ try {\r
+ return Auxiliary.handleStart(shell, exception);\r
+ } catch (InternalException e) {\r
+ return false; // no could do\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public String getId() {\r
+ return ProCoreDriver.ProCoreDriverName;\r
+ }\r
+}\r
.,\\r
win32.x86/,\\r
win32.x86_64/,\\r
- linux.x86/,\\r
linux.x86_64/\r
output.. = bin/\r
bin.includes = META-INF/,\\r
.,\\r
- graphs/*.tg,\\r
graph.tg\r
src.includes = graph/\r
bin.includes = META-INF/,\\r
.,\\r
plugin.xml,\\r
- document.war,\\r
webdefault.xml,\\r
- scl/,\\r
+ scl/\r
src.includes = scl/\r
\r
output.. = bin/\r
bin.includes = META-INF/,\\r
.,\\r
- graphs/*.tg,\\r
graph.tg\r
src.includes = graph/\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>\r
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
+ <classpathentry kind="src" path="src"/>\r
+ <classpathentry exported="true" kind="lib" path="jna-4.2.1.jar"/>\r
+ <classpathentry kind="lib" path="jna-platform-4.2.1.jar"/>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>org.simantics.nativemem</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>org.eclipse.pde.ManifestBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>org.eclipse.pde.SchemaBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>org.eclipse.pde.PluginNature</nature>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7\r
+org.eclipse.jdt.core.compiler.compliance=1.7\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.7\r
--- /dev/null
+eclipse.preferences.version=1\r
+pluginProject.extensions=false\r
+resolve.requirebundle=false\r
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Native Memory Tracking
+Bundle-SymbolicName: org.simantics.nativemem
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: Semantum Oy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.7
+Bundle-ClassPath: .,
+ jna-4.2.1.jar,
+ jna-platform-4.2.1.jar
+Export-Package: org.simantics.nativemem
--- /dev/null
+source.. = src/\r
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+ .,\\r
+ jna-4.2.1.jar,\\r
+ jna-platform-4.2.1.jar\r
--- /dev/null
+package org.simantics.nativemem;\r
+\r
+import org.simantics.nativemem.internal.Arch;\r
+import org.simantics.nativemem.internal.OS;\r
+import org.simantics.nativemem.internal.Psapi32;\r
+import org.simantics.nativemem.internal.Psapi64;\r
+\r
+import com.sun.jna.platform.win32.Kernel32;\r
+import com.sun.jna.platform.win32.WinNT.HANDLE;\r
+\r
+\r
+public class NativeMem {\r
+\r
+ /**\r
+ * @param out\r
+ * the structure to write the result into or <code>null</code> to\r
+ * create a new structure\r
+ * @return the result structure\r
+ */\r
+ public static ProcessMemoryCounters getMemoryCounters(ProcessMemoryCounters out) {\r
+ if (out == null)\r
+ out = new ProcessMemoryCounters();\r
+\r
+ OS os = OS.calculate();\r
+ Arch arch = Arch.calculate();\r
+ switch (os) {\r
+ case WINDOWS: {\r
+ HANDLE proc = Kernel32.INSTANCE.GetCurrentProcess();\r
+ switch (arch) {\r
+ case X86: {\r
+ Psapi32.PROCESS_MEMORY_COUNTERS_EX pmem = new Psapi32.PROCESS_MEMORY_COUNTERS_EX();\r
+ boolean ok = Psapi32.INSTANCE.GetProcessMemoryInfo(proc, pmem, pmem.size());\r
+ if (ok)\r
+ pmem.writeTo(out);\r
+ return out;\r
+ }\r
+\r
+ case X86_64: {\r
+ Psapi64.PROCESS_MEMORY_COUNTERS_EX pmem = new Psapi64.PROCESS_MEMORY_COUNTERS_EX();\r
+ boolean ok = Psapi64.INSTANCE.GetProcessMemoryInfo(proc, pmem, pmem.size());\r
+ if (ok)\r
+ pmem.writeTo(out);\r
+ return out;\r
+ }\r
+\r
+ default:\r
+ throw new UnsupportedOperationException("Architecture " + arch + " not supported on operating system " + os);\r
+ }\r
+ }\r
+\r
+ default:\r
+ throw new UnsupportedOperationException("Operating system " + os + " not supported");\r
+ }\r
+ }\r
+\r
+}
\ No newline at end of file
--- /dev/null
+package org.simantics.nativemem;\r
+\r
+/**\r
+ * Architecture-independent version of the Windows PsApi PROCESS_MEMORY_COUNTERS\r
+ * structure.\r
+ * \r
+ * @author Tuukka Lehtonen\r
+ */\r
+public class ProcessMemoryCounters {\r
+ public int PageFaultCount;\r
+ public long PeakWorkingSetSize;\r
+ public long WorkingSetSize;\r
+ public long QuotaPeakPagedPoolUsage;\r
+ public long QuotaPagedPoolUsage;\r
+ public long QuotaPeakNonPagedPoolUsage;\r
+ public long QuotaNonPagedPoolUsage;\r
+ public long PagefileUsage;\r
+ public long PeakPagefileUsage;\r
+ public long PrivateUsage;\r
+\r
+ @Override\r
+ public String toString() {\r
+ return "ProcessMemoryCounters [PageFaultCount="\r
+ + PageFaultCount + ", PeakWorkingSetSize=" + PeakWorkingSetSize\r
+ + ", WorkingSetSize=" + WorkingSetSize\r
+ + ", QuotaPeakPagedPoolUsage=" + QuotaPeakPagedPoolUsage\r
+ + ", QuotaPagedPoolUsage=" + QuotaPagedPoolUsage\r
+ + ", QuotaPeakNonPagedPoolUsage=" + QuotaPeakNonPagedPoolUsage\r
+ + ", QuotaNonPagedPoolUsage=" + QuotaNonPagedPoolUsage\r
+ + ", PagefileUsage=" + PagefileUsage + ", PeakPagefileUsage="\r
+ + PeakPagefileUsage + ", PrivateUsage=" + PrivateUsage + "]";\r
+ }\r
+\r
+ public String toHumanReadableString() {\r
+ StringBuilder sb = new StringBuilder();\r
+ sb.append("ProcessMemoryCounters [\n\tPageFaultCount = ").append(PageFaultCount)\r
+ .append(",\n\tPeakWorkingSetSize = ").append(toMb(PeakWorkingSetSize))\r
+ .append(" MB,\n\tWorkingSetSize = ").append(toMb(WorkingSetSize))\r
+ .append(" MB,\n\tQuotaPeakPagedPoolUsage = ").append(toMb(QuotaPeakPagedPoolUsage))\r
+ .append(" MB,\n\tQuotaPagedPoolUsage = ").append(toMb(QuotaPagedPoolUsage))\r
+ .append(" MB,\n\tQuotaPeakNonPagedPoolUsage = ").append(toMb(QuotaPeakNonPagedPoolUsage))\r
+ .append(" MB,\n\tQuotaNonPagedPoolUsage = ").append(toMb(QuotaNonPagedPoolUsage))\r
+ .append(" MB,\n\tPagefileUsage = ").append(toMb(PagefileUsage))\r
+ .append(" MB,\n\tPeakPagefileUsage = ").append(toMb(PeakPagefileUsage))\r
+ .append(" MB,\n\tPrivateUsage = ").append(toMb(PrivateUsage))\r
+ .append(" MB]");\r
+ return sb.toString();\r
+ }\r
+\r
+ private double toMb(long bytes) {\r
+ return (double) bytes / 1048576.0;\r
+ }\r
+\r
+}
\ No newline at end of file
--- /dev/null
+package org.simantics.nativemem.internal;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ */\r
+public enum Arch {\r
+ PPC, PPC_64, SPARC, X86, X86_64, UNKNOWN;\r
+\r
+ public static Arch calculate() {\r
+ String osArch = System.getProperty("os.arch");\r
+ assert osArch != null;\r
+ osArch = osArch.toLowerCase();\r
+ if (osArch.equals("i386") || osArch.equals("i586") || osArch.equals("i686") || osArch.equals("x86"))\r
+ return X86;\r
+ if (osArch.startsWith("amd64") || osArch.startsWith("x86_64"))\r
+ return X86_64;\r
+ if (osArch.equals("ppc"))\r
+ return PPC;\r
+ if (osArch.startsWith("ppc"))\r
+ return PPC_64;\r
+ if (osArch.startsWith("sparc"))\r
+ return SPARC;\r
+ return UNKNOWN;\r
+ }\r
+}\r
--- /dev/null
+package org.simantics.nativemem.internal;\r
+\r
+public enum OS {\r
+ APPLE, LINUX, SUN, WINDOWS, UNKNOWN;\r
+\r
+ public static OS calculate() {\r
+ String osName = System.getProperty("os.name");\r
+ assert osName != null;\r
+ osName = osName.toLowerCase();\r
+ if (osName.startsWith("mac os x"))\r
+ return APPLE;\r
+ if (osName.startsWith("windows"))\r
+ return WINDOWS;\r
+ if (osName.startsWith("linux"))\r
+ return LINUX;\r
+ if (osName.startsWith("sun"))\r
+ return SUN;\r
+ return UNKNOWN;\r
+ }\r
+}\r
--- /dev/null
+package org.simantics.nativemem.internal;\r
+\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import org.simantics.nativemem.ProcessMemoryCounters;\r
+\r
+import com.sun.jna.Native;\r
+import com.sun.jna.Structure;\r
+import com.sun.jna.platform.win32.WinNT.HANDLE;\r
+import com.sun.jna.win32.StdCallLibrary;\r
+\r
+public interface Psapi32 extends StdCallLibrary {\r
+\r
+ Psapi32 INSTANCE = (Psapi32) Native.loadLibrary("Psapi", Psapi32.class);\r
+\r
+ /*\r
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684874%28v=vs.85%29.aspx\r
+ */\r
+ public static class PROCESS_MEMORY_COUNTERS_EX extends Structure {\r
+ public int cb;\r
+ public int PageFaultCount;\r
+ public int PeakWorkingSetSize;\r
+ public int WorkingSetSize;\r
+ public int QuotaPeakPagedPoolUsage;\r
+ public int QuotaPagedPoolUsage;\r
+ public int QuotaPeakNonPagedPoolUsage;\r
+ public int QuotaNonPagedPoolUsage;\r
+ public int PagefileUsage;\r
+ public int PeakPagefileUsage;\r
+ public int PrivateUsage;\r
+\r
+ @SuppressWarnings("rawtypes")\r
+ @Override\r
+ protected List getFieldOrder() {\r
+ return PROCESS_MEMORY_COUNTERS_EX_FIELDS;\r
+ }\r
+\r
+ static final List<String> PROCESS_MEMORY_COUNTERS_EX_FIELDS = Arrays.asList(new String[] { \r
+ "cb", "PageFaultCount",\r
+ "PeakWorkingSetSize", "WorkingSetSize",\r
+ "QuotaPeakPagedPoolUsage", "QuotaPagedPoolUsage",\r
+ "QuotaPeakNonPagedPoolUsage", "QuotaNonPagedPoolUsage",\r
+ "PagefileUsage", "PeakPagefileUsage", "PrivateUsage"\r
+ });\r
+\r
+ public void writeTo(ProcessMemoryCounters to) {\r
+ to.PageFaultCount = PageFaultCount;\r
+ to.PeakWorkingSetSize = PeakWorkingSetSize;\r
+ to.WorkingSetSize = WorkingSetSize;\r
+ to.QuotaPeakPagedPoolUsage = QuotaPeakPagedPoolUsage;\r
+ to.QuotaPagedPoolUsage = QuotaPagedPoolUsage;\r
+ to.QuotaPeakNonPagedPoolUsage = QuotaPeakNonPagedPoolUsage;\r
+ to.QuotaNonPagedPoolUsage = QuotaNonPagedPoolUsage;\r
+ to.PagefileUsage = PagefileUsage;\r
+ to.PeakPagefileUsage = PeakPagefileUsage;\r
+ to.PrivateUsage = PrivateUsage;\r
+ }\r
+ }\r
+\r
+ /*\r
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683219%28v=vs.85%29.aspx\r
+ */\r
+ boolean GetProcessMemoryInfo(HANDLE Process, PROCESS_MEMORY_COUNTERS_EX ppsmemCounters, int cb);\r
+\r
+\r
+}
\ No newline at end of file
--- /dev/null
+package org.simantics.nativemem.internal;\r
+\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import org.simantics.nativemem.ProcessMemoryCounters;\r
+\r
+import com.sun.jna.Native;\r
+import com.sun.jna.Structure;\r
+import com.sun.jna.platform.win32.WinNT.HANDLE;\r
+import com.sun.jna.win32.StdCallLibrary;\r
+\r
+public interface Psapi64 extends StdCallLibrary {\r
+\r
+ Psapi64 INSTANCE = (Psapi64) Native.loadLibrary("Psapi", Psapi64.class);\r
+\r
+ /*\r
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684874%28v=vs.85%29.aspx\r
+ */\r
+ public static class PROCESS_MEMORY_COUNTERS_EX extends Structure {\r
+ public int cb;\r
+ public int PageFaultCount;\r
+ public long PeakWorkingSetSize;\r
+ public long WorkingSetSize;\r
+ public long QuotaPeakPagedPoolUsage;\r
+ public long QuotaPagedPoolUsage;\r
+ public long QuotaPeakNonPagedPoolUsage;\r
+ public long QuotaNonPagedPoolUsage;\r
+ public long PagefileUsage;\r
+ public long PeakPagefileUsage;\r
+ public long PrivateUsage;\r
+\r
+ @SuppressWarnings("rawtypes")\r
+ @Override\r
+ protected List getFieldOrder() {\r
+ return PROCESS_MEMORY_COUNTERS_EX_FIELDS;\r
+ }\r
+\r
+ private static final List<String> PROCESS_MEMORY_COUNTERS_EX_FIELDS = Arrays.asList(new String[] { \r
+ "cb", "PageFaultCount",\r
+ "PeakWorkingSetSize", "WorkingSetSize",\r
+ "QuotaPeakPagedPoolUsage", "QuotaPagedPoolUsage",\r
+ "QuotaPeakNonPagedPoolUsage", "QuotaNonPagedPoolUsage",\r
+ "PagefileUsage", "PeakPagefileUsage", "PrivateUsage"\r
+ });\r
+\r
+ public void writeTo(ProcessMemoryCounters to) {\r
+ to.PageFaultCount = PageFaultCount;\r
+ to.PeakWorkingSetSize = PeakWorkingSetSize;\r
+ to.WorkingSetSize = WorkingSetSize;\r
+ to.QuotaPeakPagedPoolUsage = QuotaPeakPagedPoolUsage;\r
+ to.QuotaPagedPoolUsage = QuotaPagedPoolUsage;\r
+ to.QuotaPeakNonPagedPoolUsage = QuotaPeakNonPagedPoolUsage;\r
+ to.QuotaNonPagedPoolUsage = QuotaNonPagedPoolUsage;\r
+ to.PagefileUsage = PagefileUsage;\r
+ to.PeakPagefileUsage = PeakPagefileUsage;\r
+ to.PrivateUsage = PrivateUsage;\r
+ }\r
+ }\r
+\r
+ /*\r
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683219%28v=vs.85%29.aspx\r
+ */\r
+ boolean GetProcessMemoryInfo(HANDLE Process, PROCESS_MEMORY_COUNTERS_EX ppsmemCounters, int cb);\r
+\r
+}
\ No newline at end of file
--- /dev/null
+package org.simantics.nativemem.internal;\r
+\r
+import org.simantics.nativemem.NativeMem;\r
+import org.simantics.nativemem.ProcessMemoryCounters;\r
+\r
+public class Test {\r
+\r
+ public static void main(String[] args) {\r
+ ProcessMemoryCounters mem = NativeMem.getMemoryCounters(null);\r
+ System.out.println(mem.toHumanReadableString());\r
+ }\r
+\r
+}\r
bin.includes = META-INF/,\\r
.,\\r
scl/,\\r
- schema/,\\r
- plugin.xml
\ No newline at end of file
+ schema/\r
output.. = bin/\r
bin.includes = META-INF/,\\r
.,\\r
- graphs/*.tg,\\r
graph.tg\r
src.includes = graph/\r
output.. = bin/\r
bin.includes = META-INF/,\\r
.,\\r
- graphs/*.tg,\\r
graph.tg\r
src.includes = graph/\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>\r
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
+ <classpathentry kind="src" path="src"/>\r
+ <classpathentry kind="src" path="examples"/>\r
+ <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+ <name>org.simantics.threadlog</name>\r
+ <comment></comment>\r
+ <projects>\r
+ </projects>\r
+ <buildSpec>\r
+ <buildCommand>\r
+ <name>org.eclipse.jdt.core.javabuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>org.eclipse.pde.ManifestBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ <buildCommand>\r
+ <name>org.eclipse.pde.SchemaBuilder</name>\r
+ <arguments>\r
+ </arguments>\r
+ </buildCommand>\r
+ </buildSpec>\r
+ <natures>\r
+ <nature>org.eclipse.pde.PluginNature</nature>\r
+ <nature>org.eclipse.jdt.core.javanature</nature>\r
+ </natures>\r
+</projectDescription>\r
--- /dev/null
+#Fri Oct 30 22:48:46 EET 2009\r
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6\r
+org.eclipse.jdt.core.compiler.compliance=1.6\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.6\r
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Threadlog
+Bundle-SymbolicName: org.simantics.threadlog
+Bundle-Version: 1.0.0.qualifier
+Bundle-Activator: org.simantics.threadlog.internal.Activator
+Require-Bundle: org.eclipse.core.runtime,
+ gnu.trove2;bundle-version="2.0.0"
+Bundle-ActivationPolicy: lazy
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Export-Package: org.simantics.threadlog
--- /dev/null
+###############################################################################\r
+# Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+# in Industry THTH ry.\r
+# All rights reserved. This program and the accompanying materials\r
+# are made available under the terms of the Eclipse Public License v1.0\r
+# which accompanies this distribution, and is available at\r
+# http://www.eclipse.org/legal/epl-v10.html\r
+#\r
+# Contributors:\r
+# VTT Technical Research Centre of Finland - initial API and implementation\r
+###############################################################################\r
+source.. = src/\r
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+ .\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.examples;\r
+\r
+import static org.simantics.threadlog.ThreadLog.BEGIN;\r
+\r
+import org.simantics.threadlog.Task;\r
+import org.simantics.threadlog.ui.ThreadLogController;\r
+\r
+public class Example1 {\r
+\r
+ public static void main(String[] args) throws InterruptedException {\r
+ ThreadLogController.start();\r
+ \r
+ while(true) {\r
+ Thread.sleep(50);\r
+ Task t = BEGIN("Foo");\r
+ Thread.sleep(100);\r
+ t.end();\r
+ } \r
+ }\r
+ \r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog;\r
+\r
+public interface Task {\r
+ void end();\r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog;\r
+\r
+import gnu.trove.TDoubleArrayList;\r
+import gnu.trove.TLongArrayList;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.BufferedOutputStream;\r
+import java.io.DataInput;\r
+import java.io.DataInputStream;\r
+import java.io.DataOutput;\r
+import java.io.DataOutputStream;\r
+import java.io.File;\r
+import java.io.FileInputStream;\r
+import java.io.FileOutputStream;\r
+import java.io.IOException;\r
+import java.io.StreamCorruptedException;\r
+import java.util.ArrayList;\r
+\r
+public class ThreadLog {\r
+\r
+ private static final transient String HDR = "TLOG";\r
+ private static final transient int CURRENT_VERSION = 1;\r
+\r
+ static ThreadLog defaultLog;\r
+\r
+ ArrayList<String> tasks = new ArrayList<String>();\r
+ TDoubleArrayList times = new TDoubleArrayList();\r
+ TLongArrayList threads = new TLongArrayList();\r
+\r
+ public double[] getTimes() {\r
+ return times.toNativeArray();\r
+ }\r
+\r
+ public String[] getTasks() {\r
+ return tasks.toArray(new String[tasks.size()]);\r
+ }\r
+\r
+ public long[] getThreads() {\r
+ return threads.toNativeArray();\r
+ }\r
+\r
+ private class TaskImpl implements Task {\r
+ public String name;\r
+ public long thread;\r
+ public long beginTime;\r
+\r
+ public TaskImpl(String name) {\r
+ this.name = name;\r
+ this.thread = Thread.currentThread().getId();\r
+ this.beginTime = System.nanoTime();\r
+ }\r
+\r
+ @Override\r
+ public void end() {\r
+ long endTime = System.nanoTime();\r
+ synchronized(tasks) {\r
+ tasks.add(name);\r
+ times.add(beginTime*1e-9);\r
+ times.add(endTime*1e-9);\r
+ threads.add(thread);\r
+ }\r
+ }\r
+ }\r
+\r
+ private static enum DummyTask implements Task {\r
+ INSTANCE;\r
+\r
+ @Override\r
+ public void end() {\r
+ }\r
+ }\r
+\r
+ public Task begin(String name) {\r
+ return new TaskImpl(name);\r
+ }\r
+\r
+ public static Task BEGIN(String name) {\r
+ try {\r
+ if(defaultLog != null)\r
+ return defaultLog.begin(name);\r
+ } catch(NullPointerException e) {\r
+ }\r
+ return DummyTask.INSTANCE;\r
+ }\r
+\r
+ public static ThreadLog setDefaultThreadLog(ThreadLog log) {\r
+ ThreadLog currentLog = defaultLog;\r
+ defaultLog = log;\r
+ return currentLog;\r
+ }\r
+\r
+ // ------------------------------------------------------------------------\r
+ // SERIALIZATION\r
+ // ------------------------------------------------------------------------\r
+\r
+ public static ThreadLog deserialize(File file) throws IOException {\r
+ DataInputStream in = null;\r
+ try {\r
+ in = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));\r
+ ThreadLog log = new ThreadLog();\r
+ log.doDeserialize(in);\r
+ return log;\r
+ } finally {\r
+ if (in != null) {\r
+ in.close();\r
+ }\r
+ }\r
+ }\r
+\r
+ public static ThreadLog deserialize(DataInput in) throws IOException {\r
+ ThreadLog log = new ThreadLog();\r
+ log.doDeserialize(in);\r
+ return log;\r
+ }\r
+\r
+ private void doDeserialize(DataInput in) throws IOException {\r
+ String hdr = in.readUTF();\r
+ if (!HDR.equals(hdr)) {\r
+ throw new StreamCorruptedException("invalid header '" + hdr + "', expected " + HDR);\r
+ }\r
+ int ver = in.readInt();\r
+ if (ver == CURRENT_VERSION) {\r
+ int taskCount = in.readInt();\r
+ for (int i = 0; i < taskCount; ++i) {\r
+ String task = in.readUTF();\r
+ double beginTime = in.readDouble();\r
+ double endTime = in.readDouble();\r
+ long threadId = in.readLong();\r
+ tasks.add(task);\r
+ times.add(beginTime);\r
+ times.add(endTime);\r
+ threads.add(threadId);\r
+ }\r
+ } else {\r
+ throw new StreamCorruptedException("unrecognized log version: " + ver);\r
+ }\r
+ }\r
+\r
+ public void serialize(File file) throws IOException {\r
+ DataOutputStream out = null;\r
+ try {\r
+ out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));\r
+ serialize(out);\r
+ } finally {\r
+ if (out != null) {\r
+ out.close();\r
+ }\r
+ }\r
+ }\r
+\r
+ public void serialize(DataOutput out) throws IOException {\r
+ out.writeUTF(HDR);\r
+ out.writeInt(CURRENT_VERSION);\r
+ int len = tasks.size();\r
+ out.writeInt(len);\r
+ for (int i = 0; i < len; ++i) {\r
+ out.writeUTF(tasks.get(i));\r
+ out.writeDouble(times.getQuick(i*2));\r
+ out.writeDouble(times.getQuick(i*2+1));\r
+ out.writeLong(threads.getQuick(i));\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.internal;\r
+\r
+import org.eclipse.core.runtime.Plugin;\r
+import org.osgi.framework.BundleContext;\r
+import org.simantics.threadlog.ui.ThreadLogController;\r
+\r
+/**\r
+ * The activator class controls the plug-in life cycle\r
+ */\r
+public class Activator extends Plugin {\r
+\r
+ // The plug-in ID\r
+ public static final String PLUGIN_ID = "org.simantics.threadlog";\r
+\r
+ // The shared instance\r
+ private static Activator plugin;\r
+ \r
+ /**\r
+ * The constructor\r
+ */\r
+ public Activator() {\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext)\r
+ */\r
+ public void start(BundleContext context) throws Exception {\r
+ super.start(context);\r
+ plugin = this;\r
+ \r
+ ThreadLogController.start();\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc)\r
+ * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)\r
+ */\r
+ public void stop(BundleContext context) throws Exception {\r
+ plugin = null;\r
+ super.stop(context);\r
+ }\r
+\r
+ /**\r
+ * Returns the shared instance\r
+ *\r
+ * @return the shared instance\r
+ */\r
+ public static Activator getDefault() {\r
+ return plugin;\r
+ }\r
+\r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.ui;\r
+\r
+public class Interval implements Comparable<Interval> {\r
+ String text;\r
+ double begin;\r
+ double end;\r
+ \r
+ public Interval(String text, double begin, double end) {\r
+ this.text = text;\r
+ this.begin = begin;\r
+ this.end = end;\r
+ }\r
+\r
+ @Override\r
+ public int compareTo(Interval o) {\r
+ return Double.compare(begin, o.begin);\r
+ } \r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.ui;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+public class Lane {\r
+ List<Interval> intervals = new ArrayList<Interval>();\r
+ \r
+ public void addInterval(Interval interval) {\r
+ intervals.add(interval);\r
+ }\r
+ \r
+ public double getEnd() {\r
+ return intervals.get(intervals.size()-1).end;\r
+ }\r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.ui;\r
+\r
+import java.awt.GridLayout;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.ActionListener;\r
+import java.io.IOException;\r
+\r
+import javax.swing.JButton;\r
+import javax.swing.JFileChooser;\r
+import javax.swing.JFrame;\r
+import javax.swing.filechooser.FileNameExtensionFilter;\r
+\r
+import org.simantics.threadlog.ThreadLog;\r
+\r
+public class ThreadLogController extends JFrame {\r
+\r
+ private static final long serialVersionUID = -2487997716157625672L;\r
+ \r
+ boolean isLogging = false;\r
+ JButton logButton;\r
+ JButton loadButton;\r
+\r
+ public ThreadLogController() {\r
+ super("Thread log controller");\r
+ setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);\r
+\r
+ logButton = new JButton("Start logging");\r
+ logButton.addActionListener(new ActionListener() {\r
+\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ if(isLogging) {\r
+ logButton.setText("Start logging");\r
+ isLogging = false;\r
+ ThreadLog log = ThreadLog.setDefaultThreadLog(null);\r
+\r
+ ThreadLogVisualizer visualizer = new ThreadLogVisualizer();\r
+ visualizer.setLog(log);\r
+ visualizer.setVisible(true);\r
+ }\r
+ else {\r
+ logButton.setText("Stop logging");\r
+ isLogging = true;\r
+ ThreadLog.setDefaultThreadLog(new ThreadLog());\r
+ }\r
+ }\r
+\r
+ });\r
+ loadButton = new JButton("Load log");\r
+ loadButton.addActionListener(new ActionListener() {\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ JFileChooser chooser = new JFileChooser();\r
+ FileNameExtensionFilter filter = new FileNameExtensionFilter(\r
+ "Thread Logs (*.tlog)", "tlog", "tlog");\r
+ chooser.setFileFilter(filter);\r
+ int returnVal = chooser.showOpenDialog(ThreadLogController.this);\r
+ if (returnVal != JFileChooser.APPROVE_OPTION)\r
+ return;\r
+\r
+ try {\r
+ ThreadLog log = ThreadLog.deserialize(chooser.getSelectedFile());\r
+ ThreadLogVisualizer visualizer = new ThreadLogVisualizer();\r
+ visualizer.setLog(log);\r
+ visualizer.setVisible(true);\r
+ } catch (IOException ex) {\r
+ ex.printStackTrace();\r
+ }\r
+ }\r
+ });\r
+ getContentPane().setLayout(new GridLayout(2, 1));\r
+ getContentPane().add(logButton);\r
+ getContentPane().add(loadButton);\r
+\r
+ setSize(200, 100);\r
+ }\r
+\r
+ public static void start() {\r
+ javax.swing.SwingUtilities.invokeLater(new Runnable() {\r
+ public void run() {\r
+ new ThreadLogController().setVisible(true);\r
+ }\r
+ });\r
+ }\r
+\r
+ public static void main(String[] args) {\r
+ start();\r
+ }\r
+\r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.ui;\r
+\r
+import gnu.trove.TLongObjectHashMap;\r
+import gnu.trove.TObjectProcedure;\r
+\r
+import java.awt.BorderLayout;\r
+import java.awt.event.ActionEvent;\r
+import java.awt.event.KeyEvent;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+\r
+import javax.swing.AbstractAction;\r
+import javax.swing.JButton;\r
+import javax.swing.JFileChooser;\r
+import javax.swing.JFrame;\r
+import javax.swing.JToolBar;\r
+import javax.swing.filechooser.FileNameExtensionFilter;\r
+\r
+import org.simantics.threadlog.Task;\r
+import org.simantics.threadlog.ThreadLog;\r
+\r
+public class ThreadLogVisualizer extends JFrame {\r
+\r
+ private static final long serialVersionUID = 6250996509358338304L;\r
+ \r
+ TimeLineViewer viewer = new TimeLineViewer();\r
+ JToolBar toolbar = new JToolBar("Thread Log Visualizer Tools");\r
+ JButton saveButton = new JButton(new SaveAction());\r
+\r
+ ThreadLog currentLog;\r
+\r
+ public ThreadLogVisualizer() {\r
+ super("Thread log visualizer");\r
+ setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);\r
+ setSize(800, 600);\r
+\r
+ getContentPane().setLayout(new BorderLayout());\r
+ getContentPane().add(toolbar, BorderLayout.NORTH);\r
+ getContentPane().add(viewer, BorderLayout.CENTER);\r
+\r
+ toolbar.add(saveButton);\r
+ }\r
+\r
+ class SaveAction extends AbstractAction {\r
+ private static final long serialVersionUID = 1L;\r
+\r
+ public SaveAction() {\r
+ super("Save");\r
+ putValue(SHORT_DESCRIPTION, "Save the Current Log");\r
+ putValue(MNEMONIC_KEY, KeyEvent.VK_S);\r
+ }\r
+\r
+ @Override\r
+ public void actionPerformed(ActionEvent e) {\r
+ JFileChooser chooser = new JFileChooser();\r
+ FileNameExtensionFilter filter = new FileNameExtensionFilter(\r
+ "Thread Logs (*.tlog)", "tlog", "tlog");\r
+ chooser.setFileFilter(filter);\r
+ int returnVal = chooser.showSaveDialog(ThreadLogVisualizer.this);\r
+ if (returnVal != JFileChooser.APPROVE_OPTION)\r
+ return;\r
+\r
+ try {\r
+ currentLog.serialize(chooser.getSelectedFile());\r
+ } catch (IOException ex) {\r
+ ex.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+\r
+ public void setLog(ThreadLog log) {\r
+ this.currentLog = log;\r
+\r
+ String[] tasks = log.getTasks();\r
+ double[] times = log.getTimes();\r
+\r
+ // Relativize to the first task\r
+ double minTime = Double.POSITIVE_INFINITY;\r
+ double maxTime = Double.NEGATIVE_INFINITY;\r
+\r
+ for(int i=0;i<times.length;i+=2) {\r
+ double temp = times[i];\r
+ if(temp < minTime)\r
+ minTime = temp;\r
+\r
+ temp = times[i+1];\r
+ if(temp > maxTime)\r
+ maxTime = temp;\r
+ }\r
+ for(int i=0;i<times.length;++i)\r
+ times[i] -= minTime;\r
+ maxTime -= minTime;\r
+\r
+ // Group intervals by thread\r
+ TLongObjectHashMap<ArrayList<Interval>> intervals = new TLongObjectHashMap<ArrayList<Interval>>();\r
+ long[] threads = log.getThreads();\r
+ for(int i=0;i<tasks.length;++i) {\r
+ long thread = threads[i];\r
+ ArrayList<Interval> in = intervals.get(thread);\r
+ if(in == null) {\r
+ in = new ArrayList<Interval>();\r
+ intervals.put(thread, in);\r
+ }\r
+ in.add(new Interval(tasks[i], times[i*2], times[i*2+1]));\r
+ }\r
+\r
+ // Create lanes\r
+ viewer.clear();\r
+ intervals.forEachValue(new TObjectProcedure<ArrayList<Interval>>() {\r
+\r
+ @Override\r
+ public boolean execute(ArrayList<Interval> intervals) {\r
+ Collections.sort(intervals);\r
+ ArrayList<Lane> lanes = new ArrayList<Lane>();\r
+\r
+ int curLaneId = -1;\r
+ Lane curLane = null;\r
+ for(Interval in : intervals) {\r
+ if(curLane == null || in.begin < curLane.getEnd()) {\r
+ ++curLaneId;\r
+ if(curLaneId < lanes.size())\r
+ curLane = lanes.get(curLaneId);\r
+ else {\r
+ curLane = new Lane();\r
+ lanes.add(curLane);\r
+ }\r
+ }\r
+ else {\r
+ while(curLaneId > 0) {\r
+ Lane prevLane = lanes.get(curLaneId-1);\r
+ if(prevLane.getEnd() > in.begin)\r
+ break;\r
+ --curLaneId;\r
+ curLane = prevLane;\r
+ }\r
+ }\r
+ curLane.addInterval(in);\r
+ }\r
+\r
+ for(Lane lane : lanes)\r
+ viewer.addLane(lane);\r
+ return true;\r
+ }\r
+\r
+ });\r
+\r
+ // update viewer\r
+ viewer.repaint();\r
+ }\r
+\r
+ public void saveImage() {\r
+\r
+ }\r
+\r
+ public static void main(String[] args) throws Exception {\r
+ ThreadLog.setDefaultThreadLog(new ThreadLog());\r
+\r
+ {\r
+ Task A = ThreadLog.BEGIN("A");\r
+ Thread.sleep(200);\r
+ Task B = ThreadLog.BEGIN("B");\r
+ Thread.sleep(100);\r
+ B.end();\r
+ Thread.sleep(100);\r
+ Task C = ThreadLog.BEGIN("C");\r
+ Thread.sleep(100);\r
+ C.end();\r
+ A.end();\r
+ }\r
+\r
+ ThreadLogVisualizer vis = new ThreadLogVisualizer();\r
+ vis.setLog(ThreadLog.setDefaultThreadLog(null));\r
+ vis.setVisible(true);\r
+ }\r
+}\r
--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.threadlog.ui;\r
+\r
+import java.awt.Color;\r
+import java.awt.Dimension;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.Shape;\r
+import java.awt.event.MouseAdapter;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.event.MouseMotionAdapter;\r
+import java.awt.event.MouseWheelEvent;\r
+import java.awt.event.MouseWheelListener;\r
+import java.awt.geom.Rectangle2D;\r
+import java.awt.geom.RoundRectangle2D;\r
+import java.text.DecimalFormat;\r
+import java.text.Format;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Timer;\r
+import java.util.TimerTask;\r
+\r
+import javax.swing.JPanel;\r
+\r
+public class TimeLineViewer extends JPanel {\r
+\r
+ private static final long serialVersionUID = -7410066541298449720L;\r
+ \r
+ public static long TOOL_TIP_DELAY = 500;\r
+ public static double MIN_GRID_LINE_SEPARATION = 15;\r
+ \r
+ // Time line data\r
+ List<Lane> lanes = new ArrayList<Lane>();\r
+ \r
+ // For panning and zooming\r
+ double xScale = 100.0;\r
+ double xOffset = 0.0;\r
+ double yOffset = 0.0;\r
+ int oldX;\r
+ int oldY;\r
+ \r
+ // Tool tips\r
+ Timer toolTipTimer = new Timer();\r
+ class ToolTipTask extends TimerTask {\r
+\r
+ @Override\r
+ public void run() {\r
+ System.out.println("show tooltip"); \r
+ }\r
+ \r
+ }\r
+ \r
+ public TimeLineViewer() {\r
+ addMouseListener(new MouseAdapter() {\r
+ @Override\r
+ public void mousePressed(MouseEvent e) {\r
+ oldX = e.getX();\r
+ oldY = e.getY();\r
+ }\r
+ });\r
+ addMouseMotionListener(new MouseMotionAdapter() { \r
+ @Override\r
+ public void mouseDragged(MouseEvent e) {\r
+ int curX = e.getX();\r
+ int curY = e.getY();\r
+ int diffX = curX - oldX;\r
+ int diffY = curY - oldY;\r
+ oldX = curX;\r
+ oldY = curY;\r
+ \r
+ xOffset -= diffX/xScale;\r
+ yOffset -= diffY;\r
+ repaint();\r
+ }\r
+ TimerTask toolTipTask;\r
+ @Override\r
+ public void mouseMoved(MouseEvent e) {\r
+ if(toolTipTask != null)\r
+ toolTipTask.cancel();\r
+ toolTipTask = new ToolTipTask();\r
+ toolTipTimer.schedule(toolTipTask, TOOL_TIP_DELAY);\r
+ }\r
+ });\r
+ addMouseWheelListener(new MouseWheelListener() { \r
+ @Override\r
+ public void mouseWheelMoved(MouseWheelEvent e) {\r
+ xOffset += e.getX() / xScale;\r
+ xScale *= Math.pow(1.25, -e.getWheelRotation());\r
+ xOffset -= e.getX() / xScale;\r
+ repaint();\r
+ }\r
+ });\r
+ }\r
+ \r
+ public void addLane(Lane lane) {\r
+ lanes.add(lane);\r
+ }\r
+ \r
+ public void clear() {\r
+ lanes.clear();\r
+ }\r
+ \r
+ public void paintIntervals(Graphics2D g) { \r
+ for(int laneId=0;laneId < lanes.size();++laneId) {\r
+ Lane lane = lanes.get(laneId);\r
+ \r
+ double y = 35.0*laneId - yOffset;\r
+ double height = 30.0;\r
+ \r
+ for(Interval interval : lane.intervals) {\r
+ double x = (interval.begin - xOffset) * xScale; \r
+ double width = (interval.end - interval.begin) * xScale;\r
+ \r
+ Shape shape = new RoundRectangle2D.Double(x, y, width, height, 10.0, 10.0);\r
+ g.setColor(Color.WHITE);\r
+ g.fill(shape);\r
+ g.setColor(Color.BLACK);\r
+ g.draw(shape);\r
+ \r
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(interval.text, g);\r
+ if(bounds.getWidth() < width)\r
+ g.drawString(interval.text, (float)(x+0.5*(width-bounds.getWidth())), (float)(y+20.0));\r
+ }\r
+ }\r
+ }\r
+ \r
+ public void paintGrid(Graphics2D g) {\r
+ Dimension dim = getSize();\r
+ \r
+ g.setBackground(Color.WHITE);\r
+ g.clearRect(0, 0, dim.width, dim.height);\r
+ \r
+ double stepsize = 0.001;\r
+ double majorStepsize;\r
+ while(true) {\r
+ majorStepsize = stepsize * 5.0;\r
+ if(stepsize * xScale >= MIN_GRID_LINE_SEPARATION)\r
+ break;\r
+ stepsize = majorStepsize;\r
+ majorStepsize = stepsize * 2.0;\r
+ if(stepsize * xScale >= MIN_GRID_LINE_SEPARATION)\r
+ break;\r
+ stepsize = majorStepsize;\r
+ }\r
+ \r
+ double firstP = Math.ceil(xOffset / stepsize) * stepsize;\r
+ double lastP = Math.floor((xOffset + dim.width / xScale) / stepsize) * stepsize;\r
+ if(firstP < 0.0)\r
+ firstP = 0.0;\r
+\r
+ Format format = new DecimalFormat();\r
+ for(double p = firstP;p <= lastP; p+=stepsize) {\r
+ int x = (int)((p - xOffset) * xScale);\r
+ if(Math.abs(p/majorStepsize - Math.round(p/majorStepsize)) < 1e-3) {\r
+ g.setColor(Color.BLACK);\r
+ String text = format.format(p);\r
+ Rectangle2D bounds = g.getFontMetrics().getStringBounds(text, g);\r
+ g.drawString(text, (float)(x-bounds.getWidth()*0.5), 12.0f); \r
+ }\r
+ else\r
+ g.setColor(Color.LIGHT_GRAY);\r
+ g.drawLine(x, 14, x, (int)dim.getHeight());\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void paint(Graphics _g) {\r
+ Graphics2D g = (Graphics2D)_g; \r
+ \r
+ paintGrid(g);\r
+ paintIntervals(g);\r
+ }\r
+ \r
+}\r
output.. = bin/\r
bin.includes = META-INF/,\\r
.\r
-javacSource=1.6\r
-javacTarget=1.6\r
-
\ No newline at end of file
+\r
output.. = bin/\r
bin.includes = META-INF/,\\r
.,\\r
- plugin.xml,\\r
- scl/\r
+ plugin.xml\r
<version>1.0.0-SNAPSHOT</version>\r
</parent>\r
\r
+ <build>\r
+ <plugins>\r
+ <plugin>\r
+ <groupId>org.eclipse.tycho</groupId>\r
+ <artifactId>tycho-compiler-plugin</artifactId>\r
+ <version>0.25.0</version>\r
+ <configuration>\r
+ <compilerArgument>-err:-forbidden</compilerArgument>\r
+ </configuration>\r
+ </plugin>\r
+ </plugins>\r
+ </build>\r
+\r
<modules>\r
<module>com.famfamfam.silk</module>\r
<module>org.simantics</module>\r
<module>org.simantics.diagram.profile</module>\r
<module>org.simantics.document</module>\r
<module>org.simantics.document.base.ontology</module>\r
+ <module>org.simantics.document.linking.ontology</module>\r
<module>org.simantics.document.linking.ui</module>\r
<module>org.simantics.document.ontology</module>\r
<module>org.simantics.document.server</module>\r
<module>org.simantics.modeling.template2d.ontology</module>\r
<module>org.simantics.modeling.template2d.ui</module>\r
<module>org.simantics.modeling.ui</module>\r
+ <module>org.simantics.nativemem</module>\r
<module>org.simantics.objmap2</module>\r
<module>org.simantics.platform.ui.ontology</module>\r
<module>org.simantics.project</module>\r
<module>org.simantics.scl.commands</module>\r
<module>org.simantics.scl.compiler</module>\r
<module>org.simantics.scl.compiler.dummy</module>\r
+ <module>org.simantics.scl.data</module>\r
<module>org.simantics.scl.db</module>\r
<module>org.simantics.scl.expressions</module>\r
<module>org.simantics.scl.osgi</module>\r
<module>org.simantics.structural.ui</module>\r
<module>org.simantics.structural2</module>\r
<module>org.simantics.team.ui</module>\r
+ <module>org.simantics.threadlog</module>\r
<module>org.simantics.trend</module>\r
<module>org.simantics.ui</module>\r
<module>org.simantics.user.ontology</module>\r
<module>org.simantics.workbench</module>\r
<module>org.simantics.workbench.ontology</module>\r
<module>org.simantics.workbench.search</module>\r
+ <module>winterwell.markdown</module>\r
</modules>\r
-</project>
\ No newline at end of file
+</project>\r
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="lib" path="lib/markdownj-1.0.2b4-0.3.0.jar"/>
+ <classpathentry kind="lib" path="lib/net.sf.paperclips_1.0.1.jar"/>
+ <classpathentry kind="lib" path="lib/winterwell.utils.jar"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>winterwell.markdown</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.m2e.core.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.m2e.core.maven2Nature</nature>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: Markdown
+Bundle-SymbolicName: winterwell.markdown;singleton:=true
+Bundle-Version: 1.2.0.qualifier
+Bundle-Activator: winterwell.markdown.Activator
+Bundle-Vendor: Winterwell Associates Ltd
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime,
+ org.eclipse.ui.editors,
+ org.eclipse.jface.text,
+ org.eclipse.core.resources,
+ org.eclipse.ui.views,
+ org.eclipse.jface,
+ org.eclipse.swt,
+ org.eclipse.ui.workbench
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Bundle-ActivationPolicy: lazy
+Import-Package: org.eclipse.core.internal.resources,
+ org.eclipse.jface.text,
+ org.eclipse.ui.texteditor
+Bundle-ClassPath: .,target/classes,lib/markdownj-1.0.2b4-0.3.0.jar,
+ lib/winterwell.utils.jar,
+ lib/net.sf.paperclips_1.0.1.jar
+Export-Package: com.petebevin.markdown,
+ com.petebevin.markdown.test,
+ net.sf.paperclips,
+ net.sf.paperclips.decorator,
+ winterwell.markdown,
+ winterwell.markdown.editors,
+ winterwell.markdown.pagemodel,
+ winterwell.markdown.preferences,
+ winterwell.markdown.views,
+ winterwell.utils,
+ winterwell.utils.containers,
+ winterwell.utils.gui,
+ winterwell.utils.io,
+ winterwell.utils.reporting,
+ winterwell.utils.threads,
+ winterwell.utils.time,
+ winterwell.utils.web
--- /dev/null
+source.. = src/
+bin.includes = META-INF/,\
+ plugin.xml,\
+ icons/,\
+ .,\
+ lib/
+src.includes = src/,\
+ pom.xml,\
+ plugin.xml,\
+ icons/,\
+ lib/,\
+ .project,\
+ .classpath,\
+ META-INF/,\
+ build.properties
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.7"?>
+<plugin>
+
+ <extension
+ point="org.eclipse.ui.editors">
+ <editor
+ name="Markdown Editor"
+ extensions="txt,md,mdown,markdown,mdwn"
+ icon="icons/notepad.gif"
+ contributorClass="winterwell.markdown.editors.ActionBarContributor"
+ class="winterwell.markdown.editors.MarkdownEditor"
+ id="winterwell.markdown.editors.MarkdownEditor"
+ default="true">
+ </editor>
+
+ <editor
+ class="winterwell.markdown.editors.MarkdownEditor"
+ contributorClass="winterwell.markdown.editors.ActionBarContributor"
+ default="true"
+ extensions="litcoffee"
+ icon="icons/coffee.png"
+ id="org.nodeclipse.ui.editors.LitCoffeeEditor"
+ name="LitCoffee (Markdown) Editor">
+ </editor>
+
+ </extension>
+
+ <extension
+ point="org.eclipse.ui.menus">
+ <menuContribution
+ allPopups="false"
+ locationURI="toolbar:winterwell.markdown.views.MarkdownPreview">
+ <command
+ commandId="winterwell.markdown.commands.OpenGfmView"
+ icon="icons/github-cat_yellow.png"
+ style="push">
+ </command>
+ <command
+ commandId="winterwell.markdown.commands.Preferences"
+ icon="icons/settings16_yellow.png"
+ style="push">
+ </command>
+ </menuContribution>
+ <menuContribution
+ allPopups="false"
+ locationURI="toolbar:org.eclipse.ui.main.toolbar?after=additions">
+ <toolbar
+ id="winterwell.markdown.MarkdownEditor">
+ <command
+ commandId="winterwell.markdown.commands.OpenMdView"
+ icon="icons/notepad.gif"
+ style="push">
+ <visibleWhen
+ checkEnabled="true">
+ </visibleWhen>
+ </command>
+ </toolbar>
+ </menuContribution>
+ </extension>
+ <extension
+ point="org.eclipse.ui.commands">
+ <command
+ defaultHandler="winterwell.markdown.commands.OpenGfmView"
+ id="winterwell.markdown.commands.OpenGfmView"
+ name="Open GitHub Flavored Markdown View">
+ </command>
+ <command
+ id="winterwell.markdown.commands.OpenMdView"
+ name="Open Markdown View">
+ </command>
+ <command
+ defaultHandler="winterwell.markdown.commands.Preferences"
+ id="winterwell.markdown.commands.Preferences"
+ name="Preferences">
+ </command>
+ </extension>
+
+ <extension
+ point="org.eclipse.ui.views">
+ <category
+ id="winterwell.markdown"
+ name="Markdown"/>
+ <view
+ category="winterwell.markdown"
+ class="winterwell.markdown.views.MarkdownPreview"
+ icon="icons/notepad.gif"
+ id="winterwell.markdown.views.MarkdownPreview"
+ name="Markdown View"/>
+ </extension>
+ <extension
+ point="org.eclipse.ui.preferencePages">
+ <page
+ class="winterwell.markdown.preferences.MarkdownPreferencePage"
+ id="winterwell.markdown.preferences.MarkdownPreferencePage"
+ name="Markdown"
+ category="org.eclipse.ui.preferencePages.GeneralTextEditor">
+ <keywordReference
+ id="winterwell.markdown.prefsKeywordReference">
+ </keywordReference>
+ </page>
+ </extension>
+ <extension
+ point="org.eclipse.ui.keywords">
+ <keyword
+ id="winterwell.markdown.prefsKeywordReference"
+ label="word wrapping tool tags">
+ </keyword>
+ </extension>
+ <extension
+ point="org.eclipse.ui.commands">
+ <command
+ defaultHandler="winterwell.markdown.editors.FormatAction"
+ description="Format the paragraph under the caret to fit the print margins by inserting/removing line-breaks"
+ id="winterwell.markdown.formatParagraphCommand"
+ name="Format paragraph">
+ </command>
+ </extension>
+ <extension
+ point="org.eclipse.ui.bindings">
+ <key
+ commandId="winterwell.markdown.formatParagraphCommand"
+ contextId="org.eclipse.ui.contexts.window"
+ schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
+ sequence="Alt+Q">
+ </key>
+ </extension>
+ <extension
+ point="org.eclipse.ui.actionSets">
+ <actionSet
+ id="winterwell.markdown.actionSet"
+ label="winterwell.markdown.actionSet">
+ <action
+ class="winterwell.markdown.editors.FormatAction"
+ definitionId="winterwell.markdown.formatParagraphCommand"
+ id="winterwell.markdown.formatParagraphAction"
+ label="Format paragraph"
+ menubarPath="edit"
+ style="push">
+ </action>
+ </actionSet>
+ </extension>
+ <extension
+ point="org.eclipse.ui.handlers">
+ <handler
+ class="winterwell.markdown.commands.OpenMdView"
+ commandId="winterwell.markdown.commands.OpenMdView">
+ <activeWhen>
+ <with
+ variable="activeEditorId">
+ <equals
+ value="winterwell.markdown.editors.MarkdownEditor">
+ </equals>
+ </with>
+ </activeWhen>
+ </handler>
+ </extension>
+
+</plugin>
--- /dev/null
+package winterwell.markdown;
+
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+import winterwell.markdown.preferences.MarkdownPreferencePage;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "winterwell.markdown";
+
+ // The shared instance
+ private static Activator plugin;
+
+ /**
+ * The constructor
+ */
+ public Activator() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ doInstall();
+ MarkdownPreferencePage.setDefaultPreferences(getPreferenceStore());
+ }
+
+ // ?? Have this method called by start(), saving a reminder so it doesn't repeat itself??
+ private void doInstall() {
+ // ??Try to make this the default for file types -- but is this possible??
+ // c.f. http://stackoverflow.com/questions/15877123/eclipse-rcp-programmatically-associate-file-type-with-editor
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+}
--- /dev/null
+package winterwell.markdown;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * Nodeclipse Log Util
+ * @author Lamb Gao, Paul Verest
+ */
+public class LogUtil {
+
+ public static void info(String message) {
+ log(IStatus.INFO, IStatus.OK, message, null);
+ }
+
+ public static void error(Throwable exception) {
+ error("Unexpected Exception", exception);
+ }
+
+ public static void error(String message) {
+ error(message, null);
+ }
+
+ public static void error(String message, Throwable exception) {
+ log(IStatus.ERROR, IStatus.ERROR, message, exception);
+ }
+
+ public static void log(int severity, int code, String message, Throwable exception) {
+ log(createStatus(severity, code, message, exception));
+ }
+
+ public static IStatus createStatus(int severity, int code, String message, Throwable exception) {
+ return new Status(severity, Activator.PLUGIN_ID, code, message, exception);
+ }
+
+ public static void log(IStatus status) {
+ ILog log = Activator.getDefault().getLog();
+ log.log(status);
+ }
+}
--- /dev/null
+/**\r
+ * Basic String manipulation utilities.\r
+ * (c) Winterwell 2010 and ThinkTank Mathematics 2007\r
+ */\r
+package winterwell.markdown;\r
+\r
+import java.math.BigInteger;\r
+import java.security.MessageDigest;\r
+import java.security.NoSuchAlgorithmException;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.regex.Pattern;\r
+\r
+import winterwell.utils.Mutable;\r
+import winterwell.utils.containers.Pair;\r
+\r
+/**\r
+ * A collection of general-purpose String handling methods.\r
+ * \r
+ * @author daniel.winterstein\r
+ */\r
+public final class StringMethods {\r
+\r
+ /**\r
+ * Removes xml tags, comment blocks and script blocks.\r
+ * \r
+ * @param page\r
+ * @return the page with all xml tags removed.\r
+ */\r
+ public static String stripTags(String page) {\r
+ // This code is rather ugly, but it does the job\r
+ StringBuilder stripped = new StringBuilder(page.length());\r
+ boolean inTag = false;\r
+ // Comment blocks and script blocks are given special treatment\r
+ boolean inComment = false;\r
+ boolean inScript = false;\r
+ // Go through the text\r
+ for (int i = 0; i < page.length(); i++) {\r
+ char c = page.charAt(i);\r
+ // First check whether we are ignoring text\r
+ if (inTag) {\r
+ if (c == '>')\r
+ inTag = false;\r
+ } else if (inComment) {\r
+ if (c == '>' && page.charAt(i - 1) == '-'\r
+ && page.charAt(i - 1) == '-') {\r
+ inComment = false;\r
+ }\r
+ } else if (inScript) {\r
+ if (c == '>' && page.substring(i - 7, i).equals("/script")) {\r
+ inScript = false;\r
+ }\r
+ } else {\r
+ // Check for the start of a tag - looks for '<' followed by any\r
+ // non-whitespace character\r
+ if (c == '<' && !Character.isWhitespace(page.charAt(i + 1))) {\r
+ // Comment, script-block or tag?\r
+ if (page.charAt(i + 1) == '!' && page.charAt(i + 2) == '-'\r
+ && page.charAt(i + 3) == '-') {\r
+ inComment = true;\r
+ } else if (i + 8 < page.length()\r
+ && page.substring(i + 1, i + 7).equals("script")) {\r
+ inScript = true;\r
+ i += 7;\r
+ } else\r
+ inTag = true; // Normal tag by default\r
+ } else {\r
+ // Append all non-tag chars\r
+ stripped.append(c);\r
+ }\r
+ } // end if...\r
+ }\r
+ return stripped.toString();\r
+ }\r
+ \r
+ /**\r
+ * The local line-end string. \n on unix, \r\n on windows, \r on mac.\r
+ */\r
+ public static final String LINEEND = System.getProperty("line.separator");\r
+\r
+ /**\r
+ * @param s\r
+ * @return A version of s where the first letter is uppercase and all others\r
+ * are lowercase\r
+ */\r
+ public static final String capitalise(final String s) {\r
+ return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();\r
+ }\r
+\r
+ /**\r
+ * Convert all line breaks into the system line break.\r
+ */\r
+ public static final String convertLineBreaks(String text) {\r
+ return convertLineBreaks(text, LINEEND);\r
+ }\r
+\r
+ /**\r
+ * Convert all line breaks into the specified line break.\r
+ */\r
+ public static final String convertLineBreaks(String text, String br) {\r
+ text = text.replaceAll("\r\n", br);\r
+ text = text.replaceAll("\r", br);\r
+ text = text.replaceAll("\n", br);\r
+ return text;\r
+ }\r
+\r
+ /**\r
+ * @param string\r
+ * @param character\r
+ * @return the number of times character appears in the string\r
+ * @author Sam Halliday\r
+ */\r
+ static public int countCharsInString(String string, char character) {\r
+ int count = 0;\r
+ for (char c : string.toCharArray()) {\r
+ if (c == character) {\r
+ count++;\r
+ }\r
+ }\r
+ return count;\r
+ }\r
+\r
+ /**\r
+ * \r
+ * E.g.\r
+ * <code>findEnclosingRegion("text with a [region] inside", 15, '[', ']')</code>\r
+ * is (??,??)\r
+ * \r
+ * @param text\r
+ * @param offset\r
+ * @param start\r
+ * @param end\r
+ * @return the smallest enclosed region (including start and end chars, the\r
+ * 1st number is inclusive, the 2nd exclusive), or null if none. So\r
+ * text.subString(start,end) is the specified region\r
+ */\r
+ public static Pair<Integer> findEnclosingRegion(String text, int offset,\r
+ char startMarker, char endMarker) {\r
+ // Forward\r
+ int end = findEnclosingRegion2(text, offset, endMarker, 1);\r
+ if (end == -1)\r
+ return null;\r
+ end++; // end is exclusive\r
+ // Backward\r
+ int start = findEnclosingRegion2(text, offset, startMarker, -1);\r
+ if (start == -1)\r
+ return null;\r
+ // Sanity\r
+ assert text.substring(start, end).charAt(0) == startMarker;\r
+ assert text.substring(start, end).endsWith("" + endMarker);\r
+ // Done\r
+ return new Pair<Integer>(start, end);\r
+ }\r
+\r
+ private static int findEnclosingRegion2(String text, int offset,\r
+ char endMarker, int direction) {\r
+ while (offset > -1 && offset < text.length()) {\r
+ char c = text.charAt(offset);\r
+ if (c == endMarker)\r
+ return offset;\r
+ offset += direction;\r
+ }\r
+ return -1;\r
+ }\r
+\r
+ /**\r
+ * A convenience wrapper for\r
+ * {@link #findEnclosingRegion(String, int, char, char)} E.g. <code>\r
+ findEnclosingRegion("text with a [region] inside", 15, '[', ']') .equals("[region]");\r
+ </code>\r
+ * \r
+ * @param text\r
+ * @param offset\r
+ * @param start\r
+ * @param end\r
+ * @return the smallest enclosed region (including start and end chars), or\r
+ * null if none.\r
+ */\r
+ public static String findEnclosingText(String text, int offset,\r
+ char startMarker, char endMarker) {\r
+ Pair<Integer> region = findEnclosingRegion(text, offset, startMarker,\r
+ endMarker);\r
+ if (region == null)\r
+ return null;\r
+ String s = text.substring(region.first, region.second);\r
+ return s;\r
+ }\r
+\r
+ /**\r
+ * Format a block of text to use the given line-width. I.e. adjust the line\r
+ * breaks. Also known as <i>hard</i> line-wrapping. Paragraphs are\r
+ * recognised by a line of blank space between them (e.g. two returns).\r
+ * <p>\r
+ * Note: a side-effect of this method is that it converts all line-breaks\r
+ * into the local system's line-breaks. E.g. on Windows, \n will become \r\n\r
+ * \r
+ * @param text\r
+ * The text to format\r
+ * @param lineWidth\r
+ * The number of columns in a line. Typically 78 or 80.\r
+ * @param respectLeadingCharacters\r
+ * Can be null. If set, the specified leading characters will be\r
+ * copied if the line is split. Use with " \t" to keep indented\r
+ * paragraphs properly indented. Use with "> \t" to also handle\r
+ * email-style quoting. Note that respected leading characters\r
+ * receive no special treatment when they are used inside a\r
+ * paragraph.\r
+ * @return A copy of text, formatted to the given line-width.\r
+ * <p>\r
+ * TODO: recognise paragraphs by changes in the respected leading\r
+ * characters\r
+ */\r
+ public static String format(String text, int lineWidth, int tabWidth,\r
+ String respectLeadingCharacters) {\r
+ // Switch to Linux line breaks for easier internal workings\r
+ text = convertLineBreaks(text, "\n");\r
+ // Find paragraphs\r
+ List<String> paras = format2_splitParagraphs(text,\r
+ respectLeadingCharacters);\r
+ // Rebuild text\r
+ StringBuilder sb = new StringBuilder(text.length() + 10);\r
+ for (String p : paras) {\r
+ String fp = format3_oneParagraph(p, lineWidth, tabWidth,\r
+ respectLeadingCharacters);\r
+ sb.append(fp);\r
+ // Paragraphs end with a double line break\r
+ sb.append("\n\n");\r
+ }\r
+ // Pop the last line breaks\r
+ sb.delete(sb.length() - 2, sb.length());\r
+ // Convert line breaks to system ones\r
+ text = convertLineBreaks(sb.toString());\r
+ // Done\r
+ return text;\r
+ }\r
+\r
+ private static List<String> format2_splitParagraphs(String text,\r
+ String respectLeadingCharacters) {\r
+ List<String> paras = new ArrayList<String>();\r
+ Mutable.Int index = new Mutable.Int(0);\r
+ // TODO The characters prefacing this paragraph\r
+ String leadingChars = "";\r
+ while (index.value < text.length()) {\r
+ // One paragraph\r
+ boolean inSpace = false;\r
+ int start = index.value;\r
+ while (index.value < text.length()) {\r
+ char c = text.charAt(index.value);\r
+ index.value++;\r
+ if (!Character.isWhitespace(c)) {\r
+ inSpace = false;\r
+ continue;\r
+ }\r
+ // Line end?\r
+ if (c == '\r' || c == '\n') {\r
+ // // Handle MS Windows 2 character \r\n line breaks\r
+ // if (index.value < text.length()) {\r
+ // char c2 = text.charAt(index.value);\r
+ // if (c=='\r' && c2=='\n') index.value++; // Push on past\r
+ // the 2nd line break char\r
+ // }\r
+ // Double line end - indicating a paragraph break\r
+ if (inSpace)\r
+ break;\r
+ inSpace = true;\r
+ }\r
+ // TODO Other paragraph markers, spotted by a change in\r
+ // leadingChars\r
+ }\r
+ String p = text.substring(start, index.value);\r
+ paras.add(p);\r
+ }\r
+ // Done\r
+ return paras;\r
+ }\r
+\r
+ /**\r
+ * Format a block of text to fit the given line width\r
+ * \r
+ * @param p\r
+ * @param lineWidth\r
+ * @param tabWidth\r
+ * @param respectLeadingCharacters\r
+ * @return\r
+ */\r
+ private static String format3_oneParagraph(String p, int lineWidth,\r
+ int tabWidth, String respectLeadingCharacters) {\r
+ // Collect the reformatted paragraph\r
+ StringBuilder sb = new StringBuilder(p.length() + 10); // Allow for\r
+ // some extra\r
+ // line-breaks\r
+ // Get respected leading chars\r
+ String leadingChars = format4_getLeadingChars(p,\r
+ respectLeadingCharacters);\r
+ // First Line\r
+ sb.append(leadingChars);\r
+ int lineLength = leadingChars.length();\r
+ int index = leadingChars.length();\r
+ // Loop\r
+ while (index < p.length()) {\r
+ // Get the next word\r
+ StringBuilder word = new StringBuilder();\r
+ char c = p.charAt(index);\r
+ index++;\r
+ while (!Character.isWhitespace(c)) {\r
+ word.append(c);\r
+ if (index == p.length())\r
+ break;\r
+ c = p.charAt(index);\r
+ index++;\r
+ }\r
+ // Break the line if the word will not fit\r
+ if (lineLength + word.length() > lineWidth && lineLength != 0) {\r
+ trimEnd(sb);\r
+ sb.append('\n'); // lineEnd(sb);\r
+ // New line\r
+ sb.append(leadingChars);\r
+ lineLength = leadingChars.length();\r
+ }\r
+ // Add word\r
+ sb.append(word);\r
+ lineLength += word.length();\r
+ // Add the whitespace\r
+ if (index != p.length() && lineLength < lineWidth) {\r
+ if (c == '\n') {\r
+ c = ' ';\r
+ }\r
+ sb.append(c);\r
+ lineLength += (c == '\t') ? tabWidth : 1;\r
+ }\r
+ }\r
+ // A final trim\r
+ trimEnd(sb);\r
+ // Done\r
+ return sb.toString();\r
+ }\r
+\r
+ /**\r
+ * \r
+ * @param text\r
+ * @param respectLeadingCharacters\r
+ * Can be null\r
+ * @return The characters at the beginning of text which are respected. E.g.\r
+ * ("> Hello", " \t>") --> "> "\r
+ */\r
+ private static String format4_getLeadingChars(String text,\r
+ String respectLeadingCharacters) {\r
+ if (respectLeadingCharacters == null)\r
+ return "";\r
+ // Line-breaks cannot be respected\r
+ assert respectLeadingCharacters.indexOf('\n') == -1;\r
+ // Look for the first non-respected char\r
+ for (int i = 0; i < text.length(); i++) {\r
+ char c = text.charAt(i);\r
+ if (respectLeadingCharacters.indexOf(c) == -1) {\r
+ // Return the previous chars\r
+ return text.substring(0, i);\r
+ }\r
+ }\r
+ // All chars are respected\r
+ return text;\r
+ }\r
+\r
+ /**\r
+ * Ensure that line ends with the right line-end character(s)\r
+ */\r
+ public static final String lineEnd(String line) {\r
+ // strip possibly inappropriate line-endings\r
+ if (line.endsWith("\n")) {\r
+ line = line.substring(0, line.length() - 1);\r
+ }\r
+ if (line.endsWith("\r\n")) {\r
+ line = line.substring(0, line.length() - 2);\r
+ }\r
+ if (line.endsWith("\r")) {\r
+ line = line.substring(0, line.length() - 1);\r
+ }\r
+ // add in proper line end\r
+ if (!line.endsWith(LINEEND)) {\r
+ line += LINEEND;\r
+ }\r
+ return line;\r
+ }\r
+\r
+ /**\r
+ * Ensure that line ends with the right line-end character(s). This is more\r
+ * efficient than the version for Strings.\r
+ * \r
+ * @param line\r
+ */\r
+ public static final void lineEnd(final StringBuilder line) {\r
+ if (line.length() == 0) {\r
+ line.append(LINEEND);\r
+ return;\r
+ }\r
+ // strip possibly inappropriate line-endings\r
+ final char last = line.charAt(line.length() - 1);\r
+ if (last == '\n') {\r
+ if ((line.length() > 1) && (line.charAt(line.length() - 2) == '\r')) {\r
+ // \r\n\r
+ line.replace(line.length() - 2, line.length(), LINEEND);\r
+ return;\r
+ }\r
+ line.replace(line.length() - 1, line.length(), LINEEND);\r
+ return;\r
+ }\r
+ if (last == '\r') {\r
+ line.replace(line.length() - 1, line.length(), LINEEND);\r
+ return;\r
+ }\r
+ line.append(LINEEND);\r
+ return;\r
+ }\r
+\r
+\r
+ \r
+ /**\r
+ * @param string\r
+ * @return the MD5 sum of the string using the default charset. Null if\r
+ * there was an error in calculating the hash.\r
+ * @author Sam Halliday\r
+ */\r
+ public static String md5Hash(String string) {\r
+ MessageDigest md5 = null;\r
+ try {\r
+ md5 = MessageDigest.getInstance("MD5");\r
+ } catch (NoSuchAlgorithmException e) {\r
+ // ignore this exception, we know MD5 exists\r
+ }\r
+ md5.update(string.getBytes());\r
+ BigInteger hash = new BigInteger(1, md5.digest());\r
+ return hash.toString(16);\r
+ }\r
+\r
+ /**\r
+ * Removes HTML-style tags from a string.\r
+ * \r
+ * @param s\r
+ * a String from which to remove tags\r
+ * @return a string with all instances of <.*> removed.\r
+ */\r
+ public static String removeTags(String s) {\r
+ StringBuffer sb = new StringBuffer();\r
+ boolean inTag = false;\r
+ for (int i = 0; i < s.length(); i++) {\r
+ char c = s.charAt(i);\r
+ if (c == '<')\r
+ inTag = true;\r
+ if (!inTag)\r
+ sb.append(c);\r
+ if (c == '>')\r
+ inTag = false;\r
+ }\r
+ return sb.toString();\r
+ }\r
+\r
+ /**\r
+ * Repeat a character.\r
+ * \r
+ * @param c\r
+ * @param i\r
+ * @return A String consisting of i x c.\r
+ * @example assert repeat('-', 5).equals("-----");\r
+ */\r
+ public static String repeat(Character c, int i) {\r
+ StringBuilder dashes = new StringBuilder(i);\r
+ for (int j = 0; j < i; j++)\r
+ dashes.append(c);\r
+ return dashes.toString();\r
+ }\r
+\r
+ /**\r
+ * Split a piece of text into separate lines. The line breaks are left at\r
+ * the end of each line.\r
+ * \r
+ * @param text\r
+ * @return The individual lines in the text.\r
+ */\r
+ public static List<String> splitLines(String text) {\r
+ List<String> lines = new ArrayList<String>();\r
+ // Search for lines\r
+ int start = 0;\r
+ for (int i = 0; i < text.length(); i++) {\r
+ char c = text.charAt(i);\r
+ if (c == '\r' || c == '\n') {\r
+ // Handle MS Windows 2 character \r\n line breaks\r
+ if (i + 1 < text.length()) {\r
+ char c2 = text.charAt(i + 1);\r
+ if (c == '\r' && c2 == '\n')\r
+ i++;\r
+ }\r
+ // Get the line, with the line break\r
+ String line = text.substring(start, i + 1);\r
+ lines.add(line);\r
+ start = i + 1;\r
+ }\r
+ }\r
+ // Last one\r
+ if (start != text.length()) {\r
+ String line = text.substring(start);\r
+ lines.add(line);\r
+ }\r
+ return lines;\r
+ }\r
+\r
+ /**\r
+ * Remove <i>trailing</i> whitespace. c.f. String#trim() which removes\r
+ * leading and trailing whitespace.\r
+ * \r
+ * @param sb\r
+ */\r
+ private static void trimEnd(StringBuilder sb) {\r
+ while (true) {\r
+ // Get the last character\r
+ int i = sb.length() - 1;\r
+ if (i == -1)\r
+ return; // Quit if sb is empty\r
+ char c = sb.charAt(i);\r
+ if (!Character.isWhitespace(c))\r
+ return; // Finish?\r
+ sb.deleteCharAt(i); // Remove and continue\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Returns true if the string is just whitespace, or empty, or null.\r
+ * \r
+ * @param s\r
+ */\r
+ public static final boolean whitespace(final String s) {\r
+ if (s == null) {\r
+ return true;\r
+ }\r
+ for (int i = 0; i < s.length(); i++) {\r
+ final char c = s.charAt(i);\r
+ if (!Character.isWhitespace(c)) {\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * @param text\r
+ * @return the number of words in text. Uses a crude whitespace\r
+ * measure.\r
+ */\r
+ public static int wordCount(String text) {\r
+ String[] bits = text.split("\\W+");\r
+ int wc = 0;\r
+ for (String string : bits) {\r
+ if (!whitespace(string)) wc++;\r
+ }\r
+ return wc;\r
+ }\r
+\r
+}\r
--- /dev/null
+package winterwell.markdown.commands;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+
+import winterwell.markdown.LogUtil;
+
+public class OpenGfmView extends AbstractHandler {
+
+ @Override
+ public Object execute(final ExecutionEvent event) throws ExecutionException {
+ try {
+ IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+ String gfmViewId = "code.satyagraha.gfm.viewer.views.GfmView";
+ IViewPart gfmView = activePage.showView(gfmViewId);
+ activePage.activate(gfmView);
+ } catch (PartInitException e) {
+ showError(e);
+ } catch (Exception e) {
+ showError(e);
+ }
+ return null;
+ }
+
+ private void showError(Exception e) {
+ String title = "Exception while opening GitHub Flavored Markdown View";
+ String message = title+" (code.satyagraha.gfm.viewer.views.GfmView)"
+ +"\nCheck Error Log View and continue at https://github.com/winterstein/Eclipse-Markdown-Editor-Plugin/issues/42"
+ +"\n\nYou can also right-click file in Project Explorer"
+ +"\n and select \"Show in GFM view\".";
+ LogUtil.error(message, e);
+ MessageDialog.openError(Display.getDefault().getActiveShell(), title , message);
+ }
+}
--- /dev/null
+package winterwell.markdown.commands;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IViewPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+import winterwell.markdown.LogUtil;
+
+public class OpenMdView extends AbstractHandler {
+
+ @Override
+ public Object execute(final ExecutionEvent event) throws ExecutionException {
+ try {
+ IWorkbenchPage activePage = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage();
+ String mdViewId = "winterwell.markdown.views.MarkdownPreview";
+ IViewPart mdView = activePage.showView(mdViewId);
+ activePage.activate(mdView);
+ } catch (PartInitException e) {
+ showError(e);
+ } catch (Exception e) {
+ showError(e);
+ }
+ return null;
+ }
+
+ private void showError(Exception e) {
+ String title = "Exception while opening Markdown View";
+ String message = title+" (winterwell.markdown.views.MarkdownPreview)"
+ +"\nCheck Error Log View";
+ LogUtil.error(message, e);
+ MessageDialog.openError(Display.getDefault().getActiveShell(), title , message);
+ }
+}
--- /dev/null
+package winterwell.markdown.commands;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.jface.preference.PreferenceDialog;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.dialogs.PreferencesUtil;
+
+public class Preferences extends AbstractHandler {
+
+ @Override
+ public Object execute(final ExecutionEvent event) throws ExecutionException {
+ PreferenceDialog pref = PreferencesUtil.createPreferenceDialogOn(
+ PlatformUI.getWorkbench().getDisplay().getActiveShell(),
+ "winterwell.markdown.preferences.MarkdownPreferencePage", null, null);
+ pref.open();
+ return null;
+ }
+}
--- /dev/null
+package winterwell.markdown.editors;
+
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.editors.text.TextEditorActionContributor;
+
+import winterwell.markdown.views.MarkdownPreview;
+
+public class ActionBarContributor extends TextEditorActionContributor {
+
+ private static IEditorPart activeEditor = null;
+
+// IAction print = new PrintAction();
+
+ public void setActiveEditor(IEditorPart targetEditor) {
+ super.setActiveEditor(targetEditor);
+ activeEditor = targetEditor;
+ // add print action
+ IActionBars bars= getActionBars();
+ if (bars != null) {
+// todo bars.setGlobalActionHandler(ActionFactory.PRINT.getId(), print);
+// bars.updateActionBars();
+ }
+ // Update preview?
+ if (MarkdownPreview.preview != null) {
+ MarkdownPreview.preview.update();
+ }
+ }
+ public static IEditorPart getActiveEditor() {
+ return activeEditor;
+ }
+
+ @Override
+ public void contributeToMenu(IMenuManager menu) {
+ super.contributeToMenu(menu);
+ // Add format action
+ IMenuManager edit = menu.findMenuUsingPath("edit");
+ if (edit != null) {
+ edit.add(new FormatAction());
+ }
+ // Add Export action
+ IMenuManager file = menu.findMenuUsingPath("file");
+ if (file != null) {
+ file.appendToGroup("import.ext", new ExportHTMLAction());
+ }
+ }
+
+}
--- /dev/null
+package winterwell.markdown.editors;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.widgets.Display;
+
+public class ColorManager {
+
+ protected Map fColorTable = new HashMap(10);
+
+ public void dispose() {
+ Iterator e = fColorTable.values().iterator();
+ while (e.hasNext())
+ ((Color) e.next()).dispose();
+ }
+ public Color getColor(RGB rgb) {
+ Color color = (Color) fColorTable.get(rgb);
+ if (color == null) {
+ color = new Color(Display.getCurrent(), rgb);
+ fColorTable.put(rgb, color);
+ }
+ return color;
+ }
+}
--- /dev/null
+/**\r
+ * Copyright winterwell Mathematics Ltd.\r
+ * @author Daniel Winterstein\r
+ * 11 Jan 2007\r
+ */\r
+package winterwell.markdown.editors;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.jface.text.rules.ICharacterScanner;\r
+import org.eclipse.jface.text.rules.IRule;\r
+import org.eclipse.jface.text.rules.IToken;\r
+import org.eclipse.jface.text.rules.MultiLineRule;\r
+import org.eclipse.jface.text.rules.Token;\r
+\r
+/**\r
+ * \r
+ *\r
+ * @author Daniel Winterstein\r
+ */\r
+public class EmphasisRule implements IRule {\r
+ private static char[][] fDelimiters = null;\r
+ private char[] fSequence;\r
+ protected IToken fToken;\r
+\r
+\r
+ public EmphasisRule(String marker, IToken token) {\r
+ assert marker.equals("*") || marker.equals("_") || marker.equals("**")\r
+ || marker.equals("***") || marker.equals("`") || marker.equals("``");\r
+ Assert.isNotNull(token);\r
+ fSequence = marker.toCharArray();\r
+ fToken = token;\r
+ }\r
+ \r
+ // Copied from org.eclipse.jface.text.rules.PatternRule\r
+ protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence, boolean eofAllowed) {\r
+ for (int i = 1; i < sequence.length; i++) {\r
+ int c = scanner.read();\r
+ if (c == ICharacterScanner.EOF && eofAllowed) {\r
+ return true;\r
+ } else if (c != sequence[i]) {\r
+ // Non-matching character detected, rewind the scanner back to\r
+ // the start.\r
+ // Do not unread the first character.\r
+ for (int j = i; j > 0; j--)\r
+ scanner.unread();\r
+ return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /*\r
+ * @see IRule#evaluate(ICharacterScanner)\r
+ * \r
+ * @since 2.0\r
+ */\r
+ public IToken evaluate(ICharacterScanner scanner) {\r
+ // Should be connected only on the right side\r
+ scanner.unread();\r
+ boolean sawSpaceBefore = Character.isWhitespace(scanner.read());\r
+ if (!sawSpaceBefore && scanner.getColumn() != 0) {\r
+ return Token.UNDEFINED;\r
+ }\r
+\r
+ int c = scanner.read();\r
+ // Should be connected only on right side\r
+ if (c != fSequence[0] || !sequenceDetected(scanner, fSequence, false)) {\r
+ scanner.unread();\r
+ return Token.UNDEFINED;\r
+ }\r
+ int readCount = fSequence.length;\r
+ if (fDelimiters == null) {\r
+ fDelimiters = scanner.getLegalLineDelimiters();\r
+ }\r
+ // Start sequence detected\r
+ int delimiterFound = 0;\r
+ // Is it a list item marker, or just a floating *?\r
+ if (sawSpaceBefore) {\r
+ boolean after = Character.isWhitespace(scanner.read());\r
+ scanner.unread();\r
+ if (after)\r
+ delimiterFound = 2;\r
+ }\r
+\r
+ while (delimiterFound < 2\r
+ && (c = scanner.read()) != ICharacterScanner.EOF) {\r
+ readCount++;\r
+\r
+ if (!sawSpaceBefore && c == fSequence[0]\r
+ && sequenceDetected(scanner, fSequence, false)) {\r
+ return fToken;\r
+ }\r
+\r
+ int i;\r
+ for (i = 0; i < fDelimiters.length; i++) {\r
+ if (c == fDelimiters[i][0]\r
+ && sequenceDetected(scanner, fDelimiters[i], true)) {\r
+ delimiterFound++;\r
+ break;\r
+ }\r
+ }\r
+ if (i == fDelimiters.length)\r
+ delimiterFound = 0;\r
+ sawSpaceBefore = Character.isWhitespace(c);\r
+ }\r
+ // Reached ICharacterScanner.EOF\r
+ for (; readCount > 0; readCount--)\r
+ scanner.unread();\r
+ return Token.UNDEFINED;\r
+ }\r
+ \r
+}\r
--- /dev/null
+package winterwell.markdown.editors;
+
+import java.io.File;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.action.Action;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IPathEditorInput;
+
+import winterwell.utils.io.FileUtils;
+
+
+public class ExportHTMLAction extends Action {
+ public ExportHTMLAction() {
+ super("Export to HTML");
+ }
+ @Override
+ public void run() {
+ IEditorPart ed = ActionBarContributor.getActiveEditor();
+ if (!(ed instanceof MarkdownEditor)) {
+ return;
+ }
+ MarkdownEditor editor = (MarkdownEditor) ed;
+ IEditorInput i = editor.getEditorInput();
+ if (i instanceof IPathEditorInput) {
+ IPathEditorInput input = (IPathEditorInput) i;
+ IPath path = input.getPath();
+ path = path.removeFileExtension();
+ path = path.addFileExtension("html");
+ File file = path.toFile();
+ String html = editor.getMarkdownPage().html();
+ FileUtils.write(file, html);
+ }
+ }
+
+}
--- /dev/null
+package winterwell.markdown.editors;
+
+import java.util.List;
+
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+import org.eclipse.core.commands.IHandler;
+import org.eclipse.core.commands.IHandlerListener;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.source.ISourceViewer;
+
+import winterwell.markdown.pagemodel.MarkdownFormatter;
+import winterwell.markdown.pagemodel.MarkdownPage;
+import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
+import winterwell.utils.containers.IntRange;
+
+/**
+ * TODO An action for formatting text (via hard wrapping, i.e. inserting returns).
+ *
+ *
+ * @author daniel
+ */
+public class FormatAction extends Action implements IHandler {
+
+ public FormatAction() {
+ super("&Format paragraph");
+ setActionDefinitionId("winterwell.markdown.formatParagraphCommand");
+ setToolTipText("Format the paragraph under the caret by inserting/removing line-breaks");
+ }
+
+ @Override
+ public void run() {
+ try {
+ MarkdownEditor ed = (MarkdownEditor) ActionBarContributor.getActiveEditor();
+ if (ed == null) return; // The active editor is not a markdown editor.
+ int cols = ed.getPrintColumns();
+ // Do we have a selection?
+ ITextSelection s = (ITextSelection) ed.getSelectionProvider().getSelection();
+ if (s != null && s.getLength() > 0) {
+ formatSelectedRegion(ed, s, cols);
+ return;
+ }
+ // Where is the caret?
+ ISourceViewer viewer = ed.getViewer();
+ int caretOffset = viewer.getTextWidget().getCaretOffset();
+ int lineNum = ed.getDocument().getLineOfOffset(caretOffset);
+ // Get a paragraph region
+ MarkdownPage page = ed.getMarkdownPage();
+ IRegion pRegion = getParagraph(page, lineNum, ed.getDocument());
+ if (pRegion==null) {
+ // Not in a paragraph - so give up
+ // TODO tell the user why we've given up
+ return;
+ }
+ String paragraph = ed.getDocument().get(pRegion.getOffset(), pRegion.getLength());
+ // Format
+ String formatted = MarkdownFormatter.format(paragraph, cols);
+ if (formatted.equals(paragraph)) return; // No change!
+ // Replace the unformatted region with the new formatted one
+ ed.getDocument().replace(pRegion.getOffset(), pRegion.getLength(), formatted);
+ // Done
+ } catch (Exception ex) {
+ System.out.println(ex);
+ }
+ }
+
+ private void formatSelectedRegion(MarkdownEditor ed, ITextSelection s, int cols)
+ throws BadLocationException {
+ int start = s.getStartLine();
+ int end = s.getEndLine();
+ IDocument doc = ed.getDocument();
+ int soff = doc.getLineOffset(start);
+ int eoff = lineEndOffset(end, doc);
+ IntRange editedRegion = new IntRange(soff, eoff);
+ MarkdownPage page = ed.getMarkdownPage();
+ StringBuilder sb = new StringBuilder(s.getLength());
+ for(int i=start; i<=end; i++) {
+ IRegion para = getParagraph(page, i, ed.getDocument());
+ if (para==null) {
+ sb.append(page.getText().get(i));
+ continue;
+ }
+ String paragraph = ed.getDocument().get(para.getOffset(), para.getLength());
+// int lines = StrUtils.splitLines(paragraph).length;
+ String formatted = MarkdownFormatter.format(paragraph, cols);
+ // append formatted and move forward
+ sb.append(formatted);
+ CharSequence le = lineEnd(i, doc);
+ sb.append(le);
+ int pEnd = doc.getLineOfOffset(para.getOffset()+para.getLength());
+ i = pEnd;
+ // Adjust edited region?
+ IntRange pr = new IntRange(para.getOffset(),
+ para.getOffset()+para.getLength()+le.length());
+ editedRegion = new IntRange(Math.min(pr.low, editedRegion.low),
+ Math.max(pr.high, editedRegion.high));
+ }
+ // Replace the unformatted region with the new formatted one
+ String old = doc.get(editedRegion.low, editedRegion.size());
+ String newText = sb.toString();
+ if (old.equals(newText)) return;
+ ed.getDocument().replace(editedRegion.low, editedRegion.size(), newText);
+ }
+
+ private CharSequence lineEnd(int line, IDocument doc) throws BadLocationException {
+ int eoff = doc.getLineOffset(line) + doc.getLineInformation(line).getLength();
+ char c = doc.getChar(eoff);
+ if (c=='\r' && doc.getLength() > eoff+1
+ && doc.getChar(eoff+1) =='\n') return "\r\n";
+ return ""+c;
+ }
+
+ private int lineEndOffset(int end, IDocument doc)
+ throws BadLocationException {
+ int eoff = doc.getLineOffset(end) + doc.getLineInformation(end).getLength();
+ // Include line end
+ char c = doc.getChar(eoff);
+ if (c=='\r' && doc.getLength() > eoff+1
+ && doc.getChar(eoff+1) =='\n') eoff += 2;
+ else eoff += 1;
+ return eoff;
+ }
+
+ /**
+ *
+ * @param page
+ * @param lineNum
+ * @param doc
+ * @return region of paragraph containing this line, or null
+ * @throws BadLocationException
+ */
+ private IRegion getParagraph(MarkdownPage page, int lineNum, IDocument doc)
+ throws BadLocationException {
+ // Get doc info
+ List<String> lines = page.getText();
+ List<KLineType> lineInfo = page.getLineTypes();
+ // Check we are in a paragraph or list
+ KLineType pType = lineInfo.get(lineNum);
+ switch(pType) {
+ case NORMAL: break;
+ default: // Not in a paragraph, so we cannot format.
+ return null;
+ }
+ // Work out the paragraph
+ // Beginning
+ int start;
+ for(start=lineNum; start>-1; start--) {
+ if (lineInfo.get(start) != pType) {
+ start++;
+ break;
+ }
+ }
+ // End
+ int end;
+ for(end=lineNum; end<lines.size(); end++) {
+ if (lineInfo.get(end) != pType) {
+ end--;
+ break;
+ }
+ }
+ // Get offset info
+ int sOff = doc.getLineOffset(start);
+ IRegion endLine = doc.getLineInformation(end); // exclude final line end
+ int eOff = endLine.getOffset()+endLine.getLength();
+ return new Region(sOff, eOff-sOff);
+ }
+
+ public void addHandlerListener(IHandlerListener handlerListener) {
+ // Ignore
+ }
+
+ public void dispose() {
+ // Ignore
+ }
+
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ run();
+ return null;
+ }
+
+ public void removeHandlerListener(IHandlerListener handlerListener) {
+ // Ignore
+ }
+
+}
--- /dev/null
+/**\r
+ * Copyright winterwell Mathematics Ltd.\r
+ * @author Daniel Winterstein\r
+ * 11 Jan 2007\r
+ */\r
+package winterwell.markdown.editors;\r
+\r
+import org.eclipse.jface.text.rules.IToken;\r
+import org.eclipse.jface.text.rules.PatternRule;\r
+\r
+/**\r
+ * \r
+ *\r
+ * @author Daniel Winterstein\r
+ */\r
+public class HeaderRule extends PatternRule {\r
+ \r
+ public HeaderRule(IToken token) { \r
+ super("#", null, token, (char) 0, true, true);\r
+ setColumnConstraint(0);\r
+ }\r
+}\r
--- /dev/null
+/**\r
+ * @author Telmo Brugnara\r
+ * 10 Feb 2014\r
+ */\r
+package winterwell.markdown.editors;\r
+\r
+import org.eclipse.jface.text.rules.ICharacterScanner;\r
+import org.eclipse.jface.text.rules.IRule;\r
+import org.eclipse.jface.text.rules.IToken;\r
+import org.eclipse.jface.text.rules.Token;\r
+\r
+public class HeaderWithUnderlineRule implements IRule {\r
+\r
+ IToken successToken = null;\r
+ \r
+ public HeaderWithUnderlineRule(IToken token) {\r
+ successToken = token;\r
+ }\r
+ \r
+ public IToken evaluate(ICharacterScanner scanner) {\r
+ int c = -1;\r
+ int scanCount = 0;\r
+ if (scanner.getColumn()==0) {\r
+ do {\r
+ c = scanner.read();\r
+ scanCount++;\r
+ } while (!isNewLine((char) c) && c != ICharacterScanner.EOF);\r
+ if(c == ICharacterScanner.EOF) {\r
+ // is not a header\r
+ for(int i=0;i<scanCount;i++) { scanner.unread(); }\r
+ return Token.UNDEFINED;\r
+ }\r
+ c = scanner.read();\r
+ scanCount++;\r
+ if(c == '\r') {\r
+ c = scanner.read();\r
+ scanCount++;\r
+ }\r
+ if(!isUnderline((char) c)) {\r
+ // is not a header\r
+ for(int i=0;i<scanCount;i++) { scanner.unread(); }\r
+ return Token.UNDEFINED;\r
+ }\r
+ do {\r
+ c = scanner.read();\r
+ scanCount++;\r
+ if(isNewLine((char) c) || c == ICharacterScanner.EOF) {\r
+ //scanner.unread();\r
+ return successToken;\r
+ }\r
+ if(!isUnderline((char) c) && !isWhitespace((char) c) && c != '\r') {\r
+ // is not a header\r
+ for(int i=0;i<scanCount;i++) { scanner.unread(); }\r
+ return Token.UNDEFINED;\r
+ }\r
+ } while (true);\r
+ }\r
+\r
+ return Token.UNDEFINED;\r
+ }\r
+ \r
+ boolean isNewLine(char c) {\r
+ return c == '\n';\r
+ }\r
+\r
+ boolean isUnderline(char c) {\r
+ return c == '=' || c == '-';\r
+ }\r
+\r
+ boolean isWhitespace(char c) {\r
+ return c == ' ' || c == '\t';\r
+ }\r
+}\r
--- /dev/null
+/**
+ * Copyright winterwell Mathematics Ltd.
+ * @author Daniel Winterstein
+ * 11 Jan 2007
+ */
+package winterwell.markdown.editors;
+
+import org.eclipse.jface.text.rules.ICharacterScanner;
+import org.eclipse.jface.text.rules.IToken;
+import org.eclipse.jface.text.rules.Token;
+import org.eclipse.jface.text.rules.IRule;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ *
+ *
+ * @author Amir Pakdel
+ */
+public class LinkRule implements IRule {
+ private static char[][] fDelimiters = null;
+ protected IToken fToken;
+
+ public LinkRule(IToken token) {
+ Assert.isNotNull(token);
+ fToken= token;
+ }
+
+ /*
+ * @see IPredicateRule#getSuccessToken()
+ * @since 2.0
+ */
+ public IToken getSuccessToken() {
+ return fToken;
+ }
+
+
+ // Copied from org.eclipse.jface.text.rules.PatternRule
+ protected boolean sequenceDetected(ICharacterScanner scanner, char[] sequence, boolean eofAllowed) {
+ for (int i= 1; i < sequence.length; i++) {
+ int c= scanner.read();
+ if (c == ICharacterScanner.EOF && eofAllowed) {
+ return true;
+ } else if (c != sequence[i]) {
+ // Non-matching character detected, rewind the scanner back to the start.
+ // Do not unread the first character.
+ scanner.unread();
+ for (int j= i-1; j > 0; j--)
+ scanner.unread();
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /*
+ * @see IRule#evaluate(ICharacterScanner)
+ * @since 2.0
+ */
+ public IToken evaluate(ICharacterScanner scanner) {
+ int c;
+ if ((c = scanner.read()) != '[') {
+ if ((c != 'h' || ( !sequenceDetected(scanner, "http://".toCharArray(), false) && !sequenceDetected(scanner, "https://".toCharArray(), false) ))
+ && (c != 'f' || !sequenceDetected(scanner, "ftp://".toCharArray(), false)) ) {
+ // Not even a non-standard link
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+
+ //+ preventing NPE (Non-standard link should not be below as comment above suggests) by Paul Verest
+ if (fDelimiters == null) {
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+
+ // Non-standard link
+ while ((c = scanner.read()) != ICharacterScanner.EOF && !Character.isWhitespace(c)) {
+ for (int i = 0; i < fDelimiters.length; i++) {
+ if (c == fDelimiters[i][0] && sequenceDetected(scanner, fDelimiters[i], true)) {
+ return fToken;
+ }
+ }
+ }
+ return fToken;
+ }
+ if (fDelimiters == null) {
+ fDelimiters = scanner.getLegalLineDelimiters();
+ }
+ int readCount = 1;
+
+ // Find '](' and then find ')'
+ boolean sequenceFound = false;
+ int delimiterFound = 0;
+ while ((c = scanner.read()) != ICharacterScanner.EOF && delimiterFound < 2) {
+ readCount++;
+ if ( !sequenceFound && c == ']') {
+ c = scanner.read();
+ if (c == '(') {
+ readCount++;
+ sequenceFound = true;
+ } else {
+ scanner.unread();
+ }
+ } else if (c == ')') { // '](' is already found
+ return fToken;
+ }
+
+ int i;
+ for (i = 0; i < fDelimiters.length; i++) {
+ if (c == fDelimiters[i][0] && sequenceDetected(scanner, fDelimiters[i], true)) {
+ delimiterFound ++;
+ break;
+ }
+ }
+ if (i == fDelimiters.length)
+ delimiterFound = 0;
+ }
+
+ for (; readCount > 0; readCount--)
+ scanner.unread();
+ return Token.UNDEFINED;
+ }
+
+}
--- /dev/null
+/**\r
+ * Copyright winterwell Mathematics Ltd.\r
+ * @author Daniel Winterstein\r
+ * 11 Jan 2007\r
+ */\r
+package winterwell.markdown.editors;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+import java.util.Arrays;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.jface.text.rules.ICharacterScanner;\r
+import org.eclipse.jface.text.rules.IRule;\r
+import org.eclipse.jface.text.rules.IToken;\r
+import org.eclipse.jface.text.rules.Token;\r
+\r
+/**\r
+ * \r
+ *\r
+ * @author Daniel Winterstein\r
+ */\r
+public class ListRule implements IRule {\r
+ private ArrayList<Integer> markerList;\r
+ protected IToken fToken;\r
+ \r
+ public ListRule(IToken token) { \r
+ Assert.isNotNull(token);\r
+ fToken= token;\r
+ }\r
+ \r
+\r
+ /*\r
+ * @see IRule#evaluate(ICharacterScanner)\r
+ * @since 2.0\r
+ */\r
+ public IToken evaluate(ICharacterScanner scanner) {\r
+ if (scanner.getColumn() != 0) {\r
+ return Token.UNDEFINED;\r
+ }\r
+// // Fast mode\r
+// if (scanner.read() != '-') {\r
+// scanner.unread();\r
+// return Token.UNDEFINED;\r
+// }\r
+// if (Character.isWhitespace(scanner.read())) {\r
+// return fToken;\r
+// }\r
+// scanner.unread();\r
+// scanner.unread();\r
+// return Token.UNDEFINED;\r
+// // Fast mode\r
+ int readCount = 0;\r
+ int c;\r
+ while ((c = scanner.read()) != ICharacterScanner.EOF) {\r
+ readCount++;\r
+ if( !Character.isWhitespace( c ) ) {\r
+ int after = scanner.read();\r
+// readCount++;\r
+ scanner.unread();\r
+// if ( markerList.contains(c) && Character.isWhitespace( after ) ) {\r
+ if ( (c == '-' || c == '+' || c == '*') \r
+ && Character.isWhitespace( after ) ) {\r
+ return fToken;\r
+ } else {\r
+ for (; readCount > 0; readCount--)\r
+ scanner.unread();\r
+ return Token.UNDEFINED;\r
+ }\r
+ }\r
+ }\r
+ // Reached ICharacterScanner.EOF\r
+ for (; readCount > 0; readCount--)\r
+ scanner.unread();\r
+ return Token.UNDEFINED;\r
+ }\r
+}\r
--- /dev/null
+package winterwell.markdown.editors;
+
+import org.eclipse.swt.graphics.RGB;
+
+public interface MDColorConstants {
+ RGB COMMENT = new RGB(128, 0, 0);
+ RGB HEADER = new RGB(0, 0, 128);
+ RGB DEFAULT = new RGB(0, 0, 0);
+}
--- /dev/null
+package winterwell.markdown.editors;
+
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextHover;
+import org.eclipse.jface.text.presentation.IPresentationReconciler;
+import org.eclipse.jface.text.presentation.PresentationReconciler;
+import org.eclipse.jface.text.reconciler.DirtyRegion;
+import org.eclipse.jface.text.reconciler.IReconciler;
+import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
+import org.eclipse.jface.text.reconciler.MonoReconciler;
+import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.ui.editors.text.TextSourceViewerConfiguration;
+
+public class MDConfiguration extends TextSourceViewerConfiguration {
+ private ColorManager colorManager;
+
+ public MDConfiguration(ColorManager colorManager, IPreferenceStore prefStore) {
+ super(prefStore);
+ this.colorManager = colorManager;
+ }
+
+ @Override
+ public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
+ MDScanner scanner = new MDScanner(colorManager);
+ PresentationReconciler pr = (PresentationReconciler) super.getPresentationReconciler(sourceViewer); // FIXME
+ DefaultDamagerRepairer ddr = new DefaultDamagerRepairer(scanner);
+ pr.setRepairer(ddr, IDocument.DEFAULT_CONTENT_TYPE);
+ pr.setDamager(ddr, IDocument.DEFAULT_CONTENT_TYPE);
+ return pr;
+ }
+
+
+ @Override
+ public IReconciler getReconciler(ISourceViewer sourceViewer) {
+ // This awful mess adds in update support
+ // Get super strategy
+ IReconciler rs = super.getReconciler(sourceViewer);
+ if (true) return rs; // Seems to work fine?!
+ final IReconcilingStrategy fsuperStrategy = rs==null? null : rs.getReconcilingStrategy("text");
+ // Add our own
+ IReconcilingStrategy strategy = new IReconcilingStrategy() {
+ private IDocument doc;
+ public void reconcile(IRegion partition) {
+ MarkdownEditor ed = MarkdownEditor.getEditor(doc);
+ if (ed != null) ed.updatePage(partition);
+ if (fsuperStrategy!=null) fsuperStrategy.reconcile(partition);
+ }
+ public void reconcile(DirtyRegion dirtyRegion, IRegion subRegion) {
+ MarkdownEditor ed = MarkdownEditor.getEditor(doc);
+ if (ed != null) ed.updatePage(subRegion);
+ if (fsuperStrategy!=null) fsuperStrategy.reconcile(dirtyRegion, subRegion);
+ }
+ public void setDocument(IDocument document) {
+ this.doc = document;
+ if (fsuperStrategy!=null) fsuperStrategy.setDocument(document);
+ }
+ };
+ // Make a reconciler
+ MonoReconciler m2 = new MonoReconciler(strategy, true);
+ m2.setIsIncrementalReconciler(true);
+ m2.setProgressMonitor(new NullProgressMonitor());
+ m2.setDelay(500);
+ // Done
+ return m2;
+ }
+
+ @SuppressWarnings("unused")
+ @Override
+ public ITextHover getTextHover(ISourceViewer sourceViewer,
+ String contentType) {
+ if (true) return super.getTextHover(sourceViewer, contentType);
+ // Add hover support for images
+ return new MDTextHover();
+ }
+}
+
+
--- /dev/null
+/**\r
+ * Copyright winterwell Mathematics Ltd.\r
+ * @author Daniel Winterstein\r
+ * 13 Jan 2007\r
+ */\r
+package winterwell.markdown.editors;\r
+\r
+import org.eclipse.jface.preference.IPreferenceStore;\r
+import org.eclipse.jface.preference.PreferenceConverter;\r
+import org.eclipse.jface.text.TextAttribute;\r
+import org.eclipse.jface.text.rules.IRule;\r
+import org.eclipse.jface.text.rules.IWhitespaceDetector;\r
+import org.eclipse.jface.text.rules.MultiLineRule;\r
+import org.eclipse.jface.text.rules.RuleBasedScanner;\r
+import org.eclipse.jface.text.rules.Token;\r
+import org.eclipse.jface.text.rules.WhitespaceRule;\r
+import org.eclipse.swt.SWT;\r
+\r
+import winterwell.markdown.Activator;\r
+import winterwell.markdown.preferences.MarkdownPreferencePage;\r
+\r
+/**\r
+ * \r
+ *\r
+ * @author Daniel Winterstein\r
+ */\r
+public class MDScanner extends RuleBasedScanner {\r
+ ColorManager cm;\r
+ public MDScanner(ColorManager cm) {\r
+ this.cm = cm;\r
+ IPreferenceStore pStore = Activator.getDefault().getPreferenceStore();\r
+ Token heading = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD));\r
+ Token comment = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_COMMENT))));\r
+ Token emphasis = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_DEFUALT)), null, SWT.ITALIC));\r
+ Token list = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD));\r
+ Token link = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_LINK)), null, TextAttribute.UNDERLINE));\r
+ Token code = new Token(new TextAttribute(\r
+ cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE)),\r
+ cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE_BG)),\r
+ SWT.NORMAL));\r
+ setRules(new IRule[] {\r
+ new LinkRule(link),\r
+ new HeaderRule(heading),\r
+ new HeaderWithUnderlineRule(heading),\r
+ new ListRule(list),\r
+ new EmphasisRule("_", emphasis),\r
+ new EmphasisRule("***", emphasis),\r
+ new EmphasisRule("**", emphasis),\r
+ new EmphasisRule("*", emphasis),\r
+ new EmphasisRule("``", code),\r
+ new EmphasisRule("`", code),\r
+ new MultiLineRule("<!--", "-->", comment),\r
+ // WhitespaceRule messes up with the rest of rules\r
+// new WhitespaceRule(new IWhitespaceDetector() {\r
+// public boolean isWhitespace(char c) {\r
+// return Character.isWhitespace(c);\r
+// }\r
+// }),\r
+ });\r
+ }\r
+}\r
--- /dev/null
+/**
+ * (c) Winterwell 2010 and ThinkTank Mathematics 2007
+ */
+package winterwell.markdown.editors;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextHover;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Region;
+
+import winterwell.markdown.StringMethods;
+import winterwell.utils.containers.Pair;
+
+/**
+ *
+ *
+ * @author daniel
+ */
+public class MDTextHover implements ITextHover //, ITextHoverExtension
+{
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.text.ITextHover#getHoverInfo(org.eclipse.jface.text.ITextViewer, org.eclipse.jface.text.IRegion)
+ */
+ public String getHoverInfo(ITextViewer textViewer, IRegion region) {
+ try {
+ IDocument doc = textViewer.getDocument();
+ String text = doc.get(region.getOffset(), region.getLength());
+ return "<b>"+text+"</b>";
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.text.ITextHover#getHoverRegion(org.eclipse.jface.text.ITextViewer, int)
+ */
+ public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
+ try {
+ IDocument doc = textViewer.getDocument();
+ int line = doc.getLineOfOffset(offset);
+ int lineOffset = doc.getLineOffset(line);
+ int lineLength = doc.getLineLength(line);
+ String text = doc.get(lineOffset, lineLength);
+ // Look for image tags
+ Pair<Integer> altRegion;
+ Pair<Integer> urlRegion =
+ StringMethods.findEnclosingRegion(text, offset-lineOffset, '(', ')');
+ if (urlRegion==null) {
+ altRegion = StringMethods.findEnclosingRegion(text, offset-lineOffset, '[', ']');
+ if (altRegion == null) return null;
+ urlRegion = StringMethods.findEnclosingRegion(text, altRegion.second, '(', ')');
+ } else {
+ altRegion = StringMethods.findEnclosingRegion(text, urlRegion.first-1, '[', ']');
+ }
+ if (urlRegion==null || altRegion==null) return null;
+ // Is it an image link?
+ if (text.charAt(altRegion.first-1) != '!') return null;
+ Region r = new Region(urlRegion.first+1+lineOffset, urlRegion.second-urlRegion.first-2);
+ return r;
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+// public IInformationControlCreator getHoverControlCreator() {
+// return new IInformationControlCreator() {
+// public IInformationControl createInformationControl(Shell parent) {
+// int style= fIsFocusable ? SWT.V_SCROLL | SWT.H_SCROLL : SWT.NONE;
+//
+// if (BrowserInformationControl.isAvailable(parent)) {
+// final int shellStyle= SWT.TOOL | (fIsFocusable ? SWT.RESIZE : SWT.NO_TRIM);
+// return new BrowserInformationControl(parent, shellStyle, style, null);
+// }
+// return new DefaultInformationControl(parent, style, new HTMLTextPresenter());
+// }
+// };
+// }
+
+}
--- /dev/null
+/**\r
+ * Copyright winterwell Mathematics Ltd.\r
+ * @author Daniel Winterstein\r
+ * 11 Jan 2007\r
+ */\r
+package winterwell.markdown.editors;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+\r
+import org.eclipse.jface.action.Action;\r
+import org.eclipse.jface.action.IAction;\r
+import org.eclipse.jface.action.IToolBarManager;\r
+import org.eclipse.jface.resource.ImageDescriptor;\r
+import org.eclipse.jface.text.BadLocationException;\r
+import org.eclipse.jface.text.DocumentEvent;\r
+import org.eclipse.jface.text.IDocument;\r
+import org.eclipse.jface.text.IDocumentListener;\r
+import org.eclipse.jface.text.IRegion;\r
+import org.eclipse.jface.text.Region;\r
+import org.eclipse.jface.viewers.ISelection;\r
+import org.eclipse.jface.viewers.IStructuredSelection;\r
+import org.eclipse.jface.viewers.ITreeContentProvider;\r
+import org.eclipse.jface.viewers.LabelProvider;\r
+import org.eclipse.jface.viewers.SelectionChangedEvent;\r
+import org.eclipse.jface.viewers.StructuredSelection;\r
+import org.eclipse.jface.viewers.TreeViewer;\r
+import org.eclipse.jface.viewers.Viewer;\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.events.KeyEvent;\r
+import org.eclipse.swt.events.KeyListener;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Control;\r
+import org.eclipse.ui.IActionBars;\r
+import org.eclipse.ui.part.IPageSite;\r
+import org.eclipse.ui.texteditor.IDocumentProvider;\r
+import org.eclipse.ui.views.contentoutline.ContentOutlinePage;\r
+\r
+import winterwell.markdown.pagemodel.MarkdownPage;\r
+import winterwell.markdown.pagemodel.MarkdownPage.Header;\r
+import winterwell.markdown.pagemodel.MarkdownPage.KLineType;\r
+import winterwell.utils.StrUtils;\r
+import winterwell.utils.Utils;\r
+import winterwell.utils.web.WebUtils;\r
+\r
+/**\r
+ *\r
+ *\r
+ * @author Daniel Winterstein\r
+ */\r
+public final class MarkdownContentOutlinePage extends ContentOutlinePage {\r
+\r
+ /**\r
+ *\r
+ *\r
+ * @author Daniel Winterstein\r
+ */\r
+ public final class ContentProvider implements ITreeContentProvider,\r
+ IDocumentListener {\r
+\r
+ // protected final static String SEGMENTS= "__md_segments";\r
+ // //$NON-NLS-1$\r
+ // protected IPositionUpdater fPositionUpdater= new\r
+ // DefaultPositionUpdater(SEGMENTS);\r
+ private MarkdownPage fContent;\r
+ // protected List fContent= new ArrayList(10);\r
+ private MarkdownEditor fTextEditor;\r
+\r
+ private void parse() {\r
+ fContent = fTextEditor.getMarkdownPage();\r
+ }\r
+\r
+ /*\r
+ * @see IContentProvider#inputChanged(Viewer, Object, Object)\r
+ */\r
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {\r
+ // Detach from old\r
+ if (oldInput != null) {\r
+ IDocument document = fDocumentProvider.getDocument(oldInput);\r
+ if (document != null) {\r
+ document.removeDocumentListener(this);\r
+ }\r
+ }\r
+ fContent = null;\r
+ // Attach to new\r
+ if (newInput == null)\r
+ return;\r
+ IDocument document = fDocumentProvider.getDocument(newInput);\r
+ if (document == null)\r
+ return;\r
+ fTextEditor = MarkdownEditor.getEditor(document);\r
+ document.addDocumentListener(this);\r
+ parse();\r
+ }\r
+\r
+ /*\r
+ * @see IContentProvider#dispose\r
+ */\r
+ public void dispose() {\r
+ fContent = null;\r
+ }\r
+\r
+ /*\r
+ * @see IContentProvider#isDeleted(Object)\r
+ */\r
+ public boolean isDeleted(Object element) {\r
+ return false;\r
+ }\r
+\r
+ /*\r
+ * @see IStructuredContentProvider#getElements(Object)\r
+ */\r
+ public Object[] getElements(Object element) {\r
+ return fContent.getHeadings(null).toArray();\r
+ }\r
+\r
+ /*\r
+ * @see ITreeContentProvider#hasChildren(Object)\r
+ */\r
+ public boolean hasChildren(Object element) {\r
+ if (element == fInput) {\r
+ return true;\r
+ }\r
+ if (element instanceof MarkdownPage.Header) {\r
+ MarkdownPage.Header header = (MarkdownPage.Header) element;\r
+ return header.getSubHeaders().size() > 0;\r
+ }\r
+ ;\r
+ return false;\r
+ }\r
+\r
+ /*\r
+ * @see ITreeContentProvider#getParent(Object)\r
+ */\r
+ public Object getParent(Object element) {\r
+ if (!(element instanceof MarkdownPage.Header))\r
+ return null;\r
+ return ((MarkdownPage.Header) element).getParent();\r
+ }\r
+\r
+ /*\r
+ * @see ITreeContentProvider#getChildren(Object)\r
+ */\r
+ public Object[] getChildren(Object element) {\r
+ if (element == fInput) {\r
+ return fContent.getHeadings(null).toArray();\r
+ }\r
+ if (!(element instanceof MarkdownPage.Header))\r
+ return null;\r
+ return ((MarkdownPage.Header) element).getSubHeaders().toArray();\r
+ }\r
+\r
+ public void documentAboutToBeChanged(DocumentEvent event) {\r
+ // nothing\r
+ }\r
+\r
+ public void documentChanged(DocumentEvent event) {\r
+ parse();\r
+ update();\r
+ }\r
+ }\r
+\r
+ private Object fInput = null;\r
+ private final IDocumentProvider fDocumentProvider;\r
+ private final MarkdownEditor fTextEditor;\r
+ protected boolean showWordCounts;\r
+ private List<Header> selectedHeaders;\r
+\r
+ /**\r
+ * @param documentProvider\r
+ * @param mdEditor\r
+ */\r
+ public MarkdownContentOutlinePage(IDocumentProvider documentProvider,\r
+ MarkdownEditor mdEditor) {\r
+ fDocumentProvider = documentProvider;\r
+ fTextEditor = mdEditor;\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc) Method declared on ContentOutlinePage\r
+ */\r
+ @Override\r
+ public void createControl(Composite parent) {\r
+ super.createControl(parent);\r
+ TreeViewer viewer = getTreeViewer();\r
+ viewer.setContentProvider(new ContentProvider());\r
+ // Add word count annotations\r
+ viewer.setLabelProvider(new LabelProvider() {\r
+ @Override\r
+ public String getText(Object element) {\r
+ if (!(element instanceof MarkdownPage.Header))\r
+ return super.getText(element);\r
+ Header header = ((MarkdownPage.Header) element);\r
+ String hText = header.toString();\r
+ if (!showWordCounts)\r
+ return hText;\r
+ IRegion region = getRegion(header);\r
+ String text;\r
+ try {\r
+ text = fTextEditor.getDocument().get(region.getOffset(),\r
+ region.getLength());\r
+ text = WebUtils.stripTags(text);\r
+ text = text.replaceAll("#", "").trim();\r
+ assert text.startsWith(hText);\r
+ text = text.substring(hText.length());\r
+ int wc = StrUtils.wordCount(text);\r
+ return hText + " (" + wc + ":" + text.length() + ")";\r
+ } catch (BadLocationException e) {\r
+ return hText;\r
+ }\r
+ }\r
+ });\r
+ viewer.addSelectionChangedListener(this);\r
+\r
+ if (fInput != null)\r
+ viewer.setInput(fInput);\r
+\r
+ // Buttons\r
+ IPageSite site = getSite();\r
+ IActionBars bars = site.getActionBars();\r
+ IToolBarManager toolbar = bars.getToolBarManager();\r
+ // Word count action\r
+ Action action = new Action("123", IAction.AS_CHECK_BOX) {\r
+ @Override\r
+ public void run() {\r
+ showWordCounts = isChecked();\r
+ update();\r
+ }\r
+ };\r
+ action.setToolTipText("Show/hide section word:character counts");\r
+ toolbar.add(action);\r
+ // +/- actions\r
+ action = new Action("<") {\r
+ @Override\r
+ public void run() {\r
+ doPromoteDemote(-1);\r
+ }\r
+ };\r
+ action.setToolTipText("Promote the selected section\n -- move it up a level.");\r
+ toolbar.add(action);\r
+ //\r
+ action = new Action(">") {\r
+ @Override\r
+ public void run() {\r
+ doPromoteDemote(1);\r
+ }\r
+ };\r
+ action.setToolTipText("Demote the selected section\n -- move it down a level.");\r
+ toolbar.add(action);\r
+ // up/down actions\r
+ action = new Action("/\\") {\r
+ @Override\r
+ public void run() {\r
+ try {\r
+ doMove(-1);\r
+ } catch (BadLocationException e) {\r
+ throw Utils.runtime(e);\r
+ }\r
+ }\r
+ };\r
+ action.setToolTipText("Move the selected section earlier");\r
+ toolbar.add(action);\r
+ //\r
+ action = new Action("\\/") {\r
+ @Override\r
+ public void run() {\r
+ try {\r
+ doMove(1);\r
+ } catch (BadLocationException e) {\r
+ throw Utils.runtime(e);\r
+ }\r
+ }\r
+ };\r
+ action.setToolTipText("Move the selected section later");\r
+ toolbar.add(action);\r
+ // Collapse\r
+ ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif");\r
+ action = new Action("collapse", id) {\r
+ @Override\r
+ public void run() {\r
+ doCollapseAll();\r
+ }\r
+ };\r
+ action.setImageDescriptor(id);\r
+ action.setToolTipText("Collapse outline tree");\r
+ toolbar.add(action);\r
+ // Sync\r
+ id = ImageDescriptor.createFromFile(getClass(), "synced.gif");\r
+ action = new Action("sync") {\r
+ @Override\r
+ public void run() {\r
+ try {\r
+ doSyncToEditor();\r
+ } catch (BadLocationException e) {\r
+ throw Utils.runtime(e);\r
+ }\r
+ }\r
+ };\r
+ action.setImageDescriptor(id);\r
+ action.setToolTipText("Link with editor");\r
+ toolbar.add(action);\r
+ // Add edit ability\r
+ viewer.getControl().addKeyListener(new KeyListener() {\r
+ public void keyPressed(KeyEvent e) {\r
+ if (e.keyCode==SWT.F2) {\r
+ doEditHeader();\r
+ }\r
+ }\r
+ public void keyReleased(KeyEvent e) {\r
+ //\r
+ }\r
+ });\r
+ }\r
+\r
+ /**\r
+ * @throws BadLocationException\r
+ *\r
+ */\r
+ protected void doSyncToEditor() throws BadLocationException {\r
+ TreeViewer viewer = getTreeViewer();\r
+ if (viewer == null) return;\r
+ // Get header\r
+ MarkdownPage page = fTextEditor.getMarkdownPage();\r
+ int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset();\r
+ IDocument doc = fTextEditor.getDocument();\r
+ int line = doc.getLineOfOffset(caretOffset);\r
+ List<KLineType> lineTypes = page.getLineTypes();\r
+ for(; line>-1; line--) {\r
+ KLineType lt = lineTypes.get(line);\r
+ if (lt.toString().startsWith("H")) break;\r
+ }\r
+ if (line<0) return;\r
+ Header header = (Header) page.getPageObject(line);\r
+ // Set\r
+ IStructuredSelection selection = new StructuredSelection(header);\r
+ viewer.setSelection(selection , true);\r
+ }\r
+\r
+ void doEditHeader() {\r
+ TreeViewer viewer = getTreeViewer();\r
+ viewer.editElement(selectedHeaders.get(0), 0);\r
+ }\r
+\r
+ protected void doCollapseAll() {\r
+ TreeViewer viewer = getTreeViewer();\r
+ if (viewer == null) return;\r
+// Control control = viewer.getControl();\r
+// if (control != null && !control.isDisposed()) {\r
+// control.setRedraw(false);\r
+ viewer.collapseAll();\r
+// control.setRedraw(true);\r
+// }\r
+ }\r
+\r
+ /**\r
+ * Move the selected sections up/down\r
+ * @param i 1 or -1. 1==move later, -1=earlier\r
+ * @throws BadLocationException\r
+ */\r
+ protected void doMove(int i) throws BadLocationException {\r
+ assert i==1 || i==-1;\r
+ if (selectedHeaders == null || selectedHeaders.size() == 0)\r
+ return;\r
+ // Get text region to move\r
+ MarkdownPage.Header first = selectedHeaders.get(0);\r
+ MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1);\r
+ int start = fTextEditor.getDocument().getLineOffset(\r
+ first.getLineNumber());\r
+ IRegion r = getRegion(last);\r
+ int end = r.getOffset() + r.getLength();\r
+ int length = end - start;\r
+ // Get new insertion point\r
+ int insert;\r
+ if (i==1) {\r
+ Header nextSection = last.getNext();\r
+ if (nextSection==null) return;\r
+ IRegion nr = getRegion(nextSection);\r
+ insert = nr.getOffset()+nr.getLength();\r
+ } else {\r
+ Header prevSection = first.getPrevious();\r
+ if (prevSection==null) return;\r
+ IRegion nr = getRegion(prevSection);\r
+ insert = nr.getOffset();\r
+ }\r
+ // Get text\r
+ String text = fTextEditor.getDocument().get();\r
+ // Move text\r
+ String section = text.substring(start, end);\r
+ String pre, post;\r
+ if (i==1) {\r
+ pre = text.substring(0, start) + text.substring(end, insert);\r
+ post = text.substring(insert);\r
+ } else {\r
+ pre = text.substring(0, insert);\r
+ post = text.substring(insert,start)+text.substring(end);\r
+ }\r
+ text = pre + section + post;\r
+ assert text.length() == fTextEditor.getDocument().get().length() :\r
+ text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost";\r
+ // Update doc\r
+ fTextEditor.getDocument().set(text);\r
+ }\r
+\r
+ /**\r
+ * Does not support -------- / ========= underlining, only # headers\r
+ * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1)\r
+ */\r
+ protected void doPromoteDemote(int upDown) {\r
+ assert upDown==1 || upDown==-1;\r
+ if (selectedHeaders == null || selectedHeaders.size() == 0)\r
+ return;\r
+ HashSet<Header> toAdjust = new HashSet<Header>(selectedHeaders);\r
+ HashSet<Header> adjusted = new HashSet<Header>();\r
+ // Adjust\r
+ MarkdownPage mdPage = fTextEditor.getMarkdownPage();\r
+ List<String> lines = new ArrayList<String>(mdPage.getText());\r
+ while(toAdjust.size() != 0) {\r
+ Header h = toAdjust.iterator().next();\r
+ toAdjust.remove(h);\r
+ adjusted.add(h);\r
+ String line = lines.get(h.getLineNumber());\r
+ if (upDown==-1) {\r
+ if (h.getLevel() == 1) return; // Level 1; can't promote\r
+ if (line.startsWith("##")) line = line.substring(1);\r
+ else {\r
+ return; // TODO support for ------ / ========\r
+ }\r
+ } else line = "#" + line;\r
+ int ln = h.getLineNumber();\r
+ lines.set(ln, line);\r
+ // kids\r
+ ArrayList<Header> kids = new ArrayList<Header>(h.getSubHeaders());\r
+ for (Header header : kids) {\r
+ if ( ! adjusted.contains(header)) toAdjust.add(header);\r
+ }\r
+ }\r
+ // Set\r
+ StringBuilder sb = new StringBuilder();\r
+ for (String line : lines) {\r
+ sb.append(line);\r
+ }\r
+ fTextEditor.getDocument().set(sb.toString());\r
+ }\r
+\r
+ /**\r
+ * The region of text for this header. This includes the header itself.\r
+ * @param header\r
+ * @return\r
+ * @throws BadLocationException\r
+ */\r
+ protected IRegion getRegion(Header header) {\r
+ try {\r
+ IDocument doc = fTextEditor.getDocument();\r
+ // Line numbers\r
+ int start = header.getLineNumber();\r
+ Header next = header.getNext();\r
+ int end;\r
+ if (next != null) {\r
+ end = next.getLineNumber() - 1;\r
+ } else {\r
+ end = doc.getNumberOfLines() - 1;\r
+ }\r
+ int offset = doc.getLineOffset(start);\r
+ IRegion ei = doc.getLineInformation(end);\r
+ int length = ei.getOffset() + ei.getLength() - offset;\r
+ return new Region(offset, length);\r
+ } catch (BadLocationException ex) {\r
+ throw Utils.runtime(ex);\r
+ }\r
+ }\r
+\r
+ /*\r
+ * (non-Javadoc) Method declared on ContentOutlinePage\r
+ */\r
+ @Override\r
+ public void selectionChanged(SelectionChangedEvent event) {\r
+ super.selectionChanged(event);\r
+ selectedHeaders = null;\r
+ ISelection selection = event.getSelection();\r
+ if (selection.isEmpty())\r
+ return;\r
+ if (!(selection instanceof IStructuredSelection))\r
+ return;\r
+ try {\r
+ IStructuredSelection strucSel = (IStructuredSelection) selection;\r
+ Object[] sections = strucSel.toArray();\r
+ selectedHeaders = (List) Arrays.asList(sections);\r
+ MarkdownPage.Header first = (Header) sections[0];\r
+ MarkdownPage.Header last = (Header) sections[sections.length - 1];\r
+ int start = fTextEditor.getDocument().getLineOffset(\r
+ first.getLineNumber());\r
+ int length;\r
+ if (first == last) {\r
+ length = fTextEditor.getDocument().getLineLength(\r
+ first.getLineNumber());\r
+ } else {\r
+ IRegion r = getRegion(last);\r
+ int end = r.getOffset() + r.getLength();\r
+ length = end - start;\r
+ }\r
+ fTextEditor.setHighlightRange(start, length, true);\r
+ } catch (Exception x) {\r
+ System.out.println(x.getStackTrace());\r
+ fTextEditor.resetHighlightRange();\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Sets the input of the outline page\r
+ *\r
+ * @param input\r
+ * the input of this outline page\r
+ */\r
+ public void setInput(Object input) {\r
+ fInput = input;\r
+ update();\r
+ }\r
+\r
+ /**\r
+ * Updates the outline page.\r
+ */\r
+ public void update() {\r
+ TreeViewer viewer = getTreeViewer();\r
+\r
+ if (viewer != null) {\r
+ Control control = viewer.getControl();\r
+ if (control != null && !control.isDisposed()) {\r
+ control.setRedraw(false);\r
+ viewer.setInput(fInput);\r
+ viewer.expandAll();\r
+ control.setRedraw(true);\r
+ }\r
+ }\r
+ }\r
+\r
+}\r
--- /dev/null
+package winterwell.markdown.editors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.jface.text.source.IVerticalRuler;
+import org.eclipse.jface.text.source.projection.ProjectionAnnotation;
+import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel;
+import org.eclipse.jface.text.source.projection.ProjectionSupport;
+import org.eclipse.jface.text.source.projection.ProjectionViewer;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IPathEditorInput;
+import org.eclipse.ui.editors.text.TextEditor;
+import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
+import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
+
+import winterwell.markdown.Activator;
+import winterwell.markdown.pagemodel.MarkdownPage;
+import winterwell.markdown.pagemodel.MarkdownPage.Header;
+import winterwell.markdown.preferences.MarkdownPreferencePage;
+import winterwell.markdown.views.MarkdownPreview;
+
+
+/**
+ * Text editor with markdown support.
+ * @author Daniel Winterstein
+ */
+public class MarkdownEditor extends TextEditor implements IDocumentListener
+{
+
+ /**
+ * Maximum length for a task tag message
+ */
+ private static final int MAX_TASK_MSG_LENGTH = 80;
+ private ColorManager colorManager;
+ private MarkdownContentOutlinePage fOutlinePage = null;
+
+ IDocument oldDoc = null;
+
+ private MarkdownPage page;
+
+
+ private boolean pageDirty = true;
+
+ private ProjectionSupport projectionSupport;
+ private final IPreferenceStore pStore;
+ private IPropertyChangeListener prefChangeListener;
+
+
+ public MarkdownEditor() {
+ super();
+ pStore = Activator.getDefault().getPreferenceStore();
+ colorManager = new ColorManager();
+ setSourceViewerConfiguration(new MDConfiguration(colorManager, getPreferenceStore()));
+ }
+
+
+ @Override
+ public void createPartControl(Composite parent) {
+ // Over-ride to add code-folding support
+ super.createPartControl(parent);
+ if (getSourceViewer() instanceof ProjectionViewer) {
+ ProjectionViewer viewer =(ProjectionViewer)getSourceViewer();
+ projectionSupport = new ProjectionSupport(viewer,getAnnotationAccess(),getSharedColors());
+ projectionSupport.install();
+ //turn projection mode on
+ viewer.doOperation(ProjectionViewer.TOGGLE);
+ }
+ }
+
+ /**
+ * Returns the editor's source viewer. May return null before the editor's part has been created and after disposal.
+ */
+ public ISourceViewer getViewer() {
+ return getSourceViewer();
+ }
+
+ @Override
+ protected ISourceViewer createSourceViewer(Composite parent,
+ IVerticalRuler ruler, int styles) {
+// if (true) return super.createSourceViewer(parent, ruler, styles);
+ // Create with code-folding
+ ISourceViewer viewer = new ProjectionViewer(parent, ruler,
+ getOverviewRuler(), isOverviewRulerVisible(), styles);
+ // ensure decoration support has been created and configured.
+ SourceViewerDecorationSupport decSupport = getSourceViewerDecorationSupport(viewer);
+// SourceViewer viewer = (SourceViewer) super.createSourceViewer(parent, ruler, styles);
+ // Setup word-wrapping
+ final StyledText widget = viewer.getTextWidget();
+ // Listen to pref changes
+ prefChangeListener = new IPropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent event) {
+ if (event.getProperty().equals(MarkdownPreferencePage.PREF_WORD_WRAP)) {
+ widget.setWordWrap(MarkdownPreferencePage.wordWrap());
+ }
+ }
+ };
+ pStore.addPropertyChangeListener(prefChangeListener);
+ // Switch on word-wrapping
+ if (MarkdownPreferencePage.wordWrap()) {
+ widget.setWordWrap(true);
+ }
+ return viewer;
+ }
+
+ public void dispose() {
+ if (pStore != null) {
+ pStore.removePropertyChangeListener(prefChangeListener);
+ }
+ colorManager.dispose();
+ super.dispose();
+ }
+ public void documentAboutToBeChanged(DocumentEvent event) {
+ }
+
+ public void documentChanged(DocumentEvent event) {
+ pageDirty = true;
+ }
+
+ @Override
+ protected void doSetInput(IEditorInput input) throws CoreException {
+ // Detach from old
+ if (oldDoc!= null) {
+ oldDoc.removeDocumentListener(this);
+ if (doc2editor.get(oldDoc) == this) doc2editor.remove(oldDoc);
+ }
+ // Set
+ super.doSetInput(input);
+ // Attach as a listener to new doc
+ IDocument doc = getDocument();
+ oldDoc = doc;
+ if (doc==null) return;
+ doc.addDocumentListener(this);
+ doc2editor.put(doc, this);
+ // Initialise code folding
+ haveRunFolding = false;
+ updateSectionFoldingAnnotations(null);
+ }
+
+ @Override
+ protected void editorSaved() {
+ if (MarkdownPreview.preview != null) {
+ // Update the preview when the file is saved
+ MarkdownPreview.preview.update();
+ }
+ }
+
+ public Object getAdapter(Class required) {
+ if (IContentOutlinePage.class.equals(required)) {
+ if (fOutlinePage == null) {
+ fOutlinePage= new MarkdownContentOutlinePage(getDocumentProvider(), this);
+ if (getEditorInput() != null)
+ fOutlinePage.setInput(getEditorInput());
+ }
+ return fOutlinePage;
+ }
+ return super.getAdapter(required);
+ }
+ public IDocument getDocument() {
+ IEditorInput input = getEditorInput();
+ IDocumentProvider docProvider = getDocumentProvider();
+ return docProvider==null? null : docProvider.getDocument(input);
+ }
+ /**
+ *
+ * @return The {@link MarkdownPage} for the document being edited, or null
+ * if unavailable.
+ */
+ public MarkdownPage getMarkdownPage() {
+ if (pageDirty) updateMarkdownPage();
+ return page;
+ }
+
+ public int getPrintColumns() {
+ return getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);
+ }
+
+ /**
+ * @return The text of the editor's document, or null if unavailable.
+ */
+ public String getText() {
+ IDocument doc = getDocument();
+ return doc==null? null : doc.get();
+ }
+
+ private void updateMarkdownPage() {
+ String text = getText();
+ if (text==null) text="";
+ page = new MarkdownPage(text);
+ pageDirty = false;
+ }
+
+ void updateTaskTags(IRegion region) {
+ try {
+ boolean useTags = pStore.getBoolean(MarkdownPreferencePage.PREF_TASK_TAGS);
+ if (!useTags) return;
+ // Get task tags
+// IPreferenceStore peuistore = EditorsUI.getPreferenceStore();
+//// IPreferenceStore pStore_jdt = org.eclipse.jdt.core.compiler.getDefault().getPreferenceStore();
+// String tagString = peuistore.getString("org.eclipse.jdt.core.compiler.taskTags");
+ String tagString = pStore.getString(MarkdownPreferencePage.PREF_TASK_TAGS_DEFINED);
+ List<String> tags = Arrays.asList(tagString.split(","));
+ // Get resource for editor
+ IFile docFile = getResource(this);
+ // Get existing tasks
+ IMarker[] taskMarkers = docFile.findMarkers(IMarker.TASK, true, IResource.DEPTH_INFINITE);
+ List<IMarker> markers = new ArrayList<IMarker>(Arrays.asList(taskMarkers));
+// Collections.sort(markers, c) sort for efficiency
+ // Find tags in doc
+ List<String> text = getMarkdownPage().getText();
+ for(int i=1; i<=text.size(); i++) {
+ String line = text.get(i-1); // wierd off-by-one bug
+ for (String tag : tags) {
+ tag = tag.trim();
+ int tagIndex = line.indexOf(tag);
+ if (tagIndex == -1) continue;
+ IMarker exists = updateTaskTags2_checkExisting(i, tagIndex, line, markers);
+ if (exists!=null) {
+ markers.remove(exists);
+ continue;
+ }
+ IMarker marker = docFile.createMarker(IMarker.TASK);
+ //Once we have a marker object, we can set its attributes
+ marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_NORMAL);
+ String msg = line.substring(line.indexOf(tag), Math.min(tagIndex+MAX_TASK_MSG_LENGTH, line.length()-1));
+ marker.setAttribute(IMarker.MESSAGE, msg);
+ marker.setAttribute(IMarker.LINE_NUMBER, i);
+ }
+ }
+ // Remove old markers
+ for (IMarker m : markers) {
+ try {
+ m.delete();
+ } catch (Exception ex) {
+ //
+ }
+ }
+ } catch (Exception ex) {
+ //
+ }
+ }
+
+ /**
+ * Find an existing marker, if there is one.
+ * @param i
+ * @param tagIndex
+ * @param line
+ * @param markers
+ * @return
+ */
+ private IMarker updateTaskTags2_checkExisting(int i, int tagIndex,
+ String line, List<IMarker> markers) {
+ String tagMessage = line.substring(tagIndex).trim();
+ for (IMarker marker : markers) {
+ try {
+ Integer lineNum = (Integer) marker.getAttribute(IMarker.LINE_NUMBER);
+ if (i != lineNum) continue;
+ String txt = ((String) marker.getAttribute(IMarker.MESSAGE)).trim();
+ if (tagMessage.equals(txt)) return marker;
+ } catch (Exception ex) {
+ // Ignore
+ }
+ }
+ return null;
+ }
+
+
+ private IFile getResource(MarkdownEditor markdownEditor) {
+ IPathEditorInput input = (IPathEditorInput) getEditorInput();
+ IPath path = input.getPath();
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IWorkspaceRoot root = workspace.getRoot();
+ IFile[] files = root.findFilesForLocation(path);
+ if (files.length != 1) return null;
+ IFile docFile = files[0];
+ return docFile;
+ }
+
+
+ /**
+ * @param doc
+ * @return
+ */
+ public static MarkdownEditor getEditor(IDocument doc) {
+ return doc2editor.get(doc);
+ }
+
+ private static final Map<IDocument, MarkdownEditor> doc2editor = new HashMap<IDocument, MarkdownEditor>();
+
+
+ /**
+ * @param region
+ *
+ */
+ public void updatePage(IRegion region) {
+// if (!pageDirty) return;
+ updateTaskTags(region);
+ updateSectionFoldingAnnotations(region);
+ }
+
+
+ private static final Annotation[] ANNOTATION_ARRAY = new Annotation[0];
+
+ private static final Position[] POSITION_ARRAY = new Position[0];
+
+ private boolean haveRunFolding = false;
+ private Map<Annotation, Position> oldAnnotations = new HashMap<Annotation, Position>(0);
+
+ /**
+ * @param region can be null
+ */
+ private void updateSectionFoldingAnnotations(IRegion region) {
+ if (!haveRunFolding) region = null; // Do the whole doc
+ if ( ! (getSourceViewer() instanceof ProjectionViewer)) return;
+ ProjectionViewer viewer = ((ProjectionViewer)getSourceViewer());
+ MarkdownPage mPage = getMarkdownPage();
+ List<Header> headers = mPage.getHeadings(null);
+ // this will hold the new annotations along
+ // with their corresponding positions
+ Map<Annotation, Position> annotations = new HashMap<Annotation, Position>();
+ IDocument doc = getDocument();
+ updateSectionFoldingAnnotations2(doc, headers, annotations, doc.getLength());
+ // Filter existing ones
+ Position[] newValues = annotations.values().toArray(POSITION_ARRAY);
+ List<Annotation> deletedAnnotations = new ArrayList<Annotation>();
+ for(Entry<Annotation, Position> ae : oldAnnotations.entrySet()) {
+ Position oldp = ae.getValue();
+ boolean stillExists = false;
+ for (Position newp : newValues) {
+ if (oldp.equals(newp)) {
+ annotations.remove(newp);
+ stillExists = true;
+ break;
+ }
+ }
+ if (!stillExists && intersectsRegion(oldp, region)) {
+ deletedAnnotations.add(ae.getKey());
+ }
+ }
+ // Filter out-of-region ones
+ for(Annotation a : annotations.keySet().toArray(ANNOTATION_ARRAY)) {
+ Position p = annotations.get(a);
+ if (!intersectsRegion(p , region)) annotations.remove(a);
+ }
+ // Adjust the page
+ ProjectionAnnotationModel annotationModel = viewer.getProjectionAnnotationModel();
+ if (annotationModel==null) return;
+ annotationModel.modifyAnnotations(deletedAnnotations.toArray(ANNOTATION_ARRAY), annotations, null);
+ // Remember old values
+ oldAnnotations.putAll(annotations);
+ for (Annotation a : deletedAnnotations) {
+ oldAnnotations.remove(a);
+ }
+ haveRunFolding = true;
+ }
+
+
+ /**
+ * @param p
+ * @param region
+ * @return true if p overlaps with region, or if region is null
+ */
+ private boolean intersectsRegion(Position p, IRegion region) {
+ if (region==null) return true;
+ if (p.offset > region.getOffset()+region.getLength()) return false;
+ if (p.offset+p.length < region.getOffset()) return false;
+ return true;
+ }
+
+
+ /**
+ * Calculate where to fold, sticking the info into newAnnotations
+ * @param doc
+ * @param headers
+ * @param newAnnotations
+ * @param endParent
+ */
+ private void updateSectionFoldingAnnotations2(IDocument doc, List<Header> headers,
+ Map<Annotation, Position> newAnnotations, int endParent) {
+ for (int i=0; i<headers.size(); i++) {
+ Header header = headers.get(i);
+ ProjectionAnnotation annotation = new ProjectionAnnotation();
+ try {
+ int line = header.getLineNumber();
+ int start = doc.getLineOffset(line);
+ int end = (i==headers.size()-1)? endParent
+ : doc.getLineOffset(headers.get(i+1).getLineNumber());
+ Position position = new Position(start, end-start);
+ newAnnotations.put(annotation, position);
+ // Recurse
+ List<Header> subHeaders = header.getSubHeaders();
+ if (subHeaders.size() > 0) {
+ updateSectionFoldingAnnotations2(doc, subHeaders, newAnnotations, end);
+ }
+ } catch (Exception ex) {
+ System.out.println(ex);
+ }
+ }
+ }
+
+
+}
+
+
+
+/*
+
+ <?xml version="1.0" encoding="UTF-8" ?>
+- <templates>
+ <template name="updateSWT" description="Performs an update to an SWT control in the SWT thread" context="java" enabled="true">${control}.getDisplay().syncExec(new Runnable() { public void run() { ${control}.${cursor} } });</template>
+ <template name="findView" description="Find a workbench view by ID" context="java" enabled="true" deleted="false">${viewType} ${view} = null; IWorkbenchWindow ${window} = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (${window} != null) { IWorkbenchPage ${activePage} = ${window}.getActivePage(); if (${activePage} != null) ${view} = (${viewType}) ${activePage}.findView("${viewID}"); } if (${view} != null) { ${cursor}//${todo}: Add operations for opened view }</template>
+ <template name="getActiveEditor" description="Retrieves the currently active editor in the active page" context="java" enabled="true" deleted="false">IEditorPart ${editor} = null; IWorkbenchWindow ${window} = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (${window} != null) { IWorkbenchPage ${activePage} = ${window}.getActivePage(); if (${activePage} != null) ${editor} = ${activePage}.getActiveEditor(); } if (${editor} != null) { ${cursor}//${todo}: Add operations for active editor }</template>
+ IEditorPart editor = null;
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ if (window != null) {
+ IWorkbenchPage activePage = window.getActivePage();
+ if (activePage != null) editor = activePage.getActiveEditor();
+ }
+ if (editor != null) {
+ // todo: Add operations for active editor
+ }
+
+ <template name="openDialog" description="Creates and opens a JFace dialog" context="java" enabled="true" deleted="false">${dialogType} ${dialog} = new ${dialogType}(${cursor}); ${dialog}.create(); //${todo}: Complete dialog creation if (${dialog}.open() == Dialog.OK) { //${todo}: Perform actions on success };</template>
+ <template name="scanExtensionRegistry" description="Scans the extension registry for extensions of a given extension point" context="java" enabled="true" deleted="false">IExtensionRegistry ${registry} = Platform.getExtensionRegistry(); IExtensionPoint ${point} = ${registry}.getExtensionPoint(${pluginId}, ${expointId}); IExtension[] ${extensions} = ${point}.getExtensions(); for (int ${index} = 0; ${index} < ${extensions}.length; ${index}++) { IConfigurationElement[] ${elements} = ${extensions}[${index}].getConfigurationElements(); for (int ${index2} = 0; ${index2} < ${elements}.length; ${index2}++) { IConfigurationElement ${element} = ${elements}[${index2}]; String ${attValue} = ${element}.getAttribute(${attName}); ${cursor}//${todo}: Implement processing for configuration element } }</template>
+ <template name="showView" description="finds a workbench view by ID and shows it" context="java" enabled="true" deleted="false">${viewType} ${view} = null; IWorkbenchWindow ${window} = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (${window} != null) { IWorkbenchPage ${activePage} = ${window}.getActivePage(); if (${activePage} != null) try { ${view} = (${viewType}) ${activePage}.showView("${viewID}"); } catch (${Exception} e) { // ${todo}: handle exception } } if (${view} != null) { ${cursor} }</template>
+ <template name="signalError" description="Shows an error message in the editors status line" context="java" enabled="true" deleted="false">IEditorActionBarContributor ${contributor} = ${editor}.getEditorSite().getActionBarContributor(); if (${contributor} instanceof EditorActionBarContributor) { IActionBars ${actionBars} = ((EditorActionBarContributor) ${contributor}).getActionBars(); if (${actionBars} != null) { IStatusLineManager ${manager} = ${actionBars}.getStatusLineManager(); if (${manager} != null) ${manager}.setErrorMessage(msg); } }</template>
+ </templates>
+
+*/
\ No newline at end of file
--- /dev/null
+//package winterwell.markdown.editors;
+//
+//import java.util.List;
+//
+//import net.sf.paperclips.PaperClips;
+//import net.sf.paperclips.Print;
+//import net.sf.paperclips.PrintJob;
+//import net.sf.paperclips.TextPrint;
+//
+//import org.eclipse.core.commands.ExecutionEvent;
+//import org.eclipse.core.commands.ExecutionException;
+//import org.eclipse.core.commands.IHandler;
+//import org.eclipse.core.commands.IHandlerListener;
+//import org.eclipse.jface.action.Action;
+//import org.eclipse.jface.text.BadLocationException;
+//import org.eclipse.jface.text.DocumentEvent;
+//import org.eclipse.jface.text.IDocument;
+//import org.eclipse.jface.text.IDocumentListener;
+//import org.eclipse.jface.text.IRegion;
+//import org.eclipse.jface.text.ITextSelection;
+//import org.eclipse.jface.text.Region;
+//import org.eclipse.jface.text.source.ISourceViewer;
+//import org.eclipse.jface.viewers.ISelection;
+//import org.eclipse.swt.SWT;
+//import org.eclipse.swt.printing.PrintDialog;
+//import org.eclipse.swt.printing.PrinterData;
+//import org.eclipse.swt.widgets.Display;
+//import org.eclipse.ui.IEditorPart;
+//import org.eclipse.ui.IPropertyListener;
+//import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
+//
+//import winterwell.markdown.pagemodel.MarkdownFormatter;
+//import winterwell.markdown.pagemodel.MarkdownPage;
+//import winterwell.markdown.pagemodel.MarkdownPage.KLineType;
+//import winterwell.utils.containers.Pair;
+//import winterwell.utils.containers.Range;
+//
+///**
+// * Print the file
+// *
+// *
+// * @author daniel
+// */
+//public class PrintAction extends Action {
+//
+// public PrintAction() {
+// super("Print...");
+// }
+//
+// @Override
+// public void run() {
+// try {
+// MarkdownEditor ed = (MarkdownEditor) ActionBarContributor.getActiveEditor();
+// if (ed == null) return; // The active editor is not a markdown editor.
+// PrintDialog dialog = new PrintDialog(Display.getDefault().getActiveShell(), SWT.NONE);
+// PrinterData printerData = dialog.open ();
+// if (printerData == null) return;
+// Print doc = new TextPrint(ed.getText());
+// PrintJob job = new PrintJob(ed.getTitle(), doc );
+// PaperClips.print(job, printerData);
+// // Done
+// } catch (Exception ex) {
+// System.out.println(ex);
+// }
+// }
+//
+//
+//
+// public void dispose() {
+// // Ignore
+// }
+//
+//
+//}
+//
\ No newline at end of file
--- /dev/null
+
+package winterwell.markdown.pagemodel;
+
+import java.util.List;
+
+import winterwell.utils.StrUtils;
+
+/**
+ * Formats a string that is compatible with the Markdown syntax.
+ * Strings must not include headers.
+ *
+ * @author Howard Abrams
+ */
+public class MarkdownFormatter
+{
+ // Expect everyone to simply use the public static methods...
+ private MarkdownFormatter ()
+ {
+ }
+
+ /**
+ * Formats a collection of lines to a particular width and honors typical
+ * Markdown syntax and formatting.
+ *
+ * The method <i>assumes</i> that if the first line ends with a line
+ * termination character, all the other lines will as well.
+ *
+ * @param lines A list of strings that should be formatted and wrapped.
+ * @param lineWidth The width of the page
+ * @return A string containing each
+ */
+ public static String format (List<String> lines, int lineWidth)
+ {
+ if (lines == null)
+ return null; // Should we return an empty string?
+
+ final String lineEndings;
+ if ( lines.get(0).endsWith ("\r\n") )
+ lineEndings = "\r\n";
+ else if ( lines.get(0).endsWith ("\r") )
+ lineEndings = "\r";
+ else
+ lineEndings = StrUtils.LINEEND;
+
+ final StringBuilder buf = new StringBuilder();
+ for (String line : lines) {
+ buf.append (line);
+ buf.append (' '); // We can add extra spaces with impunity, and this
+ // makes sure our lines don't run together.
+ }
+ return format ( buf.toString(), lineWidth, lineEndings );
+ }
+
+
+ /**
+ * Formats a string of text. The formatting does line wrapping at the
+ * <code>lineWidth</code> boundary, but it also honors the formatting
+ * of initial paragraph lines, allowing indentation of the entire
+ * paragraph.
+ *
+ * @param text The line of text to format
+ * @param lineWidth The width of the lines
+ * @return A string containing the formatted text.
+ */
+ public static String format ( final String text, final int lineWidth)
+ {
+ return format(text, lineWidth, StrUtils.LINEEND);
+ }
+
+ /**
+ * Formats a string of text. The formatting does line wrapping at the
+ * <code>lineWidth</code> boundary, but it also honors the formatting
+ * of initial paragraph lines, allowing indentation of the entire
+ * paragraph.
+ *
+ * @param text The line of text to format
+ * @param lineWidth The width of the lines
+ * @param lineEnding The line ending that overrides the default System value
+ * @return A string containing the formatted text.
+ */
+ public static String format (final String text, final int lineWidth, final String lineEnding)
+ {
+ return new String( format(text.toCharArray (), lineWidth, lineEnding));
+ }
+
+ /**
+ * The available cursor position states as it sits in the buffer.
+ */
+ private enum StatePosition {
+ /** The beginning of a paragraph ... the start of the buffer */
+ BEGIN_FIRST_LINE,
+
+ /** The beginning of the next line, which may be completely ignored. */
+ BEGIN_OTHER_LINE,
+
+ /** The beginning of a new line that will not be ignored, but appended. */
+ BEGIN_NEW_LINE,
+
+ /** The middle of a line. */
+ MIDDLE_OF_LINE
+ }
+
+ /**
+ * The method that does the work of formatting a string of text. The text,
+ * however, is a character array, which is more efficient to work with.
+ *
+ * TODO: Should we make the format(char[]) method public?
+ *
+ * @param text The line of text to format
+ * @param lineWidth The width of the lines
+ * @param lineEnding The line ending that overrides the default System value
+ * @return A string containing the formatted text.
+ */
+ static char[] format ( final char[] text, final int lineWidth, final String lineEnding )
+ {
+ final StringBuilder word = new StringBuilder();
+ final StringBuilder indent = new StringBuilder();
+ final StringBuilder buffer = new StringBuilder(text.length + 10);
+
+ StatePosition state = StatePosition.BEGIN_FIRST_LINE;
+ int lineLength = 0;
+
+ // There are times when we will run across a character(s) that will
+ // cause us to stop doing word wrap until we get to the
+ // "end of non-wordwrap" character(s).
+ //
+ // If this string is set to null, it tells us to "do" word-wrapping.
+ char endWordwrap1 = 0;
+ char endWordwrap2 = 0;
+
+ // We loop one character past the end of the loop, and when we get to
+ // this position, we assign 'c' to be 0 ... as a marker for the end of
+ // the string...
+
+ for (int i = 0; i <= text.length; i++)
+ {
+ final char c;
+ if (i < text.length)
+ c = text[i];
+ else
+ c = 0;
+
+ final char nextChar;
+ if (i+1 < text.length)
+ nextChar = text[i+1];
+ else
+ nextChar = 0;
+
+ // Are we actually word-wrapping?
+ if (endWordwrap1 != 0) {
+ // Did we get the ending sequence of the non-word-wrap?
+ if ( ( endWordwrap2 == 0 && c == endWordwrap1 ) ||
+ ( c == endWordwrap1 && nextChar == endWordwrap2 ) )
+ endWordwrap1 = 0;
+ buffer.append (c);
+ lineLength++;
+
+ if (endWordwrap1 == 0 && endWordwrap2 != 0) {
+ buffer.append (nextChar);
+ lineLength++;
+ i++;
+ }
+ continue;
+ }
+
+ // Check to see if we got one of our special non-word-wrapping
+ // character sequences ...
+
+ if ( c == '[' ) { // [Hyperlink]
+ endWordwrap1 = ']';
+ }
+ else if ( c == '*' && nextChar == '*' ) { // **Bold**
+ endWordwrap1 = '*';
+ endWordwrap2 = '*';
+ } // *Italics*
+ else if ( c == '*' && state == StatePosition.MIDDLE_OF_LINE ) {
+ endWordwrap1 = '*';
+ }
+ else if ( c == '`' ) { // `code`
+ endWordwrap1 = '`';
+ }
+ else if ( c == '(' && nextChar == '(' ) { // ((Footnote))
+ endWordwrap1 = ')';
+ endWordwrap2 = ')';
+ }
+ else if ( c == '!' && nextChar == '[' ) { // ![Image]
+ endWordwrap1 = ')';
+ }
+
+ // We are no longer doing word-wrapping, so tidy the situation up...
+ if (endWordwrap1 != 0) {
+ if (word.length() > 0)
+ lineLength = addWordToBuffer (lineWidth, lineEnding, word, indent, buffer, lineLength);
+ else if (buffer.length() > 0 && buffer.charAt (buffer.length()-1) != ']' )
+ buffer.append(' ');
+ // We are adding an extra space for most situations, unless we get a
+ // [link][ref] where we want them to be together without a space.
+
+ buffer.append (c);
+ lineLength++;
+ continue;
+ }
+
+ // Normal word-wrapping processing continues ...
+
+ if (state == StatePosition.BEGIN_FIRST_LINE)
+ {
+ if ( c == '\n' || c == '\r' ) { // Keep, but ignore initial line feeds
+ buffer.append (c);
+ lineLength = 0;
+ continue;
+ }
+
+ if (Character.isWhitespace (c))
+ indent.append (c);
+ else if ( (c == '*' || c == '-' || c == '.' ) &&
+ Character.isWhitespace (nextChar) )
+ indent.append (' ');
+ else if ( Character.isDigit (c) && nextChar == '.' &&
+ Character.isWhitespace (text[i+2]))
+ indent.append (' ');
+ else if ( c == '>' )
+ indent.append ('>');
+ else
+ state = StatePosition.MIDDLE_OF_LINE;
+
+ // If we are still in the initial state, then put 'er in...
+ if (state == StatePosition.BEGIN_FIRST_LINE) {
+ buffer.append (c);
+ lineLength++;
+ }
+ }
+
+ // While it would be more accurate to explicitely state the range of
+ // possibilities, with something like:
+ // EnumSet.range (StatePosition.BEGIN_OTHER_LINE, StatePosition.MIDDLE_OF_LINE ).contains (state)
+ // We know that what is left is just the BEGIN_FIRST_LINE ...
+
+ if ( state != StatePosition.BEGIN_FIRST_LINE )
+ {
+ // If not the middle of the line, then it must be at the first of a line
+ // Either BEGIN_OTHER_LINE or BEGIN_NEW_LINE
+ if (state != StatePosition.MIDDLE_OF_LINE)
+ {
+ if ( Character.isWhitespace(c) || c == '>' || c == '.' )
+ word.append (c);
+ else if ( ( ( c == '*' || c == '-' ) && Character.isWhitespace (nextChar) ) ||
+ ( Character.isDigit(c) && nextChar == '.' && Character.isWhitespace( text[i+2] ) ) ) {
+ word.append (c);
+ state = StatePosition.BEGIN_NEW_LINE;
+ }
+ else {
+ if (state == StatePosition.BEGIN_NEW_LINE) {
+ buffer.append (word);
+ lineLength = word.substring ( word.indexOf("\n")+1 ).length();
+ }
+ word.setLength (0);
+ state = StatePosition.MIDDLE_OF_LINE;
+ }
+ }
+
+ if (state == StatePosition.MIDDLE_OF_LINE)
+ {
+ // Are we at the end of a word? Then we need to calculate whether
+ // to wrap the line or not.
+ //
+ // This condition does double duty, in that is also serves to
+ // ignore multiple spaces and special characters that may be at
+ // the beginning of the line.
+ if ( Character.isWhitespace(c) || c == 0 )
+ {
+ if ( word.length() > 0) {
+ lineLength = addWordToBuffer (lineWidth, lineEnding, word, indent, buffer, lineLength);
+ }
+ // Do we we two spaces at the end of the line? Honor this...
+ else if ( c == ' ' && ( nextChar == '\r' || nextChar == '\n' ) &&
+ state != StatePosition.BEGIN_OTHER_LINE ) {
+ buffer.append (" ");
+ buffer.append (lineEnding);
+ lineLength = 0;
+ }
+
+ if ( c == '\r' || c == '\n' ) {
+ state = StatePosition.BEGIN_OTHER_LINE;
+ word.append(c);
+ }
+
+ // Linefeeds are completely ignored and just treated as whitespace,
+ // unless, of course, there are two of 'em... and of course, end of
+ // lines are simply evil on Windows machines.
+
+ if ( (c == '\n' && nextChar == '\n') || // Unix-style line-ends
+ ( c == '\r' && nextChar == '\n' && // Windows-style line-ends
+ text[i+2] == '\r' && text[i+3] == '\n' ) )
+ {
+ state = StatePosition.BEGIN_FIRST_LINE;
+ word.setLength(0);
+ indent.setLength (0);
+ lineLength = 0;
+
+ if (c == '\r') { // If we are dealing with Windows-style line-ends,
+ i++; // we need to skip past the next character...
+ buffer.append("\r\n");
+ } else
+ buffer.append(c);
+ }
+
+ } else {
+ word.append (c);
+ state = StatePosition.MIDDLE_OF_LINE;
+ }
+ }
+ }
+ }
+
+ return buffer.toString().toCharArray();
+ }
+
+ /**
+ * Adds a word to the buffer, performing word wrap if necessary.
+ * @param lineWidth The current width of the line
+ * @param lineEnding The line ending to append, if necessary
+ * @param word The word to append
+ * @param indent The indentation string to insert, if necesary
+ * @param buffer The buffer to perform all this stuff to
+ * @param lineLength The current length of the current line
+ * @return The new length of the current line
+ */
+ private static int addWordToBuffer (final int lineWidth, final String lineEnding,
+ final StringBuilder word,
+ final StringBuilder indent,
+ final StringBuilder buffer, int lineLength)
+ {
+ if ( word.length() + lineLength + 1 > lineWidth )
+ {
+ buffer.append (lineEnding);
+ buffer.append (indent);
+ buffer.append (word);
+
+ lineLength = indent.length() + word.length();
+ }
+ else {
+ if ( lineLength > indent.length() )
+ buffer.append (' ');
+ buffer.append (word);
+ lineLength += word.length() + 1;
+ }
+ word.setLength (0);
+ return lineLength;
+ }
+}
--- /dev/null
+//package winterwell.markdown.pagemodel;
+//
+//import java.util.Arrays;
+//import java.util.List;
+//
+//import junit.framework.TestCase;
+//// import winterwell.utils.MarkdownFormatter;
+//
+///**
+// * Test methods in the StringMethods utility class.
+// */
+//public class MarkdownFormatterTest extends TestCase
+//{
+// /**
+// * The local line-end string. \n on unix, \r\n on windows.
+// * I really want to run through all of these tests with both styles.
+// * We'll come back to that sort of a trick.
+// */
+// // public String LINEEND = System.getProperty("line.separator");
+// public static final String LINEEND = "\r\n";
+//
+// /**
+// * Test default word wrapping of a long line of normal text.
+// */
+// public void testFormatStringInt ()
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to the aid of " +
+// "their coopertino lattes, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to the aid of" + LINEEND + // This line is 30 characters
+// "their coopertino lattes, and" + LINEEND +
+// "begin the process of singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 30, LINEEND));
+// }
+//
+// /**
+// * If the initial part of the line contains some spaces, we use that as
+// * the "indentation" for every other line.
+// * @throws Exception
+// */
+// public void testIndentOfSpaces () throws Exception
+// {
+// final String LONG_LINE =
+// " Now is the time for all good " +
+// "chickens to come to the aid of " +
+// "their coopertino lattes, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// " Now is the time for all good" + LINEEND +
+// " chickens to come to the aid of" + LINEEND +
+// " their coopertino lattes, and" + LINEEND +
+// " begin the process of singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * Can we maintain the format of text that is already formatted?
+// * @throws Exception
+// */
+// public void testAlreadyFormatted () throws Exception
+// {
+// final String LONG_LINE =
+// " Now is the time for all good" + LINEEND +
+// " chickens to come to the aid of" + LINEEND +
+// " their coopertino lattes, and" + LINEEND +
+// " begin the process of singing.";
+// assertEquals (LONG_LINE, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * Formatting a single line is all fine and dandy, but what about
+// * formatting multiple paragraphs, that is, blank lines.
+// * @throws Exception
+// */
+// public void testMultipleParagraphs () throws Exception
+// {
+// final String LONG_LINE =
+// " Now is the time for all good " +
+// "chickens to come to their aid." + LINEEND + LINEEND +
+// " And drink coopertino lattes, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// " Now is the time for all good" + LINEEND +
+// " chickens to come to their aid." + LINEEND + LINEEND +
+// " And drink coopertino lattes," + LINEEND +
+// " and begin the process of" + LINEEND + " singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What if the section we are formatting, begins with line feeds?
+// * Do we keep 'em? Might as well. :-)
+// * @throws Exception
+// */
+// public void testInitialLineFeeds () throws Exception
+// {
+// final String LONG_LINE = LINEEND + LINEEND + LINEEND +
+// " Now is the time for all good" + LINEEND +
+// " chickens to come to the aid of" + LINEEND +
+// " their coopertino lattes, and" + LINEEND +
+// " begin the process of singing.";
+// assertEquals (LONG_LINE, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * We need to be able to format bulleted lists appropriately.
+// * @throws Exception
+// */
+// public void testSingleBulletedList () throws Exception
+// {
+// final String LONG_LINE =
+// " * Now is the time for all good " +
+// "chickens to come to the aid of " + LINEEND +
+// "their coopertino lattes, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// " * Now is the time for all good" + LINEEND +
+// " chickens to come to the aid of" + LINEEND +
+// " their coopertino lattes, and" + LINEEND +
+// " begin the process of singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with multiple bulleted lists.
+// * @throws Exception
+// */
+// public void testMultipleBulletedList () throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to:" + LINEEND + LINEEND +
+// " * Cluck" + LINEEND +
+// " * Sing" + LINEEND +
+// " * Drink coopertino lattes.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to:" + LINEEND + LINEEND +
+// " * Cluck" + LINEEND +
+// " * Sing" + LINEEND +
+// " * Drink coopertino lattes.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with multiple bulleted lists.
+// * @throws Exception
+// */
+// public void testMultipleDashedBulletedList () throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to:" + LINEEND + LINEEND +
+// " - Cluck" + LINEEND +
+// " - Sing" + LINEEND +
+// " - Drink coopertino lattes.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to:" + LINEEND + LINEEND +
+// " - Cluck" + LINEEND +
+// " - Sing" + LINEEND +
+// " - Drink coopertino lattes.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * Tests whether we can have nested bulleted lists.
+// * @throws Exception
+// */
+// public void testSubindentedBulletedLists () throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to:" + LINEEND + LINEEND +
+// " * Cluck, cluck, cluck till their little feets hurt:" + LINEEND +
+// " * Do it again and again and again and again." + LINEEND +
+// " * And maybe again and again if their mommy's say so." + LINEEND +
+// " * We can indent really, really, deep with three levels of subitems." + LINEEND +
+// " * But we aren't sure if this is getting ridiculous or just plain expected." + LINEEND +
+// " * Sing, sing, sing till their little voices break:" + LINEEND +
+// " * Do it again and again and again and again." + LINEEND +
+// " * And maybe again and again if their mommy's say so." + LINEEND +
+// " * Drink coopertino lattes.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to:" + LINEEND + LINEEND +
+// " * Cluck, cluck, cluck till their" + LINEEND +
+// " little feets hurt:" + LINEEND +
+// " * Do it again and again and" + LINEEND +
+// " again and again." + LINEEND +
+// " * And maybe again and again if" + LINEEND +
+// " their mommy's say so." + LINEEND +
+// " * We can indent really," + LINEEND +
+// " really, deep with three" + LINEEND +
+// " levels of subitems." + LINEEND +
+// " * But we aren't sure if this" + LINEEND +
+// " is getting ridiculous or " + LINEEND +
+// " just plain expected." + LINEEND +
+// " * Sing, sing, sing till their" + LINEEND +
+// " little voices break:" + LINEEND +
+// " * Do it again and again and" + LINEEND +
+// " again and again." + LINEEND +
+// " * And maybe again and again if" + LINEEND +
+// " their mommy's say so." + LINEEND +
+// " * Drink coopertino lattes.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * Tests whether we can have nested bulleted lists.
+// * @throws Exception
+// */
+// public void testSubindentedBulletedLists2 () throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to:" + LINEEND + LINEEND +
+// " * Cluck, cluck, cluck till their little feets hurt:" + LINEEND + LINEEND +
+// " * Do it again and again and again and again." + LINEEND + LINEEND +
+// " * And maybe again and again if their mommy's say so." + LINEEND + LINEEND +
+// " * We can indent really, really, deep with three levels of subitems." + LINEEND + LINEEND +
+// " * But we aren't sure if this is getting ridiculous or just plain expected." + LINEEND + LINEEND +
+// " * Sing, sing, sing till their little voices break:" + LINEEND + LINEEND +
+// " * Do it again and again and again and again." + LINEEND + LINEEND +
+// " * And maybe again and again if their mommy's say so." + LINEEND + LINEEND +
+// " * Drink coopertino lattes.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to:" + LINEEND + LINEEND +
+// " * Cluck, cluck, cluck till their" + LINEEND +
+// " little feets hurt:" + LINEEND + LINEEND +
+// " * Do it again and again and" + LINEEND +
+// " again and again." + LINEEND + LINEEND +
+// " * And maybe again and again if" + LINEEND +
+// " their mommy's say so." + LINEEND + LINEEND +
+// " * We can indent really," + LINEEND +
+// " really, deep with three" + LINEEND +
+// " levels of subitems." + LINEEND + LINEEND +
+// " * But we aren't sure if this" + LINEEND +
+// " is getting ridiculous or" + LINEEND +
+// " just plain expected." + LINEEND + LINEEND +
+// " * Sing, sing, sing till their" + LINEEND +
+// " little voices break:" + LINEEND + LINEEND +
+// " * Do it again and again and" + LINEEND +
+// " again and again." + LINEEND + LINEEND +
+// " * And maybe again and again if" + LINEEND +
+// " their mommy's say so." + LINEEND + LINEEND +
+// " * Drink coopertino lattes.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with a numeric list?
+// * @throws Exception
+// */
+// public void testSingleNumericList () throws Exception
+// {
+// final String LONG_LINE =
+// " 2. Now is the time for all good " +
+// "chickens to come to the aid of " +
+// "their coopertino lattes, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// " 2. Now is the time for all good" + LINEEND +
+// " chickens to come to the aid of" + LINEEND +
+// " their coopertino lattes, and" + LINEEND +
+// " begin the process of singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with multiple bulleted lists.
+// * @throws Exception
+// */
+// public void testMultipleNumericList () throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to:" + LINEEND + LINEEND +
+// " 1. Cluck" + LINEEND +
+// " 2. Sing" + LINEEND +
+// " 3. Drink coopertino lattes.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to:" + LINEEND + LINEEND +
+// " 1. Cluck" + LINEEND +
+// " 2. Sing" + LINEEND +
+// " 3. Drink coopertino lattes.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with sections that should not be word wrapped, like
+// * the text between brackets (since they are hyperlinks).
+// * @throws Exception
+// */
+// public void testNoWordWrapBracket() throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to [the spurious and costly][3] " +
+// "aid of their coopertino cups, " +
+// "and begin sing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to [the spurious and costly][3]" + LINEEND +
+// "aid of their coopertino cups, and" + LINEEND +
+// "begin sing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+// /**
+// * What about dealing with bracketed sections with no extra white space
+// * @throws Exception
+// */
+// public void testNoWordWrapBracket2() throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to[the spurious and costly][3] " +
+// "aid of their coopertino cups, " +
+// "and begin sing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to[the spurious and costly][3]" + LINEEND +
+// "aid of their coopertino cups, and" + LINEEND +
+// "begin sing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with bold sections that should not be word wrapped.
+// * @throws Exception
+// */
+// public void testNoWordWrapDoubleAsterix() throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to **the spurious and costly** " +
+// "aid of their coopertino cups, " +
+// "and begin sing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to **the spurious and costly**" + LINEEND +
+// "aid of their coopertino cups, and" + LINEEND +
+// "begin sing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with italic sections that should not be word wrapped
+// * @throws Exception
+// */
+// public void testNoWordWrapSingleAsterix() throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to *the spurious and costly* " +
+// "aid of their coopertino cups, " +
+// "and begin sing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to *the spurious and costly*" + LINEEND +
+// "aid of their coopertino cups, and" + LINEEND +
+// "begin sing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with sections that are code should not be broken.
+// * @throws Exception
+// */
+// public void testNoWordWrapCode() throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to `the spurious and costly` " +
+// "aid of their coopertino cups, " +
+// "and begin sing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to `the spurious and costly`" + LINEEND +
+// "aid of their coopertino cups, and" + LINEEND +
+// "begin sing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+// /**
+// * What about dealing with double parenthesis sections ... these shouldn't
+// * be broken up.
+// * @throws Exception
+// */
+// public void testNoWordWrapDoubleParens() throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come to ((the spurious and costly)) " +
+// "aid of their coopertino cups, " +
+// "and begin sing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to ((the spurious and costly))" + LINEEND +
+// "aid of their coopertino cups, and" + LINEEND +
+// "begin sing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 34, LINEEND));
+// }
+//
+//
+// /**
+// * If a line, embedded in a paragraph has two spaces at the end of the line,
+// * these need to be honored and maintained.
+// * @throws Exception
+// */
+// public void testLineBreaksHonored () throws Exception
+// {
+// final String LONG_LINE =
+// "Now is the time for all good " +
+// "chickens to come " + LINEEND +
+// "to the aid of their coopertino lattes, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come " + LINEEND +
+// "to the aid of their coopertino" + LINEEND +
+// "lattes, and begin the process of" + LINEEND +
+// "singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 33, LINEEND));
+// }
+//
+// /**
+// * A "blockquote" in Markdown can accept > characters at the beginning
+// * of all of the lines.
+// * @throws Exception
+// */
+// public void testBlockQuoteSimple () throws Exception
+// {
+// final String LONG_LINE =
+// " > Now is the time for all good " +
+// "chickens to come to the aid of " +
+// "their coopertino <lattes>, and " +
+// "begin the process of singing.";
+// final String EXPECTED =
+// " > Now is the time for all good" + LINEEND +
+// " > chickens to come to the aid of" + LINEEND +
+// " > their coopertino <lattes>, and" + LINEEND +
+// " > begin the process of singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (LONG_LINE, 33, LINEEND));
+// }
+//
+// /**
+// * A "blockquote" in Markdown can accept > characters at the beginning
+// * of all of the lines. Can we accept a version that is already formatted?
+// * @throws Exception
+// */
+// public void testBlockQuoteAlreadyFormatted () throws Exception
+// {
+// final String EXPECTED =
+// " > Now is the time for all good" + LINEEND +
+// " > chickens to come to the aid of" + LINEEND +
+// " > their coopertino <lattes>, and" + LINEEND +
+// " > begin the process of singing.";
+// assertEquals (EXPECTED, MarkdownFormatter.format (EXPECTED, 33, LINEEND));
+// }
+//
+// /**
+// * Tests that the "list" interface works if each string does not have
+// * carriage returns.
+// * @throws Exception
+// */
+// public void testListWithoutLinefeeds () throws Exception
+// {
+// final String lineend = System.getProperty("line.separator");
+//
+// final List<String> lines = Arrays.asList ( new String[] {
+// "Now is the time for all good",
+// "chickens to come to the aid of",
+// "their coopertino lattes, and",
+// "begin the process of singing."
+// } );
+// final String EXPECTED =
+// "Now is the time for all good" + lineend +
+// "chickens to come to the aid of" + lineend + // This line is 30 characters
+// "their coopertino lattes, and" + lineend +
+// "begin the process of singing.";
+//
+// final String RESULTS = MarkdownFormatter.format (lines, 30);
+// assertEquals (EXPECTED, RESULTS);
+// }
+//
+// /**
+// * Tests that the "list" interface works if each string has carriage returns.
+// * @throws Exception
+// */
+// public void testListWithLinefeeds () throws Exception
+// {
+// final List<String> lines = Arrays.asList ( new String[] {
+// "Now is the time for all good chickens to come" + LINEEND,
+// "to the aid of" + LINEEND,
+// "their coopertino lattes, and" + LINEEND,
+// "begin the process of singing."
+// } );
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to the aid of" + LINEEND + // This line is 30 characters
+// "their coopertino lattes, and" + LINEEND +
+// "begin the process of singing.";
+//
+// final String RESULTS = MarkdownFormatter.format (lines, 30);
+// assertEquals (EXPECTED, RESULTS);
+// }
+//
+// /**
+// * Tests that we don't break up image tags.
+// * @throws Exception
+// */
+// public void testImageTags () throws Exception
+// {
+// final List<String> lines = Arrays.asList ( new String[] {
+// "Now is the time for all good chickens to come " +
+// "to the aid " + LINEEND,
+// "their coopertino lattes, and" + LINEEND,
+// "begin the process of singing."
+// } );
+// final String EXPECTED =
+// "Now is the time for all good" + LINEEND +
+// "chickens to come to the aid " + // This line is 30 characters
+// "" + LINEEND +
+// "their coopertino lattes, and" + LINEEND +
+// "begin the process of singing.";
+//
+// final String RESULTS = MarkdownFormatter.format (lines, 30);
+// assertEquals (EXPECTED, RESULTS);
+// }
+//}
--- /dev/null
+/**\r
+ * Copyright winterwell Mathematics Ltd.\r
+ * @author Daniel Winterstein\r
+ * 11 Jan 2007\r
+ */\r
+package winterwell.markdown.pagemodel;\r
+\r
+import java.io.File;\r
+import java.util.ArrayList;\r
+import java.util.Collections;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.regex.Matcher;\r
+import java.util.regex.Pattern;\r
+\r
+import org.eclipse.jface.preference.IPreferenceStore;\r
+\r
+import winterwell.markdown.Activator;\r
+import winterwell.markdown.StringMethods;\r
+import winterwell.markdown.preferences.MarkdownPreferencePage;\r
+import winterwell.utils.FailureException;\r
+import winterwell.utils.Process;\r
+import winterwell.utils.StrUtils;\r
+import winterwell.utils.Utils;\r
+import winterwell.utils.io.FileUtils;\r
+\r
+import com.petebevin.markdown.MarkdownProcessor;\r
+\r
+/**\r
+ * Understands Markdown syntax.\r
+ * \r
+ * @author Daniel Winterstein\r
+ */\r
+public class MarkdownPage {\r
+\r
+ /**\r
+ * Strip leading and trailing #s and whitespace\r
+ * \r
+ * @param line\r
+ * @return cleaned up line\r
+ */\r
+ private String cleanHeader(String line) {\r
+ for (int j = 0; j < line.length(); j++) {\r
+ char c = line.charAt(j);\r
+ if (c != '#' && !Character.isWhitespace(c)) {\r
+ line = line.substring(j);\r
+ break;\r
+ }\r
+ }\r
+ for (int j = line.length() - 1; j > 0; j--) {\r
+ char c = line.charAt(j);\r
+ if (c != '#' && !Character.isWhitespace(c)) {\r
+ line = line.substring(0, j + 1);\r
+ break;\r
+ }\r
+ }\r
+ return line;\r
+ }\r
+\r
+ /**\r
+ * Represents information about a section header. E.g. ## Misc Warblings\r
+ * \r
+ * @author daniel\r
+ */\r
+ public class Header {\r
+ /**\r
+ * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc.\r
+ */\r
+ final int level;\r
+ /**\r
+ * The text of the Header\r
+ */\r
+ final String heading;\r
+ /**\r
+ * Sub-sections, if any\r
+ */\r
+ final List<Header> subHeaders = new ArrayList<Header>();\r
+ /**\r
+ * The line on which this header occurs.\r
+ */\r
+ final int lineNumber;\r
+\r
+ public int getLineNumber() {\r
+ return lineNumber;\r
+ }\r
+\r
+ /**\r
+ * \r
+ * @return the next section (at this depth if possible), null if none\r
+ */\r
+ public Header getNext() {\r
+ if (parent == null) {\r
+ int ti = level1Headers.indexOf(this);\r
+ if (ti == -1 || ti == level1Headers.size() - 1)\r
+ return null;\r
+ return level1Headers.get(ti + 1);\r
+ }\r
+ int i = parent.subHeaders.indexOf(this);\r
+ assert i != -1 : this;\r
+ if (i == parent.subHeaders.size() - 1)\r
+ return parent.getNext();\r
+ return parent.subHeaders.get(i + 1);\r
+ }\r
+ /**\r
+ * \r
+ * @return the next section (at this depth if possible), null if none\r
+ */\r
+ public Header getPrevious() {\r
+ if (parent == null) {\r
+ int ti = level1Headers.indexOf(this);\r
+ if (ti == -1 || ti == 0)\r
+ return null;\r
+ return level1Headers.get(ti - 1);\r
+ }\r
+ int i = parent.subHeaders.indexOf(this);\r
+ assert i != -1 : this;\r
+ if (i == 0)\r
+ return parent.getPrevious();\r
+ return parent.subHeaders.get(i - 1);\r
+ }\r
+ \r
+\r
+ /**\r
+ * The parent section. Can be null.\r
+ */\r
+ private Header parent;\r
+\r
+ /**\r
+ * Create a marker for a section Header\r
+ * \r
+ * @param level\r
+ * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc.\r
+ * @param lineNumber\r
+ * The line on which this header occurs\r
+ * @param heading\r
+ * The text of the Header, trimmed of #s\r
+ * @param currentHeader\r
+ * The previous Header. This is used to find the parent\r
+ * section if there is one. Can be null.\r
+ */\r
+ Header(int level, int lineNumber, String heading, Header currentHeader) {\r
+ this.lineNumber = lineNumber;\r
+ this.level = level;\r
+ this.heading = cleanHeader(heading);\r
+ // Heading Tree\r
+ setParent(currentHeader);\r
+ }\r
+\r
+ private void setParent(Header currentHeader) {\r
+ if (currentHeader == null) {\r
+ parent = null;\r
+ return;\r
+ }\r
+ if (currentHeader.level < level) {\r
+ &nb