Skip to content

Commit

Permalink
Use proper document models on change
Browse files Browse the repository at this point in the history
Previously, we just always read from the file system to get the document
truth, but that caused issues, and also required users to save to get
the compilation hints. Now, the document is only read from disk if the
client isn't managing it, otherwise, it reads it from the internal cache
which is managed by the client for open files.
  • Loading branch information
LadyCailin committed Aug 31, 2019
1 parent cb1c84b commit f1617cf
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public void setLogger(Logger logger) {
this.logger = logger;
}

private static final Object CACHE_WRITE_LOCK = new Object();

/**
* Given a file location, retrieves the ClassDiscoveryURLCache from it. If it is a jar, the file is hashed, and
* checked for a local cache copy, and if one exists, that cache is returned. If not, the jar is scanned for a
Expand Down Expand Up @@ -128,9 +130,11 @@ public ClassDiscoveryURLCache getURLCache(URL fromClassLocation) {

if(cacheOutputName != null) {
try {
try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(cacheOutputName, false))) {
zos.putNextEntry(new ZipEntry("data"));
cache.writeDescriptor(zos);
synchronized(CACHE_WRITE_LOCK) {
try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(cacheOutputName, false))) {
zos.putNextEntry(new ZipEntry("data"));
cache.writeDescriptor(zos);
}
}
} catch (IOException ex) {
//Well, we couldn't write it out, so report the error, but continue anyways.
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/com/laytonsmith/core/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,14 @@ public ArgumentParser getArgumentParser() {

@Override
public void execute(ArgumentParser.ArgumentParserResults parsedArgs) throws IOException {
CreateNewFiles(parsedArgs.getStringListArgument(), parsedArgs.isFlagSet('f'));
}

public static void CreateNewFiles(List<String> files, boolean force) throws IOException {
String li = OSUtils.GetLineEnding();
for(String file : parsedArgs.getStringListArgument()) {
for(String file : files) {
File f = new File(file);
if(f.exists() && !parsedArgs.isFlagSet('f')) {
if(f.exists() && !force) {
System.out.println(file + " already exists, refusing to create");
continue;
}
Expand Down
129 changes: 123 additions & 6 deletions src/main/java/com/laytonsmith/tools/langserv/LangServ.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.Socket;
Expand All @@ -59,6 +62,8 @@
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionList;
Expand All @@ -75,6 +80,8 @@
import org.eclipse.lsp4j.DocumentLink;
import org.eclipse.lsp4j.DocumentLinkOptions;
import org.eclipse.lsp4j.DocumentLinkParams;
import org.eclipse.lsp4j.ExecuteCommandOptions;
import org.eclipse.lsp4j.ExecuteCommandParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.eclipse.lsp4j.InitializedParams;
Expand All @@ -84,6 +91,7 @@
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.Launcher;
Expand Down Expand Up @@ -289,7 +297,7 @@ public LangServ(boolean useStdio) {
private final boolean usingStdio;

private LanguageClient client;
private final Map<String, Map<Integer, ParseTree>> documents = new HashMap<>();

/**
* This executor uses an unbounded thread pool, and should only be used for task in which a user is actively
* waiting for results, however, tasks submitted to this processor will begin immediately, as opposed to the
Expand Down Expand Up @@ -409,6 +417,42 @@ public void connect(LanguageClient client) {
});
}

@java.lang.annotation.Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public static @interface Command {
/**
* The name of the command.
* @return
*/
String value();
}

public static interface CommandProvider {
CompletableFuture<Object> execute(LanguageClient client, ExecuteCommandParams params);
}

// Need to implement stuff in the extension before this is useful
// @Command("new-ms-file")
// public static class NewMsFileCommand implements CommandProvider {
//
// @Override
// public CompletableFuture<Object> execute(LanguageClient client, ExecuteCommandParams params) {
// CompletableFuture<Object> result = new CompletableFuture<>();
//// ShowMessageRequestParams smrp = new ShowMessageRequestParams();
//// MessageActionItem action = new MessageActionItem();
//// action.setTitle("Name of file");
//// smrp.setActions(Arrays.asList(action));
//// client.showMessageRequest(smrp).thenAccept(action -> {
//// action.
//// });
// result.complete(null);
// return result;
// }
//
// }

private Map<String, CommandProvider> commandProviders = new HashMap<>();

