Tycho compilation changes for SVN version also.
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 12 Aug 2016 11:10:12 +0000 (14:10 +0300)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Fri, 12 Aug 2016 11:10:12 +0000 (14:10 +0300)
103 files changed:
bundles/com.famfamfam.silk/META-INF/MANIFEST.MF
bundles/org.simantics.db.procore.server.environment/build.properties
bundles/org.simantics.db.procore.server.environment/src/msijni.dll [deleted file]
bundles/org.simantics.db.procore.server.environment/src/msijni64.dll [deleted file]
bundles/org.simantics.db.procore.ui/META-INF/MANIFEST.MF
bundles/org.simantics.db.procore.ui/src/org/simantics/db/procore/ui/ProCoreUserAgent.java
bundles/org.simantics.db.server/build.properties
bundles/org.simantics.diagram.ontology/build.properties
bundles/org.simantics.document.server/build.properties
bundles/org.simantics.layer0/build.properties
bundles/org.simantics.nativemem/.classpath [new file with mode: 0644]
bundles/org.simantics.nativemem/.project [new file with mode: 0644]
bundles/org.simantics.nativemem/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.nativemem/.settings/org.eclipse.pde.core.prefs [new file with mode: 0644]
bundles/org.simantics.nativemem/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.nativemem/build.properties [new file with mode: 0644]
bundles/org.simantics.nativemem/jna-4.2.1.jar [new file with mode: 0644]
bundles/org.simantics.nativemem/jna-platform-4.2.1.jar [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/NativeMem.java [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/ProcessMemoryCounters.java [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Arch.java [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/OS.java [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi32.java [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi64.java [new file with mode: 0644]
bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Test.java [new file with mode: 0644]
bundles/org.simantics.scl.runtime/build.properties
bundles/org.simantics.spreadsheet.ontology/build.properties
bundles/org.simantics.structural.ontology/build.properties
bundles/org.simantics.threadlog/.classpath [new file with mode: 0644]
bundles/org.simantics.threadlog/.project [new file with mode: 0644]
bundles/org.simantics.threadlog/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
bundles/org.simantics.threadlog/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/org.simantics.threadlog/build.properties [new file with mode: 0644]
bundles/org.simantics.threadlog/examples/org/simantics/threadlog/examples/Example1.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/Task.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/ThreadLog.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/internal/Activator.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Interval.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Lane.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogController.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogVisualizer.java [new file with mode: 0644]
bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/TimeLineViewer.java [new file with mode: 0644]
bundles/org.simantics.utils.datastructures/build.properties
bundles/org.simantics/build.properties
bundles/pom.xml
bundles/winterwell.markdown/.classpath [new file with mode: 0644]
bundles/winterwell.markdown/.project [new file with mode: 0644]
bundles/winterwell.markdown/META-INF/MANIFEST.MF [new file with mode: 0644]
bundles/winterwell.markdown/build.properties [new file with mode: 0644]
bundles/winterwell.markdown/icons/coffee.png [new file with mode: 0644]
bundles/winterwell.markdown/icons/github-cat_yellow.png [new file with mode: 0644]
bundles/winterwell.markdown/icons/notepad.gif [new file with mode: 0644]
bundles/winterwell.markdown/icons/settings16_yellow.png [new file with mode: 0644]
bundles/winterwell.markdown/lib/markdownj-1.0.2b4-0.3.0.jar [new file with mode: 0644]
bundles/winterwell.markdown/lib/net.sf.paperclips_1.0.1.jar [new file with mode: 0644]
bundles/winterwell.markdown/lib/winterwell.utils.jar [new file with mode: 0644]
bundles/winterwell.markdown/plugin.xml [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/Activator.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/LogUtil.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/StringMethods.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenGfmView.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenMdView.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/commands/Preferences.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/ActionBarContributor.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/ColorManager.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/EmphasisRule.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/ExportHTMLAction.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/FormatAction.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/HeaderRule.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/HeaderWithUnderlineRule.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/LinkRule.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/ListRule.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/MDColorConstants.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/MDConfiguration.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/MDScanner.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/MDTextHover.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownEditor.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/PrintAction.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/collapseall.gif [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/editors/synced.gif [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatter.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatterTest.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPage.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPageTest.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/preferences/MarkdownPreferencePage.java [new file with mode: 0644]
bundles/winterwell.markdown/src/winterwell/markdown/views/MarkdownPreview.java [new file with mode: 0644]
features/com.lowagie.text.feature/.gitignore [new file with mode: 0644]
features/com.lowagie.text.feature/.project [new file with mode: 0644]
features/com.lowagie.text.feature/build.properties [new file with mode: 0644]
features/com.lowagie.text.feature/feature.xml [new file with mode: 0644]
features/org.apache.lucene4.feature/.gitignore [new file with mode: 0644]
features/org.apache.lucene4.feature/.project [new file with mode: 0644]
features/org.apache.lucene4.feature/build.properties [new file with mode: 0644]
features/org.apache.lucene4.feature/feature.xml [new file with mode: 0644]
features/org.simantics.eclipsec.launcher.feature/.project [new file with mode: 0644]
features/org.simantics.eclipsec.launcher.feature/build.properties [new file with mode: 0644]
features/org.simantics.eclipsec.launcher.feature/feature.xml [new file with mode: 0644]
features/pom.xml
releng/org.simantics.sdk.build.p2.site/pom.xml
releng/org.simantics.sdk.build.targetdefinition/org.simantics.sdk.build.targetdefinition.target
releng/org.simantics.sdk.repository/category.xml
releng/org.simantics.sdk.repository/pom.xml

index ed8297e4c8a67efab07ea87dd58cd0c6ec5a0192..7f1d30dab8aa875ed58fd48c4ba6fd902aee756c 100644 (file)
@@ -2,5 +2,5 @@ Manifest-Version: 1.0
 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
index 6425a96133e072c749e7f7fccf374b9f52ac6a76..93557d6946ccc24a4e5cf264c860dc50e49c55ca 100644 (file)
@@ -13,8 +13,8 @@ source.. = src/
 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
diff --git a/bundles/org.simantics.db.procore.server.environment/src/msijni.dll b/bundles/org.simantics.db.procore.server.environment/src/msijni.dll
deleted file mode 100644 (file)
index 8009884..0000000
Binary files a/bundles/org.simantics.db.procore.server.environment/src/msijni.dll and /dev/null differ
diff --git a/bundles/org.simantics.db.procore.server.environment/src/msijni64.dll b/bundles/org.simantics.db.procore.server.environment/src/msijni64.dll
deleted file mode 100644 (file)
index df74979..0000000
Binary files a/bundles/org.simantics.db.procore.server.environment/src/msijni64.dll and /dev/null differ
index 3965148e3a336a15f09587ae679d87530eba9930..f16e9813766a35fcd760545d0aae81211f33a8ad 100644 (file)
@@ -9,14 +9,13 @@ Bundle-Activator: org.simantics.db.procore.ui.internal.Activator
 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"
index dc93b2f9d9a45918a546a42383fa396523104422..9b0438042adf439991a5ffb939a2458563cedb12 100644 (file)
@@ -2,58 +2,44 @@ package org.simantics.db.procore.ui;
 \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
index af1a5578701904d6f61dabb4f79185dc52db1a9e..657c95170833515c3e397e5920b9dce38429e360 100644 (file)
@@ -15,5 +15,4 @@ bin.includes = META-INF/,\
                .,\\r
                win32.x86/,\\r
                win32.x86_64/,\\r
-               linux.x86/,\\r
                linux.x86_64/\r
index 15454c697905796bfa54049ee6db50eb44c686fc..fb164279d32b25779889cf3b7a5500310f85e3ea 100644 (file)
@@ -2,6 +2,5 @@ source.. = src/
 output.. = bin/\r
 bin.includes = META-INF/,\\r
                .,\\r
-               graphs/*.tg,\\r
                graph.tg\r
 src.includes = graph/\r
index 5959d12ac39f99b5e10f143d1c241dcdc6dd6d0f..5a15a6576dd2ac865af5604684be3c1320537021 100644 (file)
@@ -3,8 +3,7 @@ output.. = bin/
 bin.includes = META-INF/,\\r
                .,\\r
                plugin.xml,\\r
-               document.war,\\r
                webdefault.xml,\\r
-               scl/,\\r
+               scl/\r
 src.includes = scl/\r
 \r
index 15454c697905796bfa54049ee6db50eb44c686fc..fb164279d32b25779889cf3b7a5500310f85e3ea 100644 (file)
@@ -2,6 +2,5 @@ source.. = src/
 output.. = bin/\r
 bin.includes = META-INF/,\\r
                .,\\r
-               graphs/*.tg,\\r
                graph.tg\r
 src.includes = graph/\r
diff --git a/bundles/org.simantics.nativemem/.classpath b/bundles/org.simantics.nativemem/.classpath
new file mode 100644 (file)
index 0000000..2854214
--- /dev/null
@@ -0,0 +1,9 @@
+<?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
diff --git a/bundles/org.simantics.nativemem/.project b/bundles/org.simantics.nativemem/.project
new file mode 100644 (file)
index 0000000..4d55767
--- /dev/null
@@ -0,0 +1,28 @@
+<?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
diff --git a/bundles/org.simantics.nativemem/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.nativemem/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..11f6e46
--- /dev/null
@@ -0,0 +1,7 @@
+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
diff --git a/bundles/org.simantics.nativemem/.settings/org.eclipse.pde.core.prefs b/bundles/org.simantics.nativemem/.settings/org.eclipse.pde.core.prefs
new file mode 100644 (file)
index 0000000..b7e72d0
--- /dev/null
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1\r
+pluginProject.extensions=false\r
+resolve.requirebundle=false\r
diff --git a/bundles/org.simantics.nativemem/META-INF/MANIFEST.MF b/bundles/org.simantics.nativemem/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..d1de018
--- /dev/null
@@ -0,0 +1,11 @@
+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
diff --git a/bundles/org.simantics.nativemem/build.properties b/bundles/org.simantics.nativemem/build.properties
new file mode 100644 (file)
index 0000000..07df1f0
--- /dev/null
@@ -0,0 +1,6 @@
+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
diff --git a/bundles/org.simantics.nativemem/jna-4.2.1.jar b/bundles/org.simantics.nativemem/jna-4.2.1.jar
new file mode 100644 (file)
index 0000000..c21183e
Binary files /dev/null and b/bundles/org.simantics.nativemem/jna-4.2.1.jar differ
diff --git a/bundles/org.simantics.nativemem/jna-platform-4.2.1.jar b/bundles/org.simantics.nativemem/jna-platform-4.2.1.jar
new file mode 100644 (file)
index 0000000..ca6ea47
Binary files /dev/null and b/bundles/org.simantics.nativemem/jna-platform-4.2.1.jar differ
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/NativeMem.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/NativeMem.java
new file mode 100644 (file)
index 0000000..cfccc21
--- /dev/null
@@ -0,0 +1,56 @@
+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
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/ProcessMemoryCounters.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/ProcessMemoryCounters.java
new file mode 100644 (file)
index 0000000..d2192d3
--- /dev/null
@@ -0,0 +1,54 @@
+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
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Arch.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Arch.java
new file mode 100644 (file)
index 0000000..7910f06
--- /dev/null
@@ -0,0 +1,25 @@
+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
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/OS.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/OS.java
new file mode 100644 (file)
index 0000000..a1d0a48
--- /dev/null
@@ -0,0 +1,20 @@
+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
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi32.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi32.java
new file mode 100644 (file)
index 0000000..ba302c9
--- /dev/null
@@ -0,0 +1,67 @@
+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
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi64.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi64.java
new file mode 100644 (file)
index 0000000..ba58251
--- /dev/null
@@ -0,0 +1,66 @@
+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
diff --git a/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Test.java b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Test.java
new file mode 100644 (file)
index 0000000..d00db0c
--- /dev/null
@@ -0,0 +1,13 @@
+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
index accef0636d562b9c0c80ad4f68f76744dda153f9..cd7bc40446f1a1d29395145bcef3bfe25fa61cc2 100755 (executable)
@@ -3,5 +3,4 @@ output.. = bin/
 bin.includes = META-INF/,\\r
                .,\\r
                scl/,\\r
-               schema/,\\r
-               plugin.xml
\ No newline at end of file
+               schema/\r
index 15454c697905796bfa54049ee6db50eb44c686fc..fb164279d32b25779889cf3b7a5500310f85e3ea 100644 (file)
@@ -2,6 +2,5 @@ source.. = src/
 output.. = bin/\r
 bin.includes = META-INF/,\\r
                .,\\r
-               graphs/*.tg,\\r
                graph.tg\r
 src.includes = graph/\r
index 15454c697905796bfa54049ee6db50eb44c686fc..fb164279d32b25779889cf3b7a5500310f85e3ea 100644 (file)
@@ -2,6 +2,5 @@ source.. = src/
 output.. = bin/\r
 bin.includes = META-INF/,\\r
                .,\\r
-               graphs/*.tg,\\r
                graph.tg\r
 src.includes = graph/\r
diff --git a/bundles/org.simantics.threadlog/.classpath b/bundles/org.simantics.threadlog/.classpath
new file mode 100644 (file)
index 0000000..23e107f
--- /dev/null
@@ -0,0 +1,8 @@
+<?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
diff --git a/bundles/org.simantics.threadlog/.project b/bundles/org.simantics.threadlog/.project
new file mode 100644 (file)
index 0000000..2b62393
--- /dev/null
@@ -0,0 +1,28 @@
+<?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
diff --git a/bundles/org.simantics.threadlog/.settings/org.eclipse.jdt.core.prefs b/bundles/org.simantics.threadlog/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..81b6610
--- /dev/null
@@ -0,0 +1,8 @@
+#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
diff --git a/bundles/org.simantics.threadlog/META-INF/MANIFEST.MF b/bundles/org.simantics.threadlog/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..79b038e
--- /dev/null
@@ -0,0 +1,11 @@
+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
diff --git a/bundles/org.simantics.threadlog/build.properties b/bundles/org.simantics.threadlog/build.properties
new file mode 100644 (file)
index 0000000..b088bf1
--- /dev/null
@@ -0,0 +1,15 @@
+###############################################################################\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
diff --git a/bundles/org.simantics.threadlog/examples/org/simantics/threadlog/examples/Example1.java b/bundles/org.simantics.threadlog/examples/org/simantics/threadlog/examples/Example1.java
new file mode 100644 (file)
index 0000000..9530553
--- /dev/null
@@ -0,0 +1,32 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/Task.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/Task.java
new file mode 100644 (file)
index 0000000..d9198ca
--- /dev/null
@@ -0,0 +1,16 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ThreadLog.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ThreadLog.java
new file mode 100644 (file)
index 0000000..a621474
--- /dev/null
@@ -0,0 +1,175 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/internal/Activator.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/internal/Activator.java
new file mode 100644 (file)
index 0000000..908fbd0
--- /dev/null
@@ -0,0 +1,64 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Interval.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Interval.java
new file mode 100644 (file)
index 0000000..e7f2fa1
--- /dev/null
@@ -0,0 +1,29 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Lane.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Lane.java
new file mode 100644 (file)
index 0000000..2c06e41
--- /dev/null
@@ -0,0 +1,27 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogController.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogController.java
new file mode 100644 (file)
index 0000000..36a605d
--- /dev/null
@@ -0,0 +1,101 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogVisualizer.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogVisualizer.java
new file mode 100644 (file)
index 0000000..db29a91
--- /dev/null
@@ -0,0 +1,187 @@
+/*******************************************************************************\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
diff --git a/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/TimeLineViewer.java b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/TimeLineViewer.java
new file mode 100644 (file)
index 0000000..40aefb0
--- /dev/null
@@ -0,0 +1,184 @@
+/*******************************************************************************\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
index 06b47116557c835c041faa62d176d2c0d63f6b4f..12cb67a4b5e6668a45bbf3c3437398f614478077 100644 (file)
@@ -13,6 +13,4 @@ source.. = src/
 output.. = bin/\r
 bin.includes = META-INF/,\\r
                .\r
-javacSource=1.6\r
-javacTarget=1.6\r
-               
\ No newline at end of file
+\r
index b2e37f933fbaee8f3678fb5e32ee66a2d28e466c..6f20375d6c7d98fbc80bb5da99be9866b1fe2f73 100644 (file)
@@ -2,5 +2,4 @@ source.. = src/
 output.. = bin/\r
 bin.includes = META-INF/,\\r
                .,\\r
-               plugin.xml,\\r
-               scl/\r
+               plugin.xml\r
index 1dbd1ef20bdcb8f52238d43f113748b807b355dd..edae943b4dfec10f7083b6a9b748418d187c6a2f 100644 (file)
         <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
@@ -58,6 +71,7 @@
         <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
diff --git a/bundles/winterwell.markdown/.classpath b/bundles/winterwell.markdown/.classpath
new file mode 100644 (file)
index 0000000..cf4ff31
--- /dev/null
@@ -0,0 +1,10 @@
+<?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>
diff --git a/bundles/winterwell.markdown/.project b/bundles/winterwell.markdown/.project
new file mode 100644 (file)
index 0000000..a9e498d
--- /dev/null
@@ -0,0 +1,34 @@
+<?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>
diff --git a/bundles/winterwell.markdown/META-INF/MANIFEST.MF b/bundles/winterwell.markdown/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..3c0bdf2
--- /dev/null
@@ -0,0 +1,41 @@
+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
diff --git a/bundles/winterwell.markdown/build.properties b/bundles/winterwell.markdown/build.properties
new file mode 100644 (file)
index 0000000..ccfe656
--- /dev/null
@@ -0,0 +1,15 @@
+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
diff --git a/bundles/winterwell.markdown/icons/coffee.png b/bundles/winterwell.markdown/icons/coffee.png
new file mode 100644 (file)
index 0000000..2a8c92b
Binary files /dev/null and b/bundles/winterwell.markdown/icons/coffee.png differ
diff --git a/bundles/winterwell.markdown/icons/github-cat_yellow.png b/bundles/winterwell.markdown/icons/github-cat_yellow.png
new file mode 100644 (file)
index 0000000..a1e5ea9
Binary files /dev/null and b/bundles/winterwell.markdown/icons/github-cat_yellow.png differ
diff --git a/bundles/winterwell.markdown/icons/notepad.gif b/bundles/winterwell.markdown/icons/notepad.gif
new file mode 100644 (file)
index 0000000..1531593
Binary files /dev/null and b/bundles/winterwell.markdown/icons/notepad.gif differ
diff --git a/bundles/winterwell.markdown/icons/settings16_yellow.png b/bundles/winterwell.markdown/icons/settings16_yellow.png
new file mode 100644 (file)
index 0000000..27e5397
Binary files /dev/null and b/bundles/winterwell.markdown/icons/settings16_yellow.png differ
diff --git a/bundles/winterwell.markdown/lib/markdownj-1.0.2b4-0.3.0.jar b/bundles/winterwell.markdown/lib/markdownj-1.0.2b4-0.3.0.jar
new file mode 100644 (file)
index 0000000..5e8f22b
Binary files /dev/null and b/bundles/winterwell.markdown/lib/markdownj-1.0.2b4-0.3.0.jar differ
diff --git a/bundles/winterwell.markdown/lib/net.sf.paperclips_1.0.1.jar b/bundles/winterwell.markdown/lib/net.sf.paperclips_1.0.1.jar
new file mode 100644 (file)
index 0000000..1853182
Binary files /dev/null and b/bundles/winterwell.markdown/lib/net.sf.paperclips_1.0.1.jar differ
diff --git a/bundles/winterwell.markdown/lib/winterwell.utils.jar b/bundles/winterwell.markdown/lib/winterwell.utils.jar
new file mode 100644 (file)
index 0000000..1a5784a
Binary files /dev/null and b/bundles/winterwell.markdown/lib/winterwell.utils.jar differ
diff --git a/bundles/winterwell.markdown/plugin.xml b/bundles/winterwell.markdown/plugin.xml
new file mode 100644 (file)
index 0000000..8d043e7
--- /dev/null
@@ -0,0 +1,159 @@
+<?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>
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/Activator.java b/bundles/winterwell.markdown/src/winterwell/markdown/Activator.java
new file mode 100644 (file)
index 0000000..3000144
--- /dev/null
@@ -0,0 +1,60 @@
+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;
+       }
+
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/LogUtil.java b/bundles/winterwell.markdown/src/winterwell/markdown/LogUtil.java
new file mode 100644 (file)
index 0000000..384b951
--- /dev/null
@@ -0,0 +1,41 @@
+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);
+    }
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/StringMethods.java b/bundles/winterwell.markdown/src/winterwell/markdown/StringMethods.java
new file mode 100644 (file)
index 0000000..208e0ae
--- /dev/null
@@ -0,0 +1,557 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenGfmView.java b/bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenGfmView.java
new file mode 100644 (file)
index 0000000..80dc8eb
--- /dev/null
@@ -0,0 +1,41 @@
+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);
+       }
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenMdView.java b/bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenMdView.java
new file mode 100644 (file)
index 0000000..f36d7bd
--- /dev/null
@@ -0,0 +1,39 @@
+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);
+       }
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/commands/Preferences.java b/bundles/winterwell.markdown/src/winterwell/markdown/commands/Preferences.java
new file mode 100644 (file)
index 0000000..5c813a2
--- /dev/null
@@ -0,0 +1,20 @@
+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;
+       }
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/ActionBarContributor.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ActionBarContributor.java
new file mode 100644 (file)
index 0000000..e41793f
--- /dev/null
@@ -0,0 +1,49 @@
+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());
+               }               
+       }
+
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/ColorManager.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ColorManager.java
new file mode 100644 (file)
index 0000000..ff5728a
--- /dev/null
@@ -0,0 +1,28 @@
+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;
+       }
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/EmphasisRule.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/EmphasisRule.java
new file mode 100644 (file)
index 0000000..06f1420
--- /dev/null
@@ -0,0 +1,112 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/ExportHTMLAction.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ExportHTMLAction.java
new file mode 100644 (file)
index 0000000..3c9d26e
--- /dev/null
@@ -0,0 +1,37 @@
+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);
+               }
+       }
+
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/FormatAction.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/FormatAction.java
new file mode 100644 (file)
index 0000000..cd16589
--- /dev/null
@@ -0,0 +1,190 @@
+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               
+       }
+       
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/HeaderRule.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/HeaderRule.java
new file mode 100644 (file)
index 0000000..532e8ad
--- /dev/null
@@ -0,0 +1,22 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/HeaderWithUnderlineRule.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/HeaderWithUnderlineRule.java
new file mode 100644 (file)
index 0000000..f5970d0
--- /dev/null
@@ -0,0 +1,73 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/LinkRule.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/LinkRule.java
new file mode 100644 (file)
index 0000000..4beeb68
--- /dev/null
@@ -0,0 +1,123 @@
+/**
+ * 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;
+       }
+       
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/ListRule.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ListRule.java
new file mode 100644 (file)
index 0000000..11ff3f0
--- /dev/null
@@ -0,0 +1,77 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDColorConstants.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDColorConstants.java
new file mode 100644 (file)
index 0000000..7197f31
--- /dev/null
@@ -0,0 +1,9 @@
+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);
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDConfiguration.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDConfiguration.java
new file mode 100644 (file)
index 0000000..f20a642
--- /dev/null
@@ -0,0 +1,81 @@
+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();
+       }
+}
+
+
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDScanner.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDScanner.java
new file mode 100644 (file)
index 0000000..60a307b
--- /dev/null
@@ -0,0 +1,61 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDTextHover.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDTextHover.java
new file mode 100644 (file)
index 0000000..04377a6
--- /dev/null
@@ -0,0 +1,81 @@
+/**
+ * (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());
+//                     }                                               
+//             };
+//     }
+
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java
new file mode 100644 (file)
index 0000000..445a322
--- /dev/null
@@ -0,0 +1,538 @@
+/**\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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownEditor.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownEditor.java
new file mode 100644 (file)
index 0000000..86699c4
--- /dev/null
@@ -0,0 +1,455 @@
+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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/PrintAction.java b/bundles/winterwell.markdown/src/winterwell/markdown/editors/PrintAction.java
new file mode 100644 (file)
index 0000000..8d089f1
--- /dev/null
@@ -0,0 +1,75 @@
+//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
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/collapseall.gif b/bundles/winterwell.markdown/src/winterwell/markdown/editors/collapseall.gif
new file mode 100644 (file)
index 0000000..a2d80a9
Binary files /dev/null and b/bundles/winterwell.markdown/src/winterwell/markdown/editors/collapseall.gif differ
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/editors/synced.gif b/bundles/winterwell.markdown/src/winterwell/markdown/editors/synced.gif
new file mode 100644 (file)
index 0000000..870934b
Binary files /dev/null and b/bundles/winterwell.markdown/src/winterwell/markdown/editors/synced.gif differ
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatter.java b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatter.java
new file mode 100644 (file)
index 0000000..4c38f19
--- /dev/null
@@ -0,0 +1,351 @@
+
+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;
+  }
+}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatterTest.java b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatterTest.java
new file mode 100644 (file)
index 0000000..4e4dd75
--- /dev/null
@@ -0,0 +1,537 @@
+//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 ![Some text description](http://www.google.com/images/logo.gif)" + 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
+//      "![Some text description](http://www.google.com/images/logo.gif)" + LINEEND +
+//      "their coopertino lattes, and" + LINEEND +
+//      "begin the process of singing.";
+//    
+//    final String RESULTS = MarkdownFormatter.format (lines, 30);
+//    assertEquals (EXPECTED, RESULTS);
+//  }
+//}
diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPage.java b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPage.java
new file mode 100644 (file)
index 0000000..a18a5de
--- /dev/null
@@ -0,0 +1,617 @@
+/**\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) {\