From: Tuukka Lehtonen Date: Fri, 12 Aug 2016 11:10:12 +0000 (+0300) Subject: Tycho compilation changes for SVN version also. X-Git-Tag: v1.25.0~186 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=2531cdf245f42bce854d43f4d49a23983c79db96;p=simantics%2Fplatform.git Tycho compilation changes for SVN version also. --- diff --git a/bundles/com.famfamfam.silk/META-INF/MANIFEST.MF b/bundles/com.famfamfam.silk/META-INF/MANIFEST.MF index ed8297e4c..7f1d30dab 100644 --- a/bundles/com.famfamfam.silk/META-INF/MANIFEST.MF +++ b/bundles/com.famfamfam.silk/META-INF/MANIFEST.MF @@ -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 diff --git a/bundles/org.simantics.db.procore.server.environment/build.properties b/bundles/org.simantics.db.procore.server.environment/build.properties index 6425a9613..93557d694 100644 --- a/bundles/org.simantics.db.procore.server.environment/build.properties +++ b/bundles/org.simantics.db.procore.server.environment/build.properties @@ -13,8 +13,8 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ - bin/msijni.dll,\ - bin/msijni64.dll,\ + msijni.dll,\ + msijni64.dll,\ VC90.2008.SP1.KB2467174.redist.x64.exe,\ VC90.2008.SP1.KB2467174.redist.x86.exe src.includes = native/ 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 index 8009884ed..000000000 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 index df7497910..000000000 Binary files a/bundles/org.simantics.db.procore.server.environment/src/msijni64.dll and /dev/null differ diff --git a/bundles/org.simantics.db.procore.ui/META-INF/MANIFEST.MF b/bundles/org.simantics.db.procore.ui/META-INF/MANIFEST.MF index 3965148e3..f16e98137 100644 --- a/bundles/org.simantics.db.procore.ui/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.db.procore.ui/META-INF/MANIFEST.MF @@ -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" diff --git a/bundles/org.simantics.db.procore.ui/src/org/simantics/db/procore/ui/ProCoreUserAgent.java b/bundles/org.simantics.db.procore.ui/src/org/simantics/db/procore/ui/ProCoreUserAgent.java index dc93b2f9d..9b0438042 100644 --- a/bundles/org.simantics.db.procore.ui/src/org/simantics/db/procore/ui/ProCoreUserAgent.java +++ b/bundles/org.simantics.db.procore.ui/src/org/simantics/db/procore/ui/ProCoreUserAgent.java @@ -2,58 +2,44 @@ package org.simantics.db.procore.ui; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchWindow; import org.simantics.db.DatabaseUserAgent; import org.simantics.db.exception.InternalException; import org.simantics.db.procore.ProCoreDriver; public final class ProCoreUserAgent implements DatabaseUserAgent { - private static Shell getShell(IWorkbench workbench) { - IWorkbenchWindow wbw = workbench.getActiveWorkbenchWindow(); - Shell shell = null; - if (null != wbw) { - shell = wbw.getShell(); - } else { - Display d = getDisplay(); - if (d == null) - return null; - shell = d.getActiveShell(); - if (null == shell) { - Shell[] shells = d.getShells(); - if (null != shells && shells.length > 0) - shell = shells[0]; - } - } - return shell; + private static Shell getShell() { + Shell shell = null; + Display d = getDisplay(); + if (d == null) + return null; + shell = d.getActiveShell(); + if (null == shell) { + Shell[] shells = d.getShells(); + if (null != shells && shells.length > 0) + shell = shells[0]; + } + return shell; } private static Display getDisplay() { - Display d = Display.getCurrent(); - if (d == null) - d = Display.getDefault(); - return d; + Display d = Display.getCurrent(); + if (d == null) + d = Display.getDefault(); + return d; } - private IWorkbench workbench; - public ProCoreUserAgent(IWorkbench workbench) { - this.workbench = workbench; - } - private Shell getShell() { - return getShell(workbench); - } - @Override - public boolean handleStart(InternalException exception) { - Shell shell = getShell(); - if (null == shell) - return false; // no can do - try { - return Auxiliary.handleStart(shell, exception); - } catch (InternalException e) { - return false; // no could do - } - } - - @Override - public String getId() { - return ProCoreDriver.ProCoreDriverName; - } -} \ No newline at end of file + @Override + public boolean handleStart(InternalException exception) { + Shell shell = getShell(); + if (null == shell) + return false; // no can do + try { + return Auxiliary.handleStart(shell, exception); + } catch (InternalException e) { + return false; // no could do + } + } + + @Override + public String getId() { + return ProCoreDriver.ProCoreDriverName; + } +} diff --git a/bundles/org.simantics.db.server/build.properties b/bundles/org.simantics.db.server/build.properties index af1a55787..657c95170 100644 --- a/bundles/org.simantics.db.server/build.properties +++ b/bundles/org.simantics.db.server/build.properties @@ -15,5 +15,4 @@ bin.includes = META-INF/,\ .,\ win32.x86/,\ win32.x86_64/,\ - linux.x86/,\ linux.x86_64/ diff --git a/bundles/org.simantics.diagram.ontology/build.properties b/bundles/org.simantics.diagram.ontology/build.properties index 15454c697..fb164279d 100644 --- a/bundles/org.simantics.diagram.ontology/build.properties +++ b/bundles/org.simantics.diagram.ontology/build.properties @@ -2,6 +2,5 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ - graphs/*.tg,\ graph.tg src.includes = graph/ diff --git a/bundles/org.simantics.document.server/build.properties b/bundles/org.simantics.document.server/build.properties index 5959d12ac..5a15a6576 100644 --- a/bundles/org.simantics.document.server/build.properties +++ b/bundles/org.simantics.document.server/build.properties @@ -3,8 +3,7 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ plugin.xml,\ - document.war,\ webdefault.xml,\ - scl/,\ + scl/ src.includes = scl/ diff --git a/bundles/org.simantics.layer0/build.properties b/bundles/org.simantics.layer0/build.properties index 15454c697..fb164279d 100644 --- a/bundles/org.simantics.layer0/build.properties +++ b/bundles/org.simantics.layer0/build.properties @@ -2,6 +2,5 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ - graphs/*.tg,\ graph.tg src.includes = graph/ diff --git a/bundles/org.simantics.nativemem/.classpath b/bundles/org.simantics.nativemem/.classpath new file mode 100644 index 000000000..28542144e --- /dev/null +++ b/bundles/org.simantics.nativemem/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/bundles/org.simantics.nativemem/.project b/bundles/org.simantics.nativemem/.project new file mode 100644 index 000000000..4d55767d8 --- /dev/null +++ b/bundles/org.simantics.nativemem/.project @@ -0,0 +1,28 @@ + + + org.simantics.nativemem + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..11f6e462d --- /dev/null +++ b/bundles/org.simantics.nativemem/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 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 index 000000000..b7e72d019 --- /dev/null +++ b/bundles/org.simantics.nativemem/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/bundles/org.simantics.nativemem/META-INF/MANIFEST.MF b/bundles/org.simantics.nativemem/META-INF/MANIFEST.MF new file mode 100644 index 000000000..d1de018f9 --- /dev/null +++ b/bundles/org.simantics.nativemem/META-INF/MANIFEST.MF @@ -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 index 000000000..07df1f003 --- /dev/null +++ b/bundles/org.simantics.nativemem/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + jna-4.2.1.jar,\ + jna-platform-4.2.1.jar 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 index 000000000..c21183e38 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 index 000000000..ca6ea472a 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 index 000000000..cfccc2154 --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/NativeMem.java @@ -0,0 +1,56 @@ +package org.simantics.nativemem; + +import org.simantics.nativemem.internal.Arch; +import org.simantics.nativemem.internal.OS; +import org.simantics.nativemem.internal.Psapi32; +import org.simantics.nativemem.internal.Psapi64; + +import com.sun.jna.platform.win32.Kernel32; +import com.sun.jna.platform.win32.WinNT.HANDLE; + + +public class NativeMem { + + /** + * @param out + * the structure to write the result into or null to + * create a new structure + * @return the result structure + */ + public static ProcessMemoryCounters getMemoryCounters(ProcessMemoryCounters out) { + if (out == null) + out = new ProcessMemoryCounters(); + + OS os = OS.calculate(); + Arch arch = Arch.calculate(); + switch (os) { + case WINDOWS: { + HANDLE proc = Kernel32.INSTANCE.GetCurrentProcess(); + switch (arch) { + case X86: { + Psapi32.PROCESS_MEMORY_COUNTERS_EX pmem = new Psapi32.PROCESS_MEMORY_COUNTERS_EX(); + boolean ok = Psapi32.INSTANCE.GetProcessMemoryInfo(proc, pmem, pmem.size()); + if (ok) + pmem.writeTo(out); + return out; + } + + case X86_64: { + Psapi64.PROCESS_MEMORY_COUNTERS_EX pmem = new Psapi64.PROCESS_MEMORY_COUNTERS_EX(); + boolean ok = Psapi64.INSTANCE.GetProcessMemoryInfo(proc, pmem, pmem.size()); + if (ok) + pmem.writeTo(out); + return out; + } + + default: + throw new UnsupportedOperationException("Architecture " + arch + " not supported on operating system " + os); + } + } + + default: + throw new UnsupportedOperationException("Operating system " + os + " not supported"); + } + } + +} \ 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 index 000000000..d2192d3c4 --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/ProcessMemoryCounters.java @@ -0,0 +1,54 @@ +package org.simantics.nativemem; + +/** + * Architecture-independent version of the Windows PsApi PROCESS_MEMORY_COUNTERS + * structure. + * + * @author Tuukka Lehtonen + */ +public class ProcessMemoryCounters { + public int PageFaultCount; + public long PeakWorkingSetSize; + public long WorkingSetSize; + public long QuotaPeakPagedPoolUsage; + public long QuotaPagedPoolUsage; + public long QuotaPeakNonPagedPoolUsage; + public long QuotaNonPagedPoolUsage; + public long PagefileUsage; + public long PeakPagefileUsage; + public long PrivateUsage; + + @Override + public String toString() { + return "ProcessMemoryCounters [PageFaultCount=" + + PageFaultCount + ", PeakWorkingSetSize=" + PeakWorkingSetSize + + ", WorkingSetSize=" + WorkingSetSize + + ", QuotaPeakPagedPoolUsage=" + QuotaPeakPagedPoolUsage + + ", QuotaPagedPoolUsage=" + QuotaPagedPoolUsage + + ", QuotaPeakNonPagedPoolUsage=" + QuotaPeakNonPagedPoolUsage + + ", QuotaNonPagedPoolUsage=" + QuotaNonPagedPoolUsage + + ", PagefileUsage=" + PagefileUsage + ", PeakPagefileUsage=" + + PeakPagefileUsage + ", PrivateUsage=" + PrivateUsage + "]"; + } + + public String toHumanReadableString() { + StringBuilder sb = new StringBuilder(); + sb.append("ProcessMemoryCounters [\n\tPageFaultCount = ").append(PageFaultCount) + .append(",\n\tPeakWorkingSetSize = ").append(toMb(PeakWorkingSetSize)) + .append(" MB,\n\tWorkingSetSize = ").append(toMb(WorkingSetSize)) + .append(" MB,\n\tQuotaPeakPagedPoolUsage = ").append(toMb(QuotaPeakPagedPoolUsage)) + .append(" MB,\n\tQuotaPagedPoolUsage = ").append(toMb(QuotaPagedPoolUsage)) + .append(" MB,\n\tQuotaPeakNonPagedPoolUsage = ").append(toMb(QuotaPeakNonPagedPoolUsage)) + .append(" MB,\n\tQuotaNonPagedPoolUsage = ").append(toMb(QuotaNonPagedPoolUsage)) + .append(" MB,\n\tPagefileUsage = ").append(toMb(PagefileUsage)) + .append(" MB,\n\tPeakPagefileUsage = ").append(toMb(PeakPagefileUsage)) + .append(" MB,\n\tPrivateUsage = ").append(toMb(PrivateUsage)) + .append(" MB]"); + return sb.toString(); + } + + private double toMb(long bytes) { + return (double) bytes / 1048576.0; + } + +} \ 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 index 000000000..7910f06bf --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Arch.java @@ -0,0 +1,25 @@ +package org.simantics.nativemem.internal; + +/** + * @author Tuukka Lehtonen + */ +public enum Arch { + PPC, PPC_64, SPARC, X86, X86_64, UNKNOWN; + + public static Arch calculate() { + String osArch = System.getProperty("os.arch"); + assert osArch != null; + osArch = osArch.toLowerCase(); + if (osArch.equals("i386") || osArch.equals("i586") || osArch.equals("i686") || osArch.equals("x86")) + return X86; + if (osArch.startsWith("amd64") || osArch.startsWith("x86_64")) + return X86_64; + if (osArch.equals("ppc")) + return PPC; + if (osArch.startsWith("ppc")) + return PPC_64; + if (osArch.startsWith("sparc")) + return SPARC; + return UNKNOWN; + } +} 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 index 000000000..a1d0a48c3 --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/OS.java @@ -0,0 +1,20 @@ +package org.simantics.nativemem.internal; + +public enum OS { + APPLE, LINUX, SUN, WINDOWS, UNKNOWN; + + public static OS calculate() { + String osName = System.getProperty("os.name"); + assert osName != null; + osName = osName.toLowerCase(); + if (osName.startsWith("mac os x")) + return APPLE; + if (osName.startsWith("windows")) + return WINDOWS; + if (osName.startsWith("linux")) + return LINUX; + if (osName.startsWith("sun")) + return SUN; + return UNKNOWN; + } +} 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 index 000000000..ba302c9a5 --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi32.java @@ -0,0 +1,67 @@ +package org.simantics.nativemem.internal; + +import java.util.Arrays; +import java.util.List; + +import org.simantics.nativemem.ProcessMemoryCounters; + +import com.sun.jna.Native; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.win32.StdCallLibrary; + +public interface Psapi32 extends StdCallLibrary { + + Psapi32 INSTANCE = (Psapi32) Native.loadLibrary("Psapi", Psapi32.class); + + /* + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684874%28v=vs.85%29.aspx + */ + public static class PROCESS_MEMORY_COUNTERS_EX extends Structure { + public int cb; + public int PageFaultCount; + public int PeakWorkingSetSize; + public int WorkingSetSize; + public int QuotaPeakPagedPoolUsage; + public int QuotaPagedPoolUsage; + public int QuotaPeakNonPagedPoolUsage; + public int QuotaNonPagedPoolUsage; + public int PagefileUsage; + public int PeakPagefileUsage; + public int PrivateUsage; + + @SuppressWarnings("rawtypes") + @Override + protected List getFieldOrder() { + return PROCESS_MEMORY_COUNTERS_EX_FIELDS; + } + + static final List PROCESS_MEMORY_COUNTERS_EX_FIELDS = Arrays.asList(new String[] { + "cb", "PageFaultCount", + "PeakWorkingSetSize", "WorkingSetSize", + "QuotaPeakPagedPoolUsage", "QuotaPagedPoolUsage", + "QuotaPeakNonPagedPoolUsage", "QuotaNonPagedPoolUsage", + "PagefileUsage", "PeakPagefileUsage", "PrivateUsage" + }); + + public void writeTo(ProcessMemoryCounters to) { + to.PageFaultCount = PageFaultCount; + to.PeakWorkingSetSize = PeakWorkingSetSize; + to.WorkingSetSize = WorkingSetSize; + to.QuotaPeakPagedPoolUsage = QuotaPeakPagedPoolUsage; + to.QuotaPagedPoolUsage = QuotaPagedPoolUsage; + to.QuotaPeakNonPagedPoolUsage = QuotaPeakNonPagedPoolUsage; + to.QuotaNonPagedPoolUsage = QuotaNonPagedPoolUsage; + to.PagefileUsage = PagefileUsage; + to.PeakPagefileUsage = PeakPagefileUsage; + to.PrivateUsage = PrivateUsage; + } + } + + /* + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683219%28v=vs.85%29.aspx + */ + boolean GetProcessMemoryInfo(HANDLE Process, PROCESS_MEMORY_COUNTERS_EX ppsmemCounters, int cb); + + +} \ 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 index 000000000..ba58251a0 --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Psapi64.java @@ -0,0 +1,66 @@ +package org.simantics.nativemem.internal; + +import java.util.Arrays; +import java.util.List; + +import org.simantics.nativemem.ProcessMemoryCounters; + +import com.sun.jna.Native; +import com.sun.jna.Structure; +import com.sun.jna.platform.win32.WinNT.HANDLE; +import com.sun.jna.win32.StdCallLibrary; + +public interface Psapi64 extends StdCallLibrary { + + Psapi64 INSTANCE = (Psapi64) Native.loadLibrary("Psapi", Psapi64.class); + + /* + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms684874%28v=vs.85%29.aspx + */ + public static class PROCESS_MEMORY_COUNTERS_EX extends Structure { + public int cb; + public int PageFaultCount; + public long PeakWorkingSetSize; + public long WorkingSetSize; + public long QuotaPeakPagedPoolUsage; + public long QuotaPagedPoolUsage; + public long QuotaPeakNonPagedPoolUsage; + public long QuotaNonPagedPoolUsage; + public long PagefileUsage; + public long PeakPagefileUsage; + public long PrivateUsage; + + @SuppressWarnings("rawtypes") + @Override + protected List getFieldOrder() { + return PROCESS_MEMORY_COUNTERS_EX_FIELDS; + } + + private static final List PROCESS_MEMORY_COUNTERS_EX_FIELDS = Arrays.asList(new String[] { + "cb", "PageFaultCount", + "PeakWorkingSetSize", "WorkingSetSize", + "QuotaPeakPagedPoolUsage", "QuotaPagedPoolUsage", + "QuotaPeakNonPagedPoolUsage", "QuotaNonPagedPoolUsage", + "PagefileUsage", "PeakPagefileUsage", "PrivateUsage" + }); + + public void writeTo(ProcessMemoryCounters to) { + to.PageFaultCount = PageFaultCount; + to.PeakWorkingSetSize = PeakWorkingSetSize; + to.WorkingSetSize = WorkingSetSize; + to.QuotaPeakPagedPoolUsage = QuotaPeakPagedPoolUsage; + to.QuotaPagedPoolUsage = QuotaPagedPoolUsage; + to.QuotaPeakNonPagedPoolUsage = QuotaPeakNonPagedPoolUsage; + to.QuotaNonPagedPoolUsage = QuotaNonPagedPoolUsage; + to.PagefileUsage = PagefileUsage; + to.PeakPagefileUsage = PeakPagefileUsage; + to.PrivateUsage = PrivateUsage; + } + } + + /* + * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683219%28v=vs.85%29.aspx + */ + boolean GetProcessMemoryInfo(HANDLE Process, PROCESS_MEMORY_COUNTERS_EX ppsmemCounters, int cb); + +} \ 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 index 000000000..d00db0c74 --- /dev/null +++ b/bundles/org.simantics.nativemem/src/org/simantics/nativemem/internal/Test.java @@ -0,0 +1,13 @@ +package org.simantics.nativemem.internal; + +import org.simantics.nativemem.NativeMem; +import org.simantics.nativemem.ProcessMemoryCounters; + +public class Test { + + public static void main(String[] args) { + ProcessMemoryCounters mem = NativeMem.getMemoryCounters(null); + System.out.println(mem.toHumanReadableString()); + } + +} diff --git a/bundles/org.simantics.scl.runtime/build.properties b/bundles/org.simantics.scl.runtime/build.properties index accef0636..cd7bc4044 100755 --- a/bundles/org.simantics.scl.runtime/build.properties +++ b/bundles/org.simantics.scl.runtime/build.properties @@ -3,5 +3,4 @@ output.. = bin/ bin.includes = META-INF/,\ .,\ scl/,\ - schema/,\ - plugin.xml \ No newline at end of file + schema/ diff --git a/bundles/org.simantics.spreadsheet.ontology/build.properties b/bundles/org.simantics.spreadsheet.ontology/build.properties index 15454c697..fb164279d 100644 --- a/bundles/org.simantics.spreadsheet.ontology/build.properties +++ b/bundles/org.simantics.spreadsheet.ontology/build.properties @@ -2,6 +2,5 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ - graphs/*.tg,\ graph.tg src.includes = graph/ diff --git a/bundles/org.simantics.structural.ontology/build.properties b/bundles/org.simantics.structural.ontology/build.properties index 15454c697..fb164279d 100644 --- a/bundles/org.simantics.structural.ontology/build.properties +++ b/bundles/org.simantics.structural.ontology/build.properties @@ -2,6 +2,5 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ - graphs/*.tg,\ graph.tg src.includes = graph/ diff --git a/bundles/org.simantics.threadlog/.classpath b/bundles/org.simantics.threadlog/.classpath new file mode 100644 index 000000000..23e107f90 --- /dev/null +++ b/bundles/org.simantics.threadlog/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/bundles/org.simantics.threadlog/.project b/bundles/org.simantics.threadlog/.project new file mode 100644 index 000000000..2b62393d1 --- /dev/null +++ b/bundles/org.simantics.threadlog/.project @@ -0,0 +1,28 @@ + + + org.simantics.threadlog + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + 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 index 000000000..81b661013 --- /dev/null +++ b/bundles/org.simantics.threadlog/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +#Fri Oct 30 22:48:46 EET 2009 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/bundles/org.simantics.threadlog/META-INF/MANIFEST.MF b/bundles/org.simantics.threadlog/META-INF/MANIFEST.MF new file mode 100644 index 000000000..79b038e46 --- /dev/null +++ b/bundles/org.simantics.threadlog/META-INF/MANIFEST.MF @@ -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 index 000000000..b088bf189 --- /dev/null +++ b/bundles/org.simantics.threadlog/build.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2007, 2010 Association for Decentralized Information Management +# in Industry THTH ry. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# VTT Technical Research Centre of Finland - initial API and implementation +############################################################################### +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . 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 index 000000000..953055313 --- /dev/null +++ b/bundles/org.simantics.threadlog/examples/org/simantics/threadlog/examples/Example1.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.examples; + +import static org.simantics.threadlog.ThreadLog.BEGIN; + +import org.simantics.threadlog.Task; +import org.simantics.threadlog.ui.ThreadLogController; + +public class Example1 { + + public static void main(String[] args) throws InterruptedException { + ThreadLogController.start(); + + while(true) { + Thread.sleep(50); + Task t = BEGIN("Foo"); + Thread.sleep(100); + t.end(); + } + } + +} 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 index 000000000..d9198caa1 --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/Task.java @@ -0,0 +1,16 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog; + +public interface Task { + void end(); +} 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 index 000000000..a6214741a --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ThreadLog.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog; + +import gnu.trove.TDoubleArrayList; +import gnu.trove.TLongArrayList; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StreamCorruptedException; +import java.util.ArrayList; + +public class ThreadLog { + + private static final transient String HDR = "TLOG"; + private static final transient int CURRENT_VERSION = 1; + + static ThreadLog defaultLog; + + ArrayList tasks = new ArrayList(); + TDoubleArrayList times = new TDoubleArrayList(); + TLongArrayList threads = new TLongArrayList(); + + public double[] getTimes() { + return times.toNativeArray(); + } + + public String[] getTasks() { + return tasks.toArray(new String[tasks.size()]); + } + + public long[] getThreads() { + return threads.toNativeArray(); + } + + private class TaskImpl implements Task { + public String name; + public long thread; + public long beginTime; + + public TaskImpl(String name) { + this.name = name; + this.thread = Thread.currentThread().getId(); + this.beginTime = System.nanoTime(); + } + + @Override + public void end() { + long endTime = System.nanoTime(); + synchronized(tasks) { + tasks.add(name); + times.add(beginTime*1e-9); + times.add(endTime*1e-9); + threads.add(thread); + } + } + } + + private static enum DummyTask implements Task { + INSTANCE; + + @Override + public void end() { + } + } + + public Task begin(String name) { + return new TaskImpl(name); + } + + public static Task BEGIN(String name) { + try { + if(defaultLog != null) + return defaultLog.begin(name); + } catch(NullPointerException e) { + } + return DummyTask.INSTANCE; + } + + public static ThreadLog setDefaultThreadLog(ThreadLog log) { + ThreadLog currentLog = defaultLog; + defaultLog = log; + return currentLog; + } + + // ------------------------------------------------------------------------ + // SERIALIZATION + // ------------------------------------------------------------------------ + + public static ThreadLog deserialize(File file) throws IOException { + DataInputStream in = null; + try { + in = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); + ThreadLog log = new ThreadLog(); + log.doDeserialize(in); + return log; + } finally { + if (in != null) { + in.close(); + } + } + } + + public static ThreadLog deserialize(DataInput in) throws IOException { + ThreadLog log = new ThreadLog(); + log.doDeserialize(in); + return log; + } + + private void doDeserialize(DataInput in) throws IOException { + String hdr = in.readUTF(); + if (!HDR.equals(hdr)) { + throw new StreamCorruptedException("invalid header '" + hdr + "', expected " + HDR); + } + int ver = in.readInt(); + if (ver == CURRENT_VERSION) { + int taskCount = in.readInt(); + for (int i = 0; i < taskCount; ++i) { + String task = in.readUTF(); + double beginTime = in.readDouble(); + double endTime = in.readDouble(); + long threadId = in.readLong(); + tasks.add(task); + times.add(beginTime); + times.add(endTime); + threads.add(threadId); + } + } else { + throw new StreamCorruptedException("unrecognized log version: " + ver); + } + } + + public void serialize(File file) throws IOException { + DataOutputStream out = null; + try { + out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + serialize(out); + } finally { + if (out != null) { + out.close(); + } + } + } + + public void serialize(DataOutput out) throws IOException { + out.writeUTF(HDR); + out.writeInt(CURRENT_VERSION); + int len = tasks.size(); + out.writeInt(len); + for (int i = 0; i < len; ++i) { + out.writeUTF(tasks.get(i)); + out.writeDouble(times.getQuick(i*2)); + out.writeDouble(times.getQuick(i*2+1)); + out.writeLong(threads.getQuick(i)); + } + } + +} 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 index 000000000..908fbd015 --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/internal/Activator.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.internal; + +import org.eclipse.core.runtime.Plugin; +import org.osgi.framework.BundleContext; +import org.simantics.threadlog.ui.ThreadLogController; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends Plugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.simantics.threadlog"; + + // The shared instance + private static Activator plugin; + + /** + * The constructor + */ + public Activator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + + ThreadLogController.start(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.core.runtime.Plugin#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/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 index 000000000..e7f2fa181 --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Interval.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.ui; + +public class Interval implements Comparable { + String text; + double begin; + double end; + + public Interval(String text, double begin, double end) { + this.text = text; + this.begin = begin; + this.end = end; + } + + @Override + public int compareTo(Interval o) { + return Double.compare(begin, o.begin); + } +} 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 index 000000000..2c06e4185 --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/Lane.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.ui; + +import java.util.ArrayList; +import java.util.List; + +public class Lane { + List intervals = new ArrayList(); + + public void addInterval(Interval interval) { + intervals.add(interval); + } + + public double getEnd() { + return intervals.get(intervals.size()-1).end; + } +} 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 index 000000000..36a605db7 --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogController.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.ui; + +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.simantics.threadlog.ThreadLog; + +public class ThreadLogController extends JFrame { + + private static final long serialVersionUID = -2487997716157625672L; + + boolean isLogging = false; + JButton logButton; + JButton loadButton; + + public ThreadLogController() { + super("Thread log controller"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + logButton = new JButton("Start logging"); + logButton.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if(isLogging) { + logButton.setText("Start logging"); + isLogging = false; + ThreadLog log = ThreadLog.setDefaultThreadLog(null); + + ThreadLogVisualizer visualizer = new ThreadLogVisualizer(); + visualizer.setLog(log); + visualizer.setVisible(true); + } + else { + logButton.setText("Stop logging"); + isLogging = true; + ThreadLog.setDefaultThreadLog(new ThreadLog()); + } + } + + }); + loadButton = new JButton("Load log"); + loadButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + FileNameExtensionFilter filter = new FileNameExtensionFilter( + "Thread Logs (*.tlog)", "tlog", "tlog"); + chooser.setFileFilter(filter); + int returnVal = chooser.showOpenDialog(ThreadLogController.this); + if (returnVal != JFileChooser.APPROVE_OPTION) + return; + + try { + ThreadLog log = ThreadLog.deserialize(chooser.getSelectedFile()); + ThreadLogVisualizer visualizer = new ThreadLogVisualizer(); + visualizer.setLog(log); + visualizer.setVisible(true); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + }); + getContentPane().setLayout(new GridLayout(2, 1)); + getContentPane().add(logButton); + getContentPane().add(loadButton); + + setSize(200, 100); + } + + public static void start() { + javax.swing.SwingUtilities.invokeLater(new Runnable() { + public void run() { + new ThreadLogController().setVisible(true); + } + }); + } + + public static void main(String[] args) { + start(); + } + +} 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 index 000000000..db29a910a --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/ThreadLogVisualizer.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.ui; + +import gnu.trove.TLongObjectHashMap; +import gnu.trove.TObjectProcedure; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; + +import javax.swing.AbstractAction; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JToolBar; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.simantics.threadlog.Task; +import org.simantics.threadlog.ThreadLog; + +public class ThreadLogVisualizer extends JFrame { + + private static final long serialVersionUID = 6250996509358338304L; + + TimeLineViewer viewer = new TimeLineViewer(); + JToolBar toolbar = new JToolBar("Thread Log Visualizer Tools"); + JButton saveButton = new JButton(new SaveAction()); + + ThreadLog currentLog; + + public ThreadLogVisualizer() { + super("Thread log visualizer"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(800, 600); + + getContentPane().setLayout(new BorderLayout()); + getContentPane().add(toolbar, BorderLayout.NORTH); + getContentPane().add(viewer, BorderLayout.CENTER); + + toolbar.add(saveButton); + } + + class SaveAction extends AbstractAction { + private static final long serialVersionUID = 1L; + + public SaveAction() { + super("Save"); + putValue(SHORT_DESCRIPTION, "Save the Current Log"); + putValue(MNEMONIC_KEY, KeyEvent.VK_S); + } + + @Override + public void actionPerformed(ActionEvent e) { + JFileChooser chooser = new JFileChooser(); + FileNameExtensionFilter filter = new FileNameExtensionFilter( + "Thread Logs (*.tlog)", "tlog", "tlog"); + chooser.setFileFilter(filter); + int returnVal = chooser.showSaveDialog(ThreadLogVisualizer.this); + if (returnVal != JFileChooser.APPROVE_OPTION) + return; + + try { + currentLog.serialize(chooser.getSelectedFile()); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + public void setLog(ThreadLog log) { + this.currentLog = log; + + String[] tasks = log.getTasks(); + double[] times = log.getTimes(); + + // Relativize to the first task + double minTime = Double.POSITIVE_INFINITY; + double maxTime = Double.NEGATIVE_INFINITY; + + for(int i=0;i maxTime) + maxTime = temp; + } + for(int i=0;i> intervals = new TLongObjectHashMap>(); + long[] threads = log.getThreads(); + for(int i=0;i in = intervals.get(thread); + if(in == null) { + in = new ArrayList(); + intervals.put(thread, in); + } + in.add(new Interval(tasks[i], times[i*2], times[i*2+1])); + } + + // Create lanes + viewer.clear(); + intervals.forEachValue(new TObjectProcedure>() { + + @Override + public boolean execute(ArrayList intervals) { + Collections.sort(intervals); + ArrayList lanes = new ArrayList(); + + int curLaneId = -1; + Lane curLane = null; + for(Interval in : intervals) { + if(curLane == null || in.begin < curLane.getEnd()) { + ++curLaneId; + if(curLaneId < lanes.size()) + curLane = lanes.get(curLaneId); + else { + curLane = new Lane(); + lanes.add(curLane); + } + } + else { + while(curLaneId > 0) { + Lane prevLane = lanes.get(curLaneId-1); + if(prevLane.getEnd() > in.begin) + break; + --curLaneId; + curLane = prevLane; + } + } + curLane.addInterval(in); + } + + for(Lane lane : lanes) + viewer.addLane(lane); + return true; + } + + }); + + // update viewer + viewer.repaint(); + } + + public void saveImage() { + + } + + public static void main(String[] args) throws Exception { + ThreadLog.setDefaultThreadLog(new ThreadLog()); + + { + Task A = ThreadLog.BEGIN("A"); + Thread.sleep(200); + Task B = ThreadLog.BEGIN("B"); + Thread.sleep(100); + B.end(); + Thread.sleep(100); + Task C = ThreadLog.BEGIN("C"); + Thread.sleep(100); + C.end(); + A.end(); + } + + ThreadLogVisualizer vis = new ThreadLogVisualizer(); + vis.setLog(ThreadLog.setDefaultThreadLog(null)); + vis.setVisible(true); + } +} 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 index 000000000..40aefb055 --- /dev/null +++ b/bundles/org.simantics.threadlog/src/org/simantics/threadlog/ui/TimeLineViewer.java @@ -0,0 +1,184 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.threadlog.ui; + +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.text.DecimalFormat; +import java.text.Format; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +import javax.swing.JPanel; + +public class TimeLineViewer extends JPanel { + + private static final long serialVersionUID = -7410066541298449720L; + + public static long TOOL_TIP_DELAY = 500; + public static double MIN_GRID_LINE_SEPARATION = 15; + + // Time line data + List lanes = new ArrayList(); + + // For panning and zooming + double xScale = 100.0; + double xOffset = 0.0; + double yOffset = 0.0; + int oldX; + int oldY; + + // Tool tips + Timer toolTipTimer = new Timer(); + class ToolTipTask extends TimerTask { + + @Override + public void run() { + System.out.println("show tooltip"); + } + + } + + public TimeLineViewer() { + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + oldX = e.getX(); + oldY = e.getY(); + } + }); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseDragged(MouseEvent e) { + int curX = e.getX(); + int curY = e.getY(); + int diffX = curX - oldX; + int diffY = curY - oldY; + oldX = curX; + oldY = curY; + + xOffset -= diffX/xScale; + yOffset -= diffY; + repaint(); + } + TimerTask toolTipTask; + @Override + public void mouseMoved(MouseEvent e) { + if(toolTipTask != null) + toolTipTask.cancel(); + toolTipTask = new ToolTipTask(); + toolTipTimer.schedule(toolTipTask, TOOL_TIP_DELAY); + } + }); + addMouseWheelListener(new MouseWheelListener() { + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + xOffset += e.getX() / xScale; + xScale *= Math.pow(1.25, -e.getWheelRotation()); + xOffset -= e.getX() / xScale; + repaint(); + } + }); + } + + public void addLane(Lane lane) { + lanes.add(lane); + } + + public void clear() { + lanes.clear(); + } + + public void paintIntervals(Graphics2D g) { + for(int laneId=0;laneId < lanes.size();++laneId) { + Lane lane = lanes.get(laneId); + + double y = 35.0*laneId - yOffset; + double height = 30.0; + + for(Interval interval : lane.intervals) { + double x = (interval.begin - xOffset) * xScale; + double width = (interval.end - interval.begin) * xScale; + + Shape shape = new RoundRectangle2D.Double(x, y, width, height, 10.0, 10.0); + g.setColor(Color.WHITE); + g.fill(shape); + g.setColor(Color.BLACK); + g.draw(shape); + + Rectangle2D bounds = g.getFontMetrics().getStringBounds(interval.text, g); + if(bounds.getWidth() < width) + g.drawString(interval.text, (float)(x+0.5*(width-bounds.getWidth())), (float)(y+20.0)); + } + } + } + + public void paintGrid(Graphics2D g) { + Dimension dim = getSize(); + + g.setBackground(Color.WHITE); + g.clearRect(0, 0, dim.width, dim.height); + + double stepsize = 0.001; + double majorStepsize; + while(true) { + majorStepsize = stepsize * 5.0; + if(stepsize * xScale >= MIN_GRID_LINE_SEPARATION) + break; + stepsize = majorStepsize; + majorStepsize = stepsize * 2.0; + if(stepsize * xScale >= MIN_GRID_LINE_SEPARATION) + break; + stepsize = majorStepsize; + } + + double firstP = Math.ceil(xOffset / stepsize) * stepsize; + double lastP = Math.floor((xOffset + dim.width / xScale) / stepsize) * stepsize; + if(firstP < 0.0) + firstP = 0.0; + + Format format = new DecimalFormat(); + for(double p = firstP;p <= lastP; p+=stepsize) { + int x = (int)((p - xOffset) * xScale); + if(Math.abs(p/majorStepsize - Math.round(p/majorStepsize)) < 1e-3) { + g.setColor(Color.BLACK); + String text = format.format(p); + Rectangle2D bounds = g.getFontMetrics().getStringBounds(text, g); + g.drawString(text, (float)(x-bounds.getWidth()*0.5), 12.0f); + } + else + g.setColor(Color.LIGHT_GRAY); + g.drawLine(x, 14, x, (int)dim.getHeight()); + } + } + + @Override + public void paint(Graphics _g) { + Graphics2D g = (Graphics2D)_g; + + paintGrid(g); + paintIntervals(g); + } + +} diff --git a/bundles/org.simantics.utils.datastructures/build.properties b/bundles/org.simantics.utils.datastructures/build.properties index 06b471165..12cb67a4b 100644 --- a/bundles/org.simantics.utils.datastructures/build.properties +++ b/bundles/org.simantics.utils.datastructures/build.properties @@ -13,6 +13,4 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ . -javacSource=1.6 -javacTarget=1.6 - \ No newline at end of file + diff --git a/bundles/org.simantics/build.properties b/bundles/org.simantics/build.properties index b2e37f933..6f20375d6 100644 --- a/bundles/org.simantics/build.properties +++ b/bundles/org.simantics/build.properties @@ -2,5 +2,4 @@ source.. = src/ output.. = bin/ bin.includes = META-INF/,\ .,\ - plugin.xml,\ - scl/ + plugin.xml diff --git a/bundles/pom.xml b/bundles/pom.xml index 1dbd1ef20..edae943b4 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -13,6 +13,19 @@ 1.0.0-SNAPSHOT + + + + org.eclipse.tycho + tycho-compiler-plugin + 0.25.0 + + -err:-forbidden + + + + + com.famfamfam.silk org.simantics @@ -58,6 +71,7 @@ org.simantics.diagram.profile org.simantics.document org.simantics.document.base.ontology + org.simantics.document.linking.ontology org.simantics.document.linking.ui org.simantics.document.ontology org.simantics.document.server @@ -109,6 +123,7 @@ org.simantics.modeling.template2d.ontology org.simantics.modeling.template2d.ui org.simantics.modeling.ui + org.simantics.nativemem org.simantics.objmap2 org.simantics.platform.ui.ontology org.simantics.project @@ -122,6 +137,7 @@ org.simantics.scl.commands org.simantics.scl.compiler org.simantics.scl.compiler.dummy + org.simantics.scl.data org.simantics.scl.db org.simantics.scl.expressions org.simantics.scl.osgi @@ -150,6 +166,7 @@ org.simantics.structural.ui org.simantics.structural2 org.simantics.team.ui + org.simantics.threadlog org.simantics.trend org.simantics.ui org.simantics.user.ontology @@ -168,5 +185,6 @@ org.simantics.workbench org.simantics.workbench.ontology org.simantics.workbench.search + winterwell.markdown - \ No newline at end of file + diff --git a/bundles/winterwell.markdown/.classpath b/bundles/winterwell.markdown/.classpath new file mode 100644 index 000000000..cf4ff31b9 --- /dev/null +++ b/bundles/winterwell.markdown/.classpath @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/bundles/winterwell.markdown/.project b/bundles/winterwell.markdown/.project new file mode 100644 index 000000000..a9e498d9e --- /dev/null +++ b/bundles/winterwell.markdown/.project @@ -0,0 +1,34 @@ + + + winterwell.markdown + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/bundles/winterwell.markdown/META-INF/MANIFEST.MF b/bundles/winterwell.markdown/META-INF/MANIFEST.MF new file mode 100644 index 000000000..3c0bdf2bb --- /dev/null +++ b/bundles/winterwell.markdown/META-INF/MANIFEST.MF @@ -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 index 000000000..ccfe656ed --- /dev/null +++ b/bundles/winterwell.markdown/build.properties @@ -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 index 000000000..2a8c92bc4 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 index 000000000..a1e5ea993 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 index 000000000..153159366 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 index 000000000..27e53978a 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 index 000000000..5e8f22b91 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 index 000000000..18531820c 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 index 000000000..1a5784a41 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 index 000000000..8d043e788 --- /dev/null +++ b/bundles/winterwell.markdown/plugin.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/Activator.java b/bundles/winterwell.markdown/src/winterwell/markdown/Activator.java new file mode 100644 index 000000000..300014442 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/Activator.java @@ -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 index 000000000..384b951c4 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/LogUtil.java @@ -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 index 000000000..208e0ae99 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/StringMethods.java @@ -0,0 +1,557 @@ +/** + * Basic String manipulation utilities. + * (c) Winterwell 2010 and ThinkTank Mathematics 2007 + */ +package winterwell.markdown; + +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import winterwell.utils.Mutable; +import winterwell.utils.containers.Pair; + +/** + * A collection of general-purpose String handling methods. + * + * @author daniel.winterstein + */ +public final class StringMethods { + + /** + * Removes xml tags, comment blocks and script blocks. + * + * @param page + * @return the page with all xml tags removed. + */ + public static String stripTags(String page) { + // This code is rather ugly, but it does the job + StringBuilder stripped = new StringBuilder(page.length()); + boolean inTag = false; + // Comment blocks and script blocks are given special treatment + boolean inComment = false; + boolean inScript = false; + // Go through the text + for (int i = 0; i < page.length(); i++) { + char c = page.charAt(i); + // First check whether we are ignoring text + if (inTag) { + if (c == '>') + inTag = false; + } else if (inComment) { + if (c == '>' && page.charAt(i - 1) == '-' + && page.charAt(i - 1) == '-') { + inComment = false; + } + } else if (inScript) { + if (c == '>' && page.substring(i - 7, i).equals("/script")) { + inScript = false; + } + } else { + // Check for the start of a tag - looks for '<' followed by any + // non-whitespace character + if (c == '<' && !Character.isWhitespace(page.charAt(i + 1))) { + // Comment, script-block or tag? + if (page.charAt(i + 1) == '!' && page.charAt(i + 2) == '-' + && page.charAt(i + 3) == '-') { + inComment = true; + } else if (i + 8 < page.length() + && page.substring(i + 1, i + 7).equals("script")) { + inScript = true; + i += 7; + } else + inTag = true; // Normal tag by default + } else { + // Append all non-tag chars + stripped.append(c); + } + } // end if... + } + return stripped.toString(); + } + + /** + * The local line-end string. \n on unix, \r\n on windows, \r on mac. + */ + public static final String LINEEND = System.getProperty("line.separator"); + + /** + * @param s + * @return A version of s where the first letter is uppercase and all others + * are lowercase + */ + public static final String capitalise(final String s) { + return s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + } + + /** + * Convert all line breaks into the system line break. + */ + public static final String convertLineBreaks(String text) { + return convertLineBreaks(text, LINEEND); + } + + /** + * Convert all line breaks into the specified line break. + */ + public static final String convertLineBreaks(String text, String br) { + text = text.replaceAll("\r\n", br); + text = text.replaceAll("\r", br); + text = text.replaceAll("\n", br); + return text; + } + + /** + * @param string + * @param character + * @return the number of times character appears in the string + * @author Sam Halliday + */ + static public int countCharsInString(String string, char character) { + int count = 0; + for (char c : string.toCharArray()) { + if (c == character) { + count++; + } + } + return count; + } + + /** + * + * E.g. + * findEnclosingRegion("text with a [region] inside", 15, '[', ']') + * is (??,??) + * + * @param text + * @param offset + * @param start + * @param end + * @return the smallest enclosed region (including start and end chars, the + * 1st number is inclusive, the 2nd exclusive), or null if none. So + * text.subString(start,end) is the specified region + */ + public static Pair findEnclosingRegion(String text, int offset, + char startMarker, char endMarker) { + // Forward + int end = findEnclosingRegion2(text, offset, endMarker, 1); + if (end == -1) + return null; + end++; // end is exclusive + // Backward + int start = findEnclosingRegion2(text, offset, startMarker, -1); + if (start == -1) + return null; + // Sanity + assert text.substring(start, end).charAt(0) == startMarker; + assert text.substring(start, end).endsWith("" + endMarker); + // Done + return new Pair(start, end); + } + + private static int findEnclosingRegion2(String text, int offset, + char endMarker, int direction) { + while (offset > -1 && offset < text.length()) { + char c = text.charAt(offset); + if (c == endMarker) + return offset; + offset += direction; + } + return -1; + } + + /** + * A convenience wrapper for + * {@link #findEnclosingRegion(String, int, char, char)} E.g. + findEnclosingRegion("text with a [region] inside", 15, '[', ']') .equals("[region]"); + + * + * @param text + * @param offset + * @param start + * @param end + * @return the smallest enclosed region (including start and end chars), or + * null if none. + */ + public static String findEnclosingText(String text, int offset, + char startMarker, char endMarker) { + Pair region = findEnclosingRegion(text, offset, startMarker, + endMarker); + if (region == null) + return null; + String s = text.substring(region.first, region.second); + return s; + } + + /** + * Format a block of text to use the given line-width. I.e. adjust the line + * breaks. Also known as hard line-wrapping. Paragraphs are + * recognised by a line of blank space between them (e.g. two returns). + *