@Override
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called");
Expand All @@ -420,7 +464,26 @@ public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
DocumentLinkOptions documentLinkOptions = new DocumentLinkOptions();
documentLinkOptions.setResolveProvider(false);
sc.setDocumentLinkProvider(documentLinkOptions);
// sc.setHoverProvider(true);
{
ExecuteCommandOptions eco = new ExecuteCommandOptions();
List<String> commands = new ArrayList<>();
for(Class<? extends CommandProvider> c : ClassDiscovery.getDefaultInstance()
.loadClassesWithAnnotationThatExtend(Command.class, CommandProvider.class)) {
CommandProvider cp;
try {
cp = c.newInstance();
} catch(InstantiationException | IllegalAccessException ex) {
// We can't recover from this, so just skip it
Logger.getLogger(LangServ.class.getName()).log(Level.SEVERE, null, ex);
continue;
}
String command = c.getAnnotation(Command.class).value();
commands.add(command);
commandProviders.put(command, cp);
}
eco.setCommands(commands);
sc.setExecuteCommandProvider(eco);
}
CompletionOptions co = new CompletionOptions(true, Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "_"));
sc.setCompletionProvider(co);
Expand Down Expand Up @@ -507,7 +570,8 @@ public void doCompilation(CompletableFuture<ParseTree> future, Executor threadPo
compilerEnv.setLogCompilerWarnings(false); // No one would see them
GlobalEnv gEnv = env.getEnv(GlobalEnv.class);

// This disables things like security checks and whatnot. These may be present in the runtime environment,
// This disables things like security checks and whatnot.
// These may be present in the runtime environment,
// but it's not possible for us to tell that at this point.
gEnv.SetCustom("cmdline", true);
File f = Paths.get(new URI(uri)).toFile();
Expand All @@ -517,7 +581,7 @@ public void doCompilation(CompletableFuture<ParseTree> future, Executor threadPo
try {
ParseTree fTree;
logd(() -> "Compiling " + f);
code = FileUtil.read(f);
code = getDocument(uri);
if(f.getName().endsWith(".ms")) {
tokens = MethodScriptCompiler.lex(code, env, f, true);
fTree = MethodScriptCompiler.compile(tokens, env, envs);
Expand Down Expand Up @@ -584,29 +648,77 @@ public void doCompilation(CompletableFuture<ParseTree> future, Executor threadPo
});
}

//<editor-fold defaultstate="collapsed" desc="DocumentManagement">

/**
* Maps from URI to document text. If the document isn't in this map, it may be safely read from disk.
*/
private final Map<String, String> documents = new HashMap<>();

/**
* Returns the document text either from the document cache, if the client is managing the document, or from
* the file system if it isn't.
* @param uri
* @return
* @throws java.io.IOException
*/
public String getDocument(String uri) throws IOException {
if(documents.containsKey(uri)) {
return documents.get(uri);
}
File f;
try {
f = Paths.get(new URI(uri)).toFile();
} catch (URISyntaxException ex) {
throw new RuntimeException(ex);
}
return FileUtil.read(f);
}

@Override
public void didOpen(DidOpenTextDocumentParams params) {
// The document open notification is sent from the client to the server to signal newly opened text documents.
// The document’s truth is now managed by the client and the server must not try to read the document’s truth
// using the document’s Uri.
logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called");
doCompilation(null, highPriorityProcessors, params.getTextDocument().getUri());
String uri = params.getTextDocument().getUri();
documents.put(uri, params.getTextDocument().getText());
doCompilation(null, highPriorityProcessors, uri);
}

@Override
public void didChange(DidChangeTextDocumentParams params) {
logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called");
logv(() -> "Changing " + params);
String uri = params.getTextDocument().getUri();
// If the processing mode is changed to incremental, this logic needs modification
// String text = documents.get(uri);
if(params.getContentChanges().size() > 1) {
logw("Unexpected size from didChange event.");
}
for(TextDocumentContentChangeEvent change : params.getContentChanges()) {
String newText = change.getText();
documents.put(uri, newText);
}
doCompilation(null, highPriorityProcessors, uri);
}

@Override
public void didClose(DidCloseTextDocumentParams params) {
// The document close notification is sent from the client to the server when the document got closed in the
// client. The document’s truth now exists where the document’s Uri points to (e.g. if the document’s Uri is
// a file Uri the truth now exists on disk).
logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called");
documents.remove(params.getTextDocument().getUri());
}

@Override
public void didSave(DidSaveTextDocumentParams params) {
logv(this.getClass().getName() + "." + StackTraceUtils.currentMethod() + " called");
doCompilation(null, highPriorityProcessors, params.getTextDocument().getUri());
}

//</editor-fold>

public Range convertTargetToRange(ParseTree node) {
String val = Construct.nval(node.getData());
if(val == null) {
Expand Down Expand Up @@ -761,4 +873,9 @@ public CompletableFuture<List<DocumentLink>> documentLink(DocumentLinkParams par
});
return result;
}

@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
return commandProviders.get(params.getCommand()).execute(client, params);
}
}

0 comments on commit f1617cf

Please sign in to comment.