summaryrefslogtreecommitdiffhomepage
path: root/src/features/cwd-lsp/ui/LspStatusView.svelte
blob: c8c9ea8af9f18310a4ae371ba73b43028d6c96a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<script lang="ts">
  import { untrack } from "svelte";
  import {
    type Badge,
    type LoadLspStatus,
    type LspServerView,
    summarizeServers,
    viewLspServers,
  } from "../logic/view-model";

  let {
    cwd,
    canView,
    load,
  }: {
    /** The active conversation's cwd — the trigger to (re)load when it changes. */
    cwd: string | null;
    /** Whether a real conversation is focused. */
    canView: boolean;
    load: LoadLspStatus;
  } = $props();

  const badgeClass: Record<Badge, string> = {
    success: "badge-success",
    warning: "badge-warning",
    error: "badge-error",
    neutral: "badge-ghost",
  };

  let servers = $state<readonly LspServerView[]>([]);
  let loading = $state(false);
  let error = $state<string | null>(null);
  let loadedCwd = $state<string | null>(null);
  let hasLoaded = $state(false);
  let summary = $state("");

  async function refresh() {
    if (!canView) return;
    loading = true;
    error = null;
    const result = await load();
    loading = false;
    if (result === null) return;
    hasLoaded = true;
    if (result.ok) {
      servers = viewLspServers(result.servers);
      summary = summarizeServers(result.servers);
      loadedCwd = result.cwd;
    } else {
      error = result.error;
    }
  }

  // (Re)load on mount and whenever the conversation's cwd changes. The LSP GET
  // lazily spawns servers, so we avoid a redundant fetch when `cwd` resolves to
  // the value we already loaded for.
  $effect(() => {
    const target = cwd;
    const can = canView;
    untrack(() => {
      if (!can) return;
      if (!hasLoaded || target !== loadedCwd) void refresh();
    });
  });
</script>

<div class="flex flex-col gap-2">
  <div class="flex items-center justify-between gap-2">
    <span class="text-xs opacity-70">
      {#if loading}
        Resolving…
      {:else if hasLoaded && loadedCwd !== null}
        {summary}
      {:else}
        Language servers
      {/if}
    </span>
    <button
      type="button"
      class="btn btn-ghost btn-xs"
      disabled={!canView || loading}
      onclick={() => refresh()}
      aria-label="Refresh language server status"
    >
      {#if loading}
        <span class="loading loading-spinner loading-xs"></span>
      {:else}
        Refresh
      {/if}
    </button>
  </div>

  {#if !canView}
    <p class="text-xs opacity-60">Open or start a conversation to see its language servers.</p>
  {:else if error}
    <p class="text-xs text-error">{error}</p>
  {:else if hasLoaded && loadedCwd === null}
    <p class="text-xs opacity-60">
      Set a working directory in the Model panel to enable language servers.
    </p>
  {:else if hasLoaded && servers.length === 0 && !loading}
    <p class="text-xs opacity-60">No language servers configured for this directory.</p>
  {:else}
    <ul class="flex flex-col gap-2">
      {#each servers as server (server.id)}
        <li class="flex flex-col gap-1 rounded-box bg-base-200 p-2 text-sm">
          <div class="flex items-center justify-between gap-2">
            <span class="font-medium">{server.name}</span>
            <span class="badge badge-sm {badgeClass[server.badge]} gap-1">
              {#if server.busy}
                <span class="loading loading-spinner loading-xs"></span>
              {/if}
              {server.statusLabel}
            </span>
          </div>
          {#if server.extensionsLabel}
            <span class="font-mono text-xs opacity-60">{server.extensionsLabel}</span>
          {/if}
          <span class="truncate font-mono text-xs opacity-50" title={server.root}
            >{server.root}</span
          >
          {#if server.error}
            <span class="font-mono text-xs text-error">{server.error}</span>
          {/if}
        </li>
      {/each}
    </ul>
  {/if}
</div>