Skip to content

Commit 5a8cbbb

Browse files
committed
optionally don't block while asking to load projects
1 parent c6a8dc1 commit 5a8cbbb

File tree

4 files changed

+200
-41
lines changed

4 files changed

+200
-41
lines changed

README.md

+28-2
Original file line numberDiff line numberDiff line change
@@ -627,8 +627,6 @@ Once this is received, the server will ignore `workspace/didConfigurationChange`
627627
notifications. This mechanisms exists to support some client/plugin combinations
628628
where the plugin needs more direct control over the configuration.
629629

630-
------
631-
632630
#### Request `served/getInfo`
633631

634632
**Params**: `ServedInfoParams`
@@ -680,6 +678,16 @@ interface ServedInfoResponse
680678
}
681679
```
682680

681+
#### Request `served/forceLoadProjects`
682+
683+
**Params**: `string[]`
684+
685+
Forces the load of projects, regardless of manyProjects limit or action configuration of the given file paths. Returns true if successful, false otherwise, for each item in the params array in order that was given in.
686+
687+
**Returns**: `bool[]`
688+
689+
------
690+
683691
#### Client notification `coded/updateSetting`
684692

685693
**Params**: `UpdateSettingParams`
@@ -745,6 +753,24 @@ interface WorkspaceState
745753
}
746754
```
747755

756+
#### Client notification `coded/skippedLoads`
757+
758+
**Params**: `InteractiveDownload`
759+
760+
Tells the client that project loading was skipped for the given path(s). The client may then ask the user or query configuration if the paths should be loaded or skipped. When requesting to load projects, pass these roots as arguments to the reqeust `served/forceLoadProjects`.
761+
762+
Otherwise, project loading is blocking and asks the client using a message box for each lazy-loaded project. IDE functionality is otherwise limited while these message boxes are open.
763+
764+
This must be implemented if `--provide async-ask-load` is given in the command line, otherwise this is not called.
765+
766+
```ts
767+
interface SkippedLoadsNotification
768+
{
769+
/** List of folder file paths */
770+
roots: string[];
771+
}
772+
```
773+
748774
#### Client request `coded/interactiveDownload`
749775

750776
**Params**: `InteractiveDownload`

source/app.d

+6
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ int main(string[] args)
200200
useBuildTaskDollarCurrent = true;
201201
trace("Using `$current` in build tasks");
202202
break;
203+
case "async-ask-load":
204+
import served.extension : bundleAskLoads;
205+
206+
bundleAskLoads = true;
207+
trace("Bundling consecutive ManyProjectsAction.ask loads into a single async notification (skips load meanwhile)");
208+
break;
203209
default:
204210
warningf("Unknown --provide flag '%s' provided. Maybe serve-d is outdated?", provide);
205211
break;

source/served/extension.d

+159-39
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public import served.utils.async;
1111

1212
import core.time : msecs, seconds;
1313

14-
import std.algorithm : any, canFind, endsWith, map;
14+
import std.algorithm : any, canFind, endsWith, map, remove;
1515
import std.array : appender, array;
1616
import std.conv : text, to;
1717
import std.datetime.stopwatch : StopWatch;
@@ -463,6 +463,25 @@ struct RootSuggestion
463463
bool useDub;
464464
}
465465

