Skip to content

Commit

Permalink
refactor(vscode:extension): split useFileState into useIndex hook and…
Browse files Browse the repository at this point in the history
… getFileState func that can be used with or without useMemo
  • Loading branch information
emil14 committed Nov 19, 2023
1 parent 53d4a5e commit 267492e
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 167 deletions.
7 changes: 6 additions & 1 deletion cmd/lsp/general_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@ func (s *Server) Initialize(glspCtx *glsp.Context, params *protocol.InitializePa
return result, nil
}

// it's important to open these channels here and not when server is created
// to avoid sending messages to closed connection, which can happen when vscode is reloading in development mode
s.indexChan = make(chan sourcecode.Module)
s.problemsChan = make(chan string)

// first indexing
go func() {
prog, problems, err := s.indexer.index(context.Background(), *params.RootPath)
if err != nil {
Expand All @@ -40,7 +43,6 @@ func (s *Server) Initialize(glspCtx *glsp.Context, params *protocol.InitializePa

// Initialized is called when vscode-extension is initialized.
// It spawns goroutines for sending indexing messages and warnings
// Note that this methods only works correctly if any time vscode reloaded it relaunches language-server.
func (s *Server) Initialized(glspCtx *glsp.Context, params *protocol.InitializedParams) error {
go func() {
for indexedMod := range s.indexChan {
Expand All @@ -55,6 +57,9 @@ func (s *Server) Initialized(glspCtx *glsp.Context, params *protocol.Initialized
return nil
}

// Shutdown closes channels so we don't miss any sent while vscode reloading.
// It shouldn't matter in production mode where vscode (re)launches server by itself.
// But is handy for development when server is spawned by user for debugging purposes.
func (s *Server) Shutdown(context *glsp.Context) error {
close(s.indexChan)
close(s.problemsChan)
Expand Down
21 changes: 13 additions & 8 deletions web/webview/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,45 @@ import {
InterfaceView,
ComponentView,
} from "./components";
import { useFileState } from "./hooks/use_file_state";
import { useIndex } from "./helpers/use_index";
import { useMemo } from "react";
import { getFileState } from "./helpers/get_file_state";

export default function App() {
const { imports, entities } = useFileState();
const { types, constants, interfaces, components } = entities;
const index = useIndex();
const fileState = useMemo(() => getFileState(index), [index]);

return (
<div className="app">
<ImportsView imports={imports} style={{ marginBottom: "20px" }} />
<ImportsView
imports={fileState.imports}
style={{ marginBottom: "20px" }}
/>

<VSCodeDivider style={{ marginBottom: "20px" }} />

<h2>Types</h2>
<div style={{ marginBottom: "20px" }}>
<TypesView types={types} />
<TypesView types={fileState.entities.types} />
</div>

<VSCodeDivider style={{ marginBottom: "20px" }} />

<h2>Const</h2>
<ConstantView constants={constants} />
<ConstantView constants={fileState.entities.constants} />

<VSCodeDivider style={{ marginBottom: "20px" }} />

<h2>Interfaces</h2>
{interfaces.map((entry) => {
{fileState.entities.interfaces.map((entry) => {
const { name, entity } = entry;
return <InterfaceView name={name} entity={entity} />;
})}

<VSCodeDivider style={{ marginBottom: "20px" }} />

<h2>Components</h2>
{components.map((entry) => {
{fileState.entities.components.map((entry) => {
const { name, entity } = entry;
return <ComponentView name={name} entity={entity} />;
})}
Expand Down
118 changes: 118 additions & 0 deletions web/webview/src/helpers/get_file_state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
ConstEntity,
InterfaceEntity,
ComponentEntity,
Component,
TypeEntity,
File,
Const,
Interface,
} from "../generated/sourcecode";
import * as ts from "../generated/typesystem";
import { VSCodeMessageData } from "./use_index";

// File state is grouped and sorted render-friendly object
interface FileState {
imports: Array<{ alias: string; path: string }>;
entities: GroupedEntities;
}

// we use arrays instead of objects because it's faster to render
interface GroupedEntities {
types: Array<{ name: string; entity: ts.Def }>;
interfaces: Array<{ name: string; entity: Interface }>;
constants: Array<{ name: string; entity: Const }>;
components: Array<{ name: string; entity: Component }>;
}

export function getFileState(state: VSCodeMessageData | undefined): FileState {
const result: FileState = {
imports: [],
entities: { types: [], interfaces: [], constants: [], components: [] },
};

// if tab opened first time and there were no updates from vscode yet
if (!state) {
return result;
}

if (!state.programState.packages) {
return result;
}

const { currentFileName, currentPackageName } = getCurrentPackageAndFileName(
state.openedDocument.fileName,
state.workspaceUri.path
);

const currentFile: File =
state.programState.packages![currentPackageName][currentFileName];

for (const alias in currentFile.imports) {
result.imports.push({
alias,
path: currentFile.imports[alias],
});
}

result.imports.sort();

// object to array for faster rendering
for (const name in currentFile.entities) {
const entity = currentFile.entities[name];
switch (entity.kind) {
case TypeEntity:
if (entity.type === undefined) {
continue;
}
result.entities.types.push({
name: name,
entity: entity.type as ts.Def,
});
break;
case ConstEntity:
if (entity.const === undefined) {
break;
}
result.entities.constants.push({
name: name,
entity: entity.const,
});
break;
case InterfaceEntity:
if (entity.interface === undefined) {
break;
}
result.entities.interfaces.push({
name: name,
entity: entity.interface,
});
break;
case ComponentEntity:
if (entity.component === undefined) {
break;
}
result.entities.components.push({
name: name,
entity: entity.component,
});
break;
}
}

return result;
}

const getCurrentPackageAndFileName = (
openedFileName: string,
workspacePath: string
) => {
const relativePath = openedFileName.replace(workspacePath + "/", "");
const pathParts = relativePath.split("/");

const currentPackageName = pathParts.slice(0, -1).join("/"); // all but the last segment (filename)
const currentFileNameWithExtension = pathParts[pathParts.length - 1]; // last semgent (filename)
const currentFileName = currentFileNameWithExtension.split(".")[0]; // filename without .neva

return { currentPackageName, currentFileName };
};
30 changes: 30 additions & 0 deletions web/webview/src/helpers/use_index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { TextDocument, Uri } from "vscode";
import { useState, useEffect } from "react";
import { Module } from "../generated/sourcecode";

export interface VSCodeMessageData {
workspaceUri: Uri;
openedDocument: TextDocument;
programState: Module;
isDarkTheme: boolean;
}

const vscodeApi = acquireVsCodeApi<VSCodeMessageData>();

export function useIndex(): VSCodeMessageData | undefined {
const persistedState = vscodeApi.getState();
const [state, setState] = useState<VSCodeMessageData | undefined>(
persistedState
);

useEffect(() => {
const listener = (event: { data: VSCodeMessageData }) => {
setState(event.data);
vscodeApi.setState(event.data);
};
window.addEventListener("message", listener);
return () => window.removeEventListener("message", listener);
}, []);

return state;
}
158 changes: 0 additions & 158 deletions web/webview/src/hooks/use_file_state.ts

This file was deleted.

0 comments on commit 267492e

Please sign in to comment.