diff --git a/Ocaml/icons/markOccurrence.gif b/Ocaml/icons/markOccurrence.gif
old mode 100755
new mode 100644
diff --git a/Ocaml/icons/shiftLeft.png b/Ocaml/icons/shiftLeft.png
old mode 100755
new mode 100644
diff --git a/Ocaml/icons/shiftRight.png b/Ocaml/icons/shiftRight.png
old mode 100755
new mode 100644
diff --git a/Ocaml/lib/beaver-cc.jar b/Ocaml/lib/beaver-cc.jar
new file mode 100644
index 0000000..fab999e
Binary files /dev/null and b/Ocaml/lib/beaver-cc.jar differ
diff --git a/Ocaml/plugin.xml b/Ocaml/plugin.xml
index 0b50421..982b8ed 100644
--- a/Ocaml/plugin.xml
+++ b/Ocaml/plugin.xml
@@ -70,30 +70,30 @@
point="org.eclipse.ui.editors">
@@ -739,21 +739,53 @@
+
+
+
+
+
+
+
+
+
+
+ id="Ocaml_sourceActions_markOccurrences" label="Mark Occurrences" style="push"/>
+ id="Ocaml_sourceActions_shiftLeft" label="Shift Left" style="push"/>
+ id="Ocaml_sourceActions_shiftRight" label="Shift Right" style="push"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -890,6 +999,81 @@
id="Ocaml.shiftLeftCommand" name="Shift Left"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -978,7 +1159,97 @@
+ sequence="M1+M3+N"/>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1099,22 +1381,50 @@
colorPreferenceValue="255,140,0"
contributesToHeader="true"
highlightPreferenceKey="syntaxerror.highlight"
- highlightPreferenceValue="false"
+ highlightPreferenceValue="true"
includeOnPreferencePage="true"
- isGoToNextNavigationTarget="false"
- isGoToPreviousNavigationTarget="false"
+ isGoToNextNavigationTarget="true"
+ isGoToNextNavigationTargetKey="true"
+ isGoToPreviousNavigationTarget="true"
+ isGoToPreviousNavigationTargetKey="true"
label="Ocaml Syntax Error"
overviewRulerPreferenceKey="syntaxerror.rulers.overview"
overviewRulerPreferenceValue="true"
presentationLayer="1"
- showInNextPrevDropdownToolbarAction="false"
+ showInNextPrevDropdownToolbarAction="true"
+ showInNextPrevDropdownToolbarActionKey="true"
symbolicIcon="error"
textPreferenceKey="syntaxerror.text"
textPreferenceValue="true"
textStylePreferenceKey="syntaxerror.textstyle"
textStylePreferenceValue="SQUIGGLES"
verticalRulerPreferenceKey="syntaxerror.rulers.vertical"
- verticalRulerPreferenceValue="false">
+ verticalRulerPreferenceValue="true">
+
+
-
-
-
-
-
-
-
-
diff --git a/Ocaml/src/ocaml/FolderChangeListener.java b/Ocaml/src/ocaml/FolderChangeListener.java
index 0ed759e..923b77e 100644
--- a/Ocaml/src/ocaml/FolderChangeListener.java
+++ b/Ocaml/src/ocaml/FolderChangeListener.java
@@ -43,10 +43,11 @@ public boolean visit(IResourceDelta delta) throws CoreException {
switch (delta.getKind()) {
case IResourceDelta.ADDED: {
- // ignore special directories (.settings, .git, .cvsignore, etc.)
+ // ignore special directories (.settings, .git, .cvsignore, etc.) but not "."
IResource r = res;
while (r != null) {
- if (r.getName().startsWith("."))
+ String rName = r.getName();
+ if (rName.startsWith("."))
return false;
r = r.getParent();
}
@@ -55,12 +56,24 @@ public boolean visit(IResourceDelta delta) throws CoreException {
if(res.getName().equals("_build"))
return false;
- // add this path to the project paths
+ // add this path to the project paths when necessary
OcamlPaths paths = new OcamlPaths(project);
String[] strPaths = paths.getPaths();
- String[] newPaths = new String[strPaths.length + 1];
- System.arraycopy(strPaths, 0, newPaths, 0, strPaths.length);
- newPaths[strPaths.length] = res.getProjectRelativePath().toPortableString();
+ String resPath = res.getProjectRelativePath().toPortableString();
+
+ boolean needAdd = true;
+ for (String s: strPaths) {
+ if (s.compareTo(resPath) == 0) {
+ needAdd = false;
+ break;
+ }
+ }
+ String[] newPaths = strPaths;
+ if (needAdd) {
+ newPaths = new String[strPaths.length + 1];
+ System.arraycopy(strPaths, 0, newPaths, 0, strPaths.length);
+ newPaths[strPaths.length] = resPath;
+ }
paths.setPaths(newPaths);
break;
diff --git a/Ocaml/src/ocaml/OcamlPlugin.java b/Ocaml/src/ocaml/OcamlPlugin.java
index 4f2e2d2..15fa911 100644
--- a/Ocaml/src/ocaml/OcamlPlugin.java
+++ b/Ocaml/src/ocaml/OcamlPlugin.java
@@ -37,6 +37,7 @@
import java.io.File;
import java.net.URL;
+import java.util.HashMap;
import ocaml.debugging.OcamlDebugger;
import ocaml.editor.syntaxcoloring.OcamlPartitionScanner;
@@ -51,6 +52,7 @@
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
@@ -72,6 +74,8 @@ public class OcamlPlugin extends AbstractUIPlugin {
/** The singleton instance of the plug-in. */
private static OcamlPlugin instance = null;
+
+ public static HashMap ActiveBuildJobs = new HashMap<>();
/** The directory in which the plug-in was started. */
private final String pluginDirectory;
@@ -396,6 +400,11 @@ public static boolean getCommentIsBold() {
return instance.getPreferenceStore().getBoolean(PreferenceConstants.P_BOLD_COMMENTS);
}
+ /** Returns whether comments should appear in bold (from the user preferences) */
+ public static boolean getDocsCommentIsBold() {
+ return instance.getPreferenceStore().getBoolean(PreferenceConstants.P_BOLD_DOCS_COMMENTS);
+ }
+
/** Returns whether constants should appear in bold (from the user preferences) */
public static boolean getConstantIsBold() {
return instance.getPreferenceStore().getBoolean(PreferenceConstants.P_BOLD_CONSTANTS);
diff --git a/Ocaml/src/ocaml/build/graph/CompilerVisitor.java b/Ocaml/src/ocaml/build/graph/CompilerVisitor.java
index 9a3d187..79bfadb 100644
--- a/Ocaml/src/ocaml/build/graph/CompilerVisitor.java
+++ b/Ocaml/src/ocaml/build/graph/CompilerVisitor.java
@@ -22,13 +22,6 @@
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.ui.IEditorInput;
-import org.eclipse.ui.IEditorReference;
-import org.eclipse.ui.IFileEditorInput;
-import org.eclipse.ui.IWorkbenchPage;
-import org.eclipse.ui.IWorkbenchWindow;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.ui.part.FileEditorInput;
/**
* Visit a graph layer by layer, to compile all vertices in the right order.
diff --git a/Ocaml/src/ocaml/build/makefile/OcamlMakefileBuilder.java b/Ocaml/src/ocaml/build/makefile/OcamlMakefileBuilder.java
index a9de21c..27a570f 100644
--- a/Ocaml/src/ocaml/build/makefile/OcamlMakefileBuilder.java
+++ b/Ocaml/src/ocaml/build/makefile/OcamlMakefileBuilder.java
@@ -258,8 +258,8 @@ protected IStatus run(IProgressMonitor monitor) {
public void run() {
try {
project.refreshLocal(IProject.DEPTH_INFINITE, null);
- } catch (CoreException e1) {
- OcamlPlugin.logError("ocaml plugin error", e1);
+ } catch (Exception e) {
+// OcamlPlugin.logError("ocaml plugin error", e);
}
}
});
diff --git a/Ocaml/src/ocaml/debugging/OcamlDebugger.java b/Ocaml/src/ocaml/debugging/OcamlDebugger.java
index 146d985..0b565ec 100644
--- a/Ocaml/src/ocaml/debugging/OcamlDebugger.java
+++ b/Ocaml/src/ocaml/debugging/OcamlDebugger.java
@@ -76,7 +76,7 @@ public enum State {
/** The project containing the debugged executable */
private IProject project;
-
+
/** list of paths of project */
private String[] projectPaths;
@@ -240,7 +240,7 @@ public synchronized void start(
pathStr = pathStr.trim();
if (!".".equals(pathStr)) {
commandLineArgs.add("-I");
-
+
//These paths are either absolute or relative to the project
//directory. We must convert them to absolute paths in case
//we are running a bytecode within a nested directory.
@@ -248,12 +248,14 @@ public synchronized void start(
if (!path.isAbsolute()) {
path = Paths.get(projectLocation.append(pathStr).toOSString());
}
- commandLineArgs.add(path.toString());
+ commandLineArgs.add(path.toString());
}
}
// add the _build folder (for the cases making project by using Ocamlbuild)
String buildpath = projectLocation.append("_build").toOSString();
+ commandLineArgs.add("-I");
+ commandLineArgs.add(buildpath);
ArrayList buildFolders = FileUtil.findSubdirectories(buildpath);
for (String path: buildFolders) {
commandLineArgs.add("-I");
@@ -429,7 +431,7 @@ public synchronized void backstepReturn() {
send("start");
}
}
-
+
public synchronized void setFrame(int frame) {
if (!checkStarted())
return;
@@ -831,7 +833,6 @@ public void run() {
refreshEditor();
state = State.Idle;
}
-
else if (state.equals(State.Frame)) {
processFrame(output);
debuggerOutput.setLength(0);
@@ -903,7 +904,7 @@ else if (state.equals(State.Quitting)) {
}
}
-
+
private void getFrame() {
state = State.Frame;
send("frame");
@@ -1091,7 +1092,7 @@ private void processFrame(String output) {
public static final Pattern patternCallstack = Pattern
.compile("\\A#(\\d+)\\s+Pc\\s*:\\s+(\\d+)\\s+(\\w+)\\s+char\\s+(\\d+)");
-
+
private void processCallStack(final String output) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
@@ -1138,7 +1139,7 @@ public void run() {
// prettier stackview
String newOutput = "#" + s1 + " - " + module + "." + functionName
- + " - (" + line + ": " + column + ")";
+ + " - (" + line + ": " + column + ")";
backtrace[i] = newOutput;
} else {
OcamlPlugin.logError("ocamldebugger: couldn't parse call stack");
@@ -1156,7 +1157,7 @@ public void run() {
}
});
}
-
+
// get function that contains line
private synchronized String findFunctionContainingLine(String filepath, int line) {
String functionName = "";
@@ -1167,20 +1168,20 @@ private synchronized String findFunctionContainingLine(String filepath, int line
int i = 0;
for (i = 0; i < childs.size(); i++) {
Def def = childs.get(i);
- int l = Symbol.getLine(def.posStart);
- if (l >= line )
+ int l = Symbol.getLine(def.posStart);
+ if (l >= line )
break;
}
if (i > 0) {
Def child = childs.get(i-1);
if (child.type == Def.Type.Let)
functionName = child.name;
- else if (child.type == Def.Type.Module) {
+ else if (child.type == Def.Type.Module) {
List grandChilds = child.children;
int j = 0;
for (j = 0; j < grandChilds.size(); j++) {
Def def = grandChilds.get(j);
- int l = Symbol.getLine(def.posStart);
+ int l = Symbol.getLine(def.posStart);
if (l > line)
break;
}
@@ -1192,7 +1193,7 @@ else if (child.type == Def.Type.Module) {
}
return functionName;
}
-
+
private void indicateRunningState(final String message) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
@@ -1213,7 +1214,7 @@ public void run() {
DebugMarkers.getInstance().clearCurrentPosition();
refreshEditor();
}
-
+
private synchronized IEditorInput getEditorInput(final String filename)
{
String ufilename = filename.substring(0, 1).toUpperCase() + filename.substring(1);
@@ -1277,6 +1278,7 @@ private synchronized String getFilePath(final String filename)
return null;
}
+
public void highlight(final String filename, final int offset) {
final IEditorInput editorInput = getEditorInput(filename);
if (editorInput != null) {
@@ -1446,7 +1448,7 @@ public void run() {
}
});
}
-
+
public void printMessage(final String message) {
final IOConsoleOutputStream console = this.console;
diff --git a/Ocaml/src/ocaml/debugging/views/OcamlCallStackView.java b/Ocaml/src/ocaml/debugging/views/OcamlCallStackView.java
index 850f42b..1de777b 100644
--- a/Ocaml/src/ocaml/debugging/views/OcamlCallStackView.java
+++ b/Ocaml/src/ocaml/debugging/views/OcamlCallStackView.java
@@ -6,8 +6,6 @@
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
-import org.eclipse.swt.events.MouseAdapter;
-import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
@@ -30,10 +28,10 @@ public void setCallStack(String elements[]){
list.removeAll();
for(String e : elements)
list.add(e);
+ list.setRedraw(true);
if (selectionIndex >= 0 && selectionIndex < elements.length) {
list.setSelection(selectionIndex);
}
- list.setRedraw(true);
}
public void empty(){
diff --git a/Ocaml/src/ocaml/editor/actions/CancelCompileAllProjectsAction.java b/Ocaml/src/ocaml/editor/actions/CancelCompileAllProjectsAction.java
new file mode 100644
index 0000000..9f28683
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/CancelCompileAllProjectsAction.java
@@ -0,0 +1,91 @@
+package ocaml.editor.actions;
+
+import java.util.Collection;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+import ocaml.editors.lex.OcamllexEditor;
+import ocaml.editors.yacc.OcamlyaccEditor;
+import ocaml.util.Misc;
+import ocaml.views.OcamlCompilerOutput;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.part.FileEditorInput;
+
+/** This action activates completion in the OCaml editor. */
+public class CancelCompileAllProjectsAction implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page != null) {
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart != null) {
+ IProject project = null;
+ FileEditorInput editorInput = (FileEditorInput) editorPart.getEditorInput();
+ if (editorInput != null && editorInput.getFile() != null) {
+ project = editorInput.getFile().getProject();
+ }
+
+ if (project == null)
+ return;
+
+ // show compiler output
+ Misc.showView(OcamlCompilerOutput.ID);
+ // then activate current editor (to resolve shortcut-key issues)
+ page.activate(editorPart);
+
+ final String jobName = "Cancelling compiling jobs";
+
+ Job job = new Job(jobName) {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ if (!OcamlPlugin.ActiveBuildJobs.isEmpty()) {
+ // cancel all jobs
+ Collection monitors = OcamlPlugin.ActiveBuildJobs.values();
+ for (IProgressMonitor m: monitors) {
+ m.setCanceled(true);
+ }
+ // clear them from store
+ OcamlPlugin.ActiveBuildJobs.clear();
+ }
+ return Status.OK_STATUS;
+ }
+ };
+
+ job.setPriority(Job.BUILD);
+ job.setUser(action != null);
+ job.schedule(50);
+ }else
+ OcamlPlugin.logError("ContentAssistAction: editorPart is null");
+ } else
+ OcamlPlugin.logError("ContentAssistAction: page is null");
+
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/CleanProjectAction.java b/Ocaml/src/ocaml/editor/actions/CleanProjectAction.java
new file mode 100644
index 0000000..09fb562
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/CleanProjectAction.java
@@ -0,0 +1,155 @@
+package ocaml.editor.actions;
+
+import java.util.concurrent.TimeUnit;
+
+import ocaml.OcamlPlugin;
+import ocaml.build.makefile.OcamlMakefileBuilder;
+import ocaml.editors.OcamlEditor;
+import ocaml.editors.lex.OcamllexEditor;
+import ocaml.editors.yacc.OcamlyaccEditor;
+import ocaml.util.Misc;
+import ocaml.views.OcamlCompilerOutput;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.part.FileEditorInput;
+
+/** This action activates completion in the OCaml editor. */
+public class CleanProjectAction implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+ IWorkbenchPage page = window.getActivePage();
+ if (page != null) {
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart != null) {
+ IProject project = null;
+ FileEditorInput editorInput = (FileEditorInput) editorPart.getEditorInput();
+ if (editorInput != null && editorInput.getFile() != null) {
+ project = editorInput.getFile().getProject();
+ }
+
+ if (project == null)
+ return;
+
+ final IProject buildProject = project;
+ final String jobName = "Cleaning project " + project.getName();
+
+ final long[] executedTime = new long[1];
+ executedTime[0] = -1;
+
+ // show compiler output
+ Misc.showView(OcamlCompilerOutput.ID);
+ // then activate current editor (to resolve shortcut-key issues)
+ page.activate(editorPart);
+
+ Job job = new Job(jobName) {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ // save progress monitor for later use
+ OcamlPlugin.ActiveBuildJobs.put(jobName, monitor);
+
+ // cleaning
+ executedTime[0] = System.currentTimeMillis();
+ OcamlMakefileBuilder builder = new OcamlMakefileBuilder();
+ builder.clean(buildProject, monitor);
+ return Status.OK_STATUS;
+ }
+ };
+
+ job.setPriority(Job.BUILD);
+ /*
+ * If the action is directly called by the user, then we display a dialog box. Else, the launch is silent.
+ */
+ job.setUser(action != null);
+ job.schedule(500);
+ job.addJobChangeListener(new IJobChangeListener() {
+ @Override
+ public void sleeping(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void scheduled(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void running(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ // cleaning job was cancelled
+ if (!OcamlPlugin.ActiveBuildJobs.containsKey(jobName)) {
+ Misc.appendToOcamlConsole("Cleaning was cancelled!");
+ }
+ // cleaning job terminates normally
+ else {
+ OcamlPlugin.ActiveBuildJobs.remove(jobName);
+ }
+
+ // time
+ long cleaningTime = -1;
+ if (executedTime[0] > 0)
+ cleaningTime = System.currentTimeMillis() - executedTime[0];
+ if (cleaningTime >= 0) {
+ long minutes = TimeUnit.MILLISECONDS.toMinutes(cleaningTime);
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(cleaningTime) -
+ TimeUnit.MINUTES.toSeconds(minutes);
+ String time = "";
+ if (minutes > 1)
+ time = time + String.valueOf(minutes) + " mins";
+ else
+ time = time + String.valueOf(minutes) + " min";
+ if (seconds > 1)
+ time = time + ", " + String.valueOf(seconds) + " secs";
+ else
+ time = time + ", " + String.valueOf(seconds) + " sec";
+ Misc.appendToOcamlConsole("Time: " + time);
+ }
+ else
+ Misc.appendToOcamlConsole("Time: unknown");
+
+ Misc.appendToOcamlConsole("");
+ }
+
+ @Override
+ public void awake(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ }
+ });
+
+ }else
+ OcamlPlugin.logError("ContentAssistAction: editorPart is null");
+ } else
+ OcamlPlugin.logError("ContentAssistAction: page is null");
+
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/CommentSelectionAction.java b/Ocaml/src/ocaml/editor/actions/CommentSelectionAction.java
index 16d831f..fef7ec7 100644
--- a/Ocaml/src/ocaml/editor/actions/CommentSelectionAction.java
+++ b/Ocaml/src/ocaml/editor/actions/CommentSelectionAction.java
@@ -39,7 +39,7 @@ public void run(IAction action) {
int selEnd = selStart + selection.getLength();
// the last selected character can be a newline
- if (selEnd > 1)
+ if (selEnd - selStart > 1)
selEnd--;
IEditorInput input = editor.getEditorInput();
@@ -67,7 +67,7 @@ public void run(IAction action) {
editor.getSelectionProvider().setSelection(sel);
} catch (BadLocationException e) {
- OcamlPlugin.logError("Wrong offset", e);
+// OcamlPlugin.logError("Wrong offset", e);
return;
}
@@ -86,7 +86,7 @@ private String switchComment(String input) {
final int tabSize = OcamlEditor.getTabSize();
// split the string into lines
- String[] lines = input.split("\\r?\\n");
+ String[] lines = input.split("\\r?\\n", -1);
// uncomment
if (isCommented(lines)) {
@@ -163,8 +163,8 @@ else if (line.charAt(i) == ' ')
// comment character should be inserted at the shortest indentation position
// and at the end of longest line.
private String comment(String line, int indent, int length, int tabSize) {
-
- if (line.trim().equals(""))
+
+ if (line.trim().equals(""))
return line;
StringBuilder builder = new StringBuilder();
diff --git a/Ocaml/src/ocaml/editor/actions/CompileProjectAction.java b/Ocaml/src/ocaml/editor/actions/CompileProjectAction.java
new file mode 100644
index 0000000..3d3cec3
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/CompileProjectAction.java
@@ -0,0 +1,181 @@
+package ocaml.editor.actions;
+
+import java.util.concurrent.TimeUnit;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+import ocaml.editors.lex.OcamllexEditor;
+import ocaml.editors.yacc.OcamlyaccEditor;
+import ocaml.util.Misc;
+import ocaml.views.OcamlCompilerOutput;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IEditorReference;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.part.FileEditorInput;
+
+/** This action activates completion in the OCaml editor. */
+public class CompileProjectAction implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+ final IWorkbenchPage page = window.getActivePage();
+ if (page != null) {
+ /*
+ * Save all source code file before compiling
+ */
+ IEditorReference[] editorReferences = page.getEditorReferences();
+ NullProgressMonitor monitor = new NullProgressMonitor();
+ if ( editorReferences != null ){
+ for (IEditorReference iEditorReference : editorReferences) {
+ IEditorPart editor = iEditorReference.getEditor(false);
+ if (editor != null && editor.isDirty()
+ && ((editor instanceof OcamlEditor)
+ || (editor instanceof OcamllexEditor)
+ || (editor instanceof OcamlyaccEditor))) {
+ editor.doSave(monitor);
+ }
+ }
+ }
+
+ /*
+ * Now build the project of current opened file
+ */
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart != null) {
+ IProject project = null;
+ FileEditorInput editorInput = (FileEditorInput) editorPart.getEditorInput();
+ if (editorInput != null && editorInput.getFile() != null) {
+ project = editorInput.getFile().getProject();
+ }
+
+ if (project == null)
+ return;
+
+ final IProject buildProject = project;
+
+ final String jobName = "Compiling project " + project.getName();
+
+ final long[] executedTime = new long[1];
+ executedTime[0] = -1;
+
+ Job job = new Job(jobName) {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ // save progress monitor for later use
+ OcamlPlugin.ActiveBuildJobs.put(jobName, monitor);
+
+ // compile
+ executedTime[0] = System.currentTimeMillis();
+ buildProject.build(IncrementalProjectBuilder.FULL_BUILD, monitor);
+
+ } catch (CoreException e) {
+ OcamlPlugin.logError("ocaml plugin error", e);
+ }
+ return Status.OK_STATUS;
+ }
+ };
+
+ // show compiler output
+ Misc.showView(OcamlCompilerOutput.ID);
+ // then activate current editor (to resolve shortcut-key issues)
+ page.activate(editorPart);
+
+ job.setPriority(Job.BUILD);
+ /*
+ * If the action is directly called by the user, then we display a dialog box. Else, the launch is silent.
+ */
+ job.setUser(action != null);
+ job.schedule(500);
+ job.addJobChangeListener(new IJobChangeListener() {
+ @Override
+ public void sleeping(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void scheduled(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void running(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ // compiling job was cancelled
+ if (!OcamlPlugin.ActiveBuildJobs.containsKey(jobName)) {
+ Misc.appendToOcamlConsole("Compilation was cancelled!");
+ }
+ // compiling job terminates normally
+ else {
+ OcamlPlugin.ActiveBuildJobs.remove(jobName);
+ }
+
+ // time
+ long compilingTime = -1;
+ if (executedTime[0] > 0)
+ compilingTime = System.currentTimeMillis() - executedTime[0];
+ if (compilingTime >= 0) {
+ long minutes = TimeUnit.MILLISECONDS.toMinutes(compilingTime);
+ long seconds = TimeUnit.MILLISECONDS.toSeconds(compilingTime) -
+ TimeUnit.MINUTES.toSeconds(minutes);
+ String time = "";
+ if (minutes > 1)
+ time = time + String.valueOf(minutes) + " mins";
+ else
+ time = time + String.valueOf(minutes) + " min";
+ if (seconds > 1)
+ time = time + ", " + String.valueOf(seconds) + " secs";
+ else
+ time = time + ", " + String.valueOf(seconds) + " sec";
+ Misc.appendToOcamlConsole("Time: " + time);
+ }
+ else
+ Misc.appendToOcamlConsole("Time: unknown");
+
+ Misc.appendToOcamlConsole("");
+ }
+
+ @Override
+ public void awake(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ }
+ });
+
+ }else
+ OcamlPlugin.logError("ContentAssistAction: editorPart is null");
+ } else
+ OcamlPlugin.logError("ContentAssistAction: page is null");
+
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/DeleteTrailingWhitespaces.java b/Ocaml/src/ocaml/editor/actions/DeleteTrailingWhitespaces.java
new file mode 100644
index 0000000..5c1e047
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/DeleteTrailingWhitespaces.java
@@ -0,0 +1,120 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class DeleteTrailingWhitespaces implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+
+ TextSelection selection = (TextSelection) editor.getSelectionProvider().getSelection();
+
+ try {
+ int startLine = 0;
+ int endLine = 0;
+ if (selection.getLength() > 0) {
+ startLine = doc.getLineOfOffset(selection.getOffset());
+ endLine = doc.getLineOfOffset(selection.getOffset() + selection.getLength());
+ }
+ else {
+ startLine = 0;
+ endLine = doc.getNumberOfLines() - 1;
+ }
+ String newContent = removeTrailingWhitespaces(doc, startLine, endLine);
+ int startOffset = doc.getLineOffset(startLine);
+ int length = 0;
+ if (endLine < doc.getNumberOfLines() - 1)
+ length = doc.getLineOffset(endLine + 1) - doc.getLineDelimiter(endLine).length() - startOffset + 1;
+ else
+ length = doc.getLength() - startOffset;
+ doc.replace(startOffset, length, newContent);
+ editor.selectAndReveal(startOffset, newContent.length() - 1);
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+
+ private String removeTrailingWhitespaces(IDocument doc, int startLine, int endLine) throws Exception {
+ int line = startLine;
+ String newContent = "";
+ while (line <= endLine) {
+
+ int offset1 = doc.getLineOffset(line);
+
+ if (line < doc.getNumberOfLines() - 1) {
+ String lineDelimiter = doc.getLineDelimiter(line);
+ int offset2 = doc.getLineOffset(line+1) - lineDelimiter.length();
+
+ String lineContent = doc.get(offset1, offset2 - offset1 + 1);
+ lineContent = "L" + lineContent;
+ lineContent = lineContent.trim();
+ lineContent = lineContent.substring(1);
+ newContent = newContent + lineContent + lineDelimiter;
+ }
+ else {
+ String lineContent = doc.get(offset1, doc.getLength() - offset1);
+ lineContent = "L" + lineContent;
+ lineContent = lineContent.trim();
+ lineContent = lineContent.substring(1);
+ newContent = newContent + lineContent;
+ }
+
+ line++;
+ }
+
+ return newContent;
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/GotoDefinition.java b/Ocaml/src/ocaml/editor/actions/GotoDefinition.java
new file mode 100644
index 0000000..1ccccab
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/GotoDefinition.java
@@ -0,0 +1,68 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+import ocaml.editors.OcamlHyperlinkDetector;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class GotoDefinition implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ if (editorPart instanceof OcamlEditor) {
+ OcamlEditor editor = (OcamlEditor) editorPart;
+ TextSelection selection =
+ (TextSelection) editor.getSelectionProvider().getSelection();
+ int offset = selection.getOffset();
+ OcamlHyperlinkDetector hyperlinkdetector = new OcamlHyperlinkDetector(editor);
+ ITextViewer textViewer = editor.getTextViewer();
+ IHyperlink hyperlink = hyperlinkdetector.makeHyperlink(textViewer, offset);
+ if (hyperlink != null)
+ hyperlink.open();
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/MarkOccurrencesAction.java b/Ocaml/src/ocaml/editor/actions/MarkOccurrencesAction.java
index 317ee48..5fefa8d 100644
--- a/Ocaml/src/ocaml/editor/actions/MarkOccurrencesAction.java
+++ b/Ocaml/src/ocaml/editor/actions/MarkOccurrencesAction.java
@@ -55,19 +55,22 @@ public void run(IAction action) {
int endOffset = region.getOffset() + region.getLength();
marker.setAttribute(IMarker.CHAR_START, startOffset);
marker.setAttribute(IMarker.CHAR_END, endOffset);
+ marker.setAttribute(IMarker.MESSAGE, "");
region = docFind.find(endOffset + 1, text, true, true, false, false);
}
}
} catch (Exception e) {
- OcamlPlugin.logError(e);
+// OcamlPlugin.logError(e);
}
- } else
- OcamlPlugin.logError(MarkOccurrencesAction.class.getSimpleName() + ": only works on ml and mli files");
-
- } else
- OcamlPlugin.logError(MarkOccurrencesAction.class.getSimpleName() + ": editorPart is null");
- } else
- OcamlPlugin.logError(MarkOccurrencesAction.class.getSimpleName() + ": page is null");
+ }
+// else
+// OcamlPlugin.logError(MarkOccurrencesAction.class.getSimpleName() + ": only works on ml and mli files");
+ }
+// else
+// OcamlPlugin.logError(MarkOccurrencesAction.class.getSimpleName() + ": editorPart is null");
+ }
+// else
+// OcamlPlugin.logError(MarkOccurrencesAction.class.getSimpleName() + ": page is null");
}
public void dispose() {
diff --git a/Ocaml/src/ocaml/editor/actions/MoveCursorDownwardByIndent.java b/Ocaml/src/ocaml/editor/actions/MoveCursorDownwardByIndent.java
new file mode 100644
index 0000000..93c7de9
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/MoveCursorDownwardByIndent.java
@@ -0,0 +1,174 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class MoveCursorDownwardByIndent implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ try {
+ int lineNum= doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+ int docLen = doc.getLength();
+
+ if (lineNum == (numOfLine - 2)) {
+ int lastLineOffset = doc.getLineOffset(lineNum+1);
+ editor.selectAndReveal(lastLineOffset, 0);
+ return;
+ }
+
+ if (lineNum >= (numOfLine - 1)) {
+ editor.selectAndReveal(doc.getLength(), 0);
+ return;
+ }
+
+ // ignore empty lines while going down, go to the first non empty line
+ int beginOffset = doc.getLineOffset(lineNum);
+ int endOffset = doc.getLineOffset(lineNum+1) - 1;
+ String currentLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+
+ if (currentLine.trim().isEmpty()) {
+ lineNum++;
+ while (lineNum < numOfLine) {
+ beginOffset = doc.getLineOffset(lineNum);
+ endOffset = (lineNum < numOfLine - 1) ? doc.getLineOffset(lineNum+1) - 1 : docLen - 1;
+ currentLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ if (currentLine.trim().isEmpty())
+ lineNum++;
+ else
+ break;
+ }
+ if (lineNum >= numOfLine)
+ lineNum = numOfLine - 1;
+ int newOffset = doc.getLineOffset(lineNum);
+ while (newOffset < docLen) {
+ Character ch = doc.getChar(newOffset);
+ if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r')
+ break;
+ else
+ newOffset++;
+ }
+ if (newOffset >= docLen)
+ newOffset = beginOffset;
+ editor.selectAndReveal(newOffset, 0);
+ return;
+ }
+
+ // find next line which has different identation
+ int currentIndent = computeIndent(currentLine);
+ lineNum++;
+ while (lineNum < numOfLine - 1) {
+ beginOffset = doc.getLineOffset(lineNum);
+ endOffset = (lineNum < numOfLine - 1) ? doc.getLineOffset(lineNum+1) - 1 : docLen - 1;
+ String nextLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ int nextIndent = computeIndent(nextLine);
+ if (currentIndent != nextIndent) {
+ break;
+ }
+ else if (nextLine.trim().isEmpty()) {
+ currentIndent = -1;
+ lineNum++;
+ }
+ else {
+ lineNum++;
+ currentLine = nextLine;
+ currentIndent = nextIndent;
+ }
+ }
+
+ // find location to jump to
+ int newLineOffset = doc.getLineOffset(lineNum);
+ int newOffset = newLineOffset;
+ while (newOffset < docLen) {
+ Character ch = doc.getChar(newOffset);
+ if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r')
+ break;
+ else
+ newOffset++;
+ }
+ if (newOffset >= docLen)
+ newOffset = newLineOffset;
+
+ editor.selectAndReveal(newOffset, 0);
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ private int computeIndent(String line) {
+ if (line.trim().isEmpty())
+ return 0;
+
+ if (line.charAt(0) == ' ')
+ return 1 + computeIndent(line.substring(1));
+
+ if (line.charAt(0) == '\t')
+ return OcamlEditor.getTabSize() + computeIndent(line.substring(1));
+
+ return 0;
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/MoveCursorDownwardOneBlock.java b/Ocaml/src/ocaml/editor/actions/MoveCursorDownwardOneBlock.java
new file mode 100644
index 0000000..3d82fd2
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/MoveCursorDownwardOneBlock.java
@@ -0,0 +1,113 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class MoveCursorDownwardOneBlock implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ try {
+ int lineNum= doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+
+ if (lineNum == (numOfLine - 2)) {
+ int lastLineOffset = doc.getLineOffset(lineNum+1);
+ editor.selectAndReveal(lastLineOffset, 0);
+ return;
+ }
+
+ if (lineNum >= (numOfLine - 1)) {
+ editor.selectAndReveal(doc.getLength(), 0);
+ return;
+ }
+
+ int beginOffset = doc.getLineOffset(lineNum);
+ int endOffset = doc.getLineOffset(lineNum+1) - 1;
+ String currentLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ boolean isCurrentLineEmpty = currentLine.trim().isEmpty();
+
+ // find next non-empty line which follows an empty line
+ lineNum++;
+ while (lineNum < numOfLine - 1) {
+ beginOffset = doc.getLineOffset(lineNum);
+ endOffset = doc.getLineOffset(lineNum+1) - 1;
+ String nextLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ if (nextLine.trim().isEmpty()) {
+ isCurrentLineEmpty = true;
+ lineNum++;
+ }
+ else if (!isCurrentLineEmpty)
+ lineNum++;
+ else
+ break;
+ }
+ int newOffset = doc.getLineOffset(lineNum);
+ editor.selectAndReveal(newOffset, 0);
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/MoveCursorUpwardByIndent.java b/Ocaml/src/ocaml/editor/actions/MoveCursorUpwardByIndent.java
new file mode 100644
index 0000000..9373c72
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/MoveCursorUpwardByIndent.java
@@ -0,0 +1,196 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class MoveCursorUpwardByIndent implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ try {
+ int lineNum = doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+ int docLen = doc.getLength();
+
+ if (lineNum <= 0) {
+ editor.selectAndReveal(0, 0);
+ return;
+ }
+
+ // ignore empty lines when going back;
+ int beginOffsetCurrentLine = 0;
+ int endOffsetCurrentLine = 0;
+ String currentLine = "";
+ while (lineNum > 0) {
+ beginOffsetCurrentLine = doc.getLineOffset(lineNum);
+ endOffsetCurrentLine = (lineNum < numOfLine-1) ? doc.getLineOffset(lineNum+1) - 1 : docLen - 1;
+ currentLine = doc.get(beginOffsetCurrentLine, endOffsetCurrentLine - beginOffsetCurrentLine + 1);
+ if (currentLine.trim().isEmpty()) {
+ lineNum--;
+ }
+ else
+ break;
+ }
+ if (lineNum == 0) {
+ editor.selectAndReveal(0, 0);
+ return;
+ }
+
+
+ // if previous line has different indentation and cursor is not
+ // in the beginning of current block, then go to the beginning.
+ beginOffsetCurrentLine = doc.getLineOffset(lineNum);
+ int cursorColumn = cursorOffset - beginOffsetCurrentLine;
+ int newOffset = beginOffsetCurrentLine;
+ while (newOffset < docLen) {
+ Character ch = doc.getChar(newOffset);
+ if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
+ break;
+ else
+ newOffset++;
+ }
+ int currentIndent = newOffset - beginOffsetCurrentLine;
+
+
+ int beginOffsetPrevLine = doc.getLineOffset(lineNum-1);
+ int endOffsetPrevLine = doc.getLineOffset(lineNum) - 1;
+ String prevLine = doc.get(beginOffsetPrevLine, endOffsetPrevLine - beginOffsetPrevLine + 1);
+ int prevIndent = computeIndent(prevLine);
+ if (prevLine.trim().isEmpty() && cursorColumn > currentIndent) {
+ newOffset = beginOffsetCurrentLine + currentIndent;
+ editor.selectAndReveal(newOffset, 0);
+ return;
+ }
+ else if ((prevIndent != currentIndent) && (cursorColumn > currentIndent)) {
+ newOffset = beginOffsetCurrentLine + currentIndent;
+ editor.selectAndReveal(newOffset, 0);
+ return;
+ }
+
+ // ignore empty lines when going back;
+ lineNum--;
+ endOffsetCurrentLine = 0;
+ currentLine = "";
+ while (lineNum > 0) {
+ beginOffsetCurrentLine = doc.getLineOffset(lineNum);
+ endOffsetCurrentLine = (lineNum < numOfLine-1) ? doc.getLineOffset(lineNum+1) - 1 : docLen;
+ currentLine = doc.get(beginOffsetCurrentLine, endOffsetCurrentLine - beginOffsetCurrentLine + 1);
+ if (currentLine.trim().isEmpty()) {
+ lineNum--;
+ }
+ else
+ break;
+ }
+ if (lineNum == 0) {
+ editor.selectAndReveal(0, 0);
+ return;
+ }
+
+
+ // search back to find the last line has different indentation
+ currentIndent = computeIndent(currentLine);
+ while (lineNum > 0) {
+ beginOffsetPrevLine = doc.getLineOffset(lineNum-1);
+ endOffsetPrevLine = doc.getLineOffset(lineNum) - 1;
+ prevLine = doc.get(beginOffsetPrevLine, endOffsetPrevLine - beginOffsetPrevLine + 1);
+ prevIndent = computeIndent(prevLine);
+
+ if (prevLine.trim().isEmpty()) {
+ break;
+ }
+ else if (prevIndent == currentIndent) {
+ lineNum--;
+ currentLine = prevLine;
+ }
+ else
+ break;
+ }
+ newOffset = doc.getLineOffset(lineNum);
+ while (newOffset < docLen) {
+ Character ch = doc.getChar(newOffset);
+ if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
+ break;
+ else
+ newOffset++;
+ }
+ editor.selectAndReveal(newOffset, 0);
+ } catch (BadLocationException e) {
+ return;
+ }
+ }
+
+ private int computeIndent(String line) {
+ if (line.trim().isEmpty())
+ return 0;
+
+ if (line.charAt(0) == ' ')
+ return 1 + computeIndent(line.substring(1));
+
+ if (line.charAt(0) == '\t')
+ return OcamlEditor.getTabSize() + computeIndent(line.substring(1));
+
+ return 0;
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/MoveCursorUpwardOneBlock.java b/Ocaml/src/ocaml/editor/actions/MoveCursorUpwardOneBlock.java
new file mode 100644
index 0000000..5e47909
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/MoveCursorUpwardOneBlock.java
@@ -0,0 +1,115 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class MoveCursorUpwardOneBlock implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ try {
+ int lineNum= doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+
+ if (lineNum <= 0) {
+ editor.selectAndReveal(0, 0);
+ return;
+ }
+
+ int currentLineOffset = doc.getLineOffset(lineNum);
+
+ // if previous line is empty and cursor is not in beginning of
+ // current block, then go to the beginning.
+ int beginOffset = doc.getLineOffset(lineNum-1);
+ int endOffset = doc.getLineOffset(lineNum) - 1;
+ String prevLine= doc.get(beginOffset, endOffset - beginOffset + 1);
+ boolean isPrevLineEmpty = prevLine.trim().isEmpty();
+ if (isPrevLineEmpty && (cursorOffset != currentLineOffset)) {
+ editor.selectAndReveal(currentLineOffset, 0);
+ return;
+ }
+
+ // find previous non-empty line which follows an empty line
+ lineNum--;
+ while (lineNum > 0) {
+ beginOffset = doc.getLineOffset(lineNum-1);
+ endOffset = doc.getLineOffset(lineNum) - 1;
+ prevLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ if (!prevLine.trim().isEmpty()) {
+ isPrevLineEmpty = false;
+ lineNum--;
+ }
+ else if (isPrevLineEmpty)
+ lineNum--;
+ else {
+ break; // stop at this non-empty line
+ }
+ }
+ int newOffset = doc.getLineOffset(lineNum);
+ editor.selectAndReveal(newOffset, 0);
+ } catch (BadLocationException e) {
+ return;
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/SelectDownwardByIndent.java b/Ocaml/src/ocaml/editor/actions/SelectDownwardByIndent.java
new file mode 100644
index 0000000..2c7ddd0
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/SelectDownwardByIndent.java
@@ -0,0 +1,182 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class SelectDownwardByIndent implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ Point selection = styledText.getSelection();
+ int startOffset = (selection.x < cursorOffset) ? selection.x : selection.y;
+
+ try {
+ int lineNum= doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+ int docLen = doc.getLength();
+
+ if (lineNum == (numOfLine - 2)) {
+ int lastLineOffset = doc.getLineOffset(lineNum+1);
+ styledText.setSelection(startOffset, lastLineOffset);
+ return;
+ }
+
+ if (lineNum >= (numOfLine - 1)) {
+ styledText.setSelection(startOffset, doc.getLength());
+ return;
+ }
+
+ // ignore empty lines while going down, go to the first non empty line
+ int beginOffset = doc.getLineOffset(lineNum);
+ int endOffset = doc.getLineOffset(lineNum+1) - 1;
+ String currentLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+
+ if (currentLine.trim().isEmpty()) {
+ lineNum++;
+ while (lineNum < numOfLine) {
+ beginOffset = doc.getLineOffset(lineNum);
+ endOffset = (lineNum < numOfLine - 1) ? doc.getLineOffset(lineNum+1) - 1 : docLen - 1;
+ currentLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ if (currentLine.trim().isEmpty())
+ lineNum++;
+ else
+ break;
+ }
+ if (lineNum >= numOfLine)
+ lineNum = numOfLine - 1;
+ int newOffset = doc.getLineOffset(lineNum);
+ int k = newOffset;
+ while (k < docLen) {
+ Character ch = doc.getChar(k);
+ if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r')
+ break;
+ else {
+ if (ch == '\n' || ch == '\r')
+ newOffset = k + 1;
+ k++;
+ }
+ }
+
+ styledText.setSelection(startOffset, newOffset);
+ return;
+ }
+
+ // find next line which has different identation
+ int currentIndent = computeIndent(currentLine);
+ lineNum++;
+ while (lineNum < numOfLine - 1) {
+ beginOffset = doc.getLineOffset(lineNum);
+ endOffset = (lineNum < numOfLine - 1) ? doc.getLineOffset(lineNum+1) - 1 : docLen - 1;
+ String nextLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ int nextIndent = computeIndent(nextLine);
+ if (currentIndent != nextIndent) {
+ break;
+ }
+ else if (nextLine.trim().isEmpty()) {
+ currentIndent = -1;
+ lineNum++;
+ }
+ else {
+ lineNum++;
+ currentLine = nextLine;
+ currentIndent = nextIndent;
+ }
+ }
+
+ // find location to jump to
+ int newOffset = doc.getLineOffset(lineNum);
+ int k = newOffset;
+ while (k < docLen) {
+ Character ch = doc.getChar(k);
+ if (ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r')
+ break;
+ else {
+ if (ch == '\n' || ch == '\r')
+ newOffset = k + 1;
+ k++;
+ }
+ }
+
+ styledText.setSelection(startOffset, newOffset);
+ } catch (BadLocationException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ private int computeIndent(String line) {
+ if (line.trim().isEmpty())
+ return 0;
+
+ if (line.charAt(0) == ' ')
+ return 1 + computeIndent(line.substring(1));
+
+ if (line.charAt(0) == '\t')
+ return OcamlEditor.getTabSize() + computeIndent(line.substring(1));
+
+ return 0;
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/SelectDownwardOneBlock.java b/Ocaml/src/ocaml/editor/actions/SelectDownwardOneBlock.java
new file mode 100644
index 0000000..c2a77aa
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/SelectDownwardOneBlock.java
@@ -0,0 +1,116 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class SelectDownwardOneBlock implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ Point selection = styledText.getSelection();
+ int startOffset = (selection.x < cursorOffset) ? selection.x : selection.y;
+
+ try {
+ int lineNum= doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+
+ if (lineNum == (numOfLine - 2)) {
+ int lastLineOffset = doc.getLineOffset(lineNum+1);
+ styledText.setSelection(startOffset, lastLineOffset);
+ return;
+ }
+
+ if (lineNum >= (numOfLine - 1)) {
+ styledText.setSelection(startOffset, doc.getLength());
+ return;
+ }
+
+ int beginOffset = doc.getLineOffset(lineNum);
+ int endOffset = doc.getLineOffset(lineNum+1) - 1;
+ String currentLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ boolean isCurrentLineEmpty = currentLine.trim().isEmpty();
+
+ // find next non-empty line which follows an empty line
+ lineNum++;
+ while (lineNum < numOfLine - 1) {
+ beginOffset = doc.getLineOffset(lineNum);
+ endOffset = doc.getLineOffset(lineNum+1) - 1;
+ String nextLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ if (nextLine.trim().isEmpty()) {
+ isCurrentLineEmpty = true;
+ lineNum++;
+ }
+ else if (!isCurrentLineEmpty)
+ lineNum++;
+ else
+ break;
+ }
+ int newOffset = doc.getLineOffset(lineNum);
+ styledText.setSelection(startOffset, newOffset);
+ } catch (BadLocationException e) {
+ return;
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/SelectUpwardByIndent.java b/Ocaml/src/ocaml/editor/actions/SelectUpwardByIndent.java
new file mode 100644
index 0000000..3a9c351
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/SelectUpwardByIndent.java
@@ -0,0 +1,200 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class SelectUpwardByIndent implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ Point selection = styledText.getSelection();
+ int startOffset = (selection.x < cursorOffset) ? selection.x : selection.y;
+
+ try {
+ int lineNum = doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+ int docLen = doc.getLength();
+
+ if (lineNum <= 0) {
+ styledText.setSelection(startOffset, 0);
+ return;
+ }
+
+ // ignore empty lines when going back;
+ int beginOffsetCurrentLine = 0;
+ int endOffsetCurrentLine = 0;
+ String currentLine = "";
+ while (lineNum > 0) {
+ beginOffsetCurrentLine = doc.getLineOffset(lineNum);
+ endOffsetCurrentLine = (lineNum < numOfLine-1) ? doc.getLineOffset(lineNum+1) - 1 : docLen - 1;
+ currentLine = doc.get(beginOffsetCurrentLine, endOffsetCurrentLine - beginOffsetCurrentLine + 1);
+ if (currentLine.trim().isEmpty()) {
+ lineNum--;
+ }
+ else
+ break;
+ }
+ if (lineNum == 0) {
+ styledText.setSelection(startOffset, 0);
+ return;
+ }
+
+
+ // if previous line has different indentation and cursor is not
+ // in the beginning of current block, then go to the beginning.
+ beginOffsetCurrentLine = doc.getLineOffset(lineNum);
+ int cursorColumn = cursorOffset - beginOffsetCurrentLine;
+ int newOffset = beginOffsetCurrentLine;
+ while (newOffset < docLen) {
+ Character ch = doc.getChar(newOffset);
+ if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
+ break;
+ else
+ newOffset++;
+ }
+ int currentIndent = newOffset - beginOffsetCurrentLine;
+
+
+ int beginOffsetPrevLine = doc.getLineOffset(lineNum-1);
+ int endOffsetPrevLine = doc.getLineOffset(lineNum) - 1;
+ String prevLine = doc.get(beginOffsetPrevLine, endOffsetPrevLine - beginOffsetPrevLine + 1);
+ int prevIndent = computeIndent(prevLine);
+ if (prevLine.trim().isEmpty() && cursorColumn > currentIndent) {
+ newOffset = beginOffsetCurrentLine;// + currentIndent;
+ styledText.setSelection(startOffset, newOffset);
+ return;
+ }
+ else if ((prevIndent != currentIndent) && (cursorColumn > currentIndent)) {
+ newOffset = beginOffsetCurrentLine;// + currentIndent;
+ styledText.setSelection(startOffset, newOffset);
+ return;
+ }
+
+ // ignore empty lines when going back;
+ lineNum--;
+ endOffsetCurrentLine = 0;
+ currentLine = "";
+ while (lineNum > 0) {
+ beginOffsetCurrentLine = doc.getLineOffset(lineNum);
+ endOffsetCurrentLine = (lineNum < numOfLine-1) ? doc.getLineOffset(lineNum+1) - 1 : docLen;
+ currentLine = doc.get(beginOffsetCurrentLine, endOffsetCurrentLine - beginOffsetCurrentLine + 1);
+ if (currentLine.trim().isEmpty()) {
+ lineNum--;
+ }
+ else
+ break;
+ }
+ if (lineNum == 0) {
+ styledText.setSelection(startOffset, 0);
+ return;
+ }
+
+
+ // search back to find the last line has different indentation
+ currentIndent = computeIndent(currentLine);
+ while (lineNum > 0) {
+ beginOffsetPrevLine = doc.getLineOffset(lineNum-1);
+ endOffsetPrevLine = doc.getLineOffset(lineNum) - 1;
+ prevLine = doc.get(beginOffsetPrevLine, endOffsetPrevLine - beginOffsetPrevLine + 1);
+ prevIndent = computeIndent(prevLine);
+
+ if (prevLine.trim().isEmpty()) {
+ break;
+ }
+ else if (prevIndent == currentIndent) {
+ lineNum--;
+ currentLine = prevLine;
+ }
+ else
+ break;
+ }
+ newOffset = doc.getLineOffset(lineNum);
+// while (newOffset < docLen) {
+// Character ch = doc.getChar(newOffset);
+// if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n')
+// break;
+// else
+// newOffset++;
+// }
+ styledText.setSelection(startOffset, newOffset);
+ } catch (BadLocationException e) {
+ return;
+ }
+ }
+
+ private int computeIndent(String line) {
+ if (line.trim().isEmpty())
+ return 0;
+
+ if (line.charAt(0) == ' ')
+ return 1 + computeIndent(line.substring(1));
+
+ if (line.charAt(0) == '\t')
+ return OcamlEditor.getTabSize() + computeIndent(line.substring(1));
+
+ return 0;
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/SelectUpwardOneBlock.java b/Ocaml/src/ocaml/editor/actions/SelectUpwardOneBlock.java
new file mode 100644
index 0000000..37b7a56
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/SelectUpwardOneBlock.java
@@ -0,0 +1,118 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class SelectUpwardOneBlock implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ TextEditor editor = (TextEditor) editorPart;
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int cursorOffset = styledText.getCaretOffset();
+
+ Point selection = styledText.getSelection();
+ int startOffset = (selection.x < cursorOffset) ? selection.x : selection.y;
+
+ try {
+ int lineNum= doc.getLineOfOffset(cursorOffset);
+ int numOfLine = doc.getNumberOfLines();
+
+ if (lineNum <= 0) {
+ styledText.setSelection(startOffset, 0);
+ return;
+ }
+
+ int currentLineOffset = doc.getLineOffset(lineNum);
+
+ // if previous line is empty and cursor is not in beginning of
+ // current block, then go to the beginning.
+ int beginOffset = doc.getLineOffset(lineNum-1);
+ int endOffset = doc.getLineOffset(lineNum) - 1;
+ String prevLine= doc.get(beginOffset, endOffset - beginOffset + 1);
+ boolean isPrevLineEmpty = prevLine.trim().isEmpty();
+ if (isPrevLineEmpty && (cursorOffset != currentLineOffset)) {
+ styledText.setSelection(startOffset, currentLineOffset);
+ return;
+ }
+
+ // find previous non-empty line which follows an empty line
+ lineNum--;
+ while (lineNum > 0) {
+ beginOffset = doc.getLineOffset(lineNum-1);
+ endOffset = doc.getLineOffset(lineNum) - 1;
+ prevLine = doc.get(beginOffset, endOffset - beginOffset + 1);
+ if (!prevLine.trim().isEmpty()) {
+ isPrevLineEmpty = false;
+ lineNum--;
+ }
+ else if (isPrevLineEmpty)
+ lineNum--;
+ else {
+ break; // stop at this non-empty line
+ }
+ }
+ int newOffset = doc.getLineOffset(lineNum);
+ styledText.setSelection(startOffset, newOffset);
+ } catch (BadLocationException e) {
+ return;
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+}
diff --git a/Ocaml/src/ocaml/editor/actions/ShiftLeftAction.java b/Ocaml/src/ocaml/editor/actions/ShiftLeftAction.java
index cb7f626..38f9041 100644
--- a/Ocaml/src/ocaml/editor/actions/ShiftLeftAction.java
+++ b/Ocaml/src/ocaml/editor/actions/ShiftLeftAction.java
@@ -85,39 +85,38 @@ private String decreaseIndentation(String input) {
String[] lines = input.split("\\r?\\n");
if (lines.length > 0) {
- // find the shortest indentation
- int shortest = lines[0].length();
+ // compute decrease size
+ int decrease = tabSize;
for (String line : lines) {
- int indent = calculateIndent(line, tabSize);
- if (indent < shortest)
- shortest = indent;
+ if (!line.trim().isEmpty()) {
+ int indent = calculateIndent(line, tabSize);
+ if (indent < decrease)
+ decrease = indent;
+ }
}
- if (shortest == 0) // nothing left to decrease
+ if (decrease == 0) // nothing left to decrease
return input;
- // the size of indentation will be decreased
- int decrease = tabSize;
- if (shortest < tabSize)
- decrease = shortest;
-
// subtract 1 indentation from each lines
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
- int d = 0;
- int u = 0;
- for (u = 0; u < line.length(); u++) {
- if (line.charAt(u) == ' ')
- d++;
- else if (line.charAt(u) == '\t')
- d = d + tabSize;
- if (d >= decrease)
- break;
+ if (line.length() >= decrease) {
+ int d = 0;
+ int u = 0;
+ for (u = 0; u < line.length(); u++) {
+ if (line.charAt(u) == ' ')
+ d++;
+ else if (line.charAt(u) == '\t')
+ d = d + tabSize;
+ if (d >= decrease)
+ break;
+ }
+ String newline = "";
+ for (int j = 0; j < d - decrease; j++)
+ newline = newline + ' ';
+ newline = newline + line.substring(u + 1);
+ lines[i] = newline;
}
- String newline = "";
- for (int j = 0; j < d - decrease; j++)
- newline = newline + ' ';
- newline = newline + line.substring(u + 1);
- lines[i] = newline;
}
// rebuild a string from the lines
diff --git a/Ocaml/src/ocaml/editor/actions/ShowTextHover.java b/Ocaml/src/ocaml/editor/actions/ShowTextHover.java
new file mode 100644
index 0000000..58d5c1b
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/ShowTextHover.java
@@ -0,0 +1,89 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+import ocaml.editors.OcamlTextHover;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+
+public class ShowTextHover implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(GotoDefinition.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof OcamlEditor)) {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ final OcamlEditor editor = (OcamlEditor) editorPart;
+ ITextViewer viewer = editor.getTextViewer();
+ IEditorInput editorInput = editor.getEditorInput();
+ IDocument doc = editor.getDocumentProvider().getDocument(editorInput);
+ Control control = (Control)editor.getAdapter(Control.class);
+
+ if (!(control instanceof StyledText))
+ {
+ OcamlPlugin.logError(GotoDefinition.class.getSimpleName()
+ + ": Cannot get caret position");
+ return;
+ }
+
+ final StyledText styledText = (StyledText) control;
+ int offset = styledText.getCaretOffset();
+
+ OcamlTextHover hover = new OcamlTextHover(editor);
+ IRegion region = hover.getHoverRegion(viewer, offset);
+ final String hoverInfo = hover.getHoverInfoOneLine(viewer, region);
+
+ Display.getCurrent().asyncExec(new Runnable() {
+ public void run() {
+ if (!hoverInfo.equals(""))
+ editor.setStatusLineMessage(hoverInfo);
+ else
+ editor.setStatusLineMessage("");
+ }
+ });
+
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/actions/SynchronizeAndGotoOutline.java b/Ocaml/src/ocaml/editor/actions/SynchronizeAndGotoOutline.java
new file mode 100644
index 0000000..01e3936
--- /dev/null
+++ b/Ocaml/src/ocaml/editor/actions/SynchronizeAndGotoOutline.java
@@ -0,0 +1,66 @@
+package ocaml.editor.actions;
+
+import ocaml.OcamlPlugin;
+import ocaml.editors.OcamlEditor;
+import ocaml.editors.OcamlHyperlinkDetector;
+import ocaml.util.Misc;
+import ocaml.views.OcamlCompilerOutput;
+import ocaml.views.outline.OcamlOutlineControl;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.TextSelection;
+import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.IWorkbenchPage;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+import org.eclipse.ui.editors.text.TextEditor;
+
+public class SynchronizeAndGotoOutline implements IWorkbenchWindowActionDelegate {
+
+ private IWorkbenchWindow window;
+
+ public void run(IAction action) {
+
+ IWorkbenchPage page = window.getActivePage();
+
+ if (page == null) {
+ OcamlPlugin.logWarning(SynchronizeAndGotoOutline.class.getSimpleName()
+ + ": page is null");
+ return;
+ }
+
+ IEditorPart editorPart = page.getActiveEditor();
+ if (editorPart == null) {
+ OcamlPlugin.logError(SynchronizeAndGotoOutline.class.getSimpleName()
+ + ": editorPart is null");
+ return;
+ }
+
+ if (!(editorPart instanceof TextEditor)) {
+ OcamlPlugin.logError(SynchronizeAndGotoOutline.class.getSimpleName()
+ + ": only works on ml and mli files");
+ return;
+ }
+
+ if (editorPart instanceof OcamlEditor) {
+ OcamlEditor editor = (OcamlEditor) editorPart;
+ editor.synchronizeOutline();
+ // show compiler output
+ editor.getOutline().setFocus();
+ }
+ }
+
+ public void dispose() {
+ }
+
+ public void init(IWorkbenchWindow window) {
+ this.window = window;
+ }
+
+ public void selectionChanged(IAction action, ISelection selection) {
+ }
+
+}
diff --git a/Ocaml/src/ocaml/editor/completion/CompletionJob.java b/Ocaml/src/ocaml/editor/completion/CompletionJob.java
index e85c2db..1be467f 100644
--- a/Ocaml/src/ocaml/editor/completion/CompletionJob.java
+++ b/Ocaml/src/ocaml/editor/completion/CompletionJob.java
@@ -140,19 +140,22 @@ public static Def buildDefinitionsTree(IProject project, boolean bUsingEditor) {
}
if (!(dir.exists() && dir.isDirectory())) {
- OcamlPlugin.logError("Wrong path:" + dir.toString() + " (in project:"
- + project.getName() + ")");
continue;
}
// get all the ml and mli files from the directory
String[] mlmliFiles = dir.list(mlmliFilter);
+
/*
* keep all the mli files, and discard the ml files when there is a mli file with
* the same name
*/
- String[] files = Misc.filterInterfaces(mlmliFiles);
+ // String[] files = Misc.filterInterfaces(mlmliFiles);
+
+ // Trung: use both ml & mli files
+ String[] files = mlmliFiles;
+
// for each file
for (String mlmlifile : files) {
diff --git a/Ocaml/src/ocaml/editor/completion/OcamlCompletionProcessor.java b/Ocaml/src/ocaml/editor/completion/OcamlCompletionProcessor.java
index e0db003..dbbd5c6 100644
--- a/Ocaml/src/ocaml/editor/completion/OcamlCompletionProcessor.java
+++ b/Ocaml/src/ocaml/editor/completion/OcamlCompletionProcessor.java
@@ -1,6 +1,9 @@
package ocaml.editor.completion;
+import java.io.File;
+import java.lang.reflect.Array;
import java.util.ArrayList;
+import java.util.HashSet;
import ocaml.OcamlPlugin;
import ocaml.editor.syntaxcoloring.OcamlPartitionScanner;
@@ -9,16 +12,26 @@
import ocaml.editors.yacc.OcamlyaccEditor;
import ocaml.parser.Def;
import ocaml.util.Misc;
+import ocaml.typeHovers.OcamlAnnotParser;
+import ocaml.typeHovers.TypeAnnotation;
+import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
+import org.eclipse.ui.dialogs.NewFolderDialog;
+import org.eclipse.ui.editors.text.TextEditor;
+import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.texteditor.IDocumentProvider;
/**
* This class is responsible for managing completion in the OCaml editor.
@@ -28,29 +41,35 @@
*/
public class OcamlCompletionProcessor implements IContentAssistProcessor {
- // private final OcamlEditor ocamlEditor;
+ private final TextEditor editor;
private final IProject project;
+ // cache last parsed annotation file for speed up
+ private TypeAnnotation[] lastUsedAnnotations = new TypeAnnotation[0];
+ private String lastParsedFileName = "";
+ private long lastParsedTime = 0;
+ private static int cacheTime = 2000;
+
/** The partition type in which completion was triggered. */
private final String partitionType;
public OcamlCompletionProcessor(OcamlEditor edit, String regionType) {
- // this.ocamlEditor = edit;
- this.partitionType = regionType;
+ this.editor = (TextEditor)edit;
this.project = edit.getProject();
+ this.partitionType = regionType;
}
public OcamlCompletionProcessor(OcamllexEditor edit, String regionType) {
- // this.ocamlEditor = null;
- this.partitionType = regionType;
+ this.editor = (TextEditor)edit;
this.project = edit.getProject();
+ this.partitionType = regionType;
}
public OcamlCompletionProcessor(OcamlyaccEditor edit, String regionType) {
- // this.ocamlEditor = null;
- this.partitionType = regionType;
+ this.editor = (TextEditor)edit;
this.project = edit.getProject();
+ this.partitionType = regionType;
}
/**
@@ -99,20 +118,27 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
String completion = completionExpression(viewer, documentOffset);
- OcamlCompletionProposal[] proposals;
- /*
- * If the interfaces parsing Job isn't done, we return an empty list to avoid blocking the graphical
- * interface by doing a potentially long search (should be quick normally, but not on a network file
- * system for example).
- */
+ OcamlCompletionProposal[] proposals = new OcamlCompletionProposal[0];
+
+ IDocument document = viewer.getDocument();
+
+ // must wait parsing job finish first.
if (CompletionJob.isParsingFinished()) {
- Def definitionsRoot = CompletionJob.buildDefinitionsTree(this.project, true);
- proposals = findCompletionProposals(completion, definitionsRoot, documentOffset);
+ Def interfacesDefinitionsRoot = null;
+ if (project != null)
+ interfacesDefinitionsRoot = CompletionJob.buildDefinitionsTree(project, false);
+
+ Def outlineDefinitionsRoot = null; // definitions of current editting file
+ if (editor instanceof OcamlEditor) {
+ outlineDefinitionsRoot = ((OcamlEditor) editor).getOutlineDefinitionsTree();
+ }
+
+ proposals = findCompletionProposals(completion, interfacesDefinitionsRoot, outlineDefinitionsRoot, document, documentOffset);
} else {
proposals = new OcamlCompletionProposal[0];
OcamlPlugin.logInfo("Completion proposals skipped (background job not done yet)");
}
-
+
ICompletionProposal[] templateCompletionProposals;
OcamlTemplateCompletionProcessor tcp = new OcamlTemplateCompletionProcessor();
@@ -137,7 +163,7 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
* ocamlEditor.getOutlineDefinitionsTree(); if (def != null) { ICompletionProposal[]
* moduleCompletionProposals = findModuleCompletionProposals( def, documentOffset, lastWord.length(),
* lastWord);
- *
+ *
* for (int j = 0; j < moduleCompletionProposals.length; j++)
* allProposals.add(moduleCompletionProposals[j]); } else OcamlPlugin
* .logError("OcamlCompletionProcessor:computeCompletionProposals : module definitions=null"); }
@@ -150,7 +176,7 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
/*
* if (allProposals.size() == 2) { ICompletionProposal prop1 = allProposals.get(0);
* ICompletionProposal prop2 = allProposals.get(1);
- *
+ *
* if (prop1 instanceof OcamlCompletionProposal && prop2 instanceof SimpleCompletionProposal &&
* prop1.getDisplayString().equals(prop2.getDisplayString())) allProposals.remove(1); }
*/
@@ -186,53 +212,549 @@ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int
/**
* Find the completions matching the argument completion from all the definitions found in
* the definitionsRoot tree.
- *
+ *
* @return the completions found
*/
- private OcamlCompletionProposal[] findCompletionProposals(String completion, Def definitionsRoot,
+ private OcamlCompletionProposal[] findCompletionProposals(String completion,
+ Def interfacesDefsRoot,
+ Def outlineDefsRoot,
+ IDocument doc,
int offset) {
- ArrayList definitions = definitionsRoot.children;
+ ArrayList proposals;
+
+ String fileName = editor.getEditorInput().getName();
+ String moduleName = editor.getEditorInput().getName();
+ if (moduleName.endsWith(".ml"))
+ moduleName = moduleName.substring(0, moduleName.length() - 3);
+ else if (moduleName.endsWith(".mli"))
+ moduleName = moduleName.substring(0, moduleName.length() - 4);
+ if (moduleName.length() > 0)
+ moduleName = Character.toUpperCase(moduleName.charAt(0))
+ + moduleName.substring(1);
+
+ if (completion.contains("."))
+ proposals = processDottedCompletion(completion, interfacesDefsRoot,
+ outlineDefsRoot, moduleName, fileName, doc, offset, completion.length());
+ else
+ proposals = processNondottedCompletion(completion, interfacesDefsRoot,
+ outlineDefsRoot, moduleName, fileName, doc, offset, completion.length());
+
+ proposals = removeDuplicatedCompletionProposal(proposals);
+
+ return proposals.toArray(new OcamlCompletionProposal[0]);
+ }
+
+ // completion string must contained dots
+ private ArrayList processDottedCompletion(String completion,
+ Def interfacesDefsRoot,
+ Def outlineDefsRoot,
+ String moduleName,
+ String fileName,
+ IDocument document,
+ int offset,
+ int length) {
ArrayList proposals = new ArrayList();
+ if (interfacesDefsRoot == null)
+ return proposals;
+
// look in the module before the dot what's after the dot
+ // The module can be like "A.B." or "c.D.E."
if (completion.contains(".")) {
- int index = completion.indexOf('.');
- String prefix = completion.substring(0, index);
- String suffix = completion.substring(index + 1);
+ // find the first module in completion string
+ int index = completion.lastIndexOf('.');
+ String[] parts = completion.substring(0,index).split("\\.");
+ String prefix = parts[parts.length - 1];
+ String suffix = completion.substring(index+1);
+ int i = parts.length - 2;
+ while (i >= 0) {
+ if (parts[i].isEmpty())
+ break;
+ if (!Character.isUpperCase(parts[i].charAt(0)))
+ break;
+ suffix = prefix + "." + suffix;
+ prefix = parts[i];
+ i--;
+ }
- for (Def def : definitions) {
+ boolean isLowerCasePrefix= false;
+ if (prefix.length() > 0 && Character.isLowerCase(prefix.charAt(0)))
+ isLowerCasePrefix = true;
+
+ Def currentDef = null;
+ ArrayList searchDefs = interfacesDefsRoot.children;
+ if (outlineDefsRoot != null)
+ searchDefs.addAll(outlineDefsRoot.children);
+ for (Def def: searchDefs) {
+ if (def.name.equals(moduleName) && def.getFileName().endsWith(fileName)) {
+ currentDef = def;
+ break;
+ }
+ }
+
+ /*
+ * prefix is a lower-case identifier, hence look for suffix completion
+ * in the current module, sub-modules, included modules or opened modules
+ */
+ if (isLowerCasePrefix) {
+
+ // find in current def
+ for (Def def : currentDef.children) {
+ // look for completion of suffix in the current module
+ if (checkCompletion(def, suffix) && isCompletionDef(def)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef,
+ offset, suffix.length()));
+ }
+
+ // look for completion in opened or included module of current module
+ // by attach the involved module name to suffix and find new completion
+ if (def.type == Def.Type.Open || def.type == Def.Type.Include) {
+ String newCompletion = def.name + "." + suffix;
+ proposals.addAll(processDottedCompletion(newCompletion,
+ interfacesDefsRoot, outlineDefsRoot,
+ moduleName, fileName, document,
+ offset, suffix.length()));
+ }
+ }
+
+ // completion maybe modules's name
+ searchDefs = interfacesDefsRoot.children;
+ if (outlineDefsRoot != null)
+ searchDefs.addAll(outlineDefsRoot.children);
+ for (Def def : searchDefs) {
+ if (checkCompletion(def, suffix) && isCompletionDef(def)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef,
+ offset, suffix.length()));
+ }
+ }
+ }
+ /*
+ * prefix is upper-case identifier, hence look for a module which has
+ * name is or aliased by 'prefix'
+ */
+ else {
+ // bottom-up search to look for prefix in sub-module
+ // or aliased module of current modules
+ final Def nearestDef = findSmallestDefAtOffset(currentDef, offset, document);
+ String newPrefix = bottomUpFindAliasedModule(prefix, "", nearestDef);
+ String newSuffix = suffix;
+
+ // compute prefix, suffix
+ if (newPrefix.contains(".")) {
+ index = newPrefix.indexOf('.');
+ newSuffix = newPrefix.substring(index+1);
+ newSuffix = combineModuleNameParts(newSuffix, suffix);
+ newPrefix = newPrefix.substring(0,index);
+ }
+
+ // find in other modules
+ searchDefs = interfacesDefsRoot.children;
+ if (outlineDefsRoot != null)
+ searchDefs.addAll(outlineDefsRoot.children);
+ for (Def def: searchDefs) {
+ if (def.name.equals(newPrefix))
+ proposals.addAll(lookupProposalsCompletionInDef(
+ newSuffix, def, interfacesDefsRoot,
+ outlineDefsRoot, document,
+ offset, length));
+ }
+ }
+
+ }
+ // find elements starting by in the list of elements
+ else {
+ ArrayList searchDefs = interfacesDefsRoot.children;
+ if (outlineDefsRoot != null)
+ searchDefs.addAll(outlineDefsRoot.children);
+ for (Def def : searchDefs) {
+ if (checkCompletion(def, completion) && isCompletionDef(def)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef, offset, completion.length()));
+ }
+ }
+ }
+
+ return proposals;
+ }
+
+
+ private ArrayList processNondottedCompletion(String completion,
+ Def interfacesDefsRoot,
+ Def outlineDefsRoot,
+ String moduleName,
+ String fileName,
+ IDocument document,
+ int offset,
+ int length) {
+
+ ArrayList proposals = new ArrayList();
+
+ if (interfacesDefsRoot == null)
+ return proposals;
+
+ /*
+ * look in def root
+ */
+ for (Def def: interfacesDefsRoot.children) {
+ if (checkCompletion(def, completion)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef, offset, length));
+ }
+ }
+
+ /*
+ * looked in current module
+ */
+ Def currentDef = null;
+ for (Def def: interfacesDefsRoot.children) {
+ if (def.name.equals(moduleName) && def.getFileName().endsWith(fileName)) {
+ currentDef = def;
+ break;
+ }
+ }
+
+ if (currentDef != null) {
+ // search defns from current def to its parents (bottom-up search)
+ final Def nearestDef = findSmallestDefAtOffset(currentDef, offset, document);
+ proposals.addAll(bottomUpFindProposals(completion, nearestDef, offset));
+
+ // looking in children of current module
+ for (Def def: currentDef.children) {
+ if (checkCompletion(def, completion)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef, offset, length));
+ }
+ }
+
+ /*
+ * look in opened module of current module
+ */
+ for (Def def : currentDef.children) {
+ if (def.type != Def.Type.Open && def.type != Def.Type.Include)
+ continue;
+
+ String newCompletion = def.name + "." + completion;
+ proposals.addAll(processDottedCompletion(newCompletion,
+ interfacesDefsRoot, outlineDefsRoot,
+ moduleName, fileName, document, offset, length));
+ }
+ }
+
+ /*
+ * look in Pervasives module, which is always opended
+ */
+ for (Def def: interfacesDefsRoot.children) {
+ if (!def.name.equals("Pervasives"))
+ continue;
+
+ for (Def d: def.children) {
+ if (d == null || d.name == null)
+ break;
+
+ if (checkCompletion(d, completion) && isCompletionDef(d)) {
+ Def proposedDef = createProposalDef(project, d);
+ proposals.add(new OcamlCompletionProposal(proposedDef, offset, completion.length()));
+ }
+ }
+ }
+
+ return proposals;
+ }
+
+ private ArrayList lookupProposalsCompletionInDef(String completion,
+ Def defsRoot,
+ Def interfacesDefRoot,
+ Def outlineDefRoot,
+ IDocument document,
+ int offset,
+ int length) {
+
+ ArrayList proposals = new ArrayList();
+
+ if (defsRoot == null)
+ return proposals;
+
+ if (completion.contains(".")) {
+ // find the first module in completion string
+ int index = completion.lastIndexOf('.');
+ String[] parts = completion.substring(0,index).split("\\.");
+ String prefix = parts[parts.length - 1];
+ String suffix = completion.substring(index+1);
+ int i = parts.length - 2;
+ while (i >= 0) {
+ if (parts[i].isEmpty())
+ break;
+ if (!Character.isUpperCase(parts[i].charAt(0)))
+ break;
+ suffix = prefix + "." + suffix;
+ prefix = parts[i];
+ i--;
+ }
+
+ // look inside def roots first
+ for (Def def: defsRoot.children) {
if (def.name.equals(prefix))
- return findCompletionProposals(suffix, def, offset);
+ proposals.addAll(lookupProposalsCompletionInDef(suffix, def,
+ interfacesDefRoot, outlineDefRoot,
+ document, offset, length));
+ }
+
+ // look inside included module
+ for (Def def1: defsRoot.children) {
+ if (def1.type == Def.Type.Include) {
+ Def includedDef = def1;
+ // look for included def in current defs
+ for (Def def2: defsRoot.children) {
+ if (def2.name.equals(includedDef.name)) {
+ Def includedDefRoot = def2;
+ proposals.addAll(lookupProposalsCompletionInDef(
+ completion, includedDefRoot,
+ interfacesDefRoot, outlineDefRoot,
+ document, offset, length));
+ }
+
+ }
+ // look for included def in interfaces root defs
+ for (Def def2: interfacesDefRoot.children) {
+ if (def2.name.equals(includedDef.name)) {
+ Def includedDefRoot = def2;
+ proposals.addAll(lookupProposalsCompletionInDef(
+ completion, includedDefRoot,
+ interfacesDefRoot, outlineDefRoot,
+ document, offset, length));
+ }
+
+ }
+ }
}
}
// find elements starting by in the list of elements
else {
+ // look inside def roots first
+ for (Def def : defsRoot.children) {
+ if (checkCompletion(def, completion) && isCompletionDef(def)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef,
+ offset, completion.length()));
+ }
+ }
+ // look inside included module
+ for (Def def1: defsRoot.children) {
+ if (def1.type == Def.Type.Include) {
+ Def includedDef = def1;
+ // look for included def in current defs
+ for (Def def2: defsRoot.children) {
+ if (def2.name.equals(includedDef.name)) {
+ Def includedDefRoot = def2;
+ proposals.addAll(lookupProposalsCompletionInDef(
+ completion, includedDefRoot,
+ interfacesDefRoot, outlineDefRoot,
+ document, offset, length));
+ }
- for (Def def : definitions) {
- if (def.name.startsWith(completion) && isCompletionDef(def))
- proposals.add(new OcamlCompletionProposal(def, offset, completion.length()));
+ }
+ // look for included def in interfaces root defs
+ for (Def def2: interfacesDefRoot.children) {
+ if (def2.name.equals(includedDef.name)) {
+ Def includedDefRoot = def2;
+ proposals.addAll(lookupProposalsCompletionInDef(
+ completion, includedDefRoot,
+ interfacesDefRoot, outlineDefRoot,
+ document, offset, length));
+ }
+ }
+ }
}
+
}
- return proposals.toArray(new OcamlCompletionProposal[0]);
+ return proposals;
+ }
+
+ private ArrayList bottomUpFindProposals(String completion, Def node, int offset) {
+ ArrayList proposals = new ArrayList();
+
+ if (node == null)
+ return proposals;
+
+ Def travelNode = node.parent;
+ while (true) {
+ if (travelNode == null || travelNode.name == null)
+ break;
+
+ if (checkCompletion(travelNode, completion) && isCompletionDef(travelNode)) {
+ Def proposedDef = createProposalDef(project, travelNode);
+ proposals.add(new OcamlCompletionProposal(proposedDef, offset, completion.length()));
+ }
+
+ for (Def def : travelNode.children) {
+ if (def == null || def.name == null)
+ continue;
+ if (checkCompletion(def, completion)) {
+// && (def.type == Def.Type.Let
+// || def.type == Def.Type.LetIn
+// || def.type == Def.Type.Parameter)) {
+ Def proposedDef = createProposalDef(project, def);
+ proposals.add(new OcamlCompletionProposal(proposedDef, offset, completion.length()));
+ }
+ }
+
+ if (travelNode.type == Def.Type.Root)
+ break;
+
+ travelNode = travelNode.parent;
+ }
+
+ return proposals;
+ }
+
+ private String combineModuleNameParts(String prefix, String suffix) {
+ if (suffix.isEmpty())
+ return prefix;
+ else
+ return prefix + "." + suffix;
+ }
+
+ private String bottomUpFindAliasedModule(String prefixAlias, String suffixAlias, Def node) {
+ if (node == null)
+ return combineModuleNameParts(prefixAlias, suffixAlias);
+
+ // search for aliasedModuel from current def, and go upper until meeting root
+ Def currentNode = node;
+ String newPrefixAlias = prefixAlias;
+ while (true) {
+ if (currentNode == null || currentNode.name == null)
+ break;
+
+ if (currentNode.type == Def.Type.ModuleAlias
+ && (currentNode.name.compareTo(newPrefixAlias) == 0)) {
+ if (currentNode.children.size() > 0) {
+ newPrefixAlias = currentNode.children.get(0).name;
+ if (newPrefixAlias.contains(".")) // stop when name has "."
+ break;
+ }
+ }
+
+ if (currentNode.type == Def.Type.Root)
+ break;
+
+ if (currentNode.parent == null)
+ break;
+
+ ArrayList currentSiblings = currentNode.parent.children;
+ int currentIndex = -1;
+ for (int i = 0; i < currentSiblings.size(); i++)
+ if (currentSiblings.get(i).equals(currentNode)) {
+ currentIndex = i;
+ break;
+ }
+ // if current node has a prior sibling, go to that node
+ if (currentIndex > 0)
+ currentNode = currentSiblings.get(currentIndex-1);
+ // otherwise, go to its parent
+ else
+ currentNode = currentNode.parent;
+ }
+
+ // if aliased module is a sub-module (containing "."), then find
+ // the first part. Otherwise, continue to search aliased module
+ if (newPrefixAlias.contains(".")) {
+ String[] parts = newPrefixAlias.split("\\.");
+ String newSuffixAlias = "";
+ for (int i = parts.length-1; i > 0; i--)
+ newSuffixAlias = parts[i] + "." + newSuffixAlias ;
+ if (newSuffixAlias.length() > 0) {
+ newSuffixAlias = newSuffixAlias + suffixAlias;
+ } else
+ newSuffixAlias = suffixAlias;
+ newPrefixAlias = parts[0];
+ return bottomUpFindAliasedModule(newPrefixAlias, newSuffixAlias, currentNode);
+ }
+ else
+ return combineModuleNameParts(newPrefixAlias, suffixAlias);
+ }
+
+ private ArrayList removeDuplicatedCompletionProposal(ArrayList proposals) {
+
+ ArrayList newProposals = new ArrayList();
+ HashSet proposalHashSet = new HashSet<>();
+ for (OcamlCompletionProposal p: proposals) {
+ String hashStr = p.getDisplayString() + p.getAdditionalProposalInfo(null);
+ if (!proposalHashSet.contains(hashStr)) {
+ newProposals.add(p);
+ proposalHashSet.add(hashStr);
+ }
+ }
+
+ return newProposals;
+ }
+
+ /** Find an identifier (or an open directive) at a position in the document */
+ private Def findSmallestDefAtOffset(Def def, int offset, IDocument doc) {
+
+ if (def == null || doc == null)
+ return null;
+
+ if (def.children.size() == 0)
+ return def;
+
+ Def firstChild = def.children.get(0);
+ IRegion region = firstChild.getNameRegion(doc);
+ if (region != null) {
+ if (region.getOffset() > offset)
+ return def;
+ }
+ else return null;
+
+
+ Def nearestChild = null;
+ for (Def d : def.children) {
+ region = d.getNameRegion(doc);
+ if (region != null) {
+ if (region.getOffset() < offset)
+ nearestChild = d;
+ }
+ }
+
+ return findSmallestDefAtOffset(nearestChild, offset, doc);
+ }
+
+ private boolean checkCompletion(Def def, String completion) {
+ if (def == null)
+ return false;
+
+ if (def.name == null)
+ return false;
+
+ if (def.name.startsWith(completion)){
+// if (def.type != Def.Type.Identifier)
+// return true;
+// else if (def.name.length() > completion.length())
+// return true;
+ return true;
+ }
+
+ return false;
}
private boolean isCompletionDef(Def def) {
switch (def.type) {
- case Parameter:
- case Object:
- case LetIn:
+// case Parameter:
+// case Object:
+// case LetIn:
case Open:
case Include:
case In:
- case Identifier:
+// case Identifier:
case Dummy:
case Root:
- case Sig:
- case Struct:
+// case Sig:
+// case Struct:
return false;
default:
return true;
@@ -370,10 +892,14 @@ private String expressionAtOffset(ITextViewer viewer, int documentOffset) {
/** Return context informations available at a given position in the editor (at documentOffset) */
public IContextInformation[] computeContextInformation(ITextViewer viewer, int documentOffset) {
+ IProject project = null;
+ if (editor instanceof OcamlEditor) {
+ project = ((OcamlEditor) editor).getProject();
+ }
IContextInformation[] infos;
if (CompletionJob.isParsingFinished()) {
- Def definitionsRoot = CompletionJob.buildDefinitionsTree(this.project, true);
+ Def definitionsRoot = CompletionJob.buildDefinitionsTree(project, true);
String expression = expressionAtOffset(viewer, documentOffset);
@@ -397,17 +923,48 @@ private IContextInformation[] findContextInformation(String expression, Def defi
// search in the module before the dot the element after the dot
if (expression.contains(".")) {
- int index = expression.indexOf('.');
- String prefix = expression.substring(0, index);
- String suffix = expression.substring(index + 1);
+ int index = expression.lastIndexOf('.');
+ String[] parts = expression.substring(0,index).split("\\.");
+ String prefix = parts[parts.length - 1];
+ String suffix = expression.substring(index+1);
+ int i = parts.length - 2;
+ while (i >= 0) {
+ if (parts[i].isEmpty())
+ break;
+ if (!Character.isUpperCase(parts[i].charAt(0)))
+ break;
+ suffix = prefix + "." + suffix;
+ prefix = parts[i];
+ i--;
+ }
- for (Def def : definitions) {
- if (def.name.equals(prefix)) {
- IContextInformation[] informations = findContextInformation(suffix, def);
- for (IContextInformation i : informations)
- infos.add(i);
+ String moduleName = prefix;
+ boolean stop = false;
+ while (!stop) {
+ stop = true;
+ for (Def def : definitions) {
+ if (def.name.equals(moduleName)) {
+ if (def.type == Def.Type.Module) {
+ IContextInformation[] informations = findContextInformation(suffix, def);
+ for (IContextInformation d : informations)
+ infos.add(d);
+ stop = true;
+ break;
+ }
+ else if (def.type == Def.Type.ModuleAlias) {
+ String aliasedName = def.children.get(0).name;
+ if (moduleName.equals(aliasedName))
+ stop = true;
+ else {
+ moduleName = aliasedName;
+ stop = false;
+ }
+ break;
+ }
+ }
}
}
+
return infos.toArray(new IContextInformation[0]);
}
@@ -421,7 +978,7 @@ private IContextInformation[] findContextInformation(String expression, Def defi
*/
if (def.name.equals(expression)) {
- String body = def.body;
+ String body = def.getBody();
// if (!def.getParentName().equals(""))
// body = body + " (constructor of type " + def.getParentName() + ")";
@@ -435,12 +992,12 @@ private IContextInformation[] findContextInformation(String expression, Def defi
else
message = message + "\u00A0";
- String filename = def.filename;
+ String filename = def.getFileName();
message = message + "\u00A0\n\n" + filename;
message = message.trim();
if (!message.equals("")) {
- String context = def.filename + " : " + def.body;
+ String context = def.getFileName() + " : " + body;
infos.add(new ContextInformation(context, message));
}
}
@@ -454,4 +1011,186 @@ private IContextInformation[] findContextInformation(String expression, Def defi
public String getErrorMessage() {
return null;
}
+
+ private Def createProposalDef(IProject project, Def def) {
+ Def newDef = new Def(def);
+ if (newDef.type == Def.Type.Let
+ || newDef.type == Def.Type.LetIn
+ || newDef.type == Def.Type.External) {
+
+ String typeInfo = "";
+
+ // look for type infor in body first
+ String body = newDef.getBody();
+ int index = body.indexOf(newDef.name);
+ if (index >= 0 && body.length() > newDef.name.length()) {
+ typeInfo = body.substring(index);
+ newDef.setOcamlType(typeInfo);
+ }
+
+ // not found type in body
+ if (typeInfo.isEmpty()) {
+ String filename = newDef.getFileName();
+
+ // store last used annotation for caching
+ TypeAnnotation[] annotations;
+ long currentTime = System.currentTimeMillis();
+ if (filename.equals(lastParsedFileName)
+ && (currentTime - lastParsedTime < cacheTime)) {
+ annotations = lastUsedAnnotations;
+ }
+ else {
+ annotations = parseModuleAnnotation(project, filename);
+ lastUsedAnnotations = annotations;
+ lastParsedFileName = filename;
+ lastParsedTime = System.currentTimeMillis();
+ }
+
+ IDocument document = getDocument(project, filename);
+ typeInfo = computeTypeInfo(newDef, annotations, document);
+ if (!typeInfo.isEmpty()) {
+ newDef.setOcamlType(typeInfo);
+ newDef.setBody("val " + typeInfo);
+ }
+ }
+ }
+ else if (def.type == Def.Type.Type) {
+ String typeInfo = newDef.name + " 't";
+ newDef.setOcamlType(typeInfo);
+ }
+
+ return newDef;
+ }
+
+ private IDocument getDocument(IProject project, String filename) {
+ if (project == null)
+ return null;
+
+ if (filename.isEmpty())
+ return null;
+
+ try {
+ IFile[] files = project.getWorkspace().getRoot().findFilesForLocationURI(URIUtil.toURI(filename));
+ if (files.length == 0)
+ return null;
+
+ IFile file = files[0];
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ provider.connect(file);
+ IDocument document = provider.getDocument(file);
+
+ return document;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+
+ private TypeAnnotation[] parseModuleAnnotation(IProject project, String filename) {
+ if (project == null)
+ return new TypeAnnotation[0];
+
+ if (filename.isEmpty())
+ return new TypeAnnotation[0];
+
+ try {
+ IFile[] files = project.getWorkspace().getRoot().findFilesForLocationURI(URIUtil.toURI(filename));
+ if (files.length == 0)
+ return new TypeAnnotation[0];
+
+ IFile file = files[0];
+ IPath relativeProjectPath = file.getFullPath();
+ IDocumentProvider provider = new TextFileDocumentProvider();
+ provider.connect(file);
+ IDocument document = provider.getDocument(file);
+
+ File annotFile = null;
+
+ annotFile = Misc.getOtherFileFor(file.getProject(), relativeProjectPath, ".annot");
+
+ if (annotFile != null && annotFile.exists()) {
+ TypeAnnotation[] annotations = OcamlAnnotParser.parseFile(annotFile, document);
+
+ return annotations;
+ }
+ } catch (Exception e) {
+// e.printStackTrace();
+ return new TypeAnnotation[0];
+ }
+
+ return new TypeAnnotation[0];
+
+ }
+
+ private String computeTypeInfo(Def def, TypeAnnotation[] annotations, IDocument document) {
+ try {
+ String typeInfo = "";
+ IRegion region = def.getNameRegion(document);
+ int offset = region.getOffset();
+
+ ArrayList found = new ArrayList();
+
+ if (annotations != null) {
+ for (TypeAnnotation annot : annotations)
+ if (annot.getBegin() <= offset && offset < annot.getEnd())
+ found.add(annot);
+
+ /*
+ * Search for the smallest hovered type annotation
+ */
+ TypeAnnotation annot = null;
+ int minSize = Integer.MAX_VALUE;
+
+ for (TypeAnnotation a : found) {
+ int size = a.getEnd() - a.getBegin();
+ if (size < minSize) {
+ annot = a;
+ minSize = size;
+ }
+ }
+
+ String docContent = document.get();
+ if (annot != null) {
+ String expr = docContent.substring(annot.getBegin(), annot.getEnd());
+ String[] lines = expr.split("\\n");
+ if (expr.length() < 50 && lines.length <= 6)
+ typeInfo = expr + ": " + annot.getType();
+ else if (lines.length > 6) {
+ int l = lines.length;
+ typeInfo = lines[0] + "\n" + lines[1] + "\n" + lines[2]
+ + "\n" + "..." + (l - 6) + " more lines...\n" + lines[l - 3]
+ + "\n" + lines[l - 2] + "\n" + lines[l - 1] + "\n:" + annot
+ .getType();
+ } else
+ typeInfo = expr + "\n:" + annot.getType();
+ }
+ }
+ return typeInfo.trim();
+ } catch (Exception e) {
+ return "";
+ }
+
+ }
+
+ ArrayList lineOffsets = null;
+
+ private void computeLinesStartOffset(String doc) {
+ lineOffsets = new ArrayList();
+ lineOffsets.add(0);
+ for (int i = 0; i < doc.length(); i++) {
+ /*
+ * if(doc.charAt(i) == '\r') System.err.print("\n");
+ * if(doc.charAt(i) == '\n') System.err.print("\n"); else
+ * System.err.print(doc.charAt(i));
+ */
+
+ if (doc.charAt(i) == '\n') {
+ lineOffsets.add(i + 1);
+ // System.err.println(i);
+ }
+
+ }
+ }
+
+
}
diff --git a/Ocaml/src/ocaml/editor/completion/OcamlCompletionProposal.java b/Ocaml/src/ocaml/editor/completion/OcamlCompletionProposal.java
index 0442fe5..2166f7b 100644
--- a/Ocaml/src/ocaml/editor/completion/OcamlCompletionProposal.java
+++ b/Ocaml/src/ocaml/editor/completion/OcamlCompletionProposal.java
@@ -1,7 +1,10 @@
package ocaml.editor.completion;
+import java.io.File;
+
import ocaml.OcamlPlugin;
import ocaml.parser.Def;
+import ocaml.util.Misc;
import ocaml.views.outline.OcamlOutlineLabelProvider;
import org.eclipse.core.runtime.IProgressMonitor;
@@ -43,7 +46,7 @@ public OcamlCompletionProposal(Def definition, int replacementOffset, int typedW
this.typedLength = typedWordLength;
}
-
+
public void apply(IDocument document) {
String name = this.definition.name;
@@ -64,17 +67,17 @@ public IContextInformation getContextInformation() {
* We display context information only for functions (to help the user with the types of the expected
* arguments), an exception with arguments, or a constructor with arguments.
*/
- boolean bArrow = definition.body.contains("->") || definition.body.contains("\u2192");
+ final String body = definition.getBody();
+ boolean bArrow = body.contains("->") || body.contains("\u2192");
boolean bFun = type.equals(Def.Type.Let) && bArrow;
boolean bExtFun = type.equals(Def.Type.External) && bArrow;
boolean bExceptionArgs = type.equals(Def.Type.Exception)
- && definition.body.contains(" of ");
+ && body.contains(" of ");
boolean bConstructorArgs = type.equals(Def.Type.TypeConstructor)
- && definition.body.contains(" of ");
+ && body.contains(" of ");
if (!(bFun || bExtFun || bExceptionArgs || bConstructorArgs))
return null;
- final String body = definition.body;
if (body.trim().equals(""))
return null;
@@ -107,7 +110,18 @@ public Image getImage() {
}
public String getDisplayString() {
- return definition.name;
+ String displayString = definition.name;
+
+ String typeInfo = Misc.beautify(Def.cleanString(definition.getOcamlType()));
+
+ if (!typeInfo.isEmpty()) {
+ if (typeInfo.startsWith(definition.name))
+ displayString = typeInfo;
+ else
+ displayString = displayString + " - " + typeInfo;
+ }
+
+ return displayString;
}
/** @deprecated replaced by the same name function in ICompletionProposalExtension5 */
@@ -121,9 +135,11 @@ public String getAdditionalProposalInfo(IProgressMonitor monitor) {
* encodes as a string the informations that will be read back by OcamlInformationPresenter to format
* them
*/
- return definition.parentName + " $@| " + definition.body + " $@| "
+
+ return definition.parentName + " $@| " + definition.getBody() + " $@| "
+ + definition.getOcamlType() + " $@| "
+ definition.sectionComment + " $@| " + definition.comment + " $@| "
- + definition.filename;
+ + definition.getFileName();
}
}
\ No newline at end of file
diff --git a/Ocaml/src/ocaml/editor/completion/OcamlInformationPresenter.java b/Ocaml/src/ocaml/editor/completion/OcamlInformationPresenter.java
index 87eac47..a1a46d6 100644
--- a/Ocaml/src/ocaml/editor/completion/OcamlInformationPresenter.java
+++ b/Ocaml/src/ocaml/editor/completion/OcamlInformationPresenter.java
@@ -1,5 +1,6 @@
package ocaml.editor.completion;
+import java.io.File;
import java.util.ArrayList;
import ocaml.OcamlPlugin;
@@ -26,13 +27,14 @@ public String updatePresentation(Display display, String infoText,
final Color colorSection = new Color(display, 150, 50, 191);
final Color colorParent = new Color(display, 191, 100, 50);
final Color colorCode = new Color(display, 0, 0, 255);
+ final Color colorModuleName = new Color(display, 119, 131, 112);
final Color colorFilename = new Color(display, 64, 64, 64);
String[] infos = infoText.split("\\$\\@\\|");
// templates don't respect the same format
- if (infos.length != 5)
- infos = new String[] { "", "", "", infoText, "" };
+ if (infos.length != 6)
+ infos = new String[] { "", "", "", "", infoText, "" };
// the offset in the generated text, in number of characters
int offset = 0;
@@ -40,19 +42,25 @@ public String updatePresentation(Display display, String infoText,
// the result string
StringBuilder result = new StringBuilder();
- String text;
+ String text = "";
String parentName = infos[0].trim();
String body = infos[1].trim();
- String sectionComment = infos[2].trim();
- String comment = infos[3].trim();
- String filename = infos[4].trim();
+ String ocamlType = infos[2].trim();
+ String sectionComment = infos[3].trim();
+ String comment = infos[4].trim();
+ String filename = infos[5].trim();
- if (!body.equals("")) {
+ // if there is type info, then no need to print body
+ if (!ocamlType.equals("")) {
+ text = body + "\n";
+ result.append(text);
+ presentation.addStyleRange(new StyleRange(offset, text.length(), null, null, SWT.BOLD));
+ offset += text.length();
+ }
+ else if (!body.equals("")) {
text = body + "\n";
result.append(text);
-
- // Color colorBody = new Color(display, 50, 150, 200);
presentation.addStyleRange(new StyleRange(offset, text.length(), null, null, SWT.BOLD));
offset += text.length();
}
@@ -251,16 +259,28 @@ else if (bAnnotation)
}
if (!filename.equals("")) {
+ // attach module name
+ String[] parts = filename.split(File.separator);
+ String moduleName = "";
+ if (parts.length > 1) {
+ moduleName = "Module: " + parts[parts.length - 1];
+ }
String strResult = result.toString();
if (strResult.endsWith("\n\n"))
- text = filename;
+ text = moduleName;
else if (strResult.endsWith("\n"))
- text = "\n" + filename;
+ text = "\n" + moduleName;
else
- text = "\n\n" + filename;
+ text = "\n\n" + moduleName;
result.append(text);
+ presentation.addStyleRange(new StyleRange(offset, text.length(), colorModuleName, null,
+ SWT.ITALIC));
+ offset += text.length();
+ // attach file name
+ text = "\n" + filename;
+ result.append(text);
presentation.addStyleRange(new StyleRange(offset, text.length(), colorFilename, null,
SWT.ITALIC));
offset += text.length();
diff --git a/Ocaml/src/ocaml/editor/syntaxcoloring/OcamlCommentRule.java b/Ocaml/src/ocaml/editor/syntaxcoloring/OcamlCommentRule.java
index a6ecc04..83912a5 100644
--- a/Ocaml/src/ocaml/editor/syntaxcoloring/OcamlCommentRule.java
+++ b/Ocaml/src/ocaml/editor/syntaxcoloring/OcamlCommentRule.java
@@ -40,10 +40,13 @@ public IToken evaluate(ICharacterScanner scanner, boolean resume) {
ch = scanner.read();
nRead++;
+ /* //Commented out by Trung
if (ch == -1)
return token;
- else if (ch == ')') {
+ else
+ */
+ if (ch == ')') {
if (bStar) {
nestingLevel--;
if (nestingLevel <= 0)
diff --git a/Ocaml/src/ocaml/editors/OcamlAutoEditStrategy.java b/Ocaml/src/ocaml/editors/OcamlAutoEditStrategy.java
index 360c4f9..76138cf 100644
--- a/Ocaml/src/ocaml/editors/OcamlAutoEditStrategy.java
+++ b/Ocaml/src/ocaml/editors/OcamlAutoEditStrategy.java
@@ -9,9 +9,11 @@
import ocaml.preferences.PreferenceConstants;
import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.BadPartitioningException;
import org.eclipse.jface.text.DocumentCommand;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
@@ -34,6 +36,8 @@ public class OcamlAutoEditStrategy implements IAutoEditStrategy {
/** A comment opened at the beginning of the line */
private Pattern patternCommentOpen = Pattern.compile("^\\(\\*");
+
+ private Pattern patternCommentMultipleLines = Pattern.compile("^\\*");
/** A "no-format" comment opened at the beginning of the line */
private Pattern patternCommentOpenNoFormat = Pattern.compile("^\\(\\*\\|");
@@ -88,8 +92,8 @@ public void customizeDocumentCommand(IDocument document, DocumentCommand command
int offsetInLine = command.offset - lineRegion.getOffset();
- String beforeCursor = line.substring(0, offsetInLine).trim();
- String afterCursor = line.substring(offsetInLine, line.length()).trim();
+ String beforeCursor = line.substring(0, offsetInLine);
+ String afterCursor = line.substring(offsetInLine, line.length());
try {
ITypedRegion region = document.getPartition(offsetInLine);
@@ -164,7 +168,7 @@ else if (command.text.equals(eol)) {
PreferenceConstants.P_EDITOR_CONTINUE_COMMENTS)) {
Matcher matcher = patternCommentOpenNoFormat.matcher(beforeCursor);
if (matcher.find() && !beforeCursor.contains("*)")) {
- command.text = "*)" + eol + makeIndent(indent) + "(*| ";
+ command.text = " *)" + eol + makeIndent(indent) + "(*| ";
return;
}
@@ -173,9 +177,50 @@ else if (command.text.equals(eol)) {
if (matcher.find())
return;
+ // support multiple-lines comment like Ecipse for Java
matcher = patternCommentOpen.matcher(beforeCursor);
- if (matcher.find() && !beforeCursor.contains("*)")) {
- command.text = "*)" + eol + makeIndent(indent) + "(* ";
+ if (matcher.find()) {
+ try {
+ IDocumentExtension3 extension = (IDocumentExtension3) document;
+ ITypedRegion partition;
+ partition = extension.getPartition(OcamlPartitionScanner.OCAML_PARTITIONING,
+ command.offset, false);
+ if(OcamlPartitionScanner.OCAML_DOCUMENTATION_COMMENT.equals(partition.getType())||
+ OcamlPartitionScanner.OCAML_MULTILINE_COMMENT.equals(partition.getType())) {
+ command.text = eol + makeIndent(indent) + " * ";
+ }
+ else if (partition.getOffset() <= matcher.end() + lineRegion.getOffset()) {
+ String strIndent = makeIndent(indent);
+ command.text = eol + makeIndent(indent) + " * " + eol + makeIndent(indent) + " *)";
+ command.shiftsCaret = false;
+ command.caretOffset = command.offset + eol.length() + strIndent.length() + 3;
+ }
+ } catch (BadLocationException e) {
+ OcamlPlugin.logError("bad location in OcamlAutoEditStrategy", e);
+ } catch (BadPartitioningException e) {
+ OcamlPlugin.logError("bad partitioning in OcamlAutoEditStrategy", e);
+ }
+ return;
+ }
+
+ // support multiple-lines comment like Eclipse for Java
+ matcher = patternCommentMultipleLines.matcher(trimmed);
+ if (matcher.find()) {
+ try {
+ IDocumentExtension3 extension = (IDocumentExtension3) document;
+ ITypedRegion partition = extension.getPartition(OcamlPartitionScanner.OCAML_PARTITIONING,
+ command.offset, false);
+ if(OcamlPartitionScanner.OCAML_DOCUMENTATION_COMMENT.equals(partition.getType())||
+ OcamlPartitionScanner.OCAML_MULTILINE_COMMENT.equals(partition.getType())) {
+ if (trimmed.startsWith("*") && !trimmed.startsWith("*)")) {
+ command.text = eol + makeIndent(indent) + " * ";
+ }
+ }
+ } catch (BadLocationException e) {
+ OcamlPlugin.logError("bad location in OcamlAutoEditStrategy", e);
+ } catch (BadPartitioningException e) {
+ OcamlPlugin.logError("bad partitioning in OcamlAutoEditStrategy", e);
+ }
return;
}
}
@@ -246,8 +291,45 @@ else if (trimmed.startsWith("|") && trimmed.equals(beforeCursor)) {
}
else if (OcamlPlugin.getInstance().getPreferenceStore().getBoolean(
- PreferenceConstants.P_EDITOR_KEEP_INDENT))
- command.text = eol + makeIndent(indent);
+ PreferenceConstants.P_EDITOR_KEEP_INDENT)) {
+ // if current line contains only whitespace, then
+ // then trim it and indent next line by indentation
+ // of nearest non-whitespace line
+ try {
+ int lineNum = document.getLineOfOffset(command.offset);
+ int startLineFindIndent = lineNum - 1;
+ String firstPartCurrentLine =
+ document.get(lineRegion.getOffset(),
+ command.offset - lineRegion.getOffset());
+ // if enter is pressed at beginning of line, just start a new line
+ if (firstPartCurrentLine.isEmpty()) {
+ command.text = eol;
+ }
+ // if enter is pressed at middle or end of line,
+ // then new-line-and-indent
+ else {
+ if (firstPartCurrentLine.trim().isEmpty()) {
+ startLineFindIndent = lineNum - 1;
+ command.length = command.offset - lineRegion.getOffset();
+ command.offset = lineRegion.getOffset();
+ }
+ else
+ startLineFindIndent = lineNum;
+ int priorIndent = 0;
+ for (int l = startLineFindIndent; l >= 0; l--) {
+ int x = document.getLineOffset(l);
+ int y = document.getLineOffset(l+1);
+ String priorLine = document.get(x, y - x + 1);
+ if (!priorLine.trim().isEmpty()) {
+ priorIndent = OcamlFormatter.getLineIndent(priorLine);
+ break;
+ }
+ }
+ command.text = eol + makeIndent(priorIndent);
+ }
+ } catch (BadLocationException e) {
+ }
+ }
}
} else if (command.text.equals("\t")) {
diff --git a/Ocaml/src/ocaml/editors/OcamlEditor.java b/Ocaml/src/ocaml/editors/OcamlEditor.java
index df94793..13937ef 100644
--- a/Ocaml/src/ocaml/editors/OcamlEditor.java
+++ b/Ocaml/src/ocaml/editors/OcamlEditor.java
@@ -2,15 +2,17 @@
import java.io.File;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
import ocaml.OcamlPlugin;
import ocaml.debugging.DebugVisuals;
import ocaml.editor.completion.CompletionJob;
+import ocaml.editor.syntaxcoloring.OcamlEditorColors;
import ocaml.editors.util.OcamlCharacterPairMatcher;
import ocaml.natures.OcamlNatureMakefile;
import ocaml.parser.Def;
-import ocaml.popup.actions.CompileProjectAction;
+import ocaml.popup.actions.CompileProjectPopupAction;
import ocaml.preferences.PreferenceConstants;
import ocaml.views.outline.OcamlOutlineControl;
import ocaml.views.outline.OutlineJob;
@@ -26,7 +28,9 @@
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextListener;
@@ -34,10 +38,16 @@
import org.eclipse.jface.text.PaintManager;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.TextSelection;
-import org.eclipse.jface.text.TextViewer;
+import org.eclipse.jface.text.source.Annotation;
+import org.eclipse.jface.text.source.IAnnotationModel;
+import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.MatchingCharacterPainter;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.events.MouseEvent;
+import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
@@ -49,6 +59,8 @@
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
+import org.eclipse.ui.texteditor.spelling.SpellingAnnotation;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
/**
@@ -98,6 +110,12 @@ public ITextViewer getTextViewer() {
protected void createActions() {
super.createActions();
+ final ISourceViewer sourceViewer = this.getSourceViewer();
+ final StyledText textWidget = sourceViewer.getTextWidget();
+ final OcamlSourceViewerConfig sourceViewerConfig =
+ (OcamlSourceViewerConfig) this.getSourceViewerConfiguration();
+
+
try {
paintManager = new PaintManager(getSourceViewer());
matchingCharacterPainter = new MatchingCharacterPainter(getSourceViewer(),
@@ -111,7 +129,7 @@ protected void createActions() {
* AnnotationPainter(getSourceViewer(), null);
* annotationPainter.addAnnotationType
* ("Ocaml.ocamlSyntaxErrorMarker");
- *
+ *
* paintManager.addPainter(annotationPainter);
*/
@@ -120,9 +138,8 @@ protected void createActions() {
}
try {
- StyledText text = this.getSourceViewer().getTextWidget();
- caret = new DebugVisuals(text);
- text.addPaintListener(caret);
+ caret = new DebugVisuals(textWidget);
+ textWidget.addPaintListener(caret);
IPath file = getPathOfFileBeingEdited();
if (file != null)
@@ -134,7 +151,7 @@ protected void createActions() {
// parse the OCaml libraries in a background thread
try {
CompletionJob job = new CompletionJob("Parsing ocaml library mli files", null);
- job.setPriority(CompletionJob.DECORATE);
+ job.setPriority(CompletionJob.INTERACTIVE); // Trung changes priority
job.schedule();
} catch (Exception e) {
OcamlPlugin.logError("ocaml plugin error", e);
@@ -146,13 +163,49 @@ protected void createActions() {
OcamlPlugin.logError("ocaml plugin error", e);
}
- this.getSourceViewer().addTextListener(new ITextListener() {
+ // synchronize with outline when double-click mouse
+ textWidget.addMouseListener(new MouseListener() {
+ @Override
+ public void mouseUp(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDown(MouseEvent e) {
+ }
+
+ @Override
+ public void mouseDoubleClick(MouseEvent e) {
+ if (sourceViewerConfig.isContentAssistantActive() || e == null)
+ return;
+
+ synchronizeOutline();
+ }
+ });
+
+
+ // Trung: don't rebuild outline when text is changed because it
+ // slow down the system
+ /*
+ final ISourceViewer viewer = this.getSourceViewer();
+ final OcamlSourceViewerConfig viewerConfig = (OcamlSourceViewerConfig) this.getSourceViewerConfiguration();
+ viewer.addTextListener(new ITextListener() {
public void textChanged(TextEvent event) {
- if (event.getDocumentEvent() != null)
- rebuildOutline(500);
+ // Trung: rebuild only when content assistant is inactive
+ if (viewerConfig.isContentAssistantActive() || event == null)
+ return;
+
+ DocumentEvent docEvent = event.getDocumentEvent();
+ if (docEvent == null)
+ return;
+
+ String text = docEvent.getText().trim();
+ if (!text.isEmpty())
+ rebuildOutline(50, false); // don't sync outline with editor
}
});
+ */
+
}
@Override
@@ -167,7 +220,7 @@ public void doSetInput(IEditorInput input) throws CoreException {
super.doSetInput(input);
if (this.outline != null) {
- rebuildOutline(100);
+ rebuildOutline(50, false);
}
IProject project = this.getProject();
@@ -176,7 +229,7 @@ public void doSetInput(IEditorInput input) throws CoreException {
// parse the project interfaces in a background thread
CompletionJob job = new CompletionJob("Parsing ocaml project mli files", project);
- job.setPriority(CompletionJob.DECORATE);
+ job.setPriority(CompletionJob.INTERACTIVE); // Trung changes priority
job.schedule();
if (input instanceof IFileEditorInput) {
@@ -191,6 +244,8 @@ public void doSetInput(IEditorInput input) throws CoreException {
*/
}
+
+
/**
* We give the outline to Eclipse when it asks for an adapter with the
* outline class.
@@ -205,7 +260,7 @@ public Object getAdapter(@SuppressWarnings("unchecked") Class required) {
if (this.outline == null)
this.outline = new OcamlOutlineControl(this);
- rebuildOutline(100);
+ rebuildOutline(50, false);
return this.outline;
}
return super.getAdapter(required);
@@ -223,6 +278,8 @@ public void createPartControl(Composite parent) {
styledText.setTabs(getTabSize());
}
+
+
public static int getTabSize() {
return OcamlPlugin.getInstance().getPreferenceStore().getInt(
PreferenceConstants.P_EDITOR_TABS);
@@ -264,7 +321,7 @@ public int getCaretOffset() {
OcamlPlugin.logError("selection is not instanceof TextSelection");
return -1;
}
-
+
public static class LineColumn {
private final int line;
private final int column;
@@ -282,7 +339,7 @@ public int getColumn() {
return column;
}
}
-
+
/** @return the current selection offset in the editor, as a (line,column) position. */
public LineColumn getSelectionLineColumn() {
ISelection sel = getSelectionProvider().getSelection();
@@ -298,7 +355,7 @@ public LineColumn getSelectionLineColumn() {
OcamlPlugin.logError(e);
return null;
}
-
+
}
OcamlPlugin.logError("selection is not instanceof TextSelection");
return null;
@@ -407,6 +464,9 @@ public IProgressMonitor getMonitor() {
public void doSave(IProgressMonitor monitor) {
super.doSave(monitor);
+ // rebuild Outline when file is saved
+ rebuildOutline(50, false);
+
boolean bMakefileNature = false;
try {
IProject project = this.getProject();
@@ -425,7 +485,7 @@ public void doSave(IProgressMonitor monitor) {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceDescription desc = ws.getDescription();
if (desc.isAutoBuilding())
- CompileProjectAction.compileProject(this.getProject());
+ CompileProjectPopupAction.compileProject(this.getProject());
}
}
@@ -433,7 +493,7 @@ public void doSave(IProgressMonitor monitor) {
* @Override protected void editorContextMenuAboutToShow(IMenuManager menu)
* { IFile file = this.getFileBeingEdited();
* super.editorContextMenuAboutToShow(menu);
- *
+ *
* MenuManager ocamlGroup = new MenuManager("OCaml"); menu.add(new
* Separator()); menu.add(ocamlGroup); ocamlGroup.add(new
* GenDocAction("GenDoc", file)); }
@@ -446,7 +506,7 @@ public void doSave(IProgressMonitor monitor) {
* this.fOutlinePage; }
*/
- public void rebuildOutline(int delay) {
+ public void rebuildOutline(int delay, boolean syncWithEditor) {
// invalidate previous definitions
this.codeDefinitionsTree = null;
@@ -457,14 +517,14 @@ public void rebuildOutline(int delay) {
// String doc = document.get();
if (outlineJob == null)
- outlineJob = new OutlineJob("Rebuilding outline");
+ outlineJob = new OutlineJob("Rebuilding outline", syncWithEditor);
// else if (outlineJob.getState() == OutlineJob.RUNNING)
// return;
// only one Job at a time
else
outlineJob.cancel();
- outlineJob.setPriority(CompletionJob.DECORATE);
+ outlineJob.setPriority(CompletionJob.SHORT);
outlineJob.setOutline(this.outline);
outlineJob.setDoc(document);
outlineJob.setEditor(this);
@@ -479,26 +539,42 @@ public void rebuildOutline(int delay) {
@Override
public void handleCursorPositionChanged() {
super.handleCursorPositionChanged();
- synchronizeOutline();
+
fireCursorPositionChanged(getTextViewer().getSelectedRange());
+ final OcamlEditor editor = this;
+ OcamlTextHover hover = new OcamlTextHover(editor);
+ ITextViewer viewer = this.getTextViewer();
+ int offset = this.getCaretOffset();
+ IRegion region = hover.getHoverRegion(viewer, offset);
+
+ String message = "";
+
if (OcamlPlugin.getInstance().getPreferenceStore().getBoolean(
- PreferenceConstants.P_SHOW_TYPES_IN_STATUS_BAR)) {
- final String annot = OcamlTextHover.getAnnotAt(this,
- (TextViewer) this.getSourceViewer(), this.getCaretOffset()).trim();
- final OcamlEditor editor = this;
- Display.getCurrent().asyncExec(new Runnable() {
-
- public void run() {
- if (editor == null)
- return;
- if (!annot.equals(""))
- editor.setStatusLineMessage(annot);
- else
- editor.setStatusLineMessage(null); // clear
- }
- });
+ PreferenceConstants.P_SHOW_MARKERS_IN_STATUS_BAR)) {
+ message = hover.getMarkerInfoOneLine(viewer, region);
}
+
+ // only display type info when there isn't any markers
+ if (message.isEmpty()
+ && OcamlPlugin.getInstance().getPreferenceStore().getBoolean(
+ PreferenceConstants.P_SHOW_TYPES_IN_STATUS_BAR)) {
+ message = hover.getTypeInfoOneLine(viewer, region);
+ if (message.contains("more lines..."))
+ message = "";
+ }
+
+ final String statusMessage = message;
+ Display.getCurrent().asyncExec(new Runnable() {
+ public void run() {
+ editor.setStatusLineMessage(statusMessage);
+ }
+ });
+ }
+
+ @Override
+ public void setStatusLineMessage(String message) {
+ super.setStatusLineMessage(message);
}
private Def codeDefinitionsTree = null;
diff --git a/Ocaml/src/ocaml/editors/OcamlEditorActionContributor.java b/Ocaml/src/ocaml/editors/OcamlEditorActionContributor.java
new file mode 100644
index 0000000..2023258
--- /dev/null
+++ b/Ocaml/src/ocaml/editors/OcamlEditorActionContributor.java
@@ -0,0 +1,10 @@
+package ocaml.editors;
+
+import org.eclipse.ui.editors.text.TextEditorActionContributor;
+
+/*
+ * Trung: add code here to support editor action
+ */
+public class OcamlEditorActionContributor extends TextEditorActionContributor {
+
+}
diff --git a/Ocaml/src/ocaml/editors/OcamlHyperlinkDetector.java b/Ocaml/src/ocaml/editors/OcamlHyperlinkDetector.java
index a7316a8..de60514 100644
--- a/Ocaml/src/ocaml/editors/OcamlHyperlinkDetector.java
+++ b/Ocaml/src/ocaml/editors/OcamlHyperlinkDetector.java
@@ -8,6 +8,7 @@
import ocaml.OcamlPlugin;
import ocaml.editor.completion.CompletionJob;
import ocaml.parser.Def;
+import ocaml.parser.Def.Type;
import ocaml.parsers.OcamlNewInterfaceParser;
import ocaml.util.Misc;
import ocaml.util.OcamlPaths;
@@ -16,9 +17,12 @@
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.ui.IEditorPart;
@@ -47,32 +51,31 @@ public OcamlHyperlinkDetector(OcamlEditor editor) {
public IHyperlink[] detectHyperlinks(final ITextViewer textViewer, final IRegion region,
boolean canShowMultipleHyperlinks) {
+ IHyperlink hyperlink = makeHyperlink(textViewer, region.getOffset());
+ if (hyperlink != null)
+ return new IHyperlink[] {hyperlink};
+ else
+ return null;
+ }
+ public IHyperlink makeHyperlink (final ITextViewer textViewer, int offset) {
IProject project = editor.getProject();
// get the definitions from the current module
final Def modulesDefinitionsRoot = editor.getDefinitionsTree();
- /*
- * get the definitions from all the mli files in the project paths (which should include the
- * ocaml standard library)
- */
- // long before = System.currentTimeMillis();
-
- // long after = System.currentTimeMillis();
- // System.err.println("parsing for hyperlinks: " + (after - before) + " ms");
-
+
final Def interfacesDefinitionsRoot;
-
+
if(project != null)
interfacesDefinitionsRoot = CompletionJob.buildDefinitionsTree(project, false);
- /* If the project is null, that means the file is external (not in a project). In this case,
+ /* If the project is null, that means the file is external (not in a project). In this case,
* parse only the file to be able to show hyperlinks for this file.
- */
- /* TODO: Provide hyperlinks to other modules referenced by this file */
+ */
+ /* TODO: Provide hyperlinks to other modules referenced by this file */
else {
interfacesDefinitionsRoot = new Def("", Def.Type.Root, 0, 0);
OcamlNewInterfaceParser parser = OcamlNewInterfaceParser.getInstance();
-
+
File file = editor.getPathOfFileBeingEdited().toFile();
Def def = parser.parseFile(file, false);
if (def != null)
@@ -80,17 +83,17 @@ public IHyperlink[] detectHyperlinks(final ITextViewer textViewer, final IRegion
else
return null;
}
-
+
long time = System.currentTimeMillis();
/* Find which definition in the tree is at the hovered offset */
- final Def searchedDef = (time - lastTime < 1000 && lastOffset == region.getOffset() && lastDef != null) ? lastDef
- : findIdentAt(modulesDefinitionsRoot, region.getOffset(), textViewer.getDocument());
+ final Def searchedDef = (time - lastTime < 1000 && lastOffset == offset && lastDef != null) ? lastDef
+ : findIdentAt(modulesDefinitionsRoot, offset, textViewer.getDocument());
lastTime = time;
- lastOffset = region.getOffset();
+ lastOffset = offset;
lastDef = searchedDef;
if (searchedDef != null) {
@@ -103,48 +106,100 @@ public IHyperlink[] detectHyperlinks(final ITextViewer textViewer, final IRegion
return makeOpenHyperlink(textViewer, searchedDef, interfacesDefinitionsRoot);
}
- return new IHyperlink[] {
+ if (searchedDef.type == Def.Type.Identifier)
+ return makeDefinitionHyperlink(textViewer, searchedDef,
+ modulesDefinitionsRoot, interfacesDefinitionsRoot);
- new IHyperlink() {
+ return null;
- public void open() {
+ }
+ // find definition by text its self
+ else {
+ time = System.currentTimeMillis();
- Def target = findDefinitionOf(searchedDef, modulesDefinitionsRoot,
- interfacesDefinitionsRoot);
+ // TODO Trung: currently, this create a hyperlink for even Ocaml's
+ // keywords or text occurring inside comment.
+ // Need to fix this!
- if (target == null)
- return;
+ IDocument doc = textViewer.getDocument();
+ TextSelection ident = findIdentAt(doc, offset);
+ String hoveredText = ident.getText();
+ int beginOffset = ident.getOffset();
- IRegion region = target.getRegion(textViewer.getDocument());
- editor.selectAndReveal(region.getOffset(), region.getLength());
- }
+ if (hoveredText.isEmpty() || Character.isDigit(hoveredText.charAt(0)))
+ return null;
- public String getTypeLabel() {
- return null;
- }
+ return makeDefinitionHyperlink(textViewer, hoveredText, beginOffset,
+ modulesDefinitionsRoot, interfacesDefinitionsRoot);
+ }
+ }
- public String getHyperlinkText() {
- return searchedDef.name;
- }
+ private IHyperlink makeDefinitionHyperlink(final ITextViewer textViewer,
+ final Def def,
+ final Def moudulesRoot,
+ final Def interfacesRoot) {
+ IHyperlink hyperlink = new IHyperlink() {
+ public void open() {
+ Def target = findDefinitionOf(def, moudulesRoot, interfacesRoot);
+ if (target == null)
+ return;
+ IRegion region = target.getNameRegion(textViewer.getDocument());
+ editor.selectAndReveal(region.getOffset(), region.getLength());
+ }
- public IRegion getHyperlinkRegion() {
- return searchedDef.getRegion(textViewer.getDocument());
- }
+ public String getTypeLabel() {
+ return null;
+ }
+ public String getHyperlinkText() {
+ return def.name;
}
- };
+ public IRegion getHyperlinkRegion() {
+ return def.getNameRegion(textViewer.getDocument());
+ }
+ };
- }
+ return hyperlink;
+
+ }
+
+ private IHyperlink makeDefinitionHyperlink(final ITextViewer textViewer,
+ final String strDef, final int offset,
+ final Def moudulesRoot,
+ final Def interfacesRoot) {
+ IHyperlink hyperlink = new IHyperlink() {
+ public void open() {
+ Def target = findDefinitionOf(strDef, moudulesRoot, interfacesRoot);
+ if (target == null)
+ return;
+ IRegion region = target.getNameRegion(textViewer.getDocument());
+ editor.selectAndReveal(region.getOffset(), region.getLength());
+ }
+
+ public String getTypeLabel() {
+ return null;
+ }
+
+ public String getHyperlinkText() {
+ return strDef;
+ }
+
+ public IRegion getHyperlinkRegion() {
+ return new Region(offset, strDef.length());
+ }
+ };
+
+ return hyperlink;
- return null;
}
/**
* Find the definition of searchedDef in modulesDefinitionsRoot,
* and in interfacesDefinitionsRoot
*/
- private Def findDefinitionOf(final Def searchedDef, final Def modulesDefinitionsRoot,
+ private Def findDefinitionOf(final Def searchedDef,
+ final Def modulesDefinitionsRoot,
final Def interfacesDefinitionsRoot) {
Def def = null;
@@ -153,11 +208,14 @@ private Def findDefinitionOf(final Def searchedDef, final Def modulesDefinitions
* if this is a compound name "A.B.c", then we extract the first component to know what
* module to look for, and then we get the definition by entering the module
*/
+ StringBuilder fullDefName = new StringBuilder(searchedDef.name);
if (searchedDef.name.indexOf('.') != -1) {
String[] parts = searchedDef.name.split("\\.");
if (parts.length > 1) {
- Def firstPart = lookForDefinitionUp(null, parts[0], searchedDef, interfacesDefinitionsRoot, parts, true);
- // don't find it in the current module, look in the other ones
+ Def firstPart = lookForDefinitionUp(null, parts[0], searchedDef, interfacesDefinitionsRoot, fullDefName, true);
+ // since fullDefName is updated, we need to updata parts variable
+ parts = fullDefName.toString().split("\\.");
+ // don't find it in the current module, look in the other ones
if (firstPart == null) {
if (openDefInInterfaces(0, parts, interfacesDefinitionsRoot))
return null;
@@ -173,17 +231,17 @@ private Def findDefinitionOf(final Def searchedDef, final Def modulesDefinitions
Def searchedDef2 = new Def(searchedDef);
searchedDef2.name = parts[0];
for (int i = 1; i < parts.length; i++)
- searchedDef2.name = "." + searchedDef2.name;
- firstPart = lookForDefinitionUp(null, parts[0], searchedDef2, interfacesDefinitionsRoot, parts, true);
- } else
+ searchedDef2.name = "." + searchedDef2.name;
+ firstPart = lookForDefinitionUp(null, parts[0], searchedDef2, interfacesDefinitionsRoot, fullDefName, true);
+ } else
break;
}
// if the original definition of firstPart is not in current module, look in the other ones
if (firstPart == null) {
if (openDefInInterfaces(0, parts, interfacesDefinitionsRoot))
return null;
-
- }
+
+ }
// look for the whole parts in current module.
else {
Def defFromPath = findDefFromPath(1, parts, firstPart, null);
@@ -197,7 +255,7 @@ private Def findDefinitionOf(final Def searchedDef, final Def modulesDefinitions
} else {
def = lookForDefinitionUp(searchedDef, searchedDef.name, searchedDef,
- interfacesDefinitionsRoot, new String[] { searchedDef.name }, true);
+ interfacesDefinitionsRoot, fullDefName, true);
// if we didn't find it, look in Pervasives (which is always opened by default)
if (def == null) {
@@ -209,16 +267,101 @@ private Def findDefinitionOf(final Def searchedDef, final Def modulesDefinitions
// if it still wasn't found, try to open it as a module
if (def == null && searchedDef.name.length() > 0
&& Character.isUpperCase(searchedDef.name.charAt(0))) {
- IHyperlink[] hyperlinks = makeOpenHyperlink(null, searchedDef,
+ IHyperlink hyperlink = makeOpenHyperlink(null, searchedDef,
interfacesDefinitionsRoot);
- if (hyperlinks.length > 0)
- hyperlinks[0].open();
+ hyperlink.open();
}
}
return def;
}
+ /**
+ * Find the definition of searchedDef in modulesDefinitionsRoot,
+ * and in interfacesDefinitionsRoot
+ */
+ private Def findDefinitionOf(final String strDef,
+ final Def modulesDefinitionsRoot,
+ final Def interfacesDefinitionsRoot) {
+ /*
+ * use direct name
+ */
+ String[] directPath = strDef.split("\\.");
+ if (openDefInInterfaces(0, directPath, interfacesDefinitionsRoot))
+ return null;
+
+ /*
+ * look in current module
+ */
+ Def def = modulesDefinitionsRoot;
+ for (int index = 0; index < directPath.length; index++) {
+ boolean stop = true;
+ for (Def d : def.children) {
+ if (d.name.equals(directPath[index])) {
+ def = d;
+ if (index == directPath.length - 1)
+ return def;
+ else {
+ stop = false;
+ break;
+ }
+ }
+ }
+ if (stop)
+ break;
+ }
+
+ /*
+ * lookup in opened module
+ */
+ for (Def d : modulesDefinitionsRoot.children) {
+ if (d.type == Def.Type.Open) {
+ String fullStrDef = d.name + "." + strDef;
+ String[] openedPath = fullStrDef.split("\\.");
+ if (openDefInInterfaces(0, openedPath, interfacesDefinitionsRoot))
+ return null;
+ }
+ }
+
+ /*
+ * lookup in module nam or possible aliased module
+ */
+ String[] path = strDef.split("\\.");
+ String moduleName = path[0];
+ boolean stop = false;
+ while (!stop) {
+ stop = true;
+ for (Def d : modulesDefinitionsRoot.children) {
+ if (d.name.equals(moduleName) && (d.type == Def.Type.ModuleAlias)) {
+ String aliasedName = d.children.get(0).name;
+ if (moduleName.equals(aliasedName))
+ stop = true;
+ else {
+ moduleName = aliasedName;
+ stop = false;
+ }
+ break;
+ }
+ }
+ }
+ if (!moduleName.equals(path[0])) {
+ String newFullDefName = moduleName;
+ for (int i = 1; i < path.length; i++)
+ newFullDefName = newFullDefName + "." + path[i];
+ String[] aliasedPath = newFullDefName.split("\\.");
+
+ if (openDefInInterfaces(0, aliasedPath, interfacesDefinitionsRoot))
+ return null;
+ }
+
+ /*
+ * finally, look in Pervasives (which is always opened by default)
+ */
+ String[] pervasivesPath = ("Pervasives" + strDef).split("\\.");
+ openDefInInterfaces(0, pervasivesPath, interfacesDefinitionsRoot);
+ return null;
+ }
+
/** Find the definition whose complete path is given, starting at index */
private Def findDefFromPath(int index, String[] path, Def def, Def lastPartFound) {
if (index == path.length)
@@ -245,7 +388,7 @@ private Def findDefFromPath(int index, String[] path, Def def, Def lastPartFound
/**
* Look for a definition in the node node, its previous siblings, its associated
* nodes ("and"), and recurse on its parent
- *
+ *
* @param searchedNode
* the node we are looking for
* @param name
@@ -254,20 +397,27 @@ private Def findDefFromPath(int index, String[] path, Def def, Def lastPartFound
* the node from which to start
* @param interfacesDefinitionsRoot
* the root of the interfaces definitions tree
- * @param fullpath
+ * @param fullDefName
* the full path of the searched definition (used to look for it in other modules)
* @param otherBranch
* are we in another branch relative to the definition from which we started? (this
* is used to manage the non-rec definitions)
*/
private Def lookForDefinitionUp(Def searchedNode, String name, Def node,
- Def interfacesDefinitionsRoot, String[] fullpath, boolean otherBranch) {
+ Def interfacesDefinitionsRoot, StringBuilder fullDefName, boolean otherBranch) {
Def test = null;
+
+ // extract first part and search again
+ if (name.indexOf('.') > -1) {
+ String[] parts = name.split("\\.");
+ return lookForDefinitionUp(searchedNode, parts[0], node, interfacesDefinitionsRoot,
+ fullDefName, otherBranch);
+ }
if (node.type == Def.Type.In) {
/* If this is an 'in' node (in a 'let in'), go directly to the parent */
return lookForDefinitionUp(searchedNode, name, node.parent, interfacesDefinitionsRoot,
- fullpath, false);
+ fullDefName, false);
}
// is it this node?
@@ -277,19 +427,34 @@ private Def lookForDefinitionUp(Def searchedNode, String name, Def node,
// if it is an "open" node, look inside the interface of this module
if (node.type == Def.Type.Open || node.type == Def.Type.Include) {
- String[] path = new String[fullpath.length + 1];
- System.arraycopy(fullpath, 0, path, 1, fullpath.length);
- path[0] = node.name;
+ String newFullDefName = node.name + "." + fullDefName.toString();
+ String[] path = newFullDefName.split("\\.");
if (openDefInInterfaces(0, path, interfacesDefinitionsRoot))
return null;
}
+ // if it is an ModuleAlias, then using the aliased module to find def
+ if (node.type == Def.Type.ModuleAlias) {
+ if (node.children.size() > 0) {
+ String aliasModule = node.name;
+ String aliasedModule = node.children.get(0).name;
+ if (fullDefName.length() > aliasModule.length()) {
+ String str = fullDefName.substring(0, aliasModule.length()+1);
+ if (str.compareTo(aliasModule+".") == 0) {
+ fullDefName.delete(0, aliasModule.length());
+ fullDefName.insert(0, aliasedModule);
+ }
+ }
+ return lookForDefinitionUp(searchedNode, aliasedModule,
+ node, interfacesDefinitionsRoot,
+ fullDefName, false);
+ }
+ }
+
/* if we are at root, we cannot go further up in this module. */
- if (node.type == Def.Type.Root) {
- // openDefInInterfaces(0, new String[] { name }, interfacesDefinitionsRoot);
+ if (node.type == Def.Type.Root)
return null;
- }
// look in the associated "and" nodes after it
for (int i = node.getSiblingsOffset() + 1; i < node.parent.children.size(); i++) {
@@ -298,8 +463,26 @@ private Def lookForDefinitionUp(Def searchedNode, String name, Def node,
break;
test = isDef(searchedNode, name, after, true, false);
- if (test != null)
- return test;
+ if (test != null) {
+ if (test.type == Type.ModuleAlias) {
+ if (test.children.size() > 0) {
+ String aliasModule = test.name;
+ String aliasedModule = test.children.get(0).name;
+ if (fullDefName.length() > aliasModule.length()) {
+ String str = fullDefName.substring(0, aliasModule.length()+1);
+ if (str.compareTo(aliasModule+".") == 0) {
+ fullDefName.delete(0, aliasModule.length());
+ fullDefName.insert(0, aliasedModule);
+ }
+ }
+ return lookForDefinitionUp(searchedNode, aliasedModule,
+ node, interfacesDefinitionsRoot,
+ fullDefName, false);
+ }
+ }
+ else
+ return test;
+ }
}
/* look in the associated "and" nodes that precede it and also in the other (not "and") */
@@ -316,22 +499,39 @@ private Def lookForDefinitionUp(Def searchedNode, String name, Def node,
// if it is an "open" node, look inside the interface of this module
if (before.type == Def.Type.Open || before.type == Def.Type.Include) {
- String[] path = new String[fullpath.length + 1];
- System.arraycopy(fullpath, 0, path, 1, fullpath.length);
- path[0] = before.name;
+ String newFullDefName = before.name + "." + fullDefName.toString();
+ String[] path = newFullDefName.split("\\.");
if (openDefInInterfaces(0, path, interfacesDefinitionsRoot))
return null;
}
test = isDef(searchedNode, name, before, bAnd, !bAnd);
- if (test != null)
- return test;
+ if (test != null) {
+ if (test.type == Type.ModuleAlias) {
+ if (test.children.size() > 0) {
+ String aliasModule = test.name;
+ String aliasedModule = test.children.get(0).name;
+ if (fullDefName.length() > aliasModule.length()) {
+ String str = fullDefName.substring(0, aliasModule.length()+1);
+ if (str.compareTo(aliasModule+".") == 0) {
+ fullDefName.delete(0, aliasModule.length());
+ fullDefName.insert(0, aliasedModule);
+ }
+ }
+ return lookForDefinitionUp(searchedNode, aliasedModule,
+ node, interfacesDefinitionsRoot,
+ fullDefName, false);
+ }
+ }
+ else
+ return test;
+ }
}
/* Now, go one step up */
return lookForDefinitionUp(searchedNode, name, node.parent, interfacesDefinitionsRoot,
- fullpath, false);
+ fullDefName, false);
}
/**
@@ -342,7 +542,7 @@ private boolean openDefInInterfaces(int index, final String[] path, Def interfac
if (index == path.length) {
try {
- String filename = interfaceDef.filename;
+ String filename = interfaceDef.getFileName();
// open the file containing the definition
IProject project = editor.getProject();
@@ -353,9 +553,9 @@ private boolean openDefInInterfaces(int index, final String[] path, Def interfac
.getActivePage();
if (page != null) {
-
+
File file = new File(filename);
-
+
final IFileStore fileStore;
try {
URI uri = file.toURI();
@@ -366,14 +566,14 @@ private boolean openDefInInterfaces(int index, final String[] path, Def interfac
}
IEditorPart part = IDE.openEditorOnFileStore(page, fileStore);
-
+
if (part instanceof OcamlEditor) {
final OcamlEditor editor = (OcamlEditor) part;
ITextViewer textViewer = editor.getTextViewer();
if (interfaceDef != null) {
- IRegion region = interfaceDef.getRegion(textViewer.getDocument());
+ IRegion region = interfaceDef.getNameRegion(textViewer.getDocument());
editor.selectAndReveal(region.getOffset(), region.getLength());
}
} else
@@ -394,21 +594,13 @@ private boolean openDefInInterfaces(int index, final String[] path, Def interfac
return true;
}
- /*
- * if we couldn't go all the way down to the definition, but we could find the beginning of
- * the path, open it
- */
- if (index > 1)
- if (openDefInInterfaces(path.length, path, interfaceDef))
- return true;
-
return false;
}
/**
* Is node the definition of name? If true, returns the node (or
* the constructor in a type). If false, returns null.
- *
+ *
* @param bIn
* whether to accept "let in" (or parameter) nodes
* @param otherBranch
@@ -466,6 +658,8 @@ else if (!searchedNode.bInIn) {
return node;
case ModuleType:
return node;
+ case ModuleAlias:
+ return node;
case External:
return node;
case Class:
@@ -501,17 +695,15 @@ private Def findIdentAt(Def def, int offset, IDocument doc) {
if (def == null || doc == null)
return null;
- IRegion region = def.getRegion(doc);
+ IRegion region = def.getNameRegion(doc);
if (region == null)
return null;
int startOffset = region.getOffset();
- int endOffset = startOffset + region.getLength() - 1;
+ int endOffset = startOffset + region.getLength();
- if (startOffset <= offset
- && endOffset >= offset
- && (def.type == Def.Type.Identifier || def.type == Def.Type.Open || def.type == Def.Type.Include))
+ if (startOffset <= offset && endOffset >= offset)
return def;
for (Def d : def.children) {
@@ -523,20 +715,69 @@ private Def findIdentAt(Def def, int offset, IDocument doc) {
return null;
}
+ /** Find a smallest def at a position in the document */
+ private TextSelection findIdentAt(IDocument doc, int offset) {
+ int docLen = doc.getLength();
+ String text = "";
+ int i = offset;
+ while (i < docLen) {
+ char ch;
+ try {
+ ch = doc.getChar(i);
+ if (ch == '.' || ch == '_' || Character.isLetterOrDigit(ch)) {
+ text = text + ch;
+ i++;
+ }
+ else break;
+ } catch (BadLocationException e) {
+ break;
+ }
+ }
+
+ i = offset - 1;
+ while (i >= 0) {
+ char ch;
+ try {
+ ch = doc.getChar(i);
+ if (ch == '.' || ch == '_' || Character.isLetterOrDigit(ch)) {
+ text = ch + text;
+ i--;
+ }
+ else break;
+ } catch (BadLocationException e) {
+ break;
+ }
+ }
+ int beginOffset = (i >= 0) ? i + 1 : 0;
+
+
+ String[] parts = text.split("\\.");
+ String hoveredText = parts[parts.length - 1];
+ i = parts.length - 2;
+ while (i >= 0) {
+ if (parts[i].isEmpty())
+ break;
+ if (!Character.isUpperCase(parts[i].charAt(0)))
+ break;
+ hoveredText = parts[i] + "." + hoveredText;
+ i--;
+ }
+ beginOffset = beginOffset + (text.length() - hoveredText.length());
+
+ return new TextSelection(doc, beginOffset, hoveredText.length());
+ }
+
+
+
/**
* make an hyperlink for an open directive (to open the interface in an editor)
*/
- private IHyperlink[] makeOpenHyperlink(final ITextViewer textViewer, final Def searchedDef,
+ private IHyperlink makeOpenHyperlink(final ITextViewer textViewer, final Def searchedDef,
final Def interfacesDefinitionsRoot) {
- return new IHyperlink[] {
-
- new IHyperlink() {
-
+ return new IHyperlink() {
public void open() {
-
try {
-
// extract the first part of a multipart name (A.B.C)
String[] fragments = searchedDef.name.split("\\.");
@@ -634,13 +875,9 @@ public String getHyperlinkText() {
}
public IRegion getHyperlinkRegion() {
- return searchedDef.getRegion(textViewer.getDocument());
+ return searchedDef.getNameRegion(textViewer.getDocument());
}
-
- }
-
};
-
}
}
diff --git a/Ocaml/src/ocaml/editors/OcamlSourceViewerConfig.java b/Ocaml/src/ocaml/editors/OcamlSourceViewerConfig.java
index 889f476..79d1f81 100644
--- a/Ocaml/src/ocaml/editors/OcamlSourceViewerConfig.java
+++ b/Ocaml/src/ocaml/editors/OcamlSourceViewerConfig.java
@@ -13,16 +13,26 @@
import ocaml.preferences.PreferenceConstants;
import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.jface.preference.IPreferenceStore;
+import org.eclipse.jface.preference.JFacePreferences;
+import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.text.IAutoEditStrategy;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextDoubleClickStrategy;
import org.eclipse.jface.text.ITextHover;
import org.eclipse.jface.text.TextAttribute;
+import org.eclipse.jface.text.contentassist.ContentAssistEvent;
import org.eclipse.jface.text.contentassist.ContentAssistant;
+import org.eclipse.jface.text.contentassist.ICompletionListener;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.jface.text.contentassist.ICompletionProposalSorter;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.formatter.IContentFormatter;
import org.eclipse.jface.text.formatter.MultiPassContentFormatter;
+import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
+import org.eclipse.jface.text.hyperlink.IHyperlinkPresenter;
import org.eclipse.jface.text.presentation.IPresentationReconciler;
import org.eclipse.jface.text.presentation.PresentationReconciler;
import org.eclipse.jface.text.reconciler.IReconciler;
@@ -42,9 +52,15 @@
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.RGB;
import org.eclipse.ui.editors.text.EditorsUI;
+import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.texteditor.spelling.SpellingAnnotation;
import org.eclipse.ui.texteditor.spelling.SpellingService;
+import org.eclipse.ui.themes.ITheme;
+import org.eclipse.ui.themes.IThemeManager;
/**
* Configures the OCaml code editor: auto edit strategies, formatter, partitioning, completion assistant,
@@ -52,9 +68,11 @@
*/
public class OcamlSourceViewerConfig extends SourceViewerConfiguration {
private OcamlEditor ocamlEditor;
+ private boolean isContentAssistantActive;
public OcamlSourceViewerConfig(OcamlEditor ocamlEditor) {
this.ocamlEditor = ocamlEditor;
+ this.isContentAssistantActive = false;
}
/**
@@ -107,6 +125,7 @@ public String[] getConfiguredContentTypes(ISourceViewer sourceViewer) {
public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceViewer) {
int styleComment = ocaml.OcamlPlugin.getCommentIsBold() ? SWT.BOLD : SWT.NONE;
+ int styleDocsComment = ocaml.OcamlPlugin.getDocsCommentIsBold() ? SWT.BOLD : SWT.NONE;
int styleString = ocaml.OcamlPlugin.getStringIsBold() ? SWT.BOLD : SWT.NONE;
PresentationReconciler reconciler = new PresentationReconciler();
@@ -121,10 +140,10 @@ public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceVie
// a damager-repairer for doc comments and doc annotations
RuleBasedScanner scannerAnnot = new RuleBasedScanner();
IToken tokenAnnot = new Token(new TextAttribute(OcamlEditorColors.getDocAnnotationColor(), null,
- styleComment));
+ styleDocsComment));
IToken tokenDocComment = new Token(new TextAttribute(OcamlEditorColors.getDocCommentColor(), null,
- styleComment));
+ styleDocsComment));
scannerAnnot.setRules(new IRule[] { new DocumentAnnotationRule(tokenAnnot, tokenDocComment) });
dr = new DefaultDamagerRepairer(scannerAnnot);
@@ -149,21 +168,40 @@ public IPresentationReconciler getPresentationReconciler(ISourceViewer sourceVie
public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) {
ContentAssistant assistant = new ContentAssistant();
assistant.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer));
-
+ assistant.enablePrefixCompletion(true);
assistant.setContentAssistProcessor(new OcamlCompletionProcessor(this.ocamlEditor,
OcamlPartitionScanner.OCAML_DOCUMENTATION_COMMENT),
OcamlPartitionScanner.OCAML_DOCUMENTATION_COMMENT);
assistant.setContentAssistProcessor(new OcamlCompletionProcessor(this.ocamlEditor,
IDocument.DEFAULT_CONTENT_TYPE), IDocument.DEFAULT_CONTENT_TYPE);
- assistant.enableAutoInsert(true);
+ assistant.addCompletionListener(new ICompletionListener() {
+ @Override
+ public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) {
+ isContentAssistantActive = true;
+ }
+ @Override
+ public void assistSessionStarted(ContentAssistEvent event) {
+ isContentAssistantActive = true;
+ }
+ @Override
+ public void assistSessionEnded(ContentAssistEvent event) {
+ isContentAssistantActive = false;
+ }
+ });
+
+ assistant.setSorter(new ICompletionProposalSorter() {
+ @Override
+ public int compare(ICompletionProposal p1, ICompletionProposal p2) {
+ return p1.getDisplayString().compareTo(p2.getDisplayString());
+ }
+ });
boolean autoActivation = OcamlPlugin.getInstance().getPreferenceStore().getBoolean(
PreferenceConstants.P_EDITOR_AUTOCOMPLETION);
-
assistant.enableAutoActivation(autoActivation);
-
assistant.setAutoActivationDelay(100);
+ assistant.enableAutoInsert(true);
assistant.setProposalPopupOrientation(IContentAssistant.PROPOSAL_STACKED);
assistant.setContextInformationPopupOrientation(IContentAssistant.CONTEXT_INFO_ABOVE);
assistant.setInformationControlCreator(new OcamlInformationControlCreator());
@@ -179,11 +217,23 @@ public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) {
public IAnnotationHover getAnnotationHover(ISourceViewer sourceViewer) {
return new OcamlAnnotationHover();
}
+
@Override
public IHyperlinkDetector[] getHyperlinkDetectors(ISourceViewer sourceViewer) {
return new IHyperlinkDetector[] { new OcamlHyperlinkDetector(this.ocamlEditor) };
}
+
+ @Override
+ public IHyperlinkPresenter getHyperlinkPresenter(ISourceViewer sourceViewer) {
+ IThemeManager themeManager = WorkbenchPlugin.getDefault().getWorkbench().getThemeManager();
+ ITheme currentTheme = themeManager.getCurrentTheme();
+ ColorRegistry colorRegistry = currentTheme.getColorRegistry();
+ // use hyperlink color from current preference of Eclipse
+ Color color = colorRegistry.get(JFacePreferences.HYPERLINK_COLOR);
+ DefaultHyperlinkPresenter hyperlinkPresenter = new DefaultHyperlinkPresenter(color);
+ return hyperlinkPresenter;
+ }
@Override
public IReconciler getReconciler(final ISourceViewer sourceViewer) {
@@ -207,7 +257,7 @@ public IReconciler getReconciler(final ISourceViewer sourceViewer) {
reconciler.setIsIncrementalReconciler(false);
reconciler.setProgressMonitor(new NullProgressMonitor());
reconciler.setDelay(500);
-
+
OcamlPlugin.getInstance().getPreferenceStore().addPropertyChangeListener(
new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
@@ -245,6 +295,14 @@ public void propertyChange(PropertyChangeEvent event) {
else
return null;
}
+
+
+
+ public boolean isContentAssistantActive() {
+ return isContentAssistantActive;
+ }
+
+
}
diff --git a/Ocaml/src/ocaml/editors/OcamlTextHover.java b/Ocaml/src/ocaml/editors/OcamlTextHover.java
index 90f32e7..0d86d02 100644
--- a/Ocaml/src/ocaml/editors/OcamlTextHover.java
+++ b/Ocaml/src/ocaml/editors/OcamlTextHover.java
@@ -5,7 +5,6 @@
import ocaml.OcamlPlugin;
import ocaml.debugging.OcamlDebugger;
-import ocaml.preferences.PreferenceConstants;
import ocaml.typeHovers.OcamlAnnotParser;
import ocaml.typeHovers.TypeAnnotation;
import ocaml.util.Misc;
@@ -28,35 +27,85 @@
*/
public class OcamlTextHover implements ITextHover {
private OcamlEditor ocamlEditor;
+
public OcamlTextHover(OcamlEditor ocamlEditor) {
this.ocamlEditor = ocamlEditor;
}
/**
- * Returns the string to display in a pop-up which gives informations about the element currently under
- * the mouse cursor in the editor
+ * Returns the string to display in a pop-up which gives informations
+ * about the element currently under the mouse cursor in the editor
*/
+ @Override
public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
- // the file in the workspace. null if the file is external
- IFile file = ocamlEditor.getFileBeingEdited();
+ String markersInfo = getMarkerInfo(textViewer, hoverRegion);
+ String valueInfo = getExpressionValue(textViewer, hoverRegion);
+ String typeInfo = getTypeInfo(textViewer, hoverRegion);
+
+ String hoverInfo = "";
+
+ if (!markersInfo.isEmpty()) {
+ hoverInfo = markersInfo;
+ }
+
+ if (!valueInfo.isEmpty()) {
+ if (hoverInfo.isEmpty())
+ hoverInfo = valueInfo;
+ else
+ hoverInfo = hoverInfo + "\n\n" + "Value info: " + "\n" + valueInfo;
+ }
+
+ if (!typeInfo.isEmpty()) {
+ if (hoverInfo.isEmpty())
+ hoverInfo = typeInfo;
+ else
+ hoverInfo = hoverInfo + "\n\n" + "Type info: " + "\n" + typeInfo;
+ }
+
+ return hoverInfo;
+ }
+
+ // get HoverInfo in 1 line format
+ public String getHoverInfoOneLine(ITextViewer textViewer, IRegion hoverRegion) {
+ // display only marker info, value info, or type info
+ String hoverInfo = getMarkerInfo(textViewer, hoverRegion);
+
+ if (hoverInfo.isEmpty())
+ hoverInfo = getExpressionValue(textViewer, hoverRegion);
+
+ if (hoverInfo.isEmpty())
+ hoverInfo = getTypeInfo(textViewer, hoverRegion);
+
+ String[] lines = hoverInfo.split(System.lineSeparator());
+ String hoverInfoOneLine = "";
+ for (String line: lines)
+ hoverInfoOneLine = hoverInfoOneLine + " " + line.trim();
+ return hoverInfoOneLine.trim();
+ }
- // the full system path of the file
+
+ /**
+ * Returns the string to display in a pop-up which gives marker informations
+ * about the element currently under the mouse cursor in the editor
+ */
+ public String getMarkerInfo(ITextViewer textViewer, IRegion region) {
+ IFile file = ocamlEditor.getFileBeingEdited();
IPath filePath = ocamlEditor.getPathOfFileBeingEdited();
if (filePath == null)
- return "";
+ return "";
- if (hoverRegion == null) {
+ if (region == null) {
ocaml.OcamlPlugin.logError("OcamlTextHover:getHoverInfo null region");
return "";
}
try {
// error message string, if we found one in the hovered region
- String hoverMessage = "";
+ String markerInfo = "";
- int hoverOffset = hoverRegion.getOffset();
+ int hoverOffset = region.getOffset();
if (file != null) {
IMarker[] markers = file.findMarkers(null, true, 1);
@@ -66,102 +115,180 @@ public String getHoverInfo(ITextViewer textViewer, IRegion hoverRegion) {
if (hoverOffset >= markerStart && hoverOffset <= markerEnd) {
String message = marker.getAttribute(IMarker.MESSAGE, "");
- hoverMessage = hoverMessage + message.trim() + "\n";
+ markerInfo = markerInfo + message.trim() + "\n\n";
}
}
+ return markerInfo.trim();
}
+ } catch (Throwable e) {
+ // ocaml.OcamlPlugin.logError("Erreur dans OcamlTextHover:getHoverInfo", e);
+ }
- // if the debugger is started, we ask it for the value of the variable under the cursor
- if (OcamlDebugger.getInstance().isStarted()) {
- String text = textViewer.getDocument().get();
- String expression = expressionAtOffset(text, hoverOffset).trim();
+ return "";
+ }
+
+ // get markerInfo in 1 line format
+ public String getMarkerInfoOneLine(ITextViewer textViewer, IRegion region) {
+ String markerInfo = getMarkerInfo(textViewer, region);
+ String[] lines = markerInfo.split(System.lineSeparator());
+ String markerInfoOneLine = "";
+ for (String line: lines)
+ markerInfoOneLine = markerInfoOneLine + " " + line.trim();
+ return markerInfoOneLine.trim();
+ }
+
+ /**
+ * Returns the string to display in a pop-up which gives type informations
+ * about the element currently under the mouse cursor in the editor
+ */
+ public String getTypeInfo(ITextViewer textViewer, IRegion region) {
+ IFile file = ocamlEditor.getFileBeingEdited();
+ IPath filePath = ocamlEditor.getPathOfFileBeingEdited();
- if (!expression.equals("")) {
- String value = OcamlDebugger.getInstance().display(expression).trim();
+ if (filePath == null)
+ return "";
- if (!value.equals(""))
- return (hoverMessage + Misc.beautify(value)).trim();
- }
- }
+ if (region == null) {
+ ocaml.OcamlPlugin.logError("OcamlTextHover:getHoverInfo null region");
+ return "";
+ }
- if (OcamlPlugin.getInstance().getPreferenceStore().getBoolean(
- PreferenceConstants.P_SHOW_TYPES_IN_POPUPS)) {
+ try {
+ String typeInfo = "";
+ int offset = region.getOffset();
+
+ File annotFile = null;
+
+ // workspace file
+ if (file != null)
+ annotFile = Misc.getOtherFileFor(file.getProject(), file.getFullPath(), ".annot");
+ else
+ annotFile = Misc.getOtherFileFor(filePath, ".annot");
- File annotFile = null;
+ if (annotFile != null && annotFile.exists()) {
+ boolean bUpToDate = false;
- // workspace file
if (file != null)
- annotFile = Misc.getOtherFileFor(file.getProject(), file.getFullPath(), ".annot");
+ bUpToDate = file.getLocation().toFile().lastModified() <= annotFile.lastModified();
else
- annotFile = Misc.getOtherFileFor(filePath, ".annot");
-
- if (annotFile != null && annotFile.exists()) {
- boolean bUpToDate = false;
-
- if (file != null)
- bUpToDate = file.getLocation().toFile().lastModified() <= annotFile.lastModified();
- else
- bUpToDate = filePath.toFile().lastModified() <= annotFile.lastModified();
-
- if (!ocamlEditor.isDirty() && bUpToDate) {
- ArrayList found = new ArrayList();
-
- TypeAnnotation[] annotations = OcamlAnnotParser.parseFile(annotFile, textViewer
- .getDocument());
- if (annotations != null) {
- for (TypeAnnotation annot : annotations)
- if (annot.getBegin() <= hoverOffset && hoverOffset < annot.getEnd())
- found.add(annot);
-
- /*
- * Search for the smallest hovered type annotation
- */
- TypeAnnotation annot = null;
- int minSize = Integer.MAX_VALUE;
-
- for (TypeAnnotation a : found) {
- int size = a.getEnd() - a.getBegin();
- if (size < minSize) {
- annot = a;
- minSize = size;
- }
- }
+ bUpToDate = filePath.toFile().lastModified() <= annotFile.lastModified();
+
+ if (!ocamlEditor.isDirty() /*&& bUpToDate*/) {
+ ArrayList found = new ArrayList();
+
+ TypeAnnotation[] annotations = OcamlAnnotParser.parseFile(annotFile, textViewer
+ .getDocument());
+ if (annotations != null) {
+ for (TypeAnnotation annot : annotations)
+ if (annot.getBegin() <= offset && offset < annot.getEnd())
+ found.add(annot);
- if (annot != null) {
-
- String doc = ocamlEditor.getDocumentProvider().getDocument(
- ocamlEditor.getEditorInput()).get();
- String expr = doc.substring(annot.getBegin(), annot.getEnd());
- String[] lines = expr.split("\\n");
- if (expr.length() < 50 && lines.length <= 6)
- return (hoverMessage + expr + ": " + annot.getType()).trim();
- else if (lines.length > 6) {
- int l = lines.length;
-
- return (hoverMessage + lines[0] + "\n" + lines[1] + "\n" + lines[2]
- + "\n" + "..." + (l - 6) + " more lines...\n" + lines[l - 3]
- + "\n" + lines[l - 2] + "\n" + lines[l - 1] + "\n:" + annot
- .getType()).trim();
- } else
- return (hoverMessage + expr + "\n:" + annot.getType()).trim();
+ /*
+ * Search for the smallest hovered type annotation
+ */
+ TypeAnnotation annot = null;
+ int minSize = Integer.MAX_VALUE;
+
+ for (TypeAnnotation a : found) {
+ int size = a.getEnd() - a.getBegin();
+ if (size < minSize) {
+ annot = a;
+ minSize = size;
}
}
+
+ if (annot != null) {
+ String doc = ocamlEditor.getDocumentProvider().getDocument(
+ ocamlEditor.getEditorInput()).get();
+ String expr = doc.substring(annot.getBegin(), annot.getEnd());
+ String[] lines = expr.split("\\n");
+ if (expr.length() < 50 && lines.length <= 6)
+ return (typeInfo + expr + ": " + annot.getType()).trim();
+ else if (lines.length > 6) {
+ int l = lines.length;
+
+ return (typeInfo + lines[0] + "\n" + lines[1] + "\n" + lines[2]
+ + "\n" + "..." + (l - 6) + " more lines...\n" + lines[l - 3]
+ + "\n" + lines[l - 2] + "\n" + lines[l - 1] + "\n:" + annot
+ .getType()).trim();
+ } else
+ return (typeInfo + expr + "\n:" + annot.getType()).trim();
+ }
}
}
}
- return hoverMessage.trim();
+ return typeInfo.trim();
/*
* if (!this.ocamlEditor.isDirty()) return this.ocamlEditor.getTypeInfoAt(hoverOffset).trim();
*/
} catch (Throwable e) {
- ocaml.OcamlPlugin.logError("Erreur dans OcamlTextHover:getHoverInfo", e);
+ // ocaml.OcamlPlugin.logError("Erreur dans OcamlTextHover:getHoverInfo", e);
}
return "";
}
+
+ // get HoverInfo in 1 line format
+ public String getTypeInfoOneLine(ITextViewer textViewer, IRegion region) {
+ String typeInfo = getTypeInfo(textViewer, region);
+ String[] lines = typeInfo.split(System.lineSeparator());
+ String typeInfoOneLine = "";
+ for (String line: lines)
+ typeInfoOneLine = typeInfoOneLine + " " + line.trim();
+ return typeInfoOneLine.trim();
+ }
+
+ /**
+ * Returns the string to display in a pop-up which gives value of expression
+ * currently under the mouse cursor in the editor in debug mode
+ */
+ public String getExpressionValue(ITextViewer textViewer, IRegion region) {
+ IPath filePath = ocamlEditor.getPathOfFileBeingEdited();
+
+ if (filePath == null)
+ return "";
+
+ if (region == null) {
+ ocaml.OcamlPlugin.logError("OcamlTextHover:getHoverInfo null region");
+ return "";
+ }
+
+ String value = "";
+
+ try {
+ int offset = region.getOffset();
+
+ // if the debugger is started, we ask it for the value of the variable under the cursor
+ if (OcamlDebugger.getInstance().isStarted()) {
+ String text = textViewer.getDocument().get();
+ String expression = expressionAtOffset(text, offset).trim();
+
+ if (!expression.equals("")) {
+ value = OcamlDebugger.getInstance().display(expression).trim();
+ if (!value.equals(""))
+ value = Misc.beautify(value).trim();
+ }
+ }
+ } catch (Throwable e) {
+ // ocaml.OcamlPlugin.logInfo("Erreur dans OcamlTextHover:getHoverInfo", e);
+ }
+
+ return value;
+ }
+
+ // get expression value in 1 line format
+ public String getExpressionValueOneLine(ITextViewer textViewer, IRegion region) {
+ String value = getExpressionValue(textViewer, region);
+ String[] lines = value.split(System.lineSeparator());
+ String valueOneLine = "";
+ for (String line: lines)
+ valueOneLine = valueOneLine + " " + line.trim();
+ return valueOneLine.trim();
+ }
+ @Override
public IRegion getHoverRegion(ITextViewer textViewer, int offset) {
return new Region(offset, 0);
}
diff --git a/Ocaml/src/ocaml/editors/lex/OcamllexEditor.java b/Ocaml/src/ocaml/editors/lex/OcamllexEditor.java
index 9781bcd..d7b3e53 100644
--- a/Ocaml/src/ocaml/editors/lex/OcamllexEditor.java
+++ b/Ocaml/src/ocaml/editors/lex/OcamllexEditor.java
@@ -2,9 +2,10 @@
import ocaml.OcamlPlugin;
import ocaml.editor.completion.CompletionJob;
+import ocaml.editor.syntaxcoloring.OcamlEditorColors;
import ocaml.editors.util.OcamlCharacterPairMatcher;
import ocaml.natures.OcamlNatureMakefile;
-import ocaml.popup.actions.CompileProjectAction;
+import ocaml.popup.actions.CompileProjectPopupAction;
import ocaml.preferences.PreferenceConstants;
import org.eclipse.core.resources.IProject;
@@ -13,8 +14,12 @@
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.PaintManager;
+import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.MatchingCharacterPainter;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
@@ -39,9 +44,11 @@ public OcamllexEditor() {
protected void createActions() {
super.createActions();
- paintManager = new PaintManager(getSourceViewer());
+ final ISourceViewer viewer = getSourceViewer();
+
+ paintManager = new PaintManager(viewer);
matchingCharacterPainter =
- new MatchingCharacterPainter(getSourceViewer(), new OcamlCharacterPairMatcher());
+ new MatchingCharacterPainter(viewer, new OcamlCharacterPairMatcher());
matchingCharacterPainter.setColor(new Color(Display.getCurrent(), new RGB(160, 160, 160)));
paintManager.addPainter(matchingCharacterPainter);
@@ -49,7 +56,7 @@ protected void createActions() {
// effectue le parsing des bibliothèques ocaml en arrière plan
CompletionJob job = new CompletionJob("Parsing ocaml library mli files", null);
- job.setPriority(CompletionJob.DECORATE);
+ job.setPriority(CompletionJob.INTERACTIVE); // Trung changes priority
job.schedule();
}
@@ -97,7 +104,7 @@ public void doSave(IProgressMonitor monitor) {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceDescription desc = ws.getDescription();
if (desc.isAutoBuilding())
- CompileProjectAction.compileProject(this.getProject());
+ CompileProjectPopupAction.compileProject(this.getProject());
}
}
diff --git a/Ocaml/src/ocaml/editors/yacc/OcamlyaccEditor.java b/Ocaml/src/ocaml/editors/yacc/OcamlyaccEditor.java
index 67b774c..6e96bd1 100644
--- a/Ocaml/src/ocaml/editors/yacc/OcamlyaccEditor.java
+++ b/Ocaml/src/ocaml/editors/yacc/OcamlyaccEditor.java
@@ -2,11 +2,12 @@
import ocaml.OcamlPlugin;
import ocaml.editor.completion.CompletionJob;
+import ocaml.editor.syntaxcoloring.OcamlEditorColors;
import ocaml.editors.util.OcamlCharacterPairMatcher;
import ocaml.editors.yacc.outline.OcamlYaccOutlineControl;
import ocaml.editors.yacc.outline.YaccOutlineJob;
import ocaml.natures.OcamlNatureMakefile;
-import ocaml.popup.actions.CompileProjectAction;
+import ocaml.popup.actions.CompileProjectPopupAction;
import ocaml.preferences.PreferenceConstants;
import org.eclipse.core.resources.IProject;
@@ -15,11 +16,15 @@
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.PaintManager;
import org.eclipse.jface.text.TextEvent;
+import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.MatchingCharacterPainter;
+import org.eclipse.jface.util.IPropertyChangeListener;
+import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
@@ -45,9 +50,11 @@ public OcamlyaccEditor() {
protected void createActions() {
super.createActions();
- paintManager = new PaintManager(getSourceViewer());
+ final ISourceViewer viewer = getSourceViewer();
+
+ paintManager = new PaintManager(viewer);
matchingCharacterPainter =
- new MatchingCharacterPainter(getSourceViewer(), new OcamlCharacterPairMatcher());
+ new MatchingCharacterPainter(viewer, new OcamlCharacterPairMatcher());
matchingCharacterPainter.setColor(new Color(Display.getCurrent(), new RGB(160, 160, 160)));
paintManager.addPainter(matchingCharacterPainter);
@@ -55,17 +62,18 @@ protected void createActions() {
// effectue le parsing des bibliothèques ocaml en arrière plan
CompletionJob job = new CompletionJob("Parsing ocaml library mli files", null);
- job.setPriority(CompletionJob.DECORATE);
+ job.setPriority(CompletionJob.INTERACTIVE); // Trung changes priority
job.schedule();
-
-
- this.getSourceViewer().addTextListener(new ITextListener() {
+
+
+ viewer.addTextListener(new ITextListener() {
public void textChanged(TextEvent event) {
if (event.getDocumentEvent() != null)
rebuildOutline(500);
}
});
+
}
@Override
@@ -112,14 +120,14 @@ public void doSave(IProgressMonitor monitor) {
IWorkspace ws = ResourcesPlugin.getWorkspace();
IWorkspaceDescription desc = ws.getDescription();
if (desc.isAutoBuilding())
- CompileProjectAction.compileProject(this.getProject());
+ CompileProjectPopupAction.compileProject(this.getProject());
}
}
-
-
+
+
private OcamlYaccOutlineControl outline;
private YaccOutlineJob outlineJob = null;
-
+
/**
* We give the outline to Eclipse when it asks for an adapter with the outline class.
*/
@@ -133,8 +141,12 @@ public Object getAdapter(@SuppressWarnings("unchecked") Class required) {
}
return super.getAdapter(required);
}
-
+
public void rebuildOutline(int delay) {
+ /*
+ * Trung: rebuilding outline will dispose all completion proposal
+ * pop-up windows if existing.
+ */
IEditorInput input = this.getEditorInput();
IDocument document = this.getDocumentProvider().getDocument(input);
@@ -154,6 +166,6 @@ public void rebuildOutline(int delay) {
outlineJob.schedule(delay);
}
-
-
+
+
}
diff --git a/Ocaml/src/ocaml/parser/Def.java b/Ocaml/src/ocaml/parser/Def.java
index 3099d46..3bcc5ac 100644
--- a/Ocaml/src/ocaml/parser/Def.java
+++ b/Ocaml/src/ocaml/parser/Def.java
@@ -24,7 +24,7 @@ public enum Type {
/** the root of the definitions tree (module implementation) */
Root,
/** a variable name, with its position */
- Identifier, Let, LetIn, Type, Module, ModuleType, Exception, External, Class, Sig,
+ Identifier, Let, LetIn, Type, Module, ModuleAlias, ModuleType, Exception, External, Class, Sig,
Open, Object, Method, Struct, Functor, Include, Val, Constraint, Initializer,
@@ -72,7 +72,17 @@ public enum Type {
* The type inferred by the OCaml compiler for this definition (this is retrieved and displayed
* in the outline when a ".annot" file is present and up-to-date)
*/
- public String ocamlType;
+ private String ocamlType;
+
+ public String getOcamlType() {
+ return ocamlType;
+ }
+
+ public void setOcamlType(String type) {
+ if (type == null)
+ this.ocamlType = "";
+ else this.ocamlType = type;
+ }
/** The parent of this node. This is required by the outline's ContentProvider */
public Def parent;
@@ -111,6 +121,7 @@ public Def() {
this.posStart = 0;
this.posEnd = 0;
this.defPosStart = 0;
+ this.ocamlType = "";
// this.defPosEnd = 0;
}
@@ -118,14 +129,18 @@ public Def() {
public Def(String name, Type type, int start, int end) {
super();
children = new ArrayList();
- this.name = name;
+ if (name != null)
+ this.name = name;
+ else
+ this.name = "";
this.type = type;
this.posStart = start;
this.posEnd = end;
this.defPosStart = 0;
+ this.ocamlType = "";
// this.defPosEnd = 0;
}
-
+
/** copy constructor */
public Def(Def def) {
this.bAlt = def.bAlt;
@@ -156,15 +171,26 @@ public Def(Def def) {
void add(Symbol s) {
assert s instanceof Def;
- children.add((Def) s);
+
+ Def def = (Def) s;
+ children.add(def);
+
+ // update location
+ this.defOffsetEnd = def.defOffsetEnd;
}
/** Creates a new dummy node as root of a and b, and return this node */
public static Def root(Symbol a) {
assert a instanceof Def;
+
Def def = new Def();
Def defa = (Def) a;
def.children.add(defa);
+
+ // update location
+ def.defOffsetStart = defa.defOffsetStart;
+ def.defOffsetEnd = defa.defOffsetEnd;
+
return def;
}
@@ -181,6 +207,10 @@ public static Def root(Symbol a, Symbol b) {
def.children.add(defa);
def.children.add(defb);
+ // update location
+ def.defOffsetStart = defa.defOffsetStart;
+ def.defOffsetEnd = defb.defOffsetEnd;
+
return def;
}
@@ -200,6 +230,10 @@ public static Def root(Symbol a, Symbol b, Symbol c) {
def.children.add(defb);
def.children.add(defc);
+ // update location
+ def.defOffsetStart = defa.defOffsetStart;
+ def.defOffsetEnd = defc.defOffsetEnd;
+
return def;
}
@@ -222,6 +256,10 @@ public static Def root(Symbol a, Symbol b, Symbol c, Symbol d) {
def.children.add(defc);
def.children.add(defd);
+ // update location
+ def.defOffsetStart = defa.defOffsetStart;
+ def.defOffsetEnd = defd.defOffsetEnd;
+
return def;
}
@@ -251,7 +289,7 @@ private void collapseAux(Def node, ArrayList nodes, boolean bClean) {
flatList.add(node);
while(!flatList.isEmpty()) {
Def def = flatList.removeFirst();
-
+
if (def.type == Type.Dummy || bClean && def.type == Type.Identifier) {
for (int i = def.children.size() - 1; i>=0; i--) {
Def child = def.children.get(i);
@@ -263,8 +301,8 @@ private void collapseAux(Def node, ArrayList nodes, boolean bClean) {
def.clean();
}
}
-
-
+
+
}
/**
@@ -287,6 +325,13 @@ void findIdents(ArrayList idents) {
child.findIdents(idents);
}
+ void findDefNames(ArrayList defNames) {
+ if (this.name != null && !this.name.isEmpty())
+ defNames.add(this.name);
+ for (Def child: children)
+ child.findDefNames(defNames);
+ }
+
/** Find all the "lets" in the tree rooted at this definition (and do not go further down) */
void findLets(ArrayList idents) {
if (this.type == Type.Let) {
@@ -337,16 +382,17 @@ public void buildSiblingOffsets() {
}
}
- public Def cleanCopy() {
+ public Def cleanCopy(boolean parserError) {
+ this.buildParents();
Def def = new Def("", Def.Type.Root, 0, 0);
- cleanCopyAux(this, def);
- def.buildParents();
+ cleanCopyAux(this, def, parserError);
+// def.buildParents();
return def;
}
- private void cleanCopyAux(Def node, Def newNode) {
+ private void cleanCopyAux(Def node, Def newNode, boolean parserError) {
ArrayList realNodes = new ArrayList();
- findRealChildren(node, realNodes, true);
+ findRealChildren(node, realNodes, true, parserError);
for (Def d : realNodes) {
Def def = new Def(d.name, d.type, d.posStart, d.posEnd);
@@ -362,22 +408,60 @@ private void cleanCopyAux(Def node, Def newNode) {
def.sectionComment = d.sectionComment;
def.filename = d.filename;
def.body = d.body;
+ def.parent = newNode;
newNode.add(def);
}
for (int i = 0; i < realNodes.size(); i++)
- cleanCopyAux(realNodes.get(i), newNode.children.get(i));
+ cleanCopyAux(realNodes.get(i), newNode.children.get(i), parserError);
}
- private void findRealChildren(Def node, ArrayList nodes, boolean root) {
- if (node.type == Type.Dummy || node.type == Type.Identifier || node.type == Type.Parameter
- || node.type == Type.Functor || node.type == Type.Sig || node.type == Type.Object
- || node.type == Type.Struct || node.type == Type.In || root
- || "_".equals(node.name) || "()".equals(node.name)) {
+ private void findRealChildren(Def node, ArrayList nodes, boolean root, boolean parserError) {
+ // always go down when is in root
+ if (root) {
for (Def d : node.children)
- findRealChildren(d, nodes, false);
- } else {
+ findRealChildren(d, nodes, false, parserError);
+ }
+ // find parameter
+ else if (node.type == Type.Parameter) {
+ // created a cloned node without children to add into nodes
+ Def simpleNode = new Def(node);
+ simpleNode.children = new ArrayList();
+ nodes.add(simpleNode);
+
+ // find children
+ for (Def d : node.children)
+ findRealChildren(d, nodes, false, parserError);
+ }
+ // find aliased module
+ else if (node.type == Type.Identifier) {
+ // add all identifiers when parser error
+ if (parserError) {
+ nodes.add(node);
+ }
+ // if no parser error, add selected identifiers
+ else {
+ Def parent = node.parent;
+ if (parent != null) {
+ if (parent.type == Type.ModuleAlias)
+ nodes.add(node);
+ }
+ }
+
+ }
+ // go down to find real children
+ else if (node.type == Type.Dummy
+ || node.type == Type.Functor
+ || node.type == Type.Sig
+ || node.type == Type.Object
+ || node.type == Type.Struct
+ || node.type == Type.In
+ || "()".equals(node.name)) {
+ for (Def d : node.children)
+ findRealChildren(d, nodes, false, parserError);
+ }
+ else {
nodes.add(node);
}
}
@@ -385,17 +469,17 @@ private void findRealChildren(Def node, ArrayList nodes, boolean root) {
/** completely unnest the 'in' definitions */
/*
* public void completelyUnnestIn(Def parent, int index) {
- *
+ *
* for(int i = 0; i < children.size(); i++){ Def child = children.get(i);
* child.completelyUnnestIn(this, i); }
- *
+ *
* ArrayList newChildren = new ArrayList();
- *
+ *
* int j = 1; for(int i = 0; i < children.size(); i++){ Def child = children.get(i);
- *
+ *
* if(type == Type.LetIn && child.type == Type.LetIn){ parent.children.add(index + j++, child);
* }else newChildren.add(child); }
- *
+ *
* children = newChildren; }
*/
@@ -470,12 +554,12 @@ public void unnestTypes(Def parent, int index) {
}
/** Returns the region in the document covered by the name of the definition */
- public IRegion getRegion(IDocument doc) {
+ public IRegion getNameRegion(IDocument doc) {
int lineOffset = 0;
try {
lineOffset = doc.getLineOffset(getLine(posStart));
} catch (BadLocationException e) {
- OcamlPlugin.logError("offset error", e);
+// OcamlPlugin.logError("offset error", e);
return null;
}
@@ -486,6 +570,28 @@ public IRegion getRegion(IDocument doc) {
}
+ /** Returns the region in the document covered by this definition and its body */
+ public Region getFullRegion() {
+ // startOffset is computed by def name
+ int startOffset = posStart;
+
+ // endOffset is computed by def's full definition
+ Def lastDescendant = this;
+ while (lastDescendant.children != null & lastDescendant.children.size() > 0) {
+ ArrayList children = lastDescendant.children;
+ for (int i = children.size() - 1; i >= 0; i--) {
+ if (children.get(i) != null) {
+ lastDescendant = children.get(i);
+ break;
+ }
+ }
+ }
+ int endOffset = lastDescendant.posEnd;
+
+ return new Region(startOffset, endOffset - startOffset + 1);
+ }
+
+
/** The ocamldoc comment associated with this definition */
public String comment = "";
@@ -500,30 +606,27 @@ public void appendToComment(String comment) {
this.comment = this.comment + "\n________________________________________\n\n"
+ comment;
- this.comment = clean(this.comment);
+ this.comment = cleanString(this.comment);
}
public String sectionComment = "";
public void setSectionComment(String text) {
- this.sectionComment = clean(text);
+ this.sectionComment = cleanString(text);
}
public void setComment(String text) {
if (text.equals("/*"))
return;
- this.comment = clean(text);
+ this.comment = cleanString(text);
}
- public void setBody(String text) {
- this.body = Misc.beautify(clean(text));
- }
- private static String clean(String str) {
+ public static String cleanString(String str) {
if (str == null)
return "";
-
+
// remove all redundant spaces
String[] lines = str.split("\\n");
StringBuilder stringBuilder = new StringBuilder();
@@ -537,9 +640,91 @@ private static String clean(String str) {
return stringBuilder.toString().trim();
}
- public String body = "";
+ private String body = "";
+
+ private String filename = "";
+
+ public void setBody(String body) {
+// this.body = Misc.beautify(clean(body));
+ this.body = Misc.beautify(body);
+ }
+
+ public String getBody() {
+ return this.body;
+ }
+
+
+ public void setFileName(String filename) {
+ this.filename = filename;
+ }
+
+ public String getFileName() {
+ return filename;
+ }
+
+ public String getTypeName() {
+
+
+ if (type == Type.Dummy)
+ return "Dummy";
+ else if (type == Type.In)
+ return "In";
+ else if (type == Type.Root)
+ return "Root";
+ else if (type == Type.Identifier)
+ return "Identifier";
+ else if (type == Type.Let)
+ return "Let";
+ else if (type == Type.LetIn)
+ return "LetIn";
+ else if (type == Type.Type)
+ return "Type";
+ else if (type == Type.Module)
+ return "Module";
+ else if (type == Type.ModuleAlias)
+ return "ModuleAlias";
+ else if (type == Type.ModuleType)
+ return "ModuleType";
+ else if (type == Type.Exception)
+ return "Exception";
+ else if (type == Type.External)
+ return "External";
+ else if (type == Type.Class)
+ return "Class";
+ else if (type == Type.Sig)
+ return "Sig";
+ else if (type == Type.Open)
+ return "Open";
+ else if (type == Type.Object)
+ return "Object";
+ else if (type == Type.Method)
+ return "Method";
+ else if (type == Type.Struct)
+ return "Struct";
+ else if (type == Type.Functor)
+ return "Functor";
+ else if (type == Type.Include)
+ return "Include";
+ else if (type == Type.Val)
+ return "Val";
+ else if (type == Type.Constraint)
+ return "Constraint";
+ else if (type == Type.Initializer)
+ return "Initializer";
+ else if (type == Type.ClassType)
+ return "ClassType";
+ else if (type == Type.TypeConstructor)
+ return "TypeConstructor";
+ else if (type == Type.RecordTypeConstructor)
+ return "RecordTypeConstructor";
+ else if (type == Type.Parameter)
+ return "Parameter";
+ else if (type == Type.ParserError)
+ return "ParserError";
+ else
+ return "Unknown";
+ }
- public String filename = "";
public String parentName = "";
@@ -549,10 +734,10 @@ private static String clean(String str) {
* 0; try { firstLineOffset = doc.getLineOffset(getLine(defPosStart)); lastLineOffset =
* doc.getLineOffset(getLine(defPosEnd)); } catch (BadLocationException e) {
* OcamlPlugin.logError("offset error", e); return null; }
- *
+ *
* int startOffset = firstLineOffset + getColumn(defPosStart); int endOffset = lastLineOffset +
* getColumn(defPosEnd);
- *
+ *
* return new Region(startOffset, endOffset - startOffset + 1); }
*/
}
diff --git a/Ocaml/src/ocaml/parser/OcamlParser.g b/Ocaml/src/ocaml/parser/OcamlParser.g
index fa3a102..a08b9be 100644
--- a/Ocaml/src/ocaml/parser/OcamlParser.g
+++ b/Ocaml/src/ocaml/parser/OcamlParser.g
@@ -4,6 +4,7 @@
%package "ocaml.parser";
%import "java.util.ArrayList";
+%import "org.eclipse.jface.text.Region";
%embed {:
public ErrorReporting errorReporting;
@@ -16,12 +17,29 @@
we use the "bTop" boolean to know if this definition should be added to the outline */
public ArrayList recoverDefs = new ArrayList();
+ public ArrayList recoverIdents = new ArrayList();
+
/** backup a node, so as to be able to later recover from a parsing error */
- private void backup(Def def){
+ private void backupDef(Def def){
recoverDefs.add(def);
for(Def child: def.children)
//child.bTop = false;
unsetTop(child);
+
+ // remove all identifiers that is used to build this def
+ Region region = def.getFullRegion();
+ ArrayList usedIdents = new ArrayList();
+ for (Def ident: recoverIdents)
+ if (ident.posStart >= region.getOffset()
+ && (ident.posEnd <= region.getOffset() + region.getLength()))
+ usedIdents.add(ident);
+ recoverIdents.removeAll(usedIdents);
+ }
+
+ // backup identifiers for later recovering from a parsing error,
+ // some will be removed when they are used to build a Def sucessfully
+ private void backupIdent(Def def) {
+ recoverIdents.add(def);
}
private void unsetTop(Def def){
@@ -285,7 +303,7 @@ module_expr=
Def def = new Def("", Def.Type.Struct, s.getStart(), s.getEnd());
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| STRUCT.a structure.s error
@@ -293,7 +311,7 @@ module_expr=
Def def = new Def("", Def.Type.Struct, a.getStart(), a.getEnd());
def.add(s);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| FUNCTOR LPAREN UIDENT.i COLON module_type.a RPAREN MINUSGREATER module_expr.b
@@ -302,7 +320,7 @@ module_expr=
def.add(a);
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| module_expr.a LPAREN module_expr.b RPAREN
@@ -341,6 +359,10 @@ structure_tail=
{: return Def.root(a,b); :}
| structure_item.a structure_tail.b
{: return Def.root(a,b); :}
+ | SEMISEMI cppo_directive.a structure_tail.b
+ {: return Def.root(a,b); :}
+ | cppo_directive.a structure_tail.b
+ {: return Def.root(a,b); :}
| error
{: return new Def(); :}
;
@@ -366,7 +388,7 @@ structure_item=
def.add(a);
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| TYPE type_declarations.t
@@ -376,7 +398,7 @@ structure_item=
Def def = new Def((String)id.value, Def.Type.Exception, id.getStart(), id.getEnd());
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| EXCEPTION UIDENT.id EQUAL constr_longident.a
@@ -384,15 +406,19 @@ structure_item=
Def def = new Def((String)id.value, Def.Type.Exception, id.getStart(), id.getEnd());
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| MODULE UIDENT.id module_binding.a
{:
- Def def = new Def((String)id.value, Def.Type.Module, id.getStart(), id.getEnd());
+ Def.Type type = Def.Type.Module;
+ assert a instanceof Def;
+ Def.Type aType = ((Def)a).type;
+ type = (aType == Def.Type.Identifier) ? Def.Type.ModuleAlias : type;
+ Def def = new Def((String)id.value, type, id.getStart(), id.getEnd());
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| MODULE REC module_rec_bindings.a
@@ -403,14 +429,14 @@ structure_item=
Def def = new Def(ident.name, Def.Type.ModuleType, ident.posStart, ident.posEnd);
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| OPEN mod_longident.id
{:
Def ident = (Def)id;
Def def = new Def(ident.name, Def.Type.Open, ident.posStart, ident.posEnd);
- backup(def);
+ backupDef(def);
return def;
:}
| CLASS class_declarations.a
@@ -422,7 +448,7 @@ structure_item=
Def ident = (Def)id;
if(ident.type == Def.Type.Identifier){
Def def = new Def(ident.name, Def.Type.Include, ident.posStart, ident.posEnd);
- backup(def);
+ backupDef(def);
return def;
}
return new Def();
@@ -456,7 +482,7 @@ module_rec_binding=
def.add(a);
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
;
@@ -471,7 +497,7 @@ module_type=
Def def = new Def("", Def.Type.Sig, s.getStart(), s.getEnd());
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| SIG signature error
@@ -483,7 +509,7 @@ module_type=
def.add(a);
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| module_type.a WITH with_constraints.b
@@ -514,7 +540,7 @@ signature_item=
// add the start position of the definition
def.defPosStart = v.getStart();
- backup(def);
+ backupDef(def);
return def;
:}
| EXTERNAL.e val_ident.id COLON core_type.a EQUAL primitive_declaration.b
@@ -525,7 +551,7 @@ signature_item=
def.add(a);
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| TYPE.t type_declarations.a
@@ -542,7 +568,7 @@ signature_item=
def.defPosStart = e.getStart();
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| MODULE.m UIDENT.id module_declaration.a
@@ -551,7 +577,7 @@ signature_item=
def.defPosStart = m.getStart();
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| MODULE.m REC module_rec_declarations.a
@@ -567,7 +593,7 @@ signature_item=
Def ident = (Def)id;
Def def = new Def(ident.name, Def.Type.ModuleType, ident.posStart, ident.posEnd);
def.defPosStart = m.getStart();
- backup(def);
+ backupDef(def);
return def;
:}
| MODULE.m TYPE ident.id EQUAL module_type.a
@@ -577,7 +603,7 @@ signature_item=
def.defPosStart = m.getStart();
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
| OPEN.o mod_longident.id
@@ -585,7 +611,7 @@ signature_item=
Def ident = (Def)id;
Def def = new Def(ident.name, Def.Type.Open, ident.posStart, ident.posEnd);
def.defPosStart = o.getStart();
- backup(def);
+ backupDef(def);
return def;
:}
| INCLUDE.i module_type.id
@@ -594,7 +620,7 @@ signature_item=
if(ident.type == Def.Type.Identifier){
Def def = new Def(ident.name, Def.Type.Include, ident.posStart, ident.posEnd);
def.defPosStart = i.getStart();
- backup(def);
+ backupDef(def);
return def;
}
return new Def();
@@ -642,7 +668,7 @@ module_rec_declaration=
Def def = new Def((String)id.value, Def.Type.Module, id.getStart(), id.getEnd());
def.add(a);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
;
@@ -666,7 +692,7 @@ class_declaration=
def.add(a);
def.add(b);
def.collapse();
- backup(def);
+ backupDef(def);
return def;
:}
;
@@ -718,7 +744,7 @@ class_expr=
in.collapse();
last.children.add(in);
last.collapse();
- backup(last);
+ backupDef(last);
return a;
}
@@ -735,7 +761,7 @@ class_simple_expr=
Def def = new Def("