466+
bool dubRecipeExists(string rootDir, bool testPackageJson = false)
467+
{
468+
if (fs.exists(chainPath(rootDir, "dub.json")) || fs.exists(chainPath(rootDir, "dub.sdl")))
469+
return true;
470+
if (testPackageJson && fs.exists(chainPath(rootDir, "package.json")))
471+
{
472+
try
473+
{
474+
scope packageJson = fs.readText(chainPath(rootDir, "package.json"));
475+
if (seemsLikeDubJson(packageJson))
476+
return true;
477+
}
478+
catch (Exception)
479+
{
480+
}
481+
}
482+
return false;
483+
}
484+
466485
RootSuggestion[] rootsForProject(string root, bool recursive, string[] blocked,
467486
string[] extra)
468487
{
@@ -478,19 +497,7 @@ RootSuggestion[] rootsForProject(string root, bool recursive, string[] blocked,
478497
ret ~= RootSuggestion(dir, useDub);
479498
}
480499

481-
bool rootDub = fs.exists(chainPath(root, "dub.json")) || fs.exists(chainPath(root, "dub.sdl"));
482-
if (!rootDub && fs.exists(chainPath(root, "package.json")))
483-
{
484-
try
485-
{
486-
auto packageJson = fs.readText(chainPath(root, "package.json"));
487-
if (seemsLikeDubJson(packageJson))
488-
rootDub = true;
489-
}
490-
catch (Exception)
491-
{
492-
}
493-
}
500+
bool rootDub = dubRecipeExists(root, true);
494501
addSuggestion(root, rootDub);
495502

496503
if (recursive)
@@ -511,7 +518,7 @@ RootSuggestion[] rootsForProject(string root, bool recursive, string[] blocked,
511518
foreach (dir; extra)
512519
{
513520
string p = buildNormalizedPath(root, dir);
514-
addSuggestion(p, fs.exists(chainPath(p, "dub.json")) || fs.exists(chainPath(p, "dub.sdl")));
521+
addSuggestion(p, dubRecipeExists(p));
515522
}
516523
info("Root Suggestions: ", ret);
517524
return ret;
@@ -521,7 +528,7 @@ void doStartup(string workspaceUri, UserConfiguration userConfig)
521528
{
522529
ensureStartedUp(userConfig);
523530

524-
Workspace* proj = &workspace(workspaceUri);
531+
Workspace* proj = &workspace(workspaceUri, false);
525532
if (proj is &fallbackWorkspace)
526533
{
527534
error("Trying to do startup on unknown workspace ", workspaceUri, "?");
@@ -536,7 +543,6 @@ void doStartup(string workspaceUri, UserConfiguration userConfig)
536543
WorkspaceD.Instance instance;
537544
}
538545

539-
bool gotOneDub;
540546
scope roots = appender!(Root[]);
541547

542548
auto rootSuggestions = rootsForProject(workspaceUri.uriToFile, proj.config.d.scanAllFolders,
@@ -595,8 +601,102 @@ void doStartup(string workspaceUri, UserConfiguration userConfig)
595601
trace("Started all completion servers in ", dcdTimer.peek);
596602
}
597603

604+
@protocolMethod("served/forceLoadProjects")
605+
bool[] forceLoadProjects(string[] rootPaths)
606+
{
607+
struct Root
608+
{
609+
string uri;
610+
WorkspaceD.Instance instance;
611+
bool success;
612+
}
613+
614+
Root[] roots;
615+
roots.length = rootPaths.length;
616+
617+
foreach (i, rootPath; rootPaths)
618+
{
619+
string rootUri = rootPath.uriFromFile;
620+
621+
Workspace* proj = &workspace(rootUri, false);
622+
if (proj is &fallbackWorkspace)
623+
{
624+
error("Trying to do startup on unknown workspace ", rootUri, "?");
625+
roots[i].success = false;
626+
continue;
627+
}
628+
trace("Initializing serve-d for " ~ rootUri);
629+
630+
bool isDub = dubRecipeExists(rootPath, true);
631+
RootSuggestion suggestion;
632+
suggestion.dir = rootPath;
633+
suggestion.useDub = isDub;
634+
635+
reportProgress(ProgressType.workspaceStartup, i, rootPaths.length, rootUri);
636+
info("registering instance for root ", suggestion);
637+
638+
WConfiguration config;
639+
config.base = [
640+
"dcd": WConfiguration.Section([
641+
"clientPath": WConfiguration.ValueT(proj.config.dcdClientPath.userPath),
642+
"serverPath": WConfiguration.ValueT(proj.config.dcdServerPath.userPath),
643+
"port": WConfiguration.ValueT(9166)
644+
]),
645+
"dmd": WConfiguration.Section([
646+
"path": WConfiguration.ValueT(proj.config.d.dmdPath.userPath)
647+
])
648+
];
649+
auto instance = backend.addInstance(rootPath, config);
650+
if (!activeInstance)
651+
activeInstance = instance;
652+
653+
roots[i].instance = instance;
654+
655+
emitExtensionEvent!onProjectAvailable(instance, rootPath, rootUri);
656+
657+
if (auto lazyInstance = cast(LazyWorkspaceD.LazyInstance)instance)
658+
{
659+
auto lazyLoadCallback(WorkspaceD.Instance instance, string rootPath, string rootUri, RootSuggestion suggestion)
660+
{
661+
return () => delayedProjectActivation(instance, rootPath, rootUri, suggestion, true);
662+
}
663+
664+
lazyInstance.onLazyLoadInstance(lazyLoadCallback(instance, rootPath, rootUri, suggestion));
665+
}
666+
else
667+
{
668+
delayedProjectActivation(instance, rootPath, rootUri, suggestion, true);
669+
}
670+
671+
roots[i].uri = rootUri;
672+
roots[i].success = true;
673+
}
674+
675+
trace("Starting auto completion service...");
676+
StopWatch dcdTimer;
677+
dcdTimer.start();
678+
foreach (i, root; roots)
679+
{
680+
if (root.success)
681+
{
682+
reportProgress(ProgressType.completionStartup, i, roots.length,
683+
root.instance.cwd.uriFromFile);
684+
685+
lazyStartDCDServer(root.instance, root.uri);
686+
}
687+
}
688+
dcdTimer.stop();
689+
trace("Started all completion servers in ", dcdTimer.peek);
690+
691+
return roots.map!"a.success".array;
692+
}
693+
694+
/// If true, "ask" will act as "skip", but will send coded/skippedLoads afterwards
695+
__gshared bool bundleAskLoads;
598696
shared int totalLoadedProjects;
599-
void delayedProjectActivation(WorkspaceD.Instance instance, string workspaceRoot, string workspaceUri, RootSuggestion root)
697+
string[] skippedRoots;
698+
void delayedProjectActivation(WorkspaceD.Instance instance, string workspaceRoot, string workspaceUri, RootSuggestion root,
699+
bool skipManyProjectsAction = false)
600700
{
601701
import core.atomic;
602702

@@ -609,32 +709,43 @@ void delayedProjectActivation(WorkspaceD.Instance instance, string workspaceRoot
609709

610710
auto numLoaded = atomicOp!"+="(totalLoadedProjects, 1);
611711

612-
auto manyProjectsAction = cast(ManyProjectsAction) proj.config.d.manyProjectsAction;
613-
auto manyThreshold = proj.config.d.manyProjectsThreshold;
614-
if (manyThreshold > 0 && numLoaded > manyThreshold)
712+
if (!skipManyProjectsAction)
615713
{
616-
switch (manyProjectsAction)
714+
auto manyProjectsAction = cast(ManyProjectsAction) proj.config.d.manyProjectsAction;
715+
auto manyThreshold = proj.config.d.manyProjectsThreshold;
716+
if (manyThreshold > 0 && numLoaded > manyThreshold)
617717
{
618-
case ManyProjectsAction.ask:
619-
auto loadButton = translate!"d.served.tooManySubprojects.load";
620-
auto skipButton = translate!"d.served.tooManySubprojects.skip";
621-
auto res = rpc.window.requestMessage(MessageType.warning,
622-
translate!"d.served.tooManySubprojects.path"(root.dir),
623-
[loadButton, skipButton]);
624-
if (res != loadButton)
625-
goto case ManyProjectsAction.skip;
626-
break;
627-
case ManyProjectsAction.load:
628-
break;
629-
default:
630-
error("Ignoring invalid manyProjectsAction value ", manyProjectsAction, ", defaulting to skip");
631-
goto case;
632-
case ManyProjectsAction.skip:
633-
backend.removeInstance(workspaceRoot);
634-
throw new Exception("skipping load of this instance");
718+
switch (manyProjectsAction)
719+
{
720+
case ManyProjectsAction.ask:
721+
if (bundleAskLoads)
722+
{
723+
skippedRoots ~= workspaceRoot;
724+
notifySkippedRoots();
725+
goto case ManyProjectsAction.skip;
726+
}
727+
auto loadButton = translate!"d.served.tooManySubprojects.load";
728+
auto skipButton = translate!"d.served.tooManySubprojects.skip";
729+
auto res = rpc.window.requestMessage(MessageType.warning,
730+
translate!"d.served.tooManySubprojects.path"(root.dir),
731+
[loadButton, skipButton]);
732+
if (res != loadButton)
733+
goto case ManyProjectsAction.skip;
734+
break;
735+
case ManyProjectsAction.load:
736+
break;
737+
default:
738+
error("Ignoring invalid manyProjectsAction value ", manyProjectsAction, ", defaulting to skip");
739+
goto case;
740+
case ManyProjectsAction.skip:
741+
backend.removeInstance(workspaceRoot);
742+
throw new Exception("skipping load of this instance");
743+
}
635744
}
636745
}
637746

747+
skippedRoots = skippedRoots.remove!(a => a == workspaceRoot);
748+
638749
info("Initializing instance for root ", root);
639750
StopWatch rootTimer;
640751
rootTimer.start();
@@ -709,6 +820,15 @@ void delayedProjectActivation(WorkspaceD.Instance instance, string workspaceRoot
709820
info("Root ", root, " initialized in ", rootTimer.peek);
710821
}
711822

823+
void notifySkippedRoots()
824+
{
825+
static int timer;
826+
clearTimeout(timer);
827+
timer = setTimeout(delegate() {
828+
rpc.notifyMethod("coded/skippedLoads", SkippedLoadsNotification(skippedRoots));
829+
}, 500.msecs);
830+
}
831+
712832
void didLoadDubProject()
713833
{
714834
static bool loadedDub = false;

source/served/lsp/protoext.d

+7
Original file line numberDiff line numberDiff line change
@@ -494,3 +494,10 @@ struct ListDependenciesParams
494494
{
495495
mixin SingleValueParams!(string, "packageName");
496496
}
497+
498+
/// Sent with `coded/skippedLoads`
499+
struct SkippedLoadsNotification
500+
{
501+
/// List of folder file paths
502+
string[] roots;
503+
}

0 commit comments

Comments
 (0)