+ * Note: a side-effect of this method is that it converts all line-breaks + * into the local system's line-breaks. E.g. on Windows, \n will become \r\n + * + * @param text + * The text to format + * @param lineWidth + * The number of columns in a line. Typically 78 or 80. + * @param respectLeadingCharacters + * Can be null. If set, the specified leading characters will be + * copied if the line is split. Use with " \t" to keep indented + * paragraphs properly indented. Use with "> \t" to also handle + * email-style quoting. Note that respected leading characters + * receive no special treatment when they are used inside a + * paragraph. + * @return A copy of text, formatted to the given line-width. + *

+ * TODO: recognise paragraphs by changes in the respected leading + * characters + */ + public static String format(String text, int lineWidth, int tabWidth, + String respectLeadingCharacters) { + // Switch to Linux line breaks for easier internal workings + text = convertLineBreaks(text, "\n"); + // Find paragraphs + List paras = format2_splitParagraphs(text, + respectLeadingCharacters); + // Rebuild text + StringBuilder sb = new StringBuilder(text.length() + 10); + for (String p : paras) { + String fp = format3_oneParagraph(p, lineWidth, tabWidth, + respectLeadingCharacters); + sb.append(fp); + // Paragraphs end with a double line break + sb.append("\n\n"); + } + // Pop the last line breaks + sb.delete(sb.length() - 2, sb.length()); + // Convert line breaks to system ones + text = convertLineBreaks(sb.toString()); + // Done + return text; + } + + private static List format2_splitParagraphs(String text, + String respectLeadingCharacters) { + List paras = new ArrayList(); + Mutable.Int index = new Mutable.Int(0); + // TODO The characters prefacing this paragraph + String leadingChars = ""; + while (index.value < text.length()) { + // One paragraph + boolean inSpace = false; + int start = index.value; + while (index.value < text.length()) { + char c = text.charAt(index.value); + index.value++; + if (!Character.isWhitespace(c)) { + inSpace = false; + continue; + } + // Line end? + if (c == '\r' || c == '\n') { + // // Handle MS Windows 2 character \r\n line breaks + // if (index.value < text.length()) { + // char c2 = text.charAt(index.value); + // if (c=='\r' && c2=='\n') index.value++; // Push on past + // the 2nd line break char + // } + // Double line end - indicating a paragraph break + if (inSpace) + break; + inSpace = true; + } + // TODO Other paragraph markers, spotted by a change in + // leadingChars + } + String p = text.substring(start, index.value); + paras.add(p); + } + // Done + return paras; + } + + /** + * Format a block of text to fit the given line width + * + * @param p + * @param lineWidth + * @param tabWidth + * @param respectLeadingCharacters + * @return + */ + private static String format3_oneParagraph(String p, int lineWidth, + int tabWidth, String respectLeadingCharacters) { + // Collect the reformatted paragraph + StringBuilder sb = new StringBuilder(p.length() + 10); // Allow for + // some extra + // line-breaks + // Get respected leading chars + String leadingChars = format4_getLeadingChars(p, + respectLeadingCharacters); + // First Line + sb.append(leadingChars); + int lineLength = leadingChars.length(); + int index = leadingChars.length(); + // Loop + while (index < p.length()) { + // Get the next word + StringBuilder word = new StringBuilder(); + char c = p.charAt(index); + index++; + while (!Character.isWhitespace(c)) { + word.append(c); + if (index == p.length()) + break; + c = p.charAt(index); + index++; + } + // Break the line if the word will not fit + if (lineLength + word.length() > lineWidth && lineLength != 0) { + trimEnd(sb); + sb.append('\n'); // lineEnd(sb); + // New line + sb.append(leadingChars); + lineLength = leadingChars.length(); + } + // Add word + sb.append(word); + lineLength += word.length(); + // Add the whitespace + if (index != p.length() && lineLength < lineWidth) { + if (c == '\n') { + c = ' '; + } + sb.append(c); + lineLength += (c == '\t') ? tabWidth : 1; + } + } + // A final trim + trimEnd(sb); + // Done + return sb.toString(); + } + + /** + * + * @param text + * @param respectLeadingCharacters + * Can be null + * @return The characters at the beginning of text which are respected. E.g. + * ("> Hello", " \t>") --> "> " + */ + private static String format4_getLeadingChars(String text, + String respectLeadingCharacters) { + if (respectLeadingCharacters == null) + return ""; + // Line-breaks cannot be respected + assert respectLeadingCharacters.indexOf('\n') == -1; + // Look for the first non-respected char + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (respectLeadingCharacters.indexOf(c) == -1) { + // Return the previous chars + return text.substring(0, i); + } + } + // All chars are respected + return text; + } + + /** + * Ensure that line ends with the right line-end character(s) + */ + public static final String lineEnd(String line) { + // strip possibly inappropriate line-endings + if (line.endsWith("\n")) { + line = line.substring(0, line.length() - 1); + } + if (line.endsWith("\r\n")) { + line = line.substring(0, line.length() - 2); + } + if (line.endsWith("\r")) { + line = line.substring(0, line.length() - 1); + } + // add in proper line end + if (!line.endsWith(LINEEND)) { + line += LINEEND; + } + return line; + } + + /** + * Ensure that line ends with the right line-end character(s). This is more + * efficient than the version for Strings. + * + * @param line + */ + public static final void lineEnd(final StringBuilder line) { + if (line.length() == 0) { + line.append(LINEEND); + return; + } + // strip possibly inappropriate line-endings + final char last = line.charAt(line.length() - 1); + if (last == '\n') { + if ((line.length() > 1) && (line.charAt(line.length() - 2) == '\r')) { + // \r\n + line.replace(line.length() - 2, line.length(), LINEEND); + return; + } + line.replace(line.length() - 1, line.length(), LINEEND); + return; + } + if (last == '\r') { + line.replace(line.length() - 1, line.length(), LINEEND); + return; + } + line.append(LINEEND); + return; + } + + + + /** + * @param string + * @return the MD5 sum of the string using the default charset. Null if + * there was an error in calculating the hash. + * @author Sam Halliday + */ + public static String md5Hash(String string) { + MessageDigest md5 = null; + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + // ignore this exception, we know MD5 exists + } + md5.update(string.getBytes()); + BigInteger hash = new BigInteger(1, md5.digest()); + return hash.toString(16); + } + + /** + * Removes HTML-style tags from a string. + * + * @param s + * a String from which to remove tags + * @return a string with all instances of <.*> removed. + */ + public static String removeTags(String s) { + StringBuffer sb = new StringBuffer(); + boolean inTag = false; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '<') + inTag = true; + if (!inTag) + sb.append(c); + if (c == '>') + inTag = false; + } + return sb.toString(); + } + + /** + * Repeat a character. + * + * @param c + * @param i + * @return A String consisting of i x c. + * @example assert repeat('-', 5).equals("-----"); + */ + public static String repeat(Character c, int i) { + StringBuilder dashes = new StringBuilder(i); + for (int j = 0; j < i; j++) + dashes.append(c); + return dashes.toString(); + } + + /** + * Split a piece of text into separate lines. The line breaks are left at + * the end of each line. + * + * @param text + * @return The individual lines in the text. + */ + public static List splitLines(String text) { + List lines = new ArrayList(); + // Search for lines + int start = 0; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c == '\r' || c == '\n') { + // Handle MS Windows 2 character \r\n line breaks + if (i + 1 < text.length()) { + char c2 = text.charAt(i + 1); + if (c == '\r' && c2 == '\n') + i++; + } + // Get the line, with the line break + String line = text.substring(start, i + 1); + lines.add(line); + start = i + 1; + } + } + // Last one + if (start != text.length()) { + String line = text.substring(start); + lines.add(line); + } + return lines; + } + + /** + * Remove trailing whitespace. c.f. String#trim() which removes + * leading and trailing whitespace. + * + * @param sb + */ + private static void trimEnd(StringBuilder sb) { + while (true) { + // Get the last character + int i = sb.length() - 1; + if (i == -1) + return; // Quit if sb is empty + char c = sb.charAt(i); + if (!Character.isWhitespace(c)) + return; // Finish? + sb.deleteCharAt(i); // Remove and continue + } + } + + /** + * Returns true if the string is just whitespace, or empty, or null. + * + * @param s + */ + public static final boolean whitespace(final String s) { + if (s == null) { + return true; + } + for (int i = 0; i < s.length(); i++) { + final char c = s.charAt(i); + if (!Character.isWhitespace(c)) { + return false; + } + } + return true; + } + + /** + * @param text + * @return the number of words in text. Uses a crude whitespace + * measure. + */ + public static int wordCount(String text) { + String[] bits = text.split("\\W+"); + int wc = 0; + for (String string : bits) { + if (!whitespace(string)) wc++; + } + return wc; + } + +} 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 index 000000000..80dc8eb10 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenGfmView.java @@ -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 index 000000000..f36d7bd57 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/commands/OpenMdView.java @@ -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 index 000000000..5c813a2d1 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/commands/Preferences.java @@ -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 index 000000000..e41793f44 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ActionBarContributor.java @@ -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 index 000000000..ff5728a0c --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ColorManager.java @@ -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 index 000000000..06f14201d --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/EmphasisRule.java @@ -0,0 +1,112 @@ +/** + * Copyright winterwell Mathematics Ltd. + * @author Daniel Winterstein + * 11 Jan 2007 + */ +package winterwell.markdown.editors; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.rules.ICharacterScanner; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.MultiLineRule; +import org.eclipse.jface.text.rules.Token; + +/** + * + * + * @author Daniel Winterstein + */ +public class EmphasisRule implements IRule { + private static char[][] fDelimiters = null; + private char[] fSequence; + protected IToken fToken; + + + public EmphasisRule(String marker, IToken token) { + assert marker.equals("*") || marker.equals("_") || marker.equals("**") + || marker.equals("***") || marker.equals("`") || marker.equals("``"); + Assert.isNotNull(token); + fSequence = marker.toCharArray(); + fToken = token; + } + + // 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. + for (int j = i; j > 0; j--) + scanner.unread(); + return false; + } + } + return true; + } + + /* + * @see IRule#evaluate(ICharacterScanner) + * + * @since 2.0 + */ + public IToken evaluate(ICharacterScanner scanner) { + // Should be connected only on the right side + scanner.unread(); + boolean sawSpaceBefore = Character.isWhitespace(scanner.read()); + if (!sawSpaceBefore && scanner.getColumn() != 0) { + return Token.UNDEFINED; + } + + int c = scanner.read(); + // Should be connected only on right side + if (c != fSequence[0] || !sequenceDetected(scanner, fSequence, false)) { + scanner.unread(); + return Token.UNDEFINED; + } + int readCount = fSequence.length; + if (fDelimiters == null) { + fDelimiters = scanner.getLegalLineDelimiters(); + } + // Start sequence detected + int delimiterFound = 0; + // Is it a list item marker, or just a floating *? + if (sawSpaceBefore) { + boolean after = Character.isWhitespace(scanner.read()); + scanner.unread(); + if (after) + delimiterFound = 2; + } + + while (delimiterFound < 2 + && (c = scanner.read()) != ICharacterScanner.EOF) { + readCount++; + + if (!sawSpaceBefore && c == fSequence[0] + && sequenceDetected(scanner, fSequence, false)) { + 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; + sawSpaceBefore = Character.isWhitespace(c); + } + // Reached ICharacterScanner.EOF + for (; readCount > 0; readCount--) + scanner.unread(); + return Token.UNDEFINED; + } + +} 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 index 000000000..3c9d26e87 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ExportHTMLAction.java @@ -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 index 000000000..cd165892d --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/FormatAction.java @@ -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 lines = page.getText(); + List 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 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 index 000000000..11ff3f052 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/ListRule.java @@ -0,0 +1,77 @@ +/** + * Copyright winterwell Mathematics Ltd. + * @author Daniel Winterstein + * 11 Jan 2007 + */ +package winterwell.markdown.editors; + +import java.util.ArrayList; +import java.util.List; +import java.util.Arrays; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.rules.ICharacterScanner; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IToken; +import org.eclipse.jface.text.rules.Token; + +/** + * + * + * @author Daniel Winterstein + */ +public class ListRule implements IRule { + private ArrayList markerList; + protected IToken fToken; + + public ListRule(IToken token) { + Assert.isNotNull(token); + fToken= token; + } + + + /* + * @see IRule#evaluate(ICharacterScanner) + * @since 2.0 + */ + public IToken evaluate(ICharacterScanner scanner) { + if (scanner.getColumn() != 0) { + return Token.UNDEFINED; + } +// // Fast mode +// if (scanner.read() != '-') { +// scanner.unread(); +// return Token.UNDEFINED; +// } +// if (Character.isWhitespace(scanner.read())) { +// return fToken; +// } +// scanner.unread(); +// scanner.unread(); +// return Token.UNDEFINED; +// // Fast mode + int readCount = 0; + int c; + while ((c = scanner.read()) != ICharacterScanner.EOF) { + readCount++; + if( !Character.isWhitespace( c ) ) { + int after = scanner.read(); +// readCount++; + scanner.unread(); +// if ( markerList.contains(c) && Character.isWhitespace( after ) ) { + if ( (c == '-' || c == '+' || c == '*') + && Character.isWhitespace( after ) ) { + return fToken; + } else { + for (; readCount > 0; readCount--) + scanner.unread(); + return Token.UNDEFINED; + } + } + } + // Reached ICharacterScanner.EOF + for (; readCount > 0; readCount--) + scanner.unread(); + return Token.UNDEFINED; + } +} 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 index 000000000..7197f3179 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDColorConstants.java @@ -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 index 000000000..f20a64277 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDConfiguration.java @@ -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 index 000000000..60a307b78 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDScanner.java @@ -0,0 +1,61 @@ +/** + * Copyright winterwell Mathematics Ltd. + * @author Daniel Winterstein + * 13 Jan 2007 + */ +package winterwell.markdown.editors; + +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.text.TextAttribute; +import org.eclipse.jface.text.rules.IRule; +import org.eclipse.jface.text.rules.IWhitespaceDetector; +import org.eclipse.jface.text.rules.MultiLineRule; +import org.eclipse.jface.text.rules.RuleBasedScanner; +import org.eclipse.jface.text.rules.Token; +import org.eclipse.jface.text.rules.WhitespaceRule; +import org.eclipse.swt.SWT; + +import winterwell.markdown.Activator; +import winterwell.markdown.preferences.MarkdownPreferencePage; + +/** + * + * + * @author Daniel Winterstein + */ +public class MDScanner extends RuleBasedScanner { + ColorManager cm; + public MDScanner(ColorManager cm) { + this.cm = cm; + IPreferenceStore pStore = Activator.getDefault().getPreferenceStore(); + Token heading = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD)); + Token comment = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_COMMENT)))); + Token emphasis = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_DEFUALT)), null, SWT.ITALIC)); + Token list = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_HEADER)), null, SWT.BOLD)); + Token link = new Token(new TextAttribute(cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_LINK)), null, TextAttribute.UNDERLINE)); + Token code = new Token(new TextAttribute( + cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE)), + cm.getColor(PreferenceConverter.getColor(pStore, MarkdownPreferencePage.PREF_CODE_BG)), + SWT.NORMAL)); + setRules(new IRule[] { + new LinkRule(link), + new HeaderRule(heading), + new HeaderWithUnderlineRule(heading), + new ListRule(list), + new EmphasisRule("_", emphasis), + new EmphasisRule("***", emphasis), + new EmphasisRule("**", emphasis), + new EmphasisRule("*", emphasis), + new EmphasisRule("``", code), + new EmphasisRule("`", code), + new MultiLineRule("", comment), + // WhitespaceRule messes up with the rest of rules +// new WhitespaceRule(new IWhitespaceDetector() { +// public boolean isWhitespace(char c) { +// return Character.isWhitespace(c); +// } +// }), + }); + } +} 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 index 000000000..04377a622 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MDTextHover.java @@ -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 ""+text+""; + } 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 altRegion; + Pair 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 index 000000000..445a322d3 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownContentOutlinePage.java @@ -0,0 +1,538 @@ +/** + * Copyright winterwell Mathematics Ltd. + * @author Daniel Winterstein + * 11 Jan 2007 + */ +package winterwell.markdown.editors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.resource.ImageDescriptor; +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.Region; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.part.IPageSite; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.views.contentoutline.ContentOutlinePage; + +import winterwell.markdown.pagemodel.MarkdownPage; +import winterwell.markdown.pagemodel.MarkdownPage.Header; +import winterwell.markdown.pagemodel.MarkdownPage.KLineType; +import winterwell.utils.StrUtils; +import winterwell.utils.Utils; +import winterwell.utils.web.WebUtils; + +/** + * + * + * @author Daniel Winterstein + */ +public final class MarkdownContentOutlinePage extends ContentOutlinePage { + + /** + * + * + * @author Daniel Winterstein + */ + public final class ContentProvider implements ITreeContentProvider, + IDocumentListener { + + // protected final static String SEGMENTS= "__md_segments"; + // //$NON-NLS-1$ + // protected IPositionUpdater fPositionUpdater= new + // DefaultPositionUpdater(SEGMENTS); + private MarkdownPage fContent; + // protected List fContent= new ArrayList(10); + private MarkdownEditor fTextEditor; + + private void parse() { + fContent = fTextEditor.getMarkdownPage(); + } + + /* + * @see IContentProvider#inputChanged(Viewer, Object, Object) + */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // Detach from old + if (oldInput != null) { + IDocument document = fDocumentProvider.getDocument(oldInput); + if (document != null) { + document.removeDocumentListener(this); + } + } + fContent = null; + // Attach to new + if (newInput == null) + return; + IDocument document = fDocumentProvider.getDocument(newInput); + if (document == null) + return; + fTextEditor = MarkdownEditor.getEditor(document); + document.addDocumentListener(this); + parse(); + } + + /* + * @see IContentProvider#dispose + */ + public void dispose() { + fContent = null; + } + + /* + * @see IContentProvider#isDeleted(Object) + */ + public boolean isDeleted(Object element) { + return false; + } + + /* + * @see IStructuredContentProvider#getElements(Object) + */ + public Object[] getElements(Object element) { + return fContent.getHeadings(null).toArray(); + } + + /* + * @see ITreeContentProvider#hasChildren(Object) + */ + public boolean hasChildren(Object element) { + if (element == fInput) { + return true; + } + if (element instanceof MarkdownPage.Header) { + MarkdownPage.Header header = (MarkdownPage.Header) element; + return header.getSubHeaders().size() > 0; + } + ; + return false; + } + + /* + * @see ITreeContentProvider#getParent(Object) + */ + public Object getParent(Object element) { + if (!(element instanceof MarkdownPage.Header)) + return null; + return ((MarkdownPage.Header) element).getParent(); + } + + /* + * @see ITreeContentProvider#getChildren(Object) + */ + public Object[] getChildren(Object element) { + if (element == fInput) { + return fContent.getHeadings(null).toArray(); + } + if (!(element instanceof MarkdownPage.Header)) + return null; + return ((MarkdownPage.Header) element).getSubHeaders().toArray(); + } + + public void documentAboutToBeChanged(DocumentEvent event) { + // nothing + } + + public void documentChanged(DocumentEvent event) { + parse(); + update(); + } + } + + private Object fInput = null; + private final IDocumentProvider fDocumentProvider; + private final MarkdownEditor fTextEditor; + protected boolean showWordCounts; + private List

selectedHeaders; + + /** + * @param documentProvider + * @param mdEditor + */ + public MarkdownContentOutlinePage(IDocumentProvider documentProvider, + MarkdownEditor mdEditor) { + fDocumentProvider = documentProvider; + fTextEditor = mdEditor; + } + + /* + * (non-Javadoc) Method declared on ContentOutlinePage + */ + @Override + public void createControl(Composite parent) { + super.createControl(parent); + TreeViewer viewer = getTreeViewer(); + viewer.setContentProvider(new ContentProvider()); + // Add word count annotations + viewer.setLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (!(element instanceof MarkdownPage.Header)) + return super.getText(element); + Header header = ((MarkdownPage.Header) element); + String hText = header.toString(); + if (!showWordCounts) + return hText; + IRegion region = getRegion(header); + String text; + try { + text = fTextEditor.getDocument().get(region.getOffset(), + region.getLength()); + text = WebUtils.stripTags(text); + text = text.replaceAll("#", "").trim(); + assert text.startsWith(hText); + text = text.substring(hText.length()); + int wc = StrUtils.wordCount(text); + return hText + " (" + wc + ":" + text.length() + ")"; + } catch (BadLocationException e) { + return hText; + } + } + }); + viewer.addSelectionChangedListener(this); + + if (fInput != null) + viewer.setInput(fInput); + + // Buttons + IPageSite site = getSite(); + IActionBars bars = site.getActionBars(); + IToolBarManager toolbar = bars.getToolBarManager(); + // Word count action + Action action = new Action("123", IAction.AS_CHECK_BOX) { + @Override + public void run() { + showWordCounts = isChecked(); + update(); + } + }; + action.setToolTipText("Show/hide section word:character counts"); + toolbar.add(action); + // +/- actions + action = new Action("<") { + @Override + public void run() { + doPromoteDemote(-1); + } + }; + action.setToolTipText("Promote the selected section\n -- move it up a level."); + toolbar.add(action); + // + action = new Action(">") { + @Override + public void run() { + doPromoteDemote(1); + } + }; + action.setToolTipText("Demote the selected section\n -- move it down a level."); + toolbar.add(action); + // up/down actions + action = new Action("/\\") { + @Override + public void run() { + try { + doMove(-1); + } catch (BadLocationException e) { + throw Utils.runtime(e); + } + } + }; + action.setToolTipText("Move the selected section earlier"); + toolbar.add(action); + // + action = new Action("\\/") { + @Override + public void run() { + try { + doMove(1); + } catch (BadLocationException e) { + throw Utils.runtime(e); + } + } + }; + action.setToolTipText("Move the selected section later"); + toolbar.add(action); + // Collapse + ImageDescriptor id = ImageDescriptor.createFromFile(getClass(), "collapseall.gif"); + action = new Action("collapse", id) { + @Override + public void run() { + doCollapseAll(); + } + }; + action.setImageDescriptor(id); + action.setToolTipText("Collapse outline tree"); + toolbar.add(action); + // Sync + id = ImageDescriptor.createFromFile(getClass(), "synced.gif"); + action = new Action("sync") { + @Override + public void run() { + try { + doSyncToEditor(); + } catch (BadLocationException e) { + throw Utils.runtime(e); + } + } + }; + action.setImageDescriptor(id); + action.setToolTipText("Link with editor"); + toolbar.add(action); + // Add edit ability + viewer.getControl().addKeyListener(new KeyListener() { + public void keyPressed(KeyEvent e) { + if (e.keyCode==SWT.F2) { + doEditHeader(); + } + } + public void keyReleased(KeyEvent e) { + // + } + }); + } + + /** + * @throws BadLocationException + * + */ + protected void doSyncToEditor() throws BadLocationException { + TreeViewer viewer = getTreeViewer(); + if (viewer == null) return; + // Get header + MarkdownPage page = fTextEditor.getMarkdownPage(); + int caretOffset = fTextEditor.getViewer().getTextWidget().getCaretOffset(); + IDocument doc = fTextEditor.getDocument(); + int line = doc.getLineOfOffset(caretOffset); + List lineTypes = page.getLineTypes(); + for(; line>-1; line--) { + KLineType lt = lineTypes.get(line); + if (lt.toString().startsWith("H")) break; + } + if (line<0) return; + Header header = (Header) page.getPageObject(line); + // Set + IStructuredSelection selection = new StructuredSelection(header); + viewer.setSelection(selection , true); + } + + void doEditHeader() { + TreeViewer viewer = getTreeViewer(); + viewer.editElement(selectedHeaders.get(0), 0); + } + + protected void doCollapseAll() { + TreeViewer viewer = getTreeViewer(); + if (viewer == null) return; +// Control control = viewer.getControl(); +// if (control != null && !control.isDisposed()) { +// control.setRedraw(false); + viewer.collapseAll(); +// control.setRedraw(true); +// } + } + + /** + * Move the selected sections up/down + * @param i 1 or -1. 1==move later, -1=earlier + * @throws BadLocationException + */ + protected void doMove(int i) throws BadLocationException { + assert i==1 || i==-1; + if (selectedHeaders == null || selectedHeaders.size() == 0) + return; + // Get text region to move + MarkdownPage.Header first = selectedHeaders.get(0); + MarkdownPage.Header last = selectedHeaders.get(selectedHeaders.size()-1); + int start = fTextEditor.getDocument().getLineOffset( + first.getLineNumber()); + IRegion r = getRegion(last); + int end = r.getOffset() + r.getLength(); + int length = end - start; + // Get new insertion point + int insert; + if (i==1) { + Header nextSection = last.getNext(); + if (nextSection==null) return; + IRegion nr = getRegion(nextSection); + insert = nr.getOffset()+nr.getLength(); + } else { + Header prevSection = first.getPrevious(); + if (prevSection==null) return; + IRegion nr = getRegion(prevSection); + insert = nr.getOffset(); + } + // Get text + String text = fTextEditor.getDocument().get(); + // Move text + String section = text.substring(start, end); + String pre, post; + if (i==1) { + pre = text.substring(0, start) + text.substring(end, insert); + post = text.substring(insert); + } else { + pre = text.substring(0, insert); + post = text.substring(insert,start)+text.substring(end); + } + text = pre + section + post; + assert text.length() == fTextEditor.getDocument().get().length() : + text.length()-fTextEditor.getDocument().get().length()+" chars gained/lost"; + // Update doc + fTextEditor.getDocument().set(text); + } + + /** + * Does not support -------- / ========= underlining, only # headers + * @param upDown 1 for demote (e.g. h2 -> h3), -1 for promote (e.g. h2 -> h1) + */ + protected void doPromoteDemote(int upDown) { + assert upDown==1 || upDown==-1; + if (selectedHeaders == null || selectedHeaders.size() == 0) + return; + HashSet
toAdjust = new HashSet
(selectedHeaders); + HashSet
adjusted = new HashSet
(); + // Adjust + MarkdownPage mdPage = fTextEditor.getMarkdownPage(); + List lines = new ArrayList(mdPage.getText()); + while(toAdjust.size() != 0) { + Header h = toAdjust.iterator().next(); + toAdjust.remove(h); + adjusted.add(h); + String line = lines.get(h.getLineNumber()); + if (upDown==-1) { + if (h.getLevel() == 1) return; // Level 1; can't promote + if (line.startsWith("##")) line = line.substring(1); + else { + return; // TODO support for ------ / ======== + } + } else line = "#" + line; + int ln = h.getLineNumber(); + lines.set(ln, line); + // kids + ArrayList
kids = new ArrayList
(h.getSubHeaders()); + for (Header header : kids) { + if ( ! adjusted.contains(header)) toAdjust.add(header); + } + } + // Set + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line); + } + fTextEditor.getDocument().set(sb.toString()); + } + + /** + * The region of text for this header. This includes the header itself. + * @param header + * @return + * @throws BadLocationException + */ + protected IRegion getRegion(Header header) { + try { + IDocument doc = fTextEditor.getDocument(); + // Line numbers + int start = header.getLineNumber(); + Header next = header.getNext(); + int end; + if (next != null) { + end = next.getLineNumber() - 1; + } else { + end = doc.getNumberOfLines() - 1; + } + int offset = doc.getLineOffset(start); + IRegion ei = doc.getLineInformation(end); + int length = ei.getOffset() + ei.getLength() - offset; + return new Region(offset, length); + } catch (BadLocationException ex) { + throw Utils.runtime(ex); + } + } + + /* + * (non-Javadoc) Method declared on ContentOutlinePage + */ + @Override + public void selectionChanged(SelectionChangedEvent event) { + super.selectionChanged(event); + selectedHeaders = null; + ISelection selection = event.getSelection(); + if (selection.isEmpty()) + return; + if (!(selection instanceof IStructuredSelection)) + return; + try { + IStructuredSelection strucSel = (IStructuredSelection) selection; + Object[] sections = strucSel.toArray(); + selectedHeaders = (List) Arrays.asList(sections); + MarkdownPage.Header first = (Header) sections[0]; + MarkdownPage.Header last = (Header) sections[sections.length - 1]; + int start = fTextEditor.getDocument().getLineOffset( + first.getLineNumber()); + int length; + if (first == last) { + length = fTextEditor.getDocument().getLineLength( + first.getLineNumber()); + } else { + IRegion r = getRegion(last); + int end = r.getOffset() + r.getLength(); + length = end - start; + } + fTextEditor.setHighlightRange(start, length, true); + } catch (Exception x) { + System.out.println(x.getStackTrace()); + fTextEditor.resetHighlightRange(); + } + } + + /** + * Sets the input of the outline page + * + * @param input + * the input of this outline page + */ + public void setInput(Object input) { + fInput = input; + update(); + } + + /** + * Updates the outline page. + */ + public void update() { + TreeViewer viewer = getTreeViewer(); + + if (viewer != null) { + Control control = viewer.getControl(); + if (control != null && !control.isDisposed()) { + control.setRedraw(false); + viewer.setInput(fInput); + viewer.expandAll(); + control.setRedraw(true); + } + } + } + +} 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 index 000000000..86699c4b3 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/MarkdownEditor.java @@ -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 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 markers = new ArrayList(Arrays.asList(taskMarkers)); +// Collections.sort(markers, c) sort for efficiency + // Find tags in doc + List 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 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 doc2editor = new HashMap(); + + + /** + * @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 oldAnnotations = new HashMap(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
headers = mPage.getHeadings(null); + // this will hold the new annotations along + // with their corresponding positions + Map annotations = new HashMap(); + IDocument doc = getDocument(); + updateSectionFoldingAnnotations2(doc, headers, annotations, doc.getLength()); + // Filter existing ones + Position[] newValues = annotations.values().toArray(POSITION_ARRAY); + List deletedAnnotations = new ArrayList(); + for(Entry 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
headers, + Map newAnnotations, int endParent) { + for (int i=0; i subHeaders = header.getSubHeaders(); + if (subHeaders.size() > 0) { + updateSectionFoldingAnnotations2(doc, subHeaders, newAnnotations, end); + } + } catch (Exception ex) { + System.out.println(ex); + } + } + } + + +} + + + +/* + + +- + + + + 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 + } + + + + + + + +*/ \ 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 index 000000000..8d089f16e --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/editors/PrintAction.java @@ -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 index 000000000..a2d80a904 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 index 000000000..870934b69 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 index 000000000..4c38f19e8 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatter.java @@ -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 assumes 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 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 + * lineWidth 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 + * lineWidth 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 index 000000000..4e4dd75a2 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownFormatterTest.java @@ -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 , 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 , 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 , 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 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 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 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 index 000000000..a18a5ded6 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPage.java @@ -0,0 +1,617 @@ +/** + * Copyright winterwell Mathematics Ltd. + * @author Daniel Winterstein + * 11 Jan 2007 + */ +package winterwell.markdown.pagemodel; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jface.preference.IPreferenceStore; + +import winterwell.markdown.Activator; +import winterwell.markdown.StringMethods; +import winterwell.markdown.preferences.MarkdownPreferencePage; +import winterwell.utils.FailureException; +import winterwell.utils.Process; +import winterwell.utils.StrUtils; +import winterwell.utils.Utils; +import winterwell.utils.io.FileUtils; + +import com.petebevin.markdown.MarkdownProcessor; + +/** + * Understands Markdown syntax. + * + * @author Daniel Winterstein + */ +public class MarkdownPage { + + /** + * Strip leading and trailing #s and whitespace + * + * @param line + * @return cleaned up line + */ + private String cleanHeader(String line) { + for (int j = 0; j < line.length(); j++) { + char c = line.charAt(j); + if (c != '#' && !Character.isWhitespace(c)) { + line = line.substring(j); + break; + } + } + for (int j = line.length() - 1; j > 0; j--) { + char c = line.charAt(j); + if (c != '#' && !Character.isWhitespace(c)) { + line = line.substring(0, j + 1); + break; + } + } + return line; + } + + /** + * Represents information about a section header. E.g. ## Misc Warblings + * + * @author daniel + */ + public class Header { + /** + * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc. + */ + final int level; + /** + * The text of the Header + */ + final String heading; + /** + * Sub-sections, if any + */ + final List
subHeaders = new ArrayList
(); + /** + * The line on which this header occurs. + */ + final int lineNumber; + + public int getLineNumber() { + return lineNumber; + } + + /** + * + * @return the next section (at this depth if possible), null if none + */ + public Header getNext() { + if (parent == null) { + int ti = level1Headers.indexOf(this); + if (ti == -1 || ti == level1Headers.size() - 1) + return null; + return level1Headers.get(ti + 1); + } + int i = parent.subHeaders.indexOf(this); + assert i != -1 : this; + if (i == parent.subHeaders.size() - 1) + return parent.getNext(); + return parent.subHeaders.get(i + 1); + } + /** + * + * @return the next section (at this depth if possible), null if none + */ + public Header getPrevious() { + if (parent == null) { + int ti = level1Headers.indexOf(this); + if (ti == -1 || ti == 0) + return null; + return level1Headers.get(ti - 1); + } + int i = parent.subHeaders.indexOf(this); + assert i != -1 : this; + if (i == 0) + return parent.getPrevious(); + return parent.subHeaders.get(i - 1); + } + + + /** + * The parent section. Can be null. + */ + private Header parent; + + /** + * Create a marker for a section Header + * + * @param level + * 1 = top-level (i.e. #), 2= 2nd-level (i.e. ##), etc. + * @param lineNumber + * The line on which this header occurs + * @param heading + * The text of the Header, trimmed of #s + * @param currentHeader + * The previous Header. This is used to find the parent + * section if there is one. Can be null. + */ + Header(int level, int lineNumber, String heading, Header currentHeader) { + this.lineNumber = lineNumber; + this.level = level; + this.heading = cleanHeader(heading); + // Heading Tree + setParent(currentHeader); + } + + private void setParent(Header currentHeader) { + if (currentHeader == null) { + parent = null; + return; + } + if (currentHeader.level < level) { + parent = currentHeader; + parent.subHeaders.add(this); + return; + } + setParent(currentHeader.parent); + } + + public Header getParent() { + return parent; + } + + /** + * Sub-sections. May be zero-length, never null. + */ + public List
getSubHeaders() { + return subHeaders; + } + + @Override + public String toString() { + return heading; + } + + public int getLevel() { + return level; + } + } + + /** + * The raw text, broken up into individual lines. + */ + private List lines; + + /** + * The raw text, broken up into individual lines. + */ + public List getText() { + return Collections.unmodifiableList(lines); + } + + public enum KLineType { + NORMAL, H1, H2, H3, H4, H5, H6, BLANK, + // TODO LIST, BLOCKQUOTE, + /** A line marking Markdown info about the preceding line, e.g. ====== */ + MARKER, + /** A line containing meta-data, e.g. title: My Page */ + META + } + + /** + * Information about each line. + */ + private List lineTypes; + private Map pageObjects = new HashMap(); + + // TODO meta-data, footnotes, tables, link & image attributes + private static Pattern multiMarkdownTag = Pattern.compile("^([\\w].*):(.*)"); + private Map multiMarkdownTags = new HashMap(); + + // Regular expression for Github support + private static Pattern githubURLDetection = Pattern.compile("((https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|])"); + + /** + * The top-level headers. FIXME handle documents which have a 2nd level + * header before any 1st level ones + */ + private final List
level1Headers = new ArrayList
(); + private final IPreferenceStore pStore; + + /** + * Create a page. + * + * @param text + */ + public MarkdownPage(String text) { + pStore = Activator.getDefault().getPreferenceStore(); + setText(text); + } + + /** + * Reset the text for this page. + * + * @param text + */ + private void setText(String text) { + // Get lines + lines = StringMethods.splitLines(text); + // Clean out old + level1Headers.clear(); + lineTypes = new ArrayList(lines.size()); + pageObjects.clear(); + // Dummy level-1 header in case there are none + Header dummyTopHeader = new Header(1, 0, "", null); + level1Headers.add(dummyTopHeader); + Header currentHeader = dummyTopHeader; + // Identify line types + int lineNum = 0; + + // Check if we should support the Multi-Markdown Metadata + boolean multiMarkdownMetadataSupport = + pStore.getBoolean(MarkdownPreferencePage.PREF_MULTIMARKDOWN_METADATA); + + // Multi-markdown header + if (multiMarkdownMetadataSupport) { + // The key is the text before the colon, and the data is the text + // after the + // colon. In the above example, notice that there are two lines of + // information + // for the Author key. If you end a line with “space-space-newline”, + // the newline + // will be included when converted to other formats. + // + // There must not be any whitespace above the metadata, and the + // metadata block + // ends with the first whitespace only line. The metadata is + // stripped from the + // document before it is passed on to the syntax parser. + + // + // Check if the Metdatas are valid + // + boolean validMetadata = true; + for (lineNum = 0; lineNum < lines.size(); lineNum++) { + String line = lines.get(lineNum); + if (Utils.isBlank(line)) { + break; + } + Matcher m = multiMarkdownTag.matcher(line); + if (!m.find()) { + if (lineNum == 0) { + // No MultiMarkdown metadata + validMetadata = false; + break; + } else if (!line.matches("^\\s.*\n")) { + // The next line was not intended (ie. it does not start + // with a whitespace) + validMetadata = false; + break; + } + } + } + + // Valid Metadatas have been found. We need to retrieve these keys/values. + if (validMetadata) { + String data = ""; + String tag = ""; + for (lineNum = 0; lineNum < lines.size(); lineNum++) { + String line = lines.get(lineNum); + if (Utils.isBlank(line)) { + break; + } + Matcher m = multiMarkdownTag.matcher(line); + if (!m.find()) { + if (lineNum == 0) { + break; + } + // Multi-line tag + lineTypes.add(KLineType.META); + data += StrUtils.LINEEND + line.trim(); + multiMarkdownTags.put(tag, data); + } else { + lineTypes.add(KLineType.META); + tag = m.group(0); + data = m.group(1).trim(); + if (m.group(1).endsWith(line)) + multiMarkdownTags.put(tag, data); + } + } + } else { + lineNum = 0; + } + } + for (; lineNum < lines.size(); lineNum++) { + String line = lines.get(lineNum); + // Headings + int h = numHash(line); + String hLine = line; + int hLineNum = lineNum; + int underline = -1; + if (lineNum != 0) { + underline = just(line, '=') ? 1 : just(line, '-') ? 2 : -1; + } + if (underline != -1) { + h = underline; + hLineNum = lineNum - 1; + hLine = lines.get(lineNum - 1); + lineTypes.set(hLineNum, KLineType.values()[h]); + lineTypes.add(KLineType.MARKER); + } + // Create a Header object + if (h > 0) { + if (underline == -1) + lineTypes.add(KLineType.values()[h]); + Header header = new Header(h, hLineNum, hLine, currentHeader); + if (h == 1) { + level1Headers.add(header); + } + pageObjects.put(hLineNum, header); + currentHeader = header; + continue; + } + // TODO List + // TODO Block quote + // Blank line + if (Utils.isBlank(line)) { + lineTypes.add(KLineType.BLANK); + continue; + } + // Normal + lineTypes.add(KLineType.NORMAL); + } // end line-loop + // Remove dummy header? + if (dummyTopHeader.getSubHeaders().size() == 0) { + level1Headers.remove(dummyTopHeader); + } + + boolean githubSyntaxSupport = + pStore.getBoolean(MarkdownPreferencePage.PREF_GITHUB_SYNTAX); + if (githubSyntaxSupport) { + /* + * Support Code block + */ + boolean inCodeBlock = false; + for (lineNum = 0; lineNum < lines.size(); lineNum++) { + String line = lines.get(lineNum); + // Found the start or end of a code block + if (line.matches("^```.*\n")) { + // We reverse the boolean value + inCodeBlock = !inCodeBlock; + + // We force the line to be blank. But we mark it as normal + // to prevent to be stripped + lines.set(lineNum, "\n"); + lineTypes.set(lineNum, KLineType.NORMAL); + continue; + } + if (inCodeBlock) { + lines.set(lineNum, " " + line); + } + } + + /* + * Support for URL Detection + * We search for links that are not captured by Markdown syntax + */ + for (lineNum = 0; lineNum < lines.size(); lineNum++) { + String line = lines.get(lineNum); + // When a link has been replaced we need to scan again the string + // as the offsets have changed (we add '<' and '>' to the link to + // be interpreted by the markdown library) + boolean urlReplaced; + + do { + urlReplaced = false; + Matcher m = githubURLDetection.matcher(line); + while (m.find()) { + // Ignore the URL following the format + if ((m.start() - 1 >= 0) && (m.end() < line.length()) && + (line.charAt(m.start() - 1) == '<') && + (line.charAt(m.end()) == '>')) + { + continue; + } + + // Ignore the URL following the format [description](link) + if ((m.start() - 2 >= 0) && (m.end() < line.length()) && + (line.charAt(m.start() - 2) == ']') && + (line.charAt(m.start() - 1) == '(') && + (line.charAt(m.end()) == ')')) + { + continue; + } + + // Ignore the URL following the format [description](link "title") + if ((m.start() - 2 >= 0) && (m.end() + 1 < line.length()) && + (line.charAt(m.start() - 2) == ']') && + (line.charAt(m.start() - 1) == '(') && + (line.charAt(m.end()) == ' ') && + (line.charAt(m.end() + 1) == '"')) + { + continue; + } + + if (m.start() - 1 >= 0) { + // Case when the link is at the beginning of the string + line = line.substring(0, m.start()) + "<" + m.group(0) + ">" + line.substring(m.end()); + } else { + line = "<" + m.group(0) + ">" + line.substring(m.end()); + } + + // We replaced the string in the array + lines.set(lineNum, line); + urlReplaced = true; + break; + } + } while (urlReplaced); + } + } + } + + /** + * @param line + * @param c + * @return true if line is just cs (and whitespace at the start/end) + */ + boolean just(String line, char c) { + return line.matches("\\s*"+c+"+\\s*"); + } + + /** + * @param line + * @return The number of # symbols prepending the line. + */ + private int numHash(String line) { + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) != '#') + return i; + } + return line.length(); + } + + /** + * + * @param parent + * Can be null for top-level + * @return List of sub-headers. Never null. FIXME handle documents which + * have a 2nd level header before any 1st level ones + */ + public List
getHeadings(Header parent) { + if (parent == null) { + return Collections.unmodifiableList(level1Headers); + } + return Collections.unmodifiableList(parent.subHeaders); + } + + // public WebPage getWebPage() { + // WebPage page = new WebPage(); + // // Add the lines, one by one + // boolean inParagraph = false; + // for (int i=0; i"); + // line = cleanHeader(line); + // page.addText("<"+type+">"+line+""); + // continue; + // case MARKER: // Ignore + // continue; + // // TODO List? + // // TODO Block quote? + // } + // // Paragraph end? + // if (Utils.isBlank(line)) { + // if (inParagraph) page.addText("

"); + // continue; + // } + // // Paragraph start? + // if (!inParagraph) { + // page.addText("

"); + // inParagraph = true; + // } + // // Plain text + // page.addText(line); + // } + // return page; + // } + + /** + * Get the HTML for this page. Uses the MarkdownJ project. + */ + public String html() { + // Section numbers?? + boolean sectionNumbers = pStore + .getBoolean(MarkdownPreferencePage.PREF_SECTION_NUMBERS); + // Chop out multi-markdown header + StringBuilder sb = new StringBuilder(); + assert lines.size() == lineTypes.size(); + for (int i = 0, n = lines.size(); i < n; i++) { + KLineType type = lineTypes.get(i); + if (type == KLineType.META) + continue; + String line = lines.get(i); + if (sectionNumbers && isHeader(type) && line.contains("$section")) { + // TODO Header section = headers.get(i); + // String secNum = section.getSectionNumber(); + // line.replace("$section", secNum); + } + sb.append(line); + } + String text = sb.toString(); + // Use external converter? + final String cmd = pStore + .getString(MarkdownPreferencePage.PREF_MARKDOWN_COMMAND); + if (Utils.isBlank(cmd) + || (cmd.startsWith("(") && cmd.contains("MarkdownJ"))) { + // Use MarkdownJ + MarkdownProcessor markdown = new MarkdownProcessor(); + // MarkdownJ doesn't convert £s for some reason + text = text.replace("£", "£"); + String html = markdown.markdown(text); + return html; + } + // Attempt to run external command + try { + final File md = File.createTempFile("tmp", ".md"); + FileUtils.write(md, text); + Process process = new Process(cmd+" "+md.getAbsolutePath()); + process.run(); + int ok = process.waitFor(10000); + if (ok != 0) throw new FailureException(cmd+" failed:\n"+process.getError()); + String html = process.getOutput(); + FileUtils.delete(md); + return html; + } catch (Exception e) { + throw Utils.runtime(e); + } + } + + /** + * @param type + * @return + */ + private boolean isHeader(KLineType type) { + return type == KLineType.H1 || type == KLineType.H2 + || type == KLineType.H3 || type == KLineType.H4 + || type == KLineType.H5 || type == KLineType.H6; + } + + /** + * Return the raw text of this page. + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line); + } + return sb.toString(); + } + + /** + * Line type information for the raw text. + * + * @return + */ + public List getLineTypes() { + return Collections.unmodifiableList(lineTypes); + } + + /** + * @param line + * @return + */ + public Object getPageObject(int line) { + return pageObjects.get(line); + } + +} diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPageTest.java b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPageTest.java new file mode 100644 index 000000000..244d43789 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/pagemodel/MarkdownPageTest.java @@ -0,0 +1,30 @@ +package winterwell.markdown.pagemodel; + +import java.io.File; +import java.util.List; + +import winterwell.markdown.pagemodel.MarkdownPage.Header; +import winterwell.utils.io.FileUtils; + + + +public class MarkdownPageTest //extends TestCase +{ + + public static void main(String[] args) { + MarkdownPageTest mpt = new MarkdownPageTest(); + mpt.testGetHeadings(); + } + + public void testGetHeadings() { + // problem caused by a line beginning --, now fixed + String txt = FileUtils.read(new File( + "/home/daniel/winterwell/companies/DTC/projects/DTC-bayes/report1.txt")); + MarkdownPage p = new MarkdownPage(txt); + List

h1s = p.getHeadings(null); + Header h1 = h1s.get(0); + List
h2s = h1.getSubHeaders(); + assert h2s.size() > 2; + } + +} diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/preferences/MarkdownPreferencePage.java b/bundles/winterwell.markdown/src/winterwell/markdown/preferences/MarkdownPreferencePage.java new file mode 100644 index 000000000..36dadbc4c --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/preferences/MarkdownPreferencePage.java @@ -0,0 +1,214 @@ +package winterwell.markdown.preferences; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.ColorFieldEditor; +import org.eclipse.jface.preference.FieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.PreferenceConverter; +import org.eclipse.jface.preference.StringFieldEditor; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +import winterwell.markdown.Activator; + +/** + * This class represents a preference page that + * is contributed to the Preferences dialog. By + * subclassing FieldEditorPreferencePage, we + * can use the field support built into JFace that allows + * us to create a page that is small and knows how to + * save, restore and apply itself. + *

+ * This page is used to modify preferences only. They + * are stored in the preference store that belongs to + * the main plug-in class. That way, preferences can + * be accessed directly via the preference store. + */ + +public class MarkdownPreferencePage + extends FieldEditorPreferencePage + implements IWorkbenchPreferencePage { + + public static final String PREF_FOLDING = "Pref_Folding"; + public static final String PREF_WORD_WRAP = "Pref_WordWrap"; + public static final String PREF_TASK_TAGS = "Pref_TaskTagsOn"; + public static final String PREF_TASK_TAGS_DEFINED = "Pref_TaskTags"; + public static final String PREF_SECTION_NUMBERS = "Pref_SectionNumbers"; + + public static final String PREF_MARKDOWN_COMMAND = "Pref_Markdown_Command"; + private static final String MARKDOWNJ = "(use built-in MarkdownJ converter)"; + + + public static final String PREF_DEFUALT = "Pref_Default"; + public static final String PREF_COMMENT = "Pref_Comment"; + public static final String PREF_HEADER = "Pref_Header"; + public static final String PREF_LINK = "Pref_Link"; + public static final String PREF_CODE = "Pref_Code"; + public static final String PREF_CODE_BG = "Pref_Code_Background"; + + public static final String PREF_GITHUB_SYNTAX = "Pref_Github_Syntax"; + public static final String PREF_MULTIMARKDOWN_METADATA = "Pref_MultiMarkdown_Metadata"; + + private static final RGB DEF_DEFAULT = new RGB(0, 0, 0); + private static final RGB DEF_COMMENT = new RGB(128, 0, 0); + private static final RGB DEF_HEADER = new RGB(0, 128, 0); + private static final RGB DEF_LINK = new RGB(106, 131, 199); + private static final RGB DEF_CODE = new RGB(0, 0, 0); + private static final RGB DEF_CODE_BG = new RGB(244,244,244); + + public MarkdownPreferencePage() { + super(GRID); + IPreferenceStore pStore = Activator.getDefault().getPreferenceStore(); + setDefaultPreferences(pStore); + setPreferenceStore(pStore); + setDescription("Settings for the Markdown text editor. See also the general text editor preferences."); + } + + public static void setDefaultPreferences(IPreferenceStore pStore) { + pStore.setDefault(PREF_WORD_WRAP, false); + pStore.setDefault(PREF_FOLDING, true); + pStore.setDefault(PREF_TASK_TAGS, true); + pStore.setDefault(PREF_TASK_TAGS_DEFINED, "TODO,FIXME,??"); + pStore.setDefault(PREF_MARKDOWN_COMMAND, MARKDOWNJ); + pStore.setDefault(PREF_SECTION_NUMBERS, true); + pStore.setDefault(PREF_GITHUB_SYNTAX, true); + pStore.setDefault(PREF_MULTIMARKDOWN_METADATA, false); + + PreferenceConverter.setDefault(pStore, PREF_DEFUALT, DEF_DEFAULT); + PreferenceConverter.setDefault(pStore, PREF_COMMENT, DEF_COMMENT); + PreferenceConverter.setDefault(pStore, PREF_HEADER, DEF_HEADER); + PreferenceConverter.setDefault(pStore, PREF_LINK, DEF_LINK); + PreferenceConverter.setDefault(pStore, PREF_CODE, DEF_CODE); + PreferenceConverter.setDefault(pStore, PREF_CODE_BG, DEF_CODE_BG); + } + + /** + * Creates the field editors. Field editors are abstractions of + * the common GUI blocks needed to manipulate various types + * of preferences. Each field editor knows how to save and + * restore itself. + */ + @Override + public void createFieldEditors() { + // Word wrap + BooleanFieldEditor fd = new BooleanFieldEditor(PREF_WORD_WRAP, + "Soft word wrapping \r\n" ++"Note: may cause line numbers and related \r\n" + + "functionality to act a bit strangely", + getFieldEditorParent()); + addField(fd); + // Task tags + fd = new BooleanFieldEditor(PREF_TASK_TAGS, + "Manage tasks using task tags \r\n" + + "If true, this will add and delete tags in sync with edits.", + getFieldEditorParent()); + addField(fd); + StringFieldEditor tags = new StringFieldEditor(PREF_TASK_TAGS_DEFINED, + "Task tags\nComma separated list of recognised task tags.", getFieldEditorParent()); + addField(tags); + // Code folding + fd = new BooleanFieldEditor(PREF_FOLDING, + "Document folding, a.k.a. outline support", + getFieldEditorParent()); + addField(fd); + // Command line +// addField(new DummyField() { +// protected void makeComponent(Composite parent) { +// Label label = new Label(parent, 0); +// label.setText("Hello!"); +// GridData gd = new GridData(100, 20); +// label.setLayoutData(gd); +// } +// }); + StringFieldEditor cmd = new StringFieldEditor(PREF_MARKDOWN_COMMAND, + "UNSTABLE: Command-line to run Markdown.\r\n" + + "This should take in a file and output to std-out.\n" + + "Leave blank to use the built-in Java converter.", getFieldEditorParent()); + addField(cmd); + + ColorFieldEditor def = new ColorFieldEditor(PREF_DEFUALT, "Default text", getFieldEditorParent()); + addField(def); + + ColorFieldEditor com = new ColorFieldEditor(PREF_COMMENT, "Comment", getFieldEditorParent()); + addField(com); + + ColorFieldEditor link = new ColorFieldEditor(PREF_LINK, "Link", getFieldEditorParent()); + addField(link); + + ColorFieldEditor head = new ColorFieldEditor(PREF_HEADER, "Header and List indicator", getFieldEditorParent()); + addField(head); + + ColorFieldEditor code = new ColorFieldEditor(PREF_CODE, "Code", getFieldEditorParent()); + addField(code); + + ColorFieldEditor codeBg = new ColorFieldEditor(PREF_CODE_BG, "Code Background", getFieldEditorParent()); + addField(codeBg); + + /* + * Fields for the preview window + */ + + // Github Syntax support + fd = new BooleanFieldEditor(PREF_GITHUB_SYNTAX, + "Support Github Syntax", + getFieldEditorParent()); + addField(fd); + + // Multi-Markdown support + fd = new BooleanFieldEditor(PREF_MULTIMARKDOWN_METADATA, + "Support Multi-Markdown Metadata", + getFieldEditorParent()); + addField(fd); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) + */ + public void init(IWorkbench workbench) { + + } + + public static boolean wordWrap() { + IPreferenceStore pStore = Activator.getDefault().getPreferenceStore(); + if (! pStore.contains(MarkdownPreferencePage.PREF_WORD_WRAP)) { + return false; + } + return pStore.getBoolean(MarkdownPreferencePage.PREF_WORD_WRAP); + } + +} + +abstract class DummyField extends FieldEditor { + @Override + protected void adjustForNumColumns(int numColumns) { + // do nothing + } + @Override + protected void doFillIntoGrid(Composite parent, int numColumns) { + makeComponent(parent); + } + abstract protected void makeComponent(Composite parent); + + @Override + protected void doLoad() { + // + } + @Override + protected void doLoadDefault() { + // + } + + @Override + protected void doStore() { + // + } + + @Override + public int getNumberOfControls() { + return 1; + } + +} \ No newline at end of file diff --git a/bundles/winterwell.markdown/src/winterwell/markdown/views/MarkdownPreview.java b/bundles/winterwell.markdown/src/winterwell/markdown/views/MarkdownPreview.java new file mode 100644 index 000000000..c10395316 --- /dev/null +++ b/bundles/winterwell.markdown/src/winterwell/markdown/views/MarkdownPreview.java @@ -0,0 +1,100 @@ +package winterwell.markdown.views; + + +import java.io.File; +import java.net.URI; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IPathEditorInput; +import org.eclipse.ui.part.ViewPart; + +import winterwell.markdown.editors.ActionBarContributor; +import winterwell.markdown.editors.MarkdownEditor; +import winterwell.markdown.pagemodel.MarkdownPage; + + + + +public class MarkdownPreview extends ViewPart { + + public static MarkdownPreview preview = null; + + private Browser viewer = null; + + /** + * The constructor. + */ + public MarkdownPreview() { + preview = this; + } + + /** + * This is a callback that will allow us + * to create the viewer and initialize it. + */ + @Override + public void createPartControl(Composite parent) { + viewer = new Browser(parent, SWT.MULTI); // | SWT.H_SCROLL | SWT.V_SCROLL + } + + + + + /** + * Passing the focus request to the viewer's control. + */ + @Override + public void setFocus() { + if (viewer==null) return; + viewer.setFocus(); + update(); + } + + public void update() { + if (viewer==null) return; + try { + IEditorPart editor = ActionBarContributor.getActiveEditor(); + if (!(editor instanceof MarkdownEditor)) { + viewer.setText(""); + return; + } + MarkdownEditor ed = (MarkdownEditor) editor; + MarkdownPage page = ed.getMarkdownPage(); + String html = page.html(); + html = addBaseURL(editor, html); + if (page != null) viewer.setText(html); + else viewer.setText(""); + } catch (Exception ex) { + // Smother + System.out.println(ex); + + if (viewer != null && !viewer.isDisposed()) + viewer.setText(ex.getMessage()); + } + } + + /** + * Adjust the URL base to be the file's directory. + * @param editor + * @param html + * @return + */ + private String addBaseURL(IEditorPart editor, String html) { + try { + IPathEditorInput input = (IPathEditorInput) editor.getEditorInput(); + IPath path = input.getPath(); + path = path.removeLastSegments(1); + File f = path.toFile(); + URI fileURI = f.toURI(); + String html2 = "\r\n"+html + +"\r\n"; + return html2; + } catch (Exception ex) { + return html; + } + } +} \ No newline at end of file diff --git a/features/com.lowagie.text.feature/.gitignore b/features/com.lowagie.text.feature/.gitignore new file mode 100644 index 000000000..c4130baed --- /dev/null +++ b/features/com.lowagie.text.feature/.gitignore @@ -0,0 +1 @@ +/target/** diff --git a/features/com.lowagie.text.feature/.project b/features/com.lowagie.text.feature/.project new file mode 100644 index 000000000..d825fa3b8 --- /dev/null +++ b/features/com.lowagie.text.feature/.project @@ -0,0 +1,17 @@ + + + com.lowagie.text.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/features/com.lowagie.text.feature/build.properties b/features/com.lowagie.text.feature/build.properties new file mode 100644 index 000000000..82ab19c62 --- /dev/null +++ b/features/com.lowagie.text.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/features/com.lowagie.text.feature/feature.xml b/features/com.lowagie.text.feature/feature.xml new file mode 100644 index 000000000..e5a23fa70 --- /dev/null +++ b/features/com.lowagie.text.feature/feature.xml @@ -0,0 +1,514 @@ + + + + + iText is a library that allows you to generate PDF files on the +fly. + + + + Copyright © 1999-2007 by Bruno Lowagie, Adolf Baeyensstraat 121, 9040 Gent, BELGIUM + + + + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + + + + + + + + + + + diff --git a/features/org.apache.lucene4.feature/.gitignore b/features/org.apache.lucene4.feature/.gitignore new file mode 100644 index 000000000..c4130baed --- /dev/null +++ b/features/org.apache.lucene4.feature/.gitignore @@ -0,0 +1 @@ +/target/** diff --git a/features/org.apache.lucene4.feature/.project b/features/org.apache.lucene4.feature/.project new file mode 100644 index 000000000..14d6d133c --- /dev/null +++ b/features/org.apache.lucene4.feature/.project @@ -0,0 +1,17 @@ + + + org.apache.lucene4.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/features/org.apache.lucene4.feature/build.properties b/features/org.apache.lucene4.feature/build.properties new file mode 100644 index 000000000..82ab19c62 --- /dev/null +++ b/features/org.apache.lucene4.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/features/org.apache.lucene4.feature/feature.xml b/features/org.apache.lucene4.feature/feature.xml new file mode 100644 index 000000000..3dd2c5011 --- /dev/null +++ b/features/org.apache.lucene4.feature/feature.xml @@ -0,0 +1,255 @@ + + + + + Apache Lucene is a high-performance, full-featured text search engine library written entirely in Java. It is a technology suitable for nearly any application that requires full-text search, especially cross-platform. + + + + Copyright © 2006 The Apache Software Foundation. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + + + + + + + + + + diff --git a/features/org.simantics.eclipsec.launcher.feature/.project b/features/org.simantics.eclipsec.launcher.feature/.project new file mode 100644 index 000000000..31de9e105 --- /dev/null +++ b/features/org.simantics.eclipsec.launcher.feature/.project @@ -0,0 +1,17 @@ + + + org.simantics.eclipsec.launcher.feature + + + + + + org.eclipse.pde.FeatureBuilder + + + + + + org.eclipse.pde.FeatureNature + + diff --git a/features/org.simantics.eclipsec.launcher.feature/build.properties b/features/org.simantics.eclipsec.launcher.feature/build.properties new file mode 100644 index 000000000..ef8dccc63 --- /dev/null +++ b/features/org.simantics.eclipsec.launcher.feature/build.properties @@ -0,0 +1,15 @@ +############################################################################### +# Copyright (c) 2007, 2015 Association for Decentralized Information Management +# in Industry THTH ry. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# VTT Technical Research Centre of Finland - initial API and implementation +# Semantum Oy - update to Eclipse Mars.1 (4.5.1) +############################################################################### +bin.includes=feature.xml +root.win32.win32.x86=bin/win32/win32/x86 +root.win32.win32.x86_64=bin/win32/win32/x86_64 diff --git a/features/org.simantics.eclipsec.launcher.feature/feature.xml b/features/org.simantics.eclipsec.launcher.feature/feature.xml new file mode 100644 index 000000000..3c5005e4d --- /dev/null +++ b/features/org.simantics.eclipsec.launcher.feature/feature.xml @@ -0,0 +1,31 @@ + + + + + + This is a build-time feature that only includes standard eclipsec launcher executables for platforms where available since the standard org.eclipse.equinox.launcher feature does not include these. The eclipsec executables are valuable for debugging purposes where native code crashes the whole program. With the normal launcher all your possible debug information will be lost forever. Using eclipsec you will see everything put in standard output and standard error in the same very console where you launched eclipsec. + + + + [Enter Copyright Description here.] + + + + [Enter License Description here.] + + + diff --git a/features/pom.xml b/features/pom.xml index db0e8e6b1..3a593aea6 100644 --- a/features/pom.xml +++ b/features/pom.xml @@ -14,6 +14,8 @@ + com.lowagie.text.feature + org.apache.lucene4.feature org.simantics.browsing.ui.feature org.simantics.charts.feature org.simantics.data.feature @@ -22,6 +24,7 @@ org.simantics.document.base.feature org.simantics.document.linking.feature org.simantics.document.swt.feature + org.simantics.eclipsec.launcher.feature org.simantics.event.feature org.simantics.export.feature org.simantics.g2d.feature diff --git a/releng/org.simantics.sdk.build.p2.site/pom.xml b/releng/org.simantics.sdk.build.p2.site/pom.xml index 2890fb24a..69674c0e9 100644 --- a/releng/org.simantics.sdk.build.p2.site/pom.xml +++ b/releng/org.simantics.sdk.build.p2.site/pom.xml @@ -6,7 +6,7 @@ org.simantics org.simantics.sdk.build.p2.site pom - 1.24.0 + 1.25.0 4.9.0 @@ -296,4 +296,4 @@ - \ No newline at end of file + diff --git a/releng/org.simantics.sdk.build.targetdefinition/org.simantics.sdk.build.targetdefinition.target b/releng/org.simantics.sdk.build.targetdefinition/org.simantics.sdk.build.targetdefinition.target index 3f8417100..9a85c879f 100644 --- a/releng/org.simantics.sdk.build.targetdefinition/org.simantics.sdk.build.targetdefinition.target +++ b/releng/org.simantics.sdk.build.targetdefinition/org.simantics.sdk.build.targetdefinition.target @@ -161,7 +161,7 @@ - + diff --git a/releng/org.simantics.sdk.repository/category.xml b/releng/org.simantics.sdk.repository/category.xml index 647e31cb5..f97fc7574 100644 --- a/releng/org.simantics.sdk.repository/category.xml +++ b/releng/org.simantics.sdk.repository/category.xml @@ -1,6 +1,6 @@ - + diff --git a/releng/org.simantics.sdk.repository/pom.xml b/releng/org.simantics.sdk.repository/pom.xml index c25ad3ee7..5731d5fd6 100644 --- a/releng/org.simantics.sdk.repository/pom.xml +++ b/releng/org.simantics.sdk.repository/pom.xml @@ -2,7 +2,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.simantics.sdk.repository - 1.24.0-SNAPSHOT + 1.25.0-SNAPSHOT eclipse-repository @@ -69,4 +69,4 @@ - \ No newline at end of file +