summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--json.c2
-rw-r--r--lsp-parse.c16
-rw-r--r--lsp-write.c78
-rw-r--r--lsp.c60
-rw-r--r--lsp.h41
-rw-r--r--main.c7
-rw-r--r--ted.c28
-rw-r--r--ted.cfg2
-rw-r--r--test/lsp/JavaA/.ted-root0
-rw-r--r--test/lsp/JavaA/Main.java8
-rw-r--r--test/lsp/JavaB/.ted-root0
-rw-r--r--test/lsp/JavaB/B.java13
-rw-r--r--test/test.cpp (renamed from test.cpp)0
-rw-r--r--test/test.go (renamed from test.go)0
-rw-r--r--test/test.java (renamed from test.java)0
-rw-r--r--test/test.js (renamed from test.js)0
-rw-r--r--test/test.rs (renamed from test.rs)0
18 files changed, 211 insertions, 49 deletions
diff --git a/README.md b/README.md
index 3f00f1b..64718e3 100644
--- a/README.md
+++ b/README.md
@@ -234,3 +234,8 @@ ted is in the public domain (see `LICENSE.txt`).
You can report a bug by sending an email to `pommicket at pommicket.com`.
+If ted is crashing on startup try doing these things:
+
+- Delete `~/.local/share/ted/session.txt` or `C:\Users\<your user name>\AppData\Local\ted\session.txt`
+- Reset your ted configuration by moving `ted.cfg` somewhere else.
+
diff --git a/json.c b/json.c
index 2dfb368..da52efa 100644
--- a/json.c
+++ b/json.c
@@ -2,7 +2,6 @@
// provides FAST(ish) parsing but SLOW lookup
// this is especially fast for small objects
// this actually supports "extended json", where objects can have arbitrary values as keys.
-
typedef struct {
u32 pos;
u32 len;
@@ -57,6 +56,7 @@ typedef struct {
JSONValue *values;
} JSON;
+
#define SKIP_WHITESPACE while (json_is_space(text[index])) ++index;
const char *json_type_to_str(JSONValueType type) {
diff --git a/lsp-parse.c b/lsp-parse.c
index 6d436d9..8797edf 100644
--- a/lsp-parse.c
+++ b/lsp-parse.c
@@ -79,9 +79,12 @@ static bool parse_range(LSP *lsp, const JSON *json, JSONValue range_value, LSPRa
static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabilities) {
+ LSPCapabilities *cap = &lsp->capabilities;
+
+ // check CompletionOptions
JSONValue completion_value = json_object_get(json, capabilities, "completionProvider");
if (completion_value.type == JSON_OBJECT) {
- lsp->provides_completion = true;
+ cap->completion_support = true;
JSONObject completion = completion_value.val.object;
JSONArray trigger_chars = json_object_get_array(json, completion, "triggerCharacters");
@@ -104,6 +107,14 @@ static void parse_capabilities(LSP *lsp, const JSON *json, JSONObject capabiliti
}
}
}
+
+
+ JSONObject workspace = json_object_get_object(json, capabilities, "workspace");
+ // check WorkspaceFoldersServerCapabilities
+ JSONObject workspace_folders = json_object_get_object(json, workspace, "workspaceFolders");
+ if (json_object_get_bool(json, workspace_folders, "supported", false)) {
+ cap->workspace_folders_support = true;
+ }
}
static bool parse_completion(LSP *lsp, const JSON *json, LSPResponse *response) {
@@ -383,15 +394,12 @@ static void process_message(LSP *lsp, JSON *json) {
break;
case LSP_REQUEST_INITIALIZE: {
// it's the response to our initialize request!
-
if (result.type == JSON_OBJECT) {
// read server capabilities
JSONObject capabilities = json_object_get_object(json, result.val.object, "capabilities");
parse_capabilities(lsp, json, capabilities);
}
- // let's send back an "initialized" request (notification) because apparently
- // that's something we need to do.
LSPRequest initialized = {
.type = LSP_REQUEST_INITIALIZED,
.data = {0},
diff --git a/lsp-write.c b/lsp-write.c
index 5d402c4..93bdd26 100644
--- a/lsp-write.c
+++ b/lsp-write.c
@@ -32,6 +32,13 @@ static const char *lsp_language_id(Language lang) {
return "text";
}
+static const char *lsp_document_path(LSP *lsp, LSPDocumentID document) {
+ if (document >= arr_len(lsp->document_data)) {
+ assert(0);
+ return "";
+ }
+ return lsp->document_data[document].path;
+}
typedef struct {
LSP *lsp;
@@ -174,13 +181,7 @@ static void write_file_uri_direct(JSONWriter *o, const char *path) {
}
static void write_file_uri(JSONWriter *o, LSPDocumentID document) {
- if (document >= arr_len(o->lsp->document_data)) {
- assert(0);
- str_builder_append(&o->builder, "\"\"");
- return;
- }
- const char *path = o->lsp->document_data[document].path;
- write_file_uri_direct(o, path);
+ write_file_uri_direct(o, lsp_document_path(o->lsp, document));
}
static void write_key_file_uri(JSONWriter *o, const char *key, LSPDocumentID document) {
@@ -217,13 +218,18 @@ static void write_key_range(JSONWriter *o, const char *key, LSPRange range) {
write_range(o, range);
}
-static void write_workspace_folders(JSONWriter *o, char **workspace_folders) {
+static void write_workspace_folder(JSONWriter *o, LSPDocumentID folder) {
+ write_obj_start(o);
+ write_key_file_uri(o, "uri", folder);
+ write_key_string(o, "name", lsp_document_path(o->lsp, folder));
+ write_obj_end(o);
+}
+
+static void write_workspace_folders(JSONWriter *o, LSPDocumentID *workspace_folders) {
write_arr_start(o);
- arr_foreach_ptr(workspace_folders, char *, folder) {
- write_arr_elem_obj_start(o);
- write_key_file_uri_direct(o, "uri", *folder);
- write_key_string(o, "name", *folder);
- write_obj_end(o);
+ arr_foreach_ptr(workspace_folders, LSPDocumentID, folder) {
+ write_arr_elem(o);
+ write_workspace_folder(o, *folder);
}
write_arr_end(o);
}
@@ -231,14 +237,18 @@ static void write_workspace_folders(JSONWriter *o, char **workspace_folders) {
static const char *lsp_request_method(LSPRequest *request) {
switch (request->type) {
case LSP_REQUEST_NONE: break;
- case LSP_REQUEST_SHOW_MESSAGE:
- return "window/showMessage";
- case LSP_REQUEST_LOG_MESSAGE:
- return "window/logMessage";
case LSP_REQUEST_INITIALIZE:
return "initialize";
case LSP_REQUEST_INITIALIZED:
return "initialized";
+ case LSP_REQUEST_SHUTDOWN:
+ return "shutdown";
+ case LSP_REQUEST_EXIT:
+ return "exit";
+ case LSP_REQUEST_SHOW_MESSAGE:
+ return "window/showMessage";
+ case LSP_REQUEST_LOG_MESSAGE:
+ return "window/logMessage";
case LSP_REQUEST_DID_OPEN:
return "textDocument/didOpen";
case LSP_REQUEST_DID_CLOSE:
@@ -247,12 +257,10 @@ static const char *lsp_request_method(LSPRequest *request) {
return "textDocument/didChange";
case LSP_REQUEST_COMPLETION:
return "textDocument/completion";
- case LSP_REQUEST_SHUTDOWN:
- return "shutdown";
- case LSP_REQUEST_EXIT:
- return "exit";
case LSP_REQUEST_WORKSPACE_FOLDERS:
return "workspace/workspaceFolders";
+ case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS:
+ return "workspace/didChangeWorkspaceFolders";
}
assert(0);
return "$/ignore";
@@ -266,6 +274,7 @@ static bool request_type_is_notification(LSPRequestType type) {
case LSP_REQUEST_DID_OPEN:
case LSP_REQUEST_DID_CLOSE:
case LSP_REQUEST_DID_CHANGE:
+ case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS:
return true;
case LSP_REQUEST_INITIALIZE:
case LSP_REQUEST_SHUTDOWN:
@@ -385,9 +394,11 @@ static void write_request(LSP *lsp, LSPRequest *request) {
write_key_bool(o, "workspaceFolders", true);
write_obj_end(o);
write_obj_end(o);
- write_key_file_uri_direct(o, "rootUri", lsp->workspace_folders[0]);
+ SDL_LockMutex(lsp->workspace_folders_mutex);
+ write_key_file_uri(o, "rootUri", lsp->workspace_folders[0]);
write_key(o, "workspaceFolders");
write_workspace_folders(o, lsp->workspace_folders);
+ SDL_UnlockMutex(lsp->workspace_folders_mutex);
write_key_obj_start(o, "clientInfo");
write_key_string(o, "name", "ted");
write_obj_end(o);
@@ -454,6 +465,25 @@ static void write_request(LSP *lsp, LSPRequest *request) {
}
write_obj_end(o);
} break;
+ case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS: {
+ const LSPRequestDidChangeWorkspaceFolders *w = &request->data.change_workspace_folders;
+ write_key_obj_start(o, "params");
+ write_key_obj_start(o, "event");
+ write_key_arr_start(o, "added");
+ arr_foreach_ptr(w->added, LSPDocumentID, added) {
+ write_arr_elem(o);
+ write_workspace_folder(o, *added);
+ }
+ write_arr_end(o);
+ write_key_arr_start(o, "removed");
+ arr_foreach_ptr(w->removed, LSPDocumentID, removed) {
+ write_arr_elem(o);
+ write_workspace_folder(o, *removed);
+ }
+ write_arr_end(o);
+ write_obj_end(o);
+ write_obj_end(o);
+ } break;
}
write_obj_end(o);
@@ -486,7 +516,9 @@ static void write_response(LSP *lsp, LSPResponse *response) {
write_key(o, "result");
switch (response->request.type) {
case LSP_REQUEST_WORKSPACE_FOLDERS:
- write_workspace_folders(o, lsp->workspace_folders);
+ SDL_LockMutex(lsp->workspace_folders_mutex);
+ write_workspace_folders(o, lsp->workspace_folders);
+ SDL_UnlockMutex(lsp->workspace_folders_mutex);
break;
default:
// this is not a valid client-to-server response.
diff --git a/lsp.c b/lsp.c
index 0c9aede..e5bec71 100644
--- a/lsp.c
+++ b/lsp.c
@@ -1,7 +1,7 @@
// print server-to-client communication
#define LSP_SHOW_S2C 0
// print client-to-server communication
-#define LSP_SHOW_C2S 0
+#define LSP_SHOW_C2S 1
#define write_bool lsp_write_bool
@@ -60,6 +60,11 @@ static void lsp_request_free(LSPRequest *r) {
lsp_document_change_event_free(event);
arr_free(c->changes);
} break;
+ case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS: {
+ LSPRequestDidChangeWorkspaceFolders *w = &r->data.change_workspace_folders;
+ arr_free(w->added);
+ arr_free(w->removed);
+ } break;
}
memset(r, 0, sizeof *r);
}
@@ -108,6 +113,7 @@ static bool has_response(const char *data, size_t data_len, u64 *p_offset, u64 *
}
static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) {
+ LSPCapabilities *cap = &lsp->capabilities;
switch (request->type) {
case LSP_REQUEST_NONE:
// return false for server-to-client requests since we should never send them
@@ -124,7 +130,9 @@ static bool lsp_supports_request(LSP *lsp, const LSPRequest *request) {
case LSP_REQUEST_EXIT:
return true;
case LSP_REQUEST_COMPLETION:
- return lsp->provides_completion;
+ return cap->completion_support;
+ case LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS:
+ return cap->workspace_folders_support;
}
assert(0);
return false;
@@ -323,10 +331,12 @@ LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_co
#endif
str_hash_table_create(&lsp->document_ids, sizeof(u32));
- arr_add(lsp->workspace_folders, str_dup(root_dir));
lsp->language = language;
lsp->quit_sem = SDL_CreateSemaphore(0);
+ lsp->error_mutex = SDL_CreateMutex();
lsp->messages_mutex = SDL_CreateMutex();
+ arr_add(lsp->workspace_folders, lsp_document_id(lsp, root_dir));
+ lsp->workspace_folders_mutex = SDL_CreateMutex();
ProcessSettings settings = {
.stdin_blocking = true,
@@ -346,6 +356,46 @@ LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_co
return lsp;
}
+bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir) {
+ bool got_it = false;
+ SDL_LockMutex(lsp->workspace_folders_mutex);
+ if (!lsp->initialized) {
+ // pretend we have workspace folder support until we get initialize response
+ // (it's totally possible that this would be called with lsp->initialized = false,
+ // e.g. if the user starts up ted with multiple files in different projects)
+ // we'll fix things up when we get the initialize response if there's no actual support.
+ arr_add(lsp->workspace_folders, lsp_document_id(lsp, new_root_dir));
+ got_it = true;
+ } else {
+ arr_foreach_ptr(lsp->workspace_folders, LSPDocumentID, folder) {
+ if (str_has_path_prefix(new_root_dir, lsp_document_path(lsp, *folder))) {
+ got_it = true;
+ break;
+ }
+ }
+ }
+ SDL_UnlockMutex(lsp->workspace_folders_mutex);
+ if (got_it) return true;
+
+ if (!lsp->capabilities.workspace_folders_support) {
+ return false;
+ }
+
+ // send workspace/didChangeWorkspaceFolders notification
+ LSPRequest req = {.type = LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS};
+ LSPRequestDidChangeWorkspaceFolders *w = &req.data.change_workspace_folders;
+ LSPDocumentID document_id = lsp_document_id(lsp, new_root_dir);
+ arr_add(w->added, document_id);
+ lsp_send_request(lsp, &req);
+ // *technically* this is incorrect because if the server *just now sent* a
+ // workspace/workspaceFolders request, we'd give it back inconsistent information.
+ // i don't care.
+ SDL_LockMutex(lsp->workspace_folders_mutex);
+ arr_add(lsp->workspace_folders, document_id);
+ SDL_UnlockMutex(lsp->workspace_folders_mutex);
+ return true;
+}
+
bool lsp_next_message(LSP *lsp, LSPMessage *message) {
bool any = false;
SDL_LockMutex(lsp->messages_mutex);
@@ -362,6 +412,8 @@ void lsp_free(LSP *lsp) {
SDL_SemPost(lsp->quit_sem);
SDL_WaitThread(lsp->communication_thread, NULL);
SDL_DestroyMutex(lsp->messages_mutex);
+ SDL_DestroyMutex(lsp->workspace_folders_mutex);
+ SDL_DestroyMutex(lsp->error_mutex);
SDL_DestroySemaphore(lsp->quit_sem);
process_kill(&lsp->process);
@@ -384,8 +436,6 @@ void lsp_free(LSP *lsp) {
lsp_request_free(r);
arr_free(lsp->requests_sent);
- arr_foreach_ptr(lsp->workspace_folders, char *, folder)
- free(*folder);
arr_free(lsp->workspace_folders);
arr_free(lsp->trigger_chars);
diff --git a/lsp.h b/lsp.h
index 85993cb..b931b5c 100644
--- a/lsp.h
+++ b/lsp.h
@@ -26,12 +26,13 @@ typedef enum {
// client-to-server
LSP_REQUEST_INITIALIZE,
LSP_REQUEST_INITIALIZED,
+ LSP_REQUEST_SHUTDOWN,
+ LSP_REQUEST_EXIT,
LSP_REQUEST_DID_OPEN,
LSP_REQUEST_DID_CLOSE,
LSP_REQUEST_DID_CHANGE,
LSP_REQUEST_COMPLETION,
- LSP_REQUEST_SHUTDOWN,
- LSP_REQUEST_EXIT,
+ LSP_REQUEST_DID_CHANGE_WORKSPACE_FOLDERS,
// server-to-client
LSP_REQUEST_SHOW_MESSAGE,
@@ -98,17 +99,24 @@ typedef struct {
} LSPRequestCompletion;
typedef struct {
+ LSPDocumentID *removed; // dynamic array
+ LSPDocumentID *added; // dynamic array
+} LSPRequestDidChangeWorkspaceFolders;
+
+typedef struct {
// id is set by lsp.c; you shouldn't set it.
u32 id;
LSPRequestType type;
char *id_string; // if not NULL, this is the ID (only for server-to-client messages; we always use integer IDs)
- union {
+ // one member of this union is set depending on `type`.
+ union {
LSPRequestDidOpen open;
LSPRequestDidClose close;
LSPRequestDidChange change;
LSPRequestCompletion completion;
- // for LSP_SHOW_MESSAGE and LSP_LOG_MESSAGE
+ // LSP_REQUEST_SHOW_MESSAGE or LSP_REQUEST_LOG_MESSAGE
LSPRequestMessage message;
+ LSPRequestDidChangeWorkspaceFolders change_workspace_folders;
} data;
} LSPRequest;
@@ -227,6 +235,7 @@ typedef struct {
LSPCompletionItem *items;
} LSPResponseCompletion;
+
typedef LSPRequestType LSPResponseType;
typedef struct {
LSPRequest request; // the request which this is a response to
@@ -252,9 +261,19 @@ typedef struct {
u32 version_number; // for LSP
} LSPDocumentData;
+typedef struct {
+ bool completion_support;
+ // support for multiple root folders
+ // sadly, as of me writing this, clangd and rust-analyzer don't support this
+ // (but jdtls and gopls do)
+ bool workspace_folders_support;
+} LSPCapabilities;
+
typedef struct LSP {
Process process;
u32 request_id;
+ // for our purposes, folders are "documents"
+ // the spec kinda does this too: WorkspaceFolder has a `uri: DocumentUri` member.
StrHashTable document_ids; // values are u32. they are indices into document_data.
// this is a dynamic array which just keeps growing.
// but the user isn't gonna open millions of files so it's fine.
@@ -266,15 +285,18 @@ typedef struct LSP {
// so that we can process responses.
// this also lets us re-send requests if that's ever necessary.
LSPRequest *requests_sent;
- bool initialized; // has the response to the initialize request been sent?
+ // has the response to the initialize request been sent?
+ // we access this both in the main thread and in the LSP communication thread.
+ _Atomic bool initialized;
SDL_Thread *communication_thread;
SDL_sem *quit_sem;
char *received_data; // dynamic array
- bool provides_completion; // can this LSP server handle completion requests?
+ LSPCapabilities capabilities;
char32_t *trigger_chars; // dynamic array of "trigger characters"
SDL_mutex *error_mutex;
Language language;
- char **workspace_folders; // dynamic array of root directories of LSP "workspaces" (meaningless garbage)
+ SDL_mutex *workspace_folders_mutex;
+ LSPDocumentID *workspace_folders; // dynamic array of root directories of LSP "workspaces" (meaningless garbage)
char error[256];
} LSP;
@@ -293,6 +315,11 @@ void lsp_send_request(LSP *lsp, LSPRequest *request);
void lsp_send_response(LSP *lsp, LSPResponse *response);
const char *lsp_response_string(const LSPResponse *response, LSPString string);
LSP *lsp_create(const char *root_dir, Language language, const char *analyzer_command);
+// try to add a new "workspace folder" to the lsp.
+// returns true on success or if new_root_dir is already contained in a workspace folder for this LSP.
+// if this fails (i.e. if the LSP does not have workspace support), create a new LSP
+// with root directory `new_root_dir`.
+bool lsp_try_add_root_dir(LSP *lsp, const char *new_root_dir);
bool lsp_next_message(LSP *lsp, LSPMessage *message);
void lsp_document_changed(LSP *lsp, const char *document, LSPDocumentChangeEvent change);
void lsp_free(LSP *lsp);
diff --git a/main.c b/main.c
index 9339b95..d514132 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,11 @@
/*
@TODO:
- ignore telemetry/event
+- https://github.com/typescript-language-server/typescript-language-server
+ - NOTE: This supports javascript.
+ - We should also add a typescript language (but just use javascript syntax highlighting for now)
+ - (don't want to add .ts as a Javascript extension since that won't be forwards-compatible
+ if we ever do add real typescript highlighting support)
- handle window/showMessageRequest
- make sure "save as" works
- what's wrong with gopls?
@@ -8,6 +13,7 @@
- hover
- go to definition using LSP
- find usages
+ - that thing where it shows you the current function argument
- workspaceFolders support (so we don't need to start up multiple instances of rust-analyzer)
- document lsp.h and lsp.c.
- maximum queue size for requests/responses just in case?
@@ -39,6 +45,7 @@ FUTURE FEATURES:
- keyboard macros
- ctrl+9/0 to inc/dec number would be useful here
- with macros we can really test performance of buffer_insert_text_at_pos, etc. (which should ideally be fast)
+- real typescript highlighting (?)
*/
#include "base.h"
diff --git a/ted.c b/ted.c
index 34d67a2..8e2f669 100644
--- a/ted.c
+++ b/ted.c
@@ -48,15 +48,19 @@ static void *ted_realloc(Ted *ted, void *p, size_t new_size) {
return ret;
}
+char *ted_get_root_dir_of(Ted *ted, const char *path) {
+ Settings *settings = ted_active_settings(ted);
+ return settings_get_root_dir(settings, path);
+}
+
// get the project root directory (based on the active buffer or ted->cwd if there's no active buffer).
// the return value should be freed
char *ted_get_root_dir(Ted *ted) {
- Settings *settings = ted_active_settings(ted);
TextBuffer *buffer = ted->active_buffer;
if (buffer) {
- return settings_get_root_dir(settings, buffer->filename);
+ return ted_get_root_dir_of(ted, buffer->filename);
} else {
- return settings_get_root_dir(settings, ted->cwd);
+ return ted_get_root_dir_of(ted, ted->cwd);
}
}
@@ -91,6 +95,14 @@ Settings *ted_get_settings(Ted *ted, const char *path, Language language) {
return settings;
}
+// IMPORTANT NOTE ABOUT CACHING LSPs:
+// - be careful if you want to cache LSPs, e.g. adding a LSP *lsp member to `buffer`.
+// - right now we pretend that the server has workspace folder support until the initialize response is sent.
+// - specifically, this means that:
+// ted_get_lsp("/path1/a") => new LSP 0x12345
+// ted_get_lsp("/path2/b") => same LSP 0x12345
+// (receive initialize request, realize we don't have workspace folder support)
+// ted_get_lsp("/path2/b") => new LSP 0x6789A
LSP *ted_get_lsp(Ted *ted, const char *path, Language language) {
Settings *settings = ted_get_settings(ted, path, language);
if (!settings->lsp_enabled)
@@ -102,11 +114,11 @@ LSP *ted_get_lsp(Ted *ted, const char *path, Language language) {
if (!lsp) break;
if (lsp->language != language) continue;
- // check if root matches up
- arr_foreach_ptr(lsp->workspace_folders, char *, lsp_root) {
- if (str_has_path_prefix(path, *lsp_root))
- return lsp;
- }
+ // check if root matches up or if we can add a workspace folder
+ char *root = ted_get_root_dir_of(ted, path);
+ bool success = lsp_try_add_root_dir(lsp, root);
+ free(root);
+ if (success) return lsp;
}
if (i == TED_LSP_MAX)
return NULL; // why are there so many LSP open???
diff --git a/ted.cfg b/ted.cfg
index e4a6b1b..53784f2 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -51,7 +51,7 @@ regenerate-tags-if-not-found = yes
# So if /a/b/.git and /a/Makefile and /a/b/c/Cargo.toml exist,
# ted will select /a/b as the root.
# if no identifying files are found, the directory containing the current file is used.
-root-identifiers = .ted-root.out, Cargo.toml, make.bat, CMakeLists.txt, Makefile, go.mod, .git
+root-identifiers = .ted-root, .ted-root.out, Cargo.toml, make.bat, CMakeLists.txt, Makefile, go.mod, .git
# you can make your own custom background for ted using a shader.
# an example is provided here. you will have access to the following variables:
diff --git a/test/lsp/JavaA/.ted-root b/test/lsp/JavaA/.ted-root
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/lsp/JavaA/.ted-root
diff --git a/test/lsp/JavaA/Main.java b/test/lsp/JavaA/Main.java
new file mode 100644
index 0000000..2792b7c
--- /dev/null
+++ b/test/lsp/JavaA/Main.java
@@ -0,0 +1,8 @@
+public class Main {
+ public static void main(String[] args) {
+ int x = 0;
+ x++;
+ for (int i = 0; i < args.length; ++i) {
+ }
+ }
+}
diff --git a/test/lsp/JavaB/.ted-root b/test/lsp/JavaB/.ted-root
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/lsp/JavaB/.ted-root
diff --git a/test/lsp/JavaB/B.java b/test/lsp/JavaB/B.java
new file mode 100644
index 0000000..fde8107
--- /dev/null
+++ b/test/lsp/JavaB/B.java
@@ -0,0 +1,13 @@
+public class B {
+ private static class Something {
+ public int x = 0;
+ int f() {
+ return x;
+ }
+ }
+
+ public static void main(String[] args) {
+ Something s = new Something();
+ s.f();
+ }
+}
diff --git a/test.cpp b/test/test.cpp
index e373bff..e373bff 100644
--- a/test.cpp
+++ b/test/test.cpp
diff --git a/test.go b/test/test.go
index 98eea24..98eea24 100644
--- a/test.go
+++ b/test/test.go
diff --git a/test.java b/test/test.java
index 7183aed..7183aed 100644
--- a/test.java
+++ b/test/test.java
diff --git a/test.js b/test/test.js
index ad95614..ad95614 100644
--- a/test.js
+++ b/test/test.js
diff --git a/test.rs b/test/test.rs
index 40c2438..40c2438 100644
--- a/test.rs
+++ b/test/test.rs