From 126076de97081770adcaab3cb8ac7573fe6a1302 Mon Sep 17 00:00:00 2001 From: danimester23 Date: Mon, 11 May 2026 19:39:37 +0200 Subject: [PATCH 1/2] Add csharp plugin in the new web gui --- webgui-new/package-lock.json | 16 + webgui-new/package.json | 1 + .../components/codebites/codebites-node.tsx | 183 +++++++---- .../codemirror-editor/codemirror-editor.tsx | 310 +++++++++++------- .../src/components/diagrams/diagrams.tsx | 234 ++++++++----- .../file-context-menu/file-context-menu.tsx | 81 +++-- .../src/components/file-name/file-name.tsx | 149 +++++---- webgui-new/src/components/header/header.tsx | 187 +++++++---- webgui-new/src/components/metrics/metrics.tsx | 128 +++++--- webgui-new/src/enums/search-enum.ts | 101 +++--- webgui-new/src/global-context/app-context.tsx | 128 +++++--- webgui-new/src/service/language-service.ts | 125 ++++--- 12 files changed, 1047 insertions(+), 596 deletions(-) diff --git a/webgui-new/package-lock.json b/webgui-new/package-lock.json index 7ccd04977..e1a94b678 100644 --- a/webgui-new/package-lock.json +++ b/webgui-new/package-lock.json @@ -15,6 +15,7 @@ "@mui/icons-material": "^5.18.0", "@mui/material": "^5.18.0", "@mui/x-tree-view": "^6.17.0", + "@replit/codemirror-lang-csharp": "^6.2.0", "@uiw/codemirror-theme-github": "^4.25.2", "@uiw/react-codemirror": "^4.25.2", "dagre": "^0.8.5", @@ -1871,6 +1872,21 @@ "react-dom": ">=17" } }, + "node_modules/@replit/codemirror-lang-csharp": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@replit/codemirror-lang-csharp/-/codemirror-lang-csharp-6.2.0.tgz", + "integrity": "sha512-6utbaWkoymhoAXj051mkRp+VIJlpwUgCX9Toevz3YatiZsz512fw3OVCedXQx+WcR0wb6zVHjChnuxqfCLtFVQ==", + "license": "MIT", + "peerDependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", diff --git a/webgui-new/package.json b/webgui-new/package.json index 7878d9bee..48830a7cf 100644 --- a/webgui-new/package.json +++ b/webgui-new/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@codemirror/lang-cpp": "^6.0.3", + "@replit/codemirror-lang-csharp": "^6.2.0", "@codemirror/lang-python": "^6.2.1", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", diff --git a/webgui-new/src/components/codebites/codebites-node.tsx b/webgui-new/src/components/codebites/codebites-node.tsx index 8fe03c8f7..ff4f143f4 100644 --- a/webgui-new/src/components/codebites/codebites-node.tsx +++ b/webgui-new/src/components/codebites/codebites-node.tsx @@ -1,24 +1,46 @@ -import { cpp } from '@codemirror/lang-cpp'; -import { AstNodeInfo, FileInfo } from '@thrift-generated'; -import { githubDark, githubLight } from '@uiw/codemirror-theme-github'; -import ReactCodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror'; -import { ThemeContext } from 'global-context/theme-context'; -import React, { Dispatch, SetStateAction, useContext, useEffect, useRef, useState } from 'react'; +import { cpp } from "@codemirror/lang-cpp"; +import { csharp } from "@replit/codemirror-lang-csharp"; +import { python } from "@codemirror/lang-python"; +import { AstNodeInfo, FileInfo } from "@thrift-generated"; +import { githubDark, githubLight } from "@uiw/codemirror-theme-github"; +import ReactCodeMirror, { + Extension, + ReactCodeMirrorRef, +} from "@uiw/react-codemirror"; +import { ThemeContext } from "global-context/theme-context"; +import React, { + Dispatch, + SetStateAction, + useContext, + useEffect, + useRef, + useState, +} from "react"; import { - getCppAstNodeInfoByPosition, - getCppReferenceTypes, - getCppReferences, - getCppSourceText, -} from 'service/cpp-service'; -import { getFileInfo } from 'service/project-service'; -import { NodeProps, Handle, Position, useReactFlow, Node, Edge, getOutgoers, getConnectedEdges } from 'reactflow'; -import { gutter, GutterMarker } from '@codemirror/view'; -import * as SC from './styled-components'; -import { IconButton, Tooltip } from '@mui/material'; -import { Close } from '@mui/icons-material'; -import { AppContext } from 'global-context/app-context'; -import dagre from 'dagre'; -import { sendGAEvent } from 'utils/analytics'; + createClient, + getAstNodeInfoByPosition, + getReferenceTypes, + getReferences, + getSourceText, +} from "service/language-service"; +import { getFileInfo } from "service/project-service"; +import { + NodeProps, + Handle, + Position, + useReactFlow, + Node, + Edge, + getOutgoers, + getConnectedEdges, +} from "reactflow"; +import { gutter, GutterMarker } from "@codemirror/view"; +import * as SC from "./styled-components"; +import { IconButton, Tooltip } from "@mui/material"; +import { Close } from "@mui/icons-material"; +import { AppContext } from "global-context/app-context"; +import dagre from "dagre"; +import { sendGAEvent } from "utils/analytics"; type CodeBitesElement = { astNodeInfo: AstNodeInfo; @@ -38,8 +60,12 @@ dagreGraph.setDefaultEdgeLabel(() => ({})); const nodeWidth = 660; const nodeHeight = 460; -const getLayoutedElements = (nodes: Node[], edges: Edge[], direction = 'TB') => { - const isHorizontal = direction === 'LR'; +const getLayoutedElements = ( + nodes: Node[], + edges: Edge[], + direction = "TB", +) => { + const isHorizontal = direction === "LR"; dagreGraph.setGraph({ rankdir: direction }); nodes.forEach((node: { id: string }) => { @@ -61,8 +87,8 @@ const getLayoutedElements = (nodes: Node[], edges: Edge[], direction = 'TB') => // eslint-disable-next-line @typescript-eslint/no-explicit-any nodes.forEach((node: any) => { const nodeWithPosition = dagreGraph.node(node.id); - node.targetPosition = isHorizontal ? 'left' : 'top'; - node.sourcePosition = isHorizontal ? 'right' : 'bottom'; + node.targetPosition = isHorizontal ? "left" : "top"; + node.sourcePosition = isHorizontal ? "right" : "bottom"; node.position = { x: nodeWithPosition.x - nodeWidth / 2, y: nodeWithPosition.y - nodeHeight / 2, @@ -87,7 +113,7 @@ class CustomOffsetGutterMarker extends GutterMarker { } toDOM() { - const div = document.createElement('div'); + const div = document.createElement("div"); div.innerHTML = this.number; return div; } @@ -99,7 +125,7 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { const { theme } = useContext(ThemeContext); const [fileInfo, setFileInfo] = useState(undefined); - const [text, setText] = useState(''); + const [text, setText] = useState(""); const editorRef = useRef(null); @@ -108,10 +134,13 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { useEffect(() => { if (!data.astNodeInfo) return; const init = async () => { - const initFileInfo = await getFileInfo(data.astNodeInfo.range?.file as string); - const initText = await getCppSourceText(data.astNodeInfo.id as string); + const initFileInfo = await getFileInfo( + data.astNodeInfo.range?.file as string, + ); + createClient(appCtx.workspaceId, initFileInfo?.type); + const initText = await getSourceText(data.astNodeInfo.id as string); sendGAEvent({ - event_action: 'code_bites', + event_action: "code_bites", event_category: appCtx.workspaceId, event_label: `${initFileInfo?.name}: ${initText}`, }); @@ -127,26 +156,30 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { const view = editorRef.current.view; if (!view) return; - const astNodeStartLine = data.astNodeInfo?.range?.range?.startpos?.line as number; + const astNodeStartLine = data.astNodeInfo?.range?.range?.startpos + ?.line as number; const head = view.state.selection.main.head as number; const line = view.state.doc.lineAt(head); const column = view.state.selection.ranges[0].head - line.from; - const newAstNodeInfo = await getCppAstNodeInfoByPosition( + createClient(appCtx.workspaceId, fileInfo?.type); + const newAstNodeInfo = await getAstNodeInfoByPosition( fileInfo?.id as string, line.number + astNodeStartLine - 1, - column + column, ); if (!newAstNodeInfo) return; - const newAstNodeRefTypes = await getCppReferenceTypes(newAstNodeInfo.id as string); - if (newAstNodeRefTypes.get('Definition') === undefined) return; + const newAstNodeRefTypes = await getReferenceTypes( + newAstNodeInfo.id as string, + ); + if (newAstNodeRefTypes.get("Definition") === undefined) return; const newAstNodeDef = ( - await getCppReferences( + await getReferences( newAstNodeInfo.id as string, - newAstNodeRefTypes.get('Definition') as number, - newAstNodeInfo.tags ?? [] + newAstNodeRefTypes.get("Definition") as number, + newAstNodeInfo.tags ?? [], ) )[0]; if (!newAstNodeDef) return; @@ -154,12 +187,14 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { const nodes = reactFlowInstance.getNodes(); const edges = reactFlowInstance.getEdges(); - const nodeAlreadyPresent = nodes.map((node) => node.id).includes(newAstNodeDef.id as string); + const nodeAlreadyPresent = nodes + .map((node) => node.id) + .includes(newAstNodeDef.id as string); if (nodeAlreadyPresent) return; const newNode: Node = { id: newAstNodeDef.id as string, - type: 'codeBitesNode', + type: "codeBitesNode", data: { astNodeInfo: newAstNodeDef }, position: { x: 100, y: 100 }, }; @@ -168,12 +203,12 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { id: `e${data.astNodeInfo.id}-${newAstNodeDef.id}`, source: data.astNodeInfo.id as string, target: newAstNodeDef.id as string, - label: `${newAstNodeDef.astNodeValue?.split('\n')[0]}`, + label: `${newAstNodeDef.astNodeValue?.split("\n")[0]}`, }; const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( [...nodes, newNode], - [...edges, newEdge] + [...edges, newEdge], ); reactFlowInstance.setNodes(layoutedNodes); reactFlowInstance.setEdges(layoutedEdges); @@ -182,30 +217,46 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { const handleDelete = () => { const nodes = reactFlowInstance.getNodes(); const edges = reactFlowInstance.getEdges(); - const currentNode = reactFlowInstance.getNode(data.astNodeInfo.id as string) as Node; + const currentNode = reactFlowInstance.getNode( + data.astNodeInfo.id as string, + ) as Node; const relatedNodes = getOutgoers(currentNode, nodes, edges) as Node[]; const relatedNodeIds = relatedNodes.map((node) => node.id); - const relatedEdges = getConnectedEdges([...relatedNodes, currentNode], edges); + const relatedEdges = getConnectedEdges( + [...relatedNodes, currentNode], + edges, + ); const relatedEdgeIds = relatedEdges.map((edge) => edge.id); - const updatedNodes = nodes.filter((node) => !relatedNodeIds.includes(node.id) && node.id !== currentNode?.id); - const updatedEdges = edges.filter((edge) => !relatedEdgeIds.includes(edge.id)); + const updatedNodes = nodes.filter( + (node) => + !relatedNodeIds.includes(node.id) && node.id !== currentNode?.id, + ); + const updatedEdges = edges.filter( + (edge) => !relatedEdgeIds.includes(edge.id), + ); - const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(updatedNodes, updatedEdges); + const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( + updatedNodes, + updatedEdges, + ); reactFlowInstance.setNodes(layoutedNodes); reactFlowInstance.setEdges(layoutedEdges); }; const customOffsetGutter = gutter({ - class: 'customGutter', + class: "customGutter", renderEmptyElements: false, lineMarker(view, line, others) { if (others.some((m) => m.toDOM)) return null; - const number = view.state.doc.lineAt(line.from).number + (data.astNodeInfo.range?.range?.startpos?.line ?? 0) - 1; + const number = + view.state.doc.lineAt(line.from).number + + (data.astNodeInfo.range?.range?.startpos?.line ?? 0) - + 1; return new CustomOffsetGutterMarker(number.toString()); }, @@ -216,8 +267,13 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { - - {fileInfo ? (fileInfo.name as string) : 'Unknown file'} + + + {fileInfo ? (fileInfo.name as string) : "Unknown file"} + {data.astNodeInfo.id !== initialNodeId && ( handleDelete()}> @@ -227,12 +283,16 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { (editorRef.current = { view, state })} onClick={() => handleClick()} basicSetup={{ @@ -242,3 +302,16 @@ export const CodeBitesNode = ({ data }: NodeProps): JSX.Element => { ); }; + +const languageExtension = (fileType?: string): Extension | null => { + switch (fileType) { + case "CPP": + return cpp(); + case "CS": + return csharp(); + case "PY": + return python(); + default: + return null; + } +}; diff --git a/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx b/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx index 987affe9c..ffabb4c2f 100644 --- a/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx +++ b/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx @@ -1,33 +1,54 @@ -import ReactCodeMirror, { Decoration, EditorView, Extension, ReactCodeMirrorRef } from '@uiw/react-codemirror'; -import { AccordionLabel } from 'enums/accordion-enum'; -import { ThemeContext } from 'global-context/theme-context'; -import React, { useContext, useRef, useState, useEffect, MouseEvent } from 'react'; -import { createClient, getAstNodeInfoByPosition, getReferenceTypes, getReferences } from 'service/language-service'; -import { AstNodeInfo, FileInfo, Position, Range } from '@thrift-generated'; -import { cpp } from '@codemirror/lang-cpp'; -import { python } from '@codemirror/lang-python'; -import { githubDark, githubLight } from '@uiw/codemirror-theme-github'; -import { EditorContextMenu } from 'components/editor-context-menu/editor-context-menu'; -import { FileName } from 'components/file-name/file-name'; -import { AppContext } from 'global-context/app-context'; -import { getFileContent, getFileInfo } from 'service/project-service'; -import { convertSelectionRangeToString, convertSelectionStringToRange, formatDate } from 'utils/utils'; -import { TabName } from 'enums/tab-enum'; -import { getRepositoryByProjectPath } from 'service/git-service'; -import { useRouter } from 'next/router'; -import { RouterQueryType } from 'utils/types'; -import { Tooltip, alpha } from '@mui/material'; -import * as SC from './styled-components'; -import { useTranslation } from 'react-i18next'; -import { sendGAEvent } from 'utils/analytics'; +import ReactCodeMirror, { + Decoration, + EditorView, + Extension, + ReactCodeMirrorRef, +} from "@uiw/react-codemirror"; +import { AccordionLabel } from "enums/accordion-enum"; +import { ThemeContext } from "global-context/theme-context"; +import React, { + useContext, + useRef, + useState, + useEffect, + MouseEvent, +} from "react"; +import { + createClient, + getAstNodeInfoByPosition, + getReferenceTypes, + getReferences, +} from "service/language-service"; +import { AstNodeInfo, FileInfo, Position, Range } from "@thrift-generated"; +import { cpp } from "@codemirror/lang-cpp"; +import { csharp } from "@replit/codemirror-lang-csharp"; +import { python } from "@codemirror/lang-python"; +import { githubDark, githubLight } from "@uiw/codemirror-theme-github"; +import { EditorContextMenu } from "components/editor-context-menu/editor-context-menu"; +import { FileName } from "components/file-name/file-name"; +import { AppContext } from "global-context/app-context"; +import { getFileContent, getFileInfo } from "service/project-service"; +import { + convertSelectionRangeToString, + convertSelectionStringToRange, + formatDate, +} from "utils/utils"; +import { TabName } from "enums/tab-enum"; +import { getRepositoryByProjectPath } from "service/git-service"; +import { useRouter } from "next/router"; +import { RouterQueryType } from "utils/types"; +import { Tooltip, alpha } from "@mui/material"; +import * as SC from "./styled-components"; +import { useTranslation } from "react-i18next"; +import { sendGAEvent } from "utils/analytics"; type HighlightPosition = { - startpos: {line: number, column: number} - endpos: {line: number, column: number} -} + startpos: { line: number; column: number }; + endpos: { line: number; column: number }; +}; -const HIGHLIGHT_FOR_DARK = 'rgba(187, 181, 255, 0.3)'; -const HIGHLIGHT_FOR_LIGHT = '#f0d8a8'; +const HIGHLIGHT_FOR_DARK = "rgba(187, 181, 255, 0.3)"; +const HIGHLIGHT_FOR_LIGHT = "#f0d8a8"; export const CodeMirrorEditor = (): JSX.Element => { const { t } = useTranslation(); @@ -38,19 +59,24 @@ export const CodeMirrorEditor = (): JSX.Element => { const editorRef = useRef(null); const [fileInfo, setFileInfo] = useState(undefined); - const [fileContent, setFileContent] = useState(''); + const [fileContent, setFileContent] = useState(""); const [contextMenu, setContextMenu] = useState<{ mouseX: number; mouseY: number; } | null>(null); - const [highlightRanges, setHighlightRanges] = useState([]); - const [visitedLastAstNode, setVisitedLastAstNode] = useState(null); - const [highlightColor, setHighlightColor] = useState(theme === 'dark' ? HIGHLIGHT_FOR_DARK : HIGHLIGHT_FOR_LIGHT); + const [highlightRanges, setHighlightRanges] = useState( + [], + ); + const [visitedLastAstNode, setVisitedLastAstNode] = + useState(null); + const [highlightColor, setHighlightColor] = useState( + theme === "dark" ? HIGHLIGHT_FOR_DARK : HIGHLIGHT_FOR_LIGHT, + ); useEffect(() => { if (!appCtx.workspaceId) return; setFileInfo(undefined); - setFileContent(''); + setFileContent(""); }, [appCtx.workspaceId]); useEffect(() => { @@ -70,19 +96,30 @@ export const CodeMirrorEditor = (): JSX.Element => { }, [appCtx.editorSelection, fileContent]); useEffect(() => { - setHighlightColor(theme === 'dark' ? HIGHLIGHT_FOR_DARK : HIGHLIGHT_FOR_LIGHT); - }, [theme]) + setHighlightColor( + theme === "dark" ? HIGHLIGHT_FOR_DARK : HIGHLIGHT_FOR_LIGHT, + ); + }, [theme]); useEffect(() => { - if(!editorRef.current || !editorRef.current.view) return; + if (!editorRef.current || !editorRef.current.view) return; setHighlightRanges([]); createClient(appCtx.workspaceId, fileInfo?.type); - }, [appCtx.workspaceId, fileInfo, fileContent]) + }, [appCtx.workspaceId, fileInfo, fileContent]); + + const createHighlightDecoration = ( + view: EditorView, + highlightPosition: HighlightPosition, + highlightColor: string, + ) => { + if ( + !editorRef.current || + !editorRef.current.state || + !editorRef.current.view + ) + return; - const createHighlightDecoration = (view: EditorView, highlightPosition: HighlightPosition, highlightColor: string) => { - if (!editorRef.current || !editorRef.current.state || !editorRef.current.view) return; - const startPos = highlightPosition.startpos as Position; const endPos = highlightPosition.endpos as Position; @@ -91,51 +128,70 @@ export const CodeMirrorEditor = (): JSX.Element => { (startPos.column as number) - 1; const to = - view.state.doc.line(endPos.line as number).from + (endPos.column as number) - 1; + view.state.doc.line(endPos.line as number).from + + (endPos.column as number) - + 1; return Decoration.mark({ attributes: { style: `background-color:${highlightColor}` }, }).range(from, to); }; - - const highlightExtension = () => {return EditorView.decorations.of((view) => { - const decorations = highlightRanges.map((pos) => createHighlightDecoration(view, pos, highlightColor)) as never; - return Decoration.set(decorations, true); - })} - - const languageExtension = (fileType?: string) => - { - switch(fileType) - { + + const highlightExtension = () => { + return EditorView.decorations.of((view) => { + const decorations = highlightRanges.map((pos) => + createHighlightDecoration(view, pos, highlightColor), + ) as never; + return Decoration.set(decorations, true); + }); + }; + + const languageExtension = (fileType?: string) => { + switch (fileType) { case "CPP": return cpp(); + case "CS": + return csharp(); case "PY": return python(); default: return null; } - } - - const updateHighlights = async (astNode : AstNodeInfo) => { - const refTypes = await getReferenceTypes(astNode.id as string) - if(visitedLastAstNode?.id !== astNode.id){ - const allReferences = await getReferences(astNode.id as string, refTypes.get('Usage') as number, []); - const referencesInFile = allReferences.filter(ref => ref.range?.file === fileInfo?.id); - setHighlightRanges(referencesInFile.map(nodeInfo => { - const startpos = nodeInfo?.range?.range?.startpos as { line: number, column: number }; - const endpos = nodeInfo?.range?.range?.endpos as { line: number, column: number }; - return { - startpos: { line: startpos.line, column: startpos.column }, - endpos: { line: endpos.line, column: endpos.column } - }; - })); - setVisitedLastAstNode(astNode); + }; - }else{ + const updateHighlights = async (astNode: AstNodeInfo) => { + const refTypes = await getReferenceTypes(astNode.id as string); + if (visitedLastAstNode?.id !== astNode.id) { + const allReferences = await getReferences( + astNode.id as string, + refTypes.get("Usage") as number, + [], + ); + const referencesInFile = allReferences.filter( + (ref) => ref.range?.file === fileInfo?.id, + ); + setHighlightRanges( + referencesInFile.map((nodeInfo) => { + const startpos = nodeInfo?.range?.range?.startpos as { + line: number; + column: number; + }; + const endpos = nodeInfo?.range?.range?.endpos as { + line: number; + column: number; + }; + return { + startpos: { line: startpos.line, column: startpos.column }, + endpos: { line: endpos.line, column: endpos.column }, + }; + }), + ); + setVisitedLastAstNode(astNode); + } else { setHighlightRanges([]); setVisitedLastAstNode(null); } - } + }; const dispatchSelection = (range: Range) => { if (!range || !range.startpos || !range.endpos) return; @@ -146,8 +202,14 @@ export const CodeMirrorEditor = (): JSX.Element => { const editor = editorRef.current?.view; if (editor) { try { - const fromPos = editor.state.doc.line(startLine as number).from + (startCol as number) - 1; - const codeSnippetEnd = editor.state.doc.line(endLine as number).from + (endCol as number) - 1; + const fromPos = + editor.state.doc.line(startLine as number).from + + (startCol as number) - + 1; + const codeSnippetEnd = + editor.state.doc.line(endLine as number).from + + (endCol as number) - + 1; const startLineEnd = editor.state.doc.line(startLine as number).to; const toPos = Math.min(codeSnippetEnd, startLineEnd); @@ -172,7 +234,7 @@ export const CodeMirrorEditor = (): JSX.Element => { mouseX: event.clientX + 2, mouseY: event.clientY - 6, } - : null + : null, ); handleAstNodeSelect(); }; @@ -188,13 +250,17 @@ export const CodeMirrorEditor = (): JSX.Element => { const column = view.state.selection.ranges[0].head - line.from; const astNodeInfo = - fileInfo?.type === 'Unknown' + fileInfo?.type === "Unknown" ? null - : await getAstNodeInfoByPosition(fileInfo?.id as string, line.number, column); + : await getAstNodeInfoByPosition( + fileInfo?.id as string, + line.number, + column, + ); if (astNodeInfo) { sendGAEvent({ - event_action: 'click_on_word', + event_action: "click_on_word", event_category: appCtx.workspaceId, event_label: `${fileInfo?.name}: ${astNodeInfo.astNodeValue}`, }); @@ -202,7 +268,7 @@ export const CodeMirrorEditor = (): JSX.Element => { await updateHighlights(astNodeInfo); dispatchSelection(nodeRange); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, editorSelection: convertSelectionRangeToString(nodeRange), @@ -222,7 +288,7 @@ export const CodeMirrorEditor = (): JSX.Element => { }), }); sendGAEvent({ - event_action: 'click_on_word', + event_action: "click_on_word", event_category: appCtx.workspaceId, event_label: `${fileInfo?.name}: ${convertSelectionRangeToString(range)}`, }); @@ -230,11 +296,11 @@ export const CodeMirrorEditor = (): JSX.Element => { setVisitedLastAstNode(null); dispatchSelection(range); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, editorSelection: convertSelectionRangeToString(range), - languageNodeId: '', + languageNodeId: "", } as RouterQueryType, }); } @@ -243,7 +309,7 @@ export const CodeMirrorEditor = (): JSX.Element => { const getCommitInfo = async (finalCommitId: string) => { const repo = await getRepositoryByProjectPath(fileInfo?.path as string); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, gitRepoId: repo?.repoId as string, @@ -258,9 +324,11 @@ export const CodeMirrorEditor = (): JSX.Element => { const renderedLines = appCtx.gitBlameInfo.map((info, idx) => { const trimmedMessage = (info?.finalCommitMessage?.length as number) > 27 - ? `${info.finalCommitMessage?.split('').slice(0, 27).join('')}...` + ? `${info.finalCommitMessage?.split("").slice(0, 27).join("")}...` : info?.finalCommitMessage; - const date = formatDate(new Date((info.finalSignature?.time as unknown as number) * 1000)); + const date = formatDate( + new Date((info.finalSignature?.time as unknown as number) * 1000), + ); previousLineCount = info.linesInHunk as number; const lineHeight = 17.9 * previousLineCount; @@ -269,39 +337,40 @@ export const CodeMirrorEditor = (): JSX.Element => { +
{`#${info.finalCommitId?.substring(0, 8)} `}
{info.finalCommitMessage}
{`${info.finalSignature?.name} (${info.finalSignature?.email})`}
-
{t('gitBlame.commitedOn', { date })}
+
{t("gitBlame.commitedOn", { date })}
} - placement={'top-start'} + placement={"top-start"} componentsProps={{ tooltip: { sx: { - fontSize: '0.85rem', - padding: '10px', - width: '400px', - height: 'auto', - overflow: 'scroll', + fontSize: "0.85rem", + padding: "10px", + width: "400px", + height: "auto", + overflow: "scroll", }, }, }} > `1px solid ${theme.colors?.primary}`, - ':hover': { - backgroundColor: (theme) => alpha(theme.backgroundColors?.secondary as string, 0.3), + ":hover": { + backgroundColor: (theme) => + alpha(theme.backgroundColors?.secondary as string, 0.3), }, }} onClick={() => getCommitInfo(info.finalCommitId as string)} >
{trimmedMessage}
-
{t('gitBlame.onDate', { date })}
+
{t("gitBlame.onDate", { date })}
); @@ -313,19 +382,19 @@ export const CodeMirrorEditor = (): JSX.Element => { return ( <> { - const editor = document.querySelector('.cm-scroller'); + const editor = document.querySelector(".cm-scroller"); if (!editor) return; const scroll = e.currentTarget.scrollTop; editor.scrollTop = scroll; @@ -335,28 +404,40 @@ export const CodeMirrorEditor = (): JSX.Element => { e) as Extension[]} - theme={theme === 'dark' ? githubDark : githubLight} + extensions={ + [languageExtension(fileInfo?.type), highlightExtension()].filter( + (e) => e, + ) as Extension[] + } + theme={theme === "dark" ? githubDark : githubLight} basicSetup={{ syntaxHighlighting: false, highlightSelectionMatches: false, - highlightActiveLine: false + highlightActiveLine: false, }} - style={{ fontSize: '0.8rem' }} - width={appCtx.gitBlameInfo.length !== 0 ? 'calc(100vw - 280px - 400px)' : 'calc(100vw - 280px)'} - height={'100%'} - minWidth={'calc(1460px - 280px)'} - maxWidth={'calc(100vw - 280px)'} - maxHeight={'calc(100vh - 78px - 48px - 49px)'} - value={fileContent ?? ''} + style={{ fontSize: "0.8rem" }} + width={ + appCtx.gitBlameInfo.length !== 0 + ? "calc(100vw - 280px - 400px)" + : "calc(100vw - 280px)" + } + height={"100%"} + minWidth={"calc(1460px - 280px)"} + maxWidth={"calc(100vw - 280px)"} + maxHeight={"calc(100vh - 78px - 48px - 49px)"} + value={fileContent ?? ""} ref={editorRef} onCreateEditor={(view, state) => { editorRef.current = { view, state }; - const cmScroller = document.querySelector('.cm-scroller') as HTMLDivElement; - const gitBlameContainer = document.querySelector('#gitBlameContainer') as HTMLDivElement; + const cmScroller = document.querySelector( + ".cm-scroller", + ) as HTMLDivElement; + const gitBlameContainer = document.querySelector( + "#gitBlameContainer", + ) as HTMLDivElement; - cmScroller.addEventListener('scroll', () => { + cmScroller.addEventListener("scroll", () => { gitBlameContainer.scrollTop = cmScroller.scrollTop; }); }} @@ -367,7 +448,10 @@ export const CodeMirrorEditor = (): JSX.Element => { onContextMenu={(e) => handleContextMenu(e)} /> - + ); }; diff --git a/webgui-new/src/components/diagrams/diagrams.tsx b/webgui-new/src/components/diagrams/diagrams.tsx index beb54c45a..0971482df 100644 --- a/webgui-new/src/components/diagrams/diagrams.tsx +++ b/webgui-new/src/components/diagrams/diagrams.tsx @@ -1,27 +1,38 @@ -import { Button, IconButton, Modal, Tooltip } from '@mui/material'; -import { ZoomIn, ZoomOut } from '@mui/icons-material'; -import { FileName } from 'components/file-name/file-name'; -import React, { useContext, useEffect, useRef, useState, MouseEvent } from 'react'; +import { Button, IconButton, Modal, Tooltip } from "@mui/material"; +import { ZoomIn, ZoomOut } from "@mui/icons-material"; +import { FileName } from "components/file-name/file-name"; +import React, { + useContext, + useEffect, + useRef, + useState, + MouseEvent, +} from "react"; import { - getCppAstNodeInfo, - getCppDiagram, - getCppDiagramLegend, - getCppFileDiagram, - getCppFileDiagramLegend, - getCppReferenceTypes, - getCppReferences, -} from 'service/cpp-service'; -import { TransformWrapper, TransformComponent, ReactZoomPanPinchRef } from 'react-zoom-pan-pinch'; -import { FileInfo, AstNodeInfo } from '@thrift-generated'; -import { AppContext } from 'global-context/app-context'; -import { getFileInfo } from 'service/project-service'; -import { CodeBites } from 'components/codebites/codebites'; -import * as SC from './styled-components'; -import { convertSelectionRangeToString } from 'utils/utils'; -import { useRouter } from 'next/router'; -import { RouterQueryType } from 'utils/types'; -import { useTranslation } from 'react-i18next'; -import { sendGAEvent } from 'utils/analytics'; + createClient, + getAstNodeInfo, + getDiagram, + getDiagramLegend, + getFileDiagram, + getFileDiagramLegend, + getReferenceTypes, + getReferences, +} from "service/language-service"; +import { + TransformWrapper, + TransformComponent, + ReactZoomPanPinchRef, +} from "react-zoom-pan-pinch"; +import { FileInfo, AstNodeInfo } from "@thrift-generated"; +import { AppContext } from "global-context/app-context"; +import { getFileInfo } from "service/project-service"; +import { CodeBites } from "components/codebites/codebites"; +import * as SC from "./styled-components"; +import { convertSelectionRangeToString } from "utils/utils"; +import { useRouter } from "next/router"; +import { RouterQueryType } from "utils/types"; +import { useTranslation } from "react-i18next"; +import { sendGAEvent } from "utils/analytics"; export const Diagrams = (): JSX.Element => { const { t } = useTranslation(); @@ -30,7 +41,9 @@ export const Diagrams = (): JSX.Element => { const [legendModalOpen, setLegendModalOpen] = useState(false); const [exportTooltipOpen, setExportTooltipOpen] = useState(false); - const [diagramInfo, setDiagramInfo] = useState(undefined); + const [diagramInfo, setDiagramInfo] = useState< + FileInfo | AstNodeInfo | undefined + >(undefined); const [codeBitesDisplayed, setCodeBitesDisplayed] = useState(false); const diagramContainerRef = useRef(null); @@ -38,23 +51,41 @@ export const Diagrams = (): JSX.Element => { const transformComponentRef = useRef(null); useEffect(() => { - if (appCtx.diagramGenId === '' || appCtx.diagramTypeId === '' || !appCtx.diagramType) return; + if ( + appCtx.diagramGenId === "" || + appCtx.diagramTypeId === "" || + !appCtx.diagramType + ) + return; const init = async () => { - const initDiagramInfo = - appCtx.diagramType === 'file' + const fileInfoForClient = + appCtx.diagramType === "file" ? await getFileInfo(appCtx.diagramGenId) - : appCtx.diagramType === 'ast' - ? await getCppAstNodeInfo(appCtx.diagramGenId) - : undefined; + : appCtx.projectFileId + ? await getFileInfo(appCtx.projectFileId) + : undefined; + + createClient(appCtx.workspaceId, fileInfoForClient?.type); + + const initDiagramInfo = + appCtx.diagramType === "file" + ? fileInfoForClient + : appCtx.diagramType === "ast" + ? await getAstNodeInfo(appCtx.diagramGenId) + : undefined; if (!initDiagramInfo) return; if (parseInt(appCtx.diagramTypeId) === 999) { - const refTypes = await getCppReferenceTypes(initDiagramInfo.id as string); - if (refTypes.get('Definition') === undefined) return; + const refTypes = await getReferenceTypes(initDiagramInfo.id as string); + if (refTypes.get("Definition") === undefined) return; const astNodeDef = ( - await getCppReferences(initDiagramInfo.id as string, refTypes.get('Definition') as number, []) + await getReferences( + initDiagramInfo.id as string, + refTypes.get("Definition") as number, + [], + ) )[0]; setDiagramInfo(astNodeDef); @@ -64,11 +95,17 @@ export const Diagrams = (): JSX.Element => { const diagram = initDiagramInfo instanceof FileInfo - ? await getCppFileDiagram(appCtx.diagramGenId, parseInt(appCtx.diagramTypeId)) + ? await getFileDiagram( + appCtx.diagramGenId, + parseInt(appCtx.diagramTypeId), + ) : initDiagramInfo instanceof AstNodeInfo - ? await getCppDiagram(appCtx.diagramGenId, parseInt(appCtx.diagramTypeId)) - : ''; - + ? await getDiagram( + appCtx.diagramGenId, + parseInt(appCtx.diagramTypeId), + ) + : ""; + sendGAEvent({ event_action: `load_diagram: ${appCtx.diagramTypeId}`, event_category: appCtx.workspaceId, @@ -76,16 +113,16 @@ export const Diagrams = (): JSX.Element => { initDiagramInfo instanceof FileInfo ? initDiagramInfo.name : initDiagramInfo instanceof AstNodeInfo - ? initDiagramInfo.astNodeValue - : '', + ? initDiagramInfo.astNodeValue + : "", }); - + const parser = new DOMParser(); - const parsedDiagram = parser.parseFromString(diagram, 'text/xml'); - const diagramSvg = parsedDiagram.getElementsByTagName('svg')[0]; + const parsedDiagram = parser.parseFromString(diagram, "text/xml"); + const diagramSvg = parsedDiagram.getElementsByTagName("svg")[0]; - diagramSvg.style.height = '100%'; - diagramSvg.style.cursor = 'pointer'; + diagramSvg.style.height = "100%"; + diagramSvg.style.cursor = "pointer"; transformComponentRef?.current?.resetTransform(); diagramContainerRef?.current?.replaceChildren(diagramSvg); @@ -93,47 +130,67 @@ export const Diagrams = (): JSX.Element => { setDiagramInfo(initDiagramInfo); }; init(); - }, [appCtx.diagramGenId, appCtx.diagramTypeId, appCtx.diagramType, appCtx.workspaceId]); + }, [ + appCtx.diagramGenId, + appCtx.diagramTypeId, + appCtx.diagramType, + appCtx.projectFileId, + appCtx.workspaceId, + ]); const generateDiagram = async (e: MouseEvent) => { const parentNode = (e.target as HTMLElement)?.parentElement; - if ((parentNode?.className as unknown as SVGAnimatedString).baseVal !== 'node') return; + if ( + (parentNode?.className as unknown as SVGAnimatedString).baseVal !== "node" + ) + return; const diagramGenId = parentNode?.id as string; - const initDiagramInfo = - appCtx.diagramType === 'file' + const fileInfoForClient = + appCtx.diagramType === "file" ? await getFileInfo(appCtx.diagramGenId) - : appCtx.diagramType === 'ast' - ? await getCppAstNodeInfo(appCtx.diagramGenId) - : undefined; + : appCtx.projectFileId + ? await getFileInfo(appCtx.projectFileId) + : undefined; + + createClient(appCtx.workspaceId, fileInfoForClient?.type); + + const initDiagramInfo = + appCtx.diagramType === "file" + ? fileInfoForClient + : appCtx.diagramType === "ast" + ? await getAstNodeInfo(appCtx.diagramGenId) + : undefined; if (!initDiagramInfo) return; if (initDiagramInfo instanceof FileInfo) { router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, projectFileId: diagramGenId, } as RouterQueryType, }); } else if (initDiagramInfo instanceof AstNodeInfo) { - const astNodeInfo = await getCppAstNodeInfo(diagramGenId); + const astNodeInfo = await getAstNodeInfo(diagramGenId); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, projectFileId: astNodeInfo?.range?.file as string, - editorSelection: convertSelectionRangeToString(astNodeInfo?.range?.range), + editorSelection: convertSelectionRangeToString( + astNodeInfo?.range?.range, + ), languageNodeId: diagramGenId, } as RouterQueryType, }); } router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, diagramGenId: diagramGenId, @@ -146,23 +203,27 @@ export const Diagrams = (): JSX.Element => { const diagramLegend = diagramInfo instanceof FileInfo - ? await getCppFileDiagramLegend(parseInt(appCtx.diagramTypeId)) + ? await getFileDiagramLegend(parseInt(appCtx.diagramTypeId)) : diagramInfo instanceof AstNodeInfo - ? await getCppDiagramLegend(parseInt(appCtx.diagramTypeId)) - : ''; + ? await getDiagramLegend(parseInt(appCtx.diagramTypeId)) + : ""; const parser = new DOMParser(); - const parsedDiagramLegend = parser.parseFromString(diagramLegend, 'text/xml'); - const diagramLegendSvg = parsedDiagramLegend.getElementsByTagName('svg')[0]; + const parsedDiagramLegend = parser.parseFromString( + diagramLegend, + "text/xml", + ); + const diagramLegendSvg = parsedDiagramLegend.getElementsByTagName("svg")[0]; - diagramLegendSvg.style.height = '100%'; + diagramLegendSvg.style.height = "100%"; diagramLegendContainerRef.current.replaceChildren(diagramLegendSvg); setLegendModalOpen(true); }; const exportDiagramSVG = () => { - const svgString = diagramContainerRef.current?.querySelector('svg')?.outerHTML as string; + const svgString = diagramContainerRef.current?.querySelector("svg") + ?.outerHTML as string; navigator.clipboard.writeText(svgString); setExportTooltipOpen(true); setTimeout(() => { @@ -171,16 +232,18 @@ export const Diagrams = (): JSX.Element => { }; const downloadSVG = () => { - const svgElement = diagramContainerRef.current?.querySelector('svg') as SVGSVGElement; + const svgElement = diagramContainerRef.current?.querySelector( + "svg", + ) as SVGSVGElement; if (!svgElement) return; const svgCode = new XMLSerializer().serializeToString(svgElement); - const blob = new Blob([svgCode], { type: 'image/svg+xml' }); + const blob = new Blob([svgCode], { type: "image/svg+xml" }); const url = URL.createObjectURL(blob); - const link = document.createElement('a'); - link.setAttribute('download', 'file-diagram.svg'); - link.setAttribute('href', url); + const link = document.createElement("a"); + link.setAttribute("download", "file-diagram.svg"); + link.setAttribute("href", url); link.click(); }; @@ -188,8 +251,8 @@ export const Diagrams = (): JSX.Element => { <> {diagramInfo instanceof FileInfo ? ( @@ -210,20 +273,27 @@ export const Diagrams = (): JSX.Element => { zoomIn()}> - + zoomOut()}> - generateDiagram(e)} /> + generateDiagram(e)} + /> )} - + { disableFocusListener disableHoverListener disableTouchListener - title={t('diagrams.copyNotification')} + title={t("diagrams.copyNotification")} arrow > - + - - setLegendModalOpen(false)} keepMounted> + + setLegendModalOpen(false)} + keepMounted + > @@ -250,6 +328,6 @@ export const Diagrams = (): JSX.Element => { ) : ( - {t('diagrams.noDiagram')} + {t("diagrams.noDiagram")} ); }; diff --git a/webgui-new/src/components/file-context-menu/file-context-menu.tsx b/webgui-new/src/components/file-context-menu/file-context-menu.tsx index e3e58db40..cfd2c3480 100644 --- a/webgui-new/src/components/file-context-menu/file-context-menu.tsx +++ b/webgui-new/src/components/file-context-menu/file-context-menu.tsx @@ -1,18 +1,24 @@ -import React, { Dispatch, SetStateAction, useContext, useEffect, useState } from 'react'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import { TabName } from 'enums/tab-enum'; -import { FileInfo } from '@thrift-generated'; -import { AppContext } from 'global-context/app-context'; -import { getCppFileDiagramTypes } from 'service/cpp-service'; -import { Tooltip } from '@mui/material'; -import { ChevronRight } from '@mui/icons-material'; -import { getBlameInfo, getRepositoryByProjectPath } from 'service/git-service'; -import { useRouter } from 'next/router'; -import { RouterQueryType } from 'utils/types'; -import { useTranslation } from 'react-i18next'; -import { diagramTypeArray } from 'enums/entity-types'; -import { sendGAEvent } from 'utils/analytics'; +import React, { + Dispatch, + SetStateAction, + useContext, + useEffect, + useState, +} from "react"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import { TabName } from "enums/tab-enum"; +import { FileInfo } from "@thrift-generated"; +import { AppContext } from "global-context/app-context"; +import { createClient, getFileDiagramTypes } from "service/language-service"; +import { Tooltip } from "@mui/material"; +import { ChevronRight } from "@mui/icons-material"; +import { getBlameInfo, getRepositoryByProjectPath } from "service/git-service"; +import { useRouter } from "next/router"; +import { RouterQueryType } from "utils/types"; +import { useTranslation } from "react-i18next"; +import { diagramTypeArray } from "enums/entity-types"; +import { sendGAEvent } from "utils/analytics"; export const FileContextMenu = ({ contextMenu, @@ -35,25 +41,30 @@ export const FileContextMenu = ({ const router = useRouter(); const appCtx = useContext(AppContext); - const [diagramTypes, setDiagramTypes] = useState>(new Map()); + const [diagramTypes, setDiagramTypes] = useState>( + new Map(), + ); useEffect(() => { if (!fileInfo) return; const init = async () => { - const initDiagramTypes = await getCppFileDiagramTypes(fileInfo.id as string); + createClient(appCtx.workspaceId, fileInfo?.type); + const initDiagramTypes = await getFileDiagramTypes(fileInfo.id as string); setDiagramTypes(initDiagramTypes); }; init(); - }, [fileInfo]); + }, [appCtx.workspaceId, fileInfo]); const getGitBlameInfo = async () => { setContextMenu(null); - const currentRepo = await getRepositoryByProjectPath(fileInfo.path as string); + const currentRepo = await getRepositoryByProjectPath( + fileInfo.path as string, + ); const blameInfo = await getBlameInfo( currentRepo?.repoId as string, currentRepo?.commitId as string, currentRepo?.repoPath as string, - fileInfo.id as string + fileInfo.id as string, ); return blameInfo; }; @@ -63,19 +74,23 @@ export const FileContextMenu = ({ open={contextMenu !== null} onClose={() => setContextMenu(null)} anchorReference="anchorPosition" - anchorPosition={contextMenu !== null ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined} + anchorPosition={ + contextMenu !== null + ? { top: contextMenu.mouseY, left: contextMenu.mouseX } + : undefined + } > {fileInfo && fileInfo.isDirectory && ( { sendGAEvent({ - event_action: 'metrics', + event_action: "metrics", event_category: appCtx.workspaceId, event_label: fileInfo.name, }); setContextMenu(null); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, metricsGenId: fileInfo?.id as string, @@ -84,7 +99,7 @@ export const FileContextMenu = ({ }); }} > - {t('fileContextMenu.metrics')} + {t("fileContextMenu.metrics")} )} {fileInfo && !fileInfo.isDirectory && ( @@ -94,7 +109,7 @@ export const FileContextMenu = ({ appCtx.setGitBlameInfo(blameInfo); }} > - {t('fileContextMenu.gitBlame')} + {t("fileContextMenu.gitBlame")} )} {diagramTypes.size !== 0 ? ( @@ -107,12 +122,14 @@ export const FileContextMenu = ({ onClick={() => { setContextMenu(null); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, diagramGenId: fileInfo.id as string, - diagramTypeId: (diagramTypes.get(type) as number).toString(), - diagramType: 'file', + diagramTypeId: ( + diagramTypes.get(type) as number + ).toString(), + diagramType: "file", activeTab: TabName.DIAGRAMS.toString(), } as RouterQueryType, }); @@ -123,15 +140,15 @@ export const FileContextMenu = ({ ))} } - placement={'right-start'} + placement={"right-start"} > - -
{t('fileContextMenu.diagrams')}
+ +
{t("fileContextMenu.diagrams")}
) : ( - '' + "" )} ); diff --git a/webgui-new/src/components/file-name/file-name.tsx b/webgui-new/src/components/file-name/file-name.tsx index 7858cb82e..411bae70f 100644 --- a/webgui-new/src/components/file-name/file-name.tsx +++ b/webgui-new/src/components/file-name/file-name.tsx @@ -1,18 +1,30 @@ -import { Button, Table, TableHead, TableBody, TableRow, TableCell } from '@mui/material'; -import { ChevronRight, Code, ExpandMore } from '@mui/icons-material'; -import { AstNodeInfo, BuildLog, FileInfo, Range } from '@thrift-generated'; -import React, { useContext, useEffect, useState } from 'react'; -import { getCppFileReferenceCount, getCppFileReferences, getCppFileReferenceTypes } from 'service/cpp-service'; -import { TabName } from 'enums/tab-enum'; -import { AppContext } from 'global-context/app-context'; -import { RefIcon } from 'components/custom-icon/custom-icon'; -import * as SC from './styled-components'; -import { convertSelectionRangeToString } from 'utils/utils'; -import { useRouter } from 'next/router'; -import { RouterQueryType } from 'utils/types'; -import { getBuildLog } from 'service/project-service'; -import { useTranslation } from 'react-i18next'; -import { fileReferenceTypeArray } from 'enums/entity-types'; +import { + Button, + Table, + TableHead, + TableBody, + TableRow, + TableCell, +} from "@mui/material"; +import { ChevronRight, Code, ExpandMore } from "@mui/icons-material"; +import { AstNodeInfo, BuildLog, FileInfo, Range } from "@thrift-generated"; +import React, { useContext, useEffect, useState } from "react"; +import { + createClient, + getFileReferenceCount, + getFileReferences, + getFileReferenceTypes, +} from "service/language-service"; +import { TabName } from "enums/tab-enum"; +import { AppContext } from "global-context/app-context"; +import { RefIcon } from "components/custom-icon/custom-icon"; +import * as SC from "./styled-components"; +import { convertSelectionRangeToString } from "utils/utils"; +import { useRouter } from "next/router"; +import { RouterQueryType } from "utils/types"; +import { getBuildLog } from "service/project-service"; +import { useTranslation } from "react-i18next"; +import { fileReferenceTypeArray } from "enums/entity-types"; export const FileName = ({ fileName, @@ -34,43 +46,49 @@ export const FileName = ({ const appCtx = useContext(AppContext); const buildLogMessages = { - 0: t('fileName.buildLogs.messageTypes.unknown'), - 1: t('fileName.buildLogs.messageTypes.error'), - 2: t('fileName.buildLogs.messageTypes.fatalError'), - 3: t('fileName.buildLogs.messageTypes.warning'), - 4: t('fileName.buildLogs.messageTypes.note'), - 5: t('fileName.buildLogs.messageTypes.codingRule'), + 0: t("fileName.buildLogs.messageTypes.unknown"), + 1: t("fileName.buildLogs.messageTypes.error"), + 2: t("fileName.buildLogs.messageTypes.fatalError"), + 3: t("fileName.buildLogs.messageTypes.warning"), + 4: t("fileName.buildLogs.messageTypes.note"), + 5: t("fileName.buildLogs.messageTypes.codingRule"), }; const [anchorEl, setAnchorEl] = useState(null); - const [buildLogAnchorEl, setBuildLogAnchorEl] = useState(null); + const [buildLogAnchorEl, setBuildLogAnchorEl] = useState( + null, + ); const [references, setReferences] = useState([]); const [buildLog, setBuildLog] = useState([]); const getParseStatusText = (status: number): string => { if (status === 2) { - return t('fileName.parseStatus.partial'); + return t("fileName.parseStatus.partial"); } else if (status === 3) { - return t('fileName.parseStatus.full'); + return t("fileName.parseStatus.full"); } else { - return t('fileName.parseStatus.noParse'); + return t("fileName.parseStatus.noParse"); } }; const renderFileReferences = async () => { - const fileRefTypes = await getCppFileReferenceTypes(info?.id as string); + createClient(appCtx.workspaceId, info?.type); + const fileRefTypes = await getFileReferenceTypes(info?.id as string); const elements: React.ReactNode[] = []; for (const [key, value] of fileRefTypes) { - const refCountForType = await getCppFileReferenceCount(info?.id as string, value); - const refsForType = await getCppFileReferences(info?.id as string, value); + const refCountForType = await getFileReferenceCount( + info?.id as string, + value, + ); + const refsForType = await getFileReferences(info?.id as string, value); elements.push( + {fileReferenceTypeArray[value]} ({refCountForType}) } @@ -79,12 +97,12 @@ export const FileName = ({ {refsForType.map((info) => { return ( jumpToRef(info)}> - + {info.astNodeValue} ); })} - + , ); } @@ -94,11 +112,13 @@ export const FileName = ({ const jumpToRef = async (astNodeInfo: AstNodeInfo) => { const fileId = astNodeInfo.range?.file as string; router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, projectFileId: fileId, - editorSelection: convertSelectionRangeToString(astNodeInfo.range?.range as Range), + editorSelection: convertSelectionRangeToString( + astNodeInfo.range?.range as Range, + ), activeTab: TabName.CODE.toString(), } as RouterQueryType, }); @@ -106,7 +126,7 @@ export const FileName = ({ const jumpToBuildLog = (range: Range) => { router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, projectFileId: info?.id as string, @@ -130,7 +150,9 @@ export const FileName = ({ {info ? ( <> - + {!info.isDirectory && ( <> parseStatus === 2 ? theme.colors?.warning : parseStatus === 3 - ? theme.colors?.success - : theme.colors?.error, + ? theme.colors?.success + : theme.colors?.error, border: (theme) => `1px solid ${ parseStatus === 2 ? theme.colors?.warning : parseStatus === 3 - ? theme.colors?.success - : theme.colors?.error + ? theme.colors?.success + : theme.colors?.error }`, - cursor: 'pointer', + cursor: "pointer", }} > {getParseStatusText(parseStatus)} @@ -166,21 +188,30 @@ export const FileName = ({ open={Boolean(buildLogAnchorEl)} onClose={() => setBuildLogAnchorEl(null)} > - {t('fileName.buildLogs.title')} + + {t("fileName.buildLogs.title")} + {buildLog.length ? ( - {t('fileName.buildLogs.type')} - {t('fileName.buildLogs.message')} - {t('fileName.buildLogs.from')} - {t('fileName.buildLogs.to')} + {t("fileName.buildLogs.type")} + + {t("fileName.buildLogs.message")} + + {t("fileName.buildLogs.from")} + {t("fileName.buildLogs.to")} {buildLog.map((log, idx) => ( - jumpToBuildLog(log.range as Range)}> - {buildLogMessages[log.messageType ?? '0']} + jumpToBuildLog(log.range as Range)} + > + + {buildLogMessages[log.messageType ?? "0"]} + {log.message} {`${log.range?.startpos?.line}:${log.range?.startpos?.column}`} {`${log.range?.endpos?.line}:${log.range?.endpos?.column}`} @@ -189,16 +220,18 @@ export const FileName = ({
) : ( -
{t('fileName.buildLogs.noBuildLogs')}
+
{t("fileName.buildLogs.noBuildLogs")}
)} )}
{fileName}
-
{'::'}
+
{"::"}
{filePath}
{gitBlameEnabled && ( - + )}
{!info.isDirectory && !hideFileRefMenu && ( @@ -209,14 +242,18 @@ export const FileName = ({ renderFileReferences(); }} > - {t('fileName.fileRefs.title')} + {t("fileName.fileRefs.title")} - setAnchorEl(null)}> + setAnchorEl(null)} + > } defaultEndIcon={} defaultCollapseIcon={} - sx={{ width: 'max-content' }} + sx={{ width: "max-content" }} > {references} @@ -225,7 +262,7 @@ export const FileName = ({ )} ) : ( -
{t('fileName.noFile')}
+
{t("fileName.noFile")}
)}
); diff --git a/webgui-new/src/components/header/header.tsx b/webgui-new/src/components/header/header.tsx index a76985445..68573622a 100644 --- a/webgui-new/src/components/header/header.tsx +++ b/webgui-new/src/components/header/header.tsx @@ -1,21 +1,32 @@ -import { IconButton, InputAdornment, TextField, Tooltip } from '@mui/material'; -import { ProjectSelect } from 'components/project-select/project-select'; -import { Search, Settings, LightMode, DarkMode, Info } from '@mui/icons-material'; -import React, { KeyboardEvent, useContext, useEffect, useState } from 'react'; -import { SearchOptions, SearchMethods, SearchMainLanguages, SearchTypeOptions } from 'enums/search-enum'; -import { enumToArray } from 'utils/utils'; -import { ThemeContext } from 'global-context/theme-context'; -import { SettingsMenu } from 'components/settings-menu/settings-menu'; -import { getTooltipText } from './get-tooltip-text'; -import { getSearchTypes } from 'service/search-service'; -import { SearchType } from '@thrift-generated'; -import { AccordionLabel } from 'enums/accordion-enum'; -import { getStore, removeStore, setStore } from 'utils/store'; -import { AppContext } from 'global-context/app-context'; -import * as SC from './styled-components'; -import { useRouter } from 'next/router'; -import { RouterQueryType } from 'utils/types'; -import { sendGAEvent } from 'utils/analytics'; +import { IconButton, InputAdornment, TextField, Tooltip } from "@mui/material"; +import { ProjectSelect } from "components/project-select/project-select"; +import { + Search, + Settings, + LightMode, + DarkMode, + Info, +} from "@mui/icons-material"; +import React, { KeyboardEvent, useContext, useEffect, useState } from "react"; +import { + SearchOptions, + SearchMethods, + SearchMainLanguages, + SearchTypeOptions, +} from "enums/search-enum"; +import { enumToArray } from "utils/utils"; +import { ThemeContext } from "global-context/theme-context"; +import { SettingsMenu } from "components/settings-menu/settings-menu"; +import { getTooltipText } from "./get-tooltip-text"; +import { getSearchTypes } from "service/search-service"; +import { SearchType } from "@thrift-generated"; +import { AccordionLabel } from "enums/accordion-enum"; +import { getStore, removeStore, setStore } from "utils/store"; +import { AppContext } from "global-context/app-context"; +import * as SC from "./styled-components"; +import { useRouter } from "next/router"; +import { RouterQueryType } from "utils/types"; +import { sendGAEvent } from "utils/analytics"; export const Header = (): JSX.Element => { const router = useRouter(); @@ -23,14 +34,26 @@ export const Header = (): JSX.Element => { const { theme, setTheme } = useContext(ThemeContext); const searchTypeOptions = enumToArray(SearchTypeOptions); - const [settingsAnchorEl, setSettingsAnchorEl] = useState(null); - const [searchLanguage, setSearchLanguage] = useState(undefined); - const [searchType, setSearchType] = useState(undefined); + const [settingsAnchorEl, setSettingsAnchorEl] = useState( + null, + ); + const [searchLanguage, setSearchLanguage] = useState( + undefined, + ); + const [searchType, setSearchType] = useState( + undefined, + ); const [searchTypes, setSearchTypes] = useState([]); - const [selectedSearchTypeOptions, setSelectedSearchTypeOptions] = useState(undefined); + const [selectedSearchTypeOptions, setSelectedSearchTypeOptions] = useState< + string[] | undefined + >(undefined); const [searchQuery, setSearchQuery] = useState(undefined); - const [searchFileFilterQuery, setSearchFileFilterQuery] = useState(undefined); - const [searchDirFilterQuery, setSearchDirFilterQuery] = useState(undefined); + const [searchFileFilterQuery, setSearchFileFilterQuery] = useState< + string | undefined + >(undefined); + const [searchDirFilterQuery, setSearchDirFilterQuery] = useState< + string | undefined + >(undefined); useEffect(() => { const init = async () => { @@ -38,11 +61,18 @@ export const Header = (): JSX.Element => { const initSearchTypeOptions = enumToArray(SearchTypeOptions); const initSearchLanguage = enumToArray(SearchMainLanguages)[0]; - const { storedSearchLanguage, storedSearchType, storedSelectedSearchTypeOptions, storedSearchProps } = getStore(); + const { + storedSearchLanguage, + storedSearchType, + storedSelectedSearchTypeOptions, + storedSearchProps, + } = getStore(); setSearchLanguage(storedSearchLanguage ?? initSearchLanguage); setSearchType(storedSearchType ?? initSearchTypes[0]); setSearchTypes(initSearchTypes); - setSelectedSearchTypeOptions(storedSelectedSearchTypeOptions ?? initSearchTypeOptions); + setSelectedSearchTypeOptions( + storedSelectedSearchTypeOptions ?? initSearchTypeOptions, + ); if (storedSearchProps) { setSearchQuery(storedSearchProps.initialQuery); @@ -62,20 +92,21 @@ export const Header = (): JSX.Element => { }, [searchType, searchLanguage, selectedSearchTypeOptions]); const handleSearch = async (e: KeyboardEvent) => { - if (e.key === 'Enter') { + if (e.key === "Enter") { if (!searchQuery) return; const query = createQueryString(searchQuery); - if (query === '') return; + if (query === "") return; - const fileSearch = searchType?.name === SearchOptions.FILE_NAME.toString(); + const fileSearch = + searchType?.name === SearchOptions.FILE_NAME.toString(); const initSearchProps = { initialQuery: searchQuery, fileSearch: fileSearch, type: searchType?.id as number, query: query, - fileFilter: searchFileFilterQuery ?? '', - dirFilter: searchDirFilterQuery ?? '', + fileFilter: searchFileFilterQuery ?? "", + dirFilter: searchDirFilterQuery ?? "", start: 0, size: 10, }; @@ -87,19 +118,22 @@ export const Header = (): JSX.Element => { }); sendGAEvent({ - event_action: `search: ${searchType ? searchType.name : 'undefined'}`, + event_action: `search: ${searchType ? searchType.name : "undefined"}`, event_category: appCtx.workspaceId, event_label: query, }); router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, activeAccordion: AccordionLabel.SEARCH_RESULTS, } as RouterQueryType, }); - removeStore(['storedExpandedSearchFileNodes', 'storedExpandedSearchPathNodes']); + removeStore([ + "storedExpandedSearchFileNodes", + "storedExpandedSearchPathNodes", + ]); } else { return; } @@ -107,29 +141,31 @@ export const Header = (): JSX.Element => { const getSearchTypeOptionQuery = (searchType: string): string => { if (searchType === SearchTypeOptions.TYPE) { - return 'type'; + return "type"; } else if (searchType === SearchTypeOptions.FUNCTION) { - return 'func'; + return "func"; } else if (searchType === SearchTypeOptions.CONSTANT) { - return 'const'; + return "const"; } else if (searchType === SearchTypeOptions.VARIABLE) { - return 'var'; + return "var"; } else if (searchType === SearchTypeOptions.FIELD) { - return 'field'; + return "field"; } else if (searchType === SearchTypeOptions.LABEL) { - return 'label'; + return "label"; } else if (searchType === SearchTypeOptions.MACRO) { - return 'macro'; + return "macro"; } else if (searchType === SearchTypeOptions.MODULE) { - return 'module'; + return "module"; } else { - return ''; + return ""; } }; const getSearchLanguageQuery = (language: string): string => { if (language === SearchMainLanguages.C_CPP) { return '(mime:("text/x-c") OR mime:("text/x-c++"))'; + } else if (language === SearchMainLanguages.C_SHARP) { + return '(mime:("text/x-csharp") OR mime:("text/x-cs"))'; } else if (language === SearchMainLanguages.JAVA) { return '(mime:("text/x-java") OR mime:("text/x-java-source"))'; } else if (language === SearchMainLanguages.JAVASCRIPT) { @@ -140,44 +176,47 @@ export const Header = (): JSX.Element => { return '(mime:("text/x-perl"))'; } else if (language === SearchMainLanguages.PYTHON) { return '(mime:("text/x-python"))'; - } else if (language === 'Any') { - return ''; + } else if (language === "Any") { + return ""; } else { return `(mime:("${language}"))`; } }; const createQueryString = (queryString: string): string => { - if (!searchLanguage || !selectedSearchTypeOptions) return ''; + if (!searchLanguage || !selectedSearchTypeOptions) return ""; - let modifiedQueryString = ''; + let modifiedQueryString = ""; const textSearch = searchType?.name === SearchOptions.TEXT.toString(); - const definitionSearch = searchType?.name === SearchOptions.DEFINITION.toString(); - const allTypesSelected = searchTypeOptions.every((t) => selectedSearchTypeOptions.includes(t)); - const anyLanguage = searchLanguage === 'Any'; + const definitionSearch = + searchType?.name === SearchOptions.DEFINITION.toString(); + const allTypesSelected = searchTypeOptions.every((t) => + selectedSearchTypeOptions.includes(t), + ); + const anyLanguage = searchLanguage === "Any"; if (definitionSearch) { - modifiedQueryString = queryString === '' ? '' : `defs:(${queryString})`; + modifiedQueryString = queryString === "" ? "" : `defs:(${queryString})`; if (!allTypesSelected) { - modifiedQueryString += queryString === '' ? '' : ' AND '; + modifiedQueryString += queryString === "" ? "" : " AND "; modifiedQueryString += `(${getSearchTypeOptionQuery(selectedSearchTypeOptions[0])}:(${queryString})`; if (selectedSearchTypeOptions.length > 1) { for (let i = 1; i < selectedSearchTypeOptions.length; ++i) { modifiedQueryString += ` OR ${getSearchTypeOptionQuery(selectedSearchTypeOptions[i])}:(${queryString})`; } } - modifiedQueryString += ')'; + modifiedQueryString += ")"; } if (!anyLanguage) { modifiedQueryString += - queryString === '' && modifiedQueryString === '' + queryString === "" && modifiedQueryString === "" ? `${getSearchLanguageQuery(searchLanguage)}` : ` AND ${getSearchLanguageQuery(searchLanguage)}`; } } else if (textSearch) { if (!anyLanguage) { modifiedQueryString = - queryString === '' + queryString === "" ? `${getSearchLanguageQuery(searchLanguage)}` : `${queryString} AND ${getSearchLanguageQuery(searchLanguage)}`; } else { @@ -196,25 +235,27 @@ export const Header = (): JSX.Element => { setSearchQuery(e.target.value)} onKeyDown={(e) => handleSearch(e)} placeholder={ searchType?.name === SearchOptions.FILE_NAME - ? 'File name regex' + ? "File name regex" : searchType?.name === SearchOptions.LOG - ? 'Arbitrary log message' - : 'Search by expression' + ? "Arbitrary log message" + : "Search by expression" } InputProps={{ startAdornment: ( - + ), endAdornment: ( - - setSettingsAnchorEl(e.currentTarget)}> + + setSettingsAnchorEl(e.currentTarget)} + > @@ -225,18 +266,18 @@ export const Header = (): JSX.Element => { }} /> setSearchFileFilterQuery(e.target.value)} onKeyDown={(e) => handleSearch(e)} - placeholder={'File name filter regex'} + placeholder={"File name filter regex"} InputProps={{ startAdornment: ( - + ), endAdornment: ( - + @@ -245,18 +286,18 @@ export const Header = (): JSX.Element => { }} /> setSearchDirFilterQuery(e.target.value)} onKeyDown={(e) => handleSearch(e)} - placeholder={'Path filter regex'} + placeholder={"Path filter regex"} InputProps={{ startAdornment: ( - + ), endAdornment: ( - + @@ -276,8 +317,10 @@ export const Header = (): JSX.Element => { setSelectedSearchTypeOptions={setSelectedSearchTypeOptions} /> - setTheme(theme === 'dark' ? 'light' : 'dark')}> - {theme === 'dark' ? : } + setTheme(theme === "dark" ? "light" : "dark")} + > + {theme === "dark" ? : } diff --git a/webgui-new/src/components/metrics/metrics.tsx b/webgui-new/src/components/metrics/metrics.tsx index 17ddb2ad1..3e5b787cd 100644 --- a/webgui-new/src/components/metrics/metrics.tsx +++ b/webgui-new/src/components/metrics/metrics.tsx @@ -8,16 +8,23 @@ import { OutlinedInput, Select, Tooltip, -} from '@mui/material'; -import { FileName } from 'components/file-name/file-name'; -import React, { Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'; -import { getMetrics, getMetricsTypeNames } from 'service/metrics-service'; -import { FileInfo, MetricsType, MetricsTypeName } from '@thrift-generated'; -import { Treemap } from 'recharts'; -import { AppContext } from 'global-context/app-context'; -import { getFileInfoByPath } from 'service/project-service'; -import * as SC from './styled-components'; -import { useTranslation } from 'react-i18next'; +} from "@mui/material"; +import { FileName } from "components/file-name/file-name"; +import React, { + Dispatch, + SetStateAction, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import { getMetrics, getMetricsTypeNames } from "service/metrics-service"; +import { FileInfo, MetricsType, MetricsTypeName } from "@thrift-generated"; +import { Treemap } from "recharts"; +import { AppContext } from "global-context/app-context"; +import { getFileInfoByPath } from "service/project-service"; +import * as SC from "./styled-components"; +import { useTranslation } from "react-i18next"; type RespType = { [key: string]: { @@ -50,7 +57,7 @@ const CustomizedContent = ( filePath: string; setFilePath: Dispatch>; setFileInfo: Dispatch>; - } + }, ): JSX.Element => { const { x, y, width, height, depth, size, name, children } = props as { x: number; @@ -79,10 +86,10 @@ const CustomizedContent = ( const newFileInfo = await getFileInfoByPath(newPath); const newData = children.map((child) => Object.fromEntries([ - ['name', child.name], - ['size', child.size], - ['children', child.children], - ]) + ["name", child.name], + ["size", child.size], + ["children", child.children], + ]), ); props.setFileInfo(newFileInfo); @@ -100,14 +107,21 @@ const CustomizedContent = ( height={height} sx={{ fill: getDarkenedRGBCode(size), - stroke: '#FFF', - ':hover': { - fill: props.children ? '#9597E4' : getDarkenedRGBCode(size), + stroke: "#FFF", + ":hover": { + fill: props.children ? "#9597E4" : getDarkenedRGBCode(size), }, - cursor: props.children ? 'pointer' : '', + cursor: props.children ? "pointer" : "", }} /> - + {name} @@ -117,7 +131,7 @@ const CustomizedContent = ( ); }; -const fileTypeOptions = ['Unknown', 'Dir', 'Binary', 'CPP']; +const fileTypeOptions = ["Unknown", "Dir", "Binary", "CPP", "CS"]; const sumSizes = (node: DataItem): number => { let sum = 0; @@ -148,7 +162,7 @@ const convertResObject = (res: RespType): DataItem => { for (const key in obj) { const value = obj[key]; - if (typeof value === 'object') { + if (typeof value === "object") { const child = convertResObject({ [key]: value } as RespType); child.name = key; child.size = sumSizes(child); @@ -166,12 +180,17 @@ export const Metrics = (): JSX.Element => { const appCtx = useContext(AppContext); const [fileInfo, setFileInfo] = useState(undefined); - const [filePath, setFilePath] = useState(''); - const [initialPath, setInitialPath] = useState(''); + const [filePath, setFilePath] = useState(""); + const [initialPath, setInitialPath] = useState(""); const [data, setData] = useState(undefined); - const [selectedFileTypeOptions, setSelectedFileTypeOptions] = useState(fileTypeOptions); - const [sizeDimension, setSizeDimension] = useState(undefined); - const [metricsTypeNames, setMetricsTypeNames] = useState([]); + const [selectedFileTypeOptions, setSelectedFileTypeOptions] = + useState(fileTypeOptions); + const [sizeDimension, setSizeDimension] = useState< + MetricsTypeName | undefined + >(undefined); + const [metricsTypeNames, setMetricsTypeNames] = useState( + [], + ); useEffect(() => { if (!appCtx.metricsGenId) return; @@ -181,7 +200,7 @@ export const Metrics = (): JSX.Element => { const metricsRes = await getMetrics( appCtx.metricsGenId, fileTypeOptions, - initMetricsTypeNames[0].type as MetricsType + initMetricsTypeNames[0].type as MetricsType, ); const convertedObject = convertResObject(JSON.parse(metricsRes)); @@ -209,7 +228,7 @@ export const Metrics = (): JSX.Element => { const metricsRes = await getMetrics( fInfo?.id as string, selectedFileTypeOptions, - sizeDimension?.type as MetricsType + sizeDimension?.type as MetricsType, ); const convertedObject = convertResObject(JSON.parse(metricsRes)); if (fPath) { @@ -246,15 +265,15 @@ export const Metrics = (): JSX.Element => { return appCtx.metricsGenId && fileInfo && sizeDimension ? ( <> - {t('metrics.fileType')} + {t("metrics.fileType")} - {t('metrics.sizeDimension')} + {t("metrics.sizeDimension")} - + - {filePath.split('/').map((p, idx) => ( + {filePath.split("/").map((p, idx) => ( theme.backgroundColors?.secondary, }, } : {} } onClick={() => { - if (idx === filePath.split('/').length - 1 || initialPath.slice(0, -1).includes(p)) return; + if ( + idx === filePath.split("/").length - 1 || + initialPath.slice(0, -1).includes(p) + ) + return; const trimmedPath = filePath - ?.split('/') + ?.split("/") .slice(0, idx + 1) - .join('/') as string; + .join("/") as string; generateMetrics(trimmedPath); }} > @@ -326,6 +358,6 @@ export const Metrics = (): JSX.Element => { ) : ( - {t('metrics.noMetrics')} + {t("metrics.noMetrics")} ); }; diff --git a/webgui-new/src/enums/search-enum.ts b/webgui-new/src/enums/search-enum.ts index 3ea3de14a..81887f24c 100644 --- a/webgui-new/src/enums/search-enum.ts +++ b/webgui-new/src/enums/search-enum.ts @@ -1,65 +1,68 @@ /* eslint-disable no-unused-vars */ -import i18n from 'i18n/i18n'; +import i18n from "i18n/i18n"; export const SearchMethods = { - EXPRESSION: i18n.t('searchSettings.methods.expression'), - FILE_REGEX: i18n.t('searchSettings.methods.fileRegex'), - PATH_REGEX: i18n.t('searchSettings.methods.pathRegex'), + EXPRESSION: i18n.t("searchSettings.methods.expression"), + FILE_REGEX: i18n.t("searchSettings.methods.fileRegex"), + PATH_REGEX: i18n.t("searchSettings.methods.pathRegex"), }; export const SearchOptions = { - TEXT: i18n.t('searchSettings.options.textOption'), - DEFINITION: i18n.t('searchSettings.options.definitionOption'), - FILE_NAME: i18n.t('searchSettings.options.fileNameOption'), - LOG: i18n.t('searchSettings.options.logOption'), + TEXT: i18n.t("searchSettings.options.textOption"), + DEFINITION: i18n.t("searchSettings.options.definitionOption"), + FILE_NAME: i18n.t("searchSettings.options.fileNameOption"), + LOG: i18n.t("searchSettings.options.logOption"), }; export enum SearchMainLanguages { - C_CPP = 'C/C++', - JAVA = 'Java', - JAVASCRIPT = 'JavaScript', - SHELLSCRIPT = 'ShellScript', - PEARL = 'Pearl', - PYTHON = 'Python', + C_CPP = "C/C++", + C_SHARP = "C#", + JAVA = "Java", + JAVASCRIPT = "JavaScript", + SHELLSCRIPT = "ShellScript", + PEARL = "Pearl", + PYTHON = "Python", } export enum SearchOtherLanguages { - X_ASM = 'text/x-asm', - X_BCPL = 'text/x-bcpl', - X_C = 'text/x-c', - X_CPP = 'text/x-c++', - X_AWK = 'text/x-awk', - X_GAWK = 'text/x-gawk', - X_PHP = 'text/x-php', - X_SHELLSCRIPT = 'text/x-shellscript', - X_FORTRAN = 'text/x-fortran', - X_PO = 'text/x-po', - X_JAVA = 'text/x-java', - X_JAVA_SOURCE = 'text/x-java-source', - X_XMCD = 'text/x-xmcd', - X_LISP = 'text/x-lisp', - X_LUA = 'text/x-lua', - X_M4 = 'text/x-m4', - X_MAKEFILE = 'text/x-makefile', - X_VCARD = 'text/x-vcard', - X_MSDOS_BATCH = 'text/x-msdos-batch', - X_PASCAL = 'text/x-pascal', - X_PERL = 'text/x-perl', - X_PYTHON = 'text/x-python', - X_RUBY = 'text/x-ruby', - X_TCL = 'text/x_tcl', - X_INFO = 'text/x-info', - X_TEX = 'text/x-tex', - X_TEXINFO = 'text/x-texinfo', + X_ASM = "text/x-asm", + X_BCPL = "text/x-bcpl", + X_C = "text/x-c", + X_CPP = "text/x-c++", + X_CSHARP = "text/x-csharp", + X_CS = "text/x-cs", + X_AWK = "text/x-awk", + X_GAWK = "text/x-gawk", + X_PHP = "text/x-php", + X_SHELLSCRIPT = "text/x-shellscript", + X_FORTRAN = "text/x-fortran", + X_PO = "text/x-po", + X_JAVA = "text/x-java", + X_JAVA_SOURCE = "text/x-java-source", + X_XMCD = "text/x-xmcd", + X_LISP = "text/x-lisp", + X_LUA = "text/x-lua", + X_M4 = "text/x-m4", + X_MAKEFILE = "text/x-makefile", + X_VCARD = "text/x-vcard", + X_MSDOS_BATCH = "text/x-msdos-batch", + X_PASCAL = "text/x-pascal", + X_PERL = "text/x-perl", + X_PYTHON = "text/x-python", + X_RUBY = "text/x-ruby", + X_TCL = "text/x_tcl", + X_INFO = "text/x-info", + X_TEX = "text/x-tex", + X_TEXINFO = "text/x-texinfo", } export const SearchTypeOptions = { - TYPE: i18n.t('searchSettings.types.type'), - FUNCTION: i18n.t('searchSettings.types.function'), - CONSTANT: i18n.t('searchSettings.types.constant'), - VARIABLE: i18n.t('searchSettings.types.variable'), - FIELD: i18n.t('searchSettings.types.field'), - LABEL: i18n.t('searchSettings.types.label'), - MACRO: i18n.t('searchSettings.types.macro'), - MODULE: i18n.t('searchSettings.types.module'), + TYPE: i18n.t("searchSettings.types.type"), + FUNCTION: i18n.t("searchSettings.types.function"), + CONSTANT: i18n.t("searchSettings.types.constant"), + VARIABLE: i18n.t("searchSettings.types.variable"), + FIELD: i18n.t("searchSettings.types.field"), + LABEL: i18n.t("searchSettings.types.label"), + MACRO: i18n.t("searchSettings.types.macro"), + MODULE: i18n.t("searchSettings.types.module"), }; diff --git a/webgui-new/src/global-context/app-context.tsx b/webgui-new/src/global-context/app-context.tsx index 7fa462f20..852556ebd 100644 --- a/webgui-new/src/global-context/app-context.tsx +++ b/webgui-new/src/global-context/app-context.tsx @@ -1,19 +1,21 @@ -import React, { createContext, useEffect, useState } from 'react'; -import { GitBlameHunk, WorkspaceInfo } from '@thrift-generated'; -import { createWorkspaceClient, getWorkspaces } from 'service/workspace-service'; -import { createProjectClient, getLabels } from 'service/project-service'; -import { createSearchClient } from 'service/search-service'; -import { createCppClient } from 'service/cpp-service'; -import { createCppReparseClient } from 'service/cpp-reparse-service'; -import { createMetricsClient } from 'service/metrics-service'; -import { createGitClient } from 'service/git-service'; -import { createConfig } from 'service/config'; -import { SearchProps } from 'utils/types'; -import { TabName } from 'enums/tab-enum'; -import { AccordionLabel } from 'enums/accordion-enum'; -import { useUrlState } from 'hooks/use-url-state'; -import { getStore } from 'utils/store'; -import { Box, CircularProgress } from '@mui/material'; +import React, { createContext, useEffect, useState } from "react"; +import { GitBlameHunk, WorkspaceInfo } from "@thrift-generated"; +import { + createWorkspaceClient, + getWorkspaces, +} from "service/workspace-service"; +import { createProjectClient, getLabels } from "service/project-service"; +import { createSearchClient } from "service/search-service"; +import { createCppReparseClient } from "service/cpp-reparse-service"; +import { createMetricsClient } from "service/metrics-service"; +import { createGitClient } from "service/git-service"; +import { createConfig } from "service/config"; +import { SearchProps } from "utils/types"; +import { TabName } from "enums/tab-enum"; +import { AccordionLabel } from "enums/accordion-enum"; +import { useUrlState } from "hooks/use-url-state"; +import { getStore } from "utils/store"; +import { Box, CircularProgress } from "@mui/material"; /* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-empty-function */ @@ -62,64 +64,82 @@ export const AppContext = createContext({ setWorkspaces: (_val) => {}, labels: new Map(), setLabels: (_val) => {}, - workspaceId: '', + workspaceId: "", setWorkspaceId: (_val) => {}, - projectFileId: '', + projectFileId: "", setProjectFileId: (_val) => {}, searchProps: undefined, setSearchProps: (_val) => {}, - metricsGenId: '', + metricsGenId: "", setMetricsGenId: (_val) => {}, - diagramGenId: '', + diagramGenId: "", setDiagramGenId: (_val) => {}, - diagramTypeId: '', + diagramTypeId: "", setDiagramTypeId: (_val) => {}, - diagramType: 'file', + diagramType: "file", setDiagramType: (_val) => {}, - languageNodeId: '', + languageNodeId: "", setLanguageNodeId: (_val) => {}, - editorSelection: '', + editorSelection: "", setEditorSelection: (_val) => {}, - gitRepoId: '', + gitRepoId: "", setGitRepoId: (_val) => {}, - gitBranch: '', + gitBranch: "", setGitBranch: (_val) => {}, - gitCommitId: '', + gitCommitId: "", setGitCommitId: (_val) => {}, gitBlameInfo: [], setGitBlameInfo: (_val) => {}, - activeAccordion: '', + activeAccordion: "", setActiveAccordion: (_val) => {}, - activeTab: '0', + activeTab: "0", setActiveTab: (_val) => {}, - treeViewOption: 'false', + treeViewOption: "false", setTreeViewOption: (_val) => {}, }); /* eslint-enable no-unused-vars */ /* eslint-enable @typescript-eslint/no-empty-function */ /* eslint-enable @typescript-eslint/no-unused-vars */ -export const AppContextController = ({ children }: { children: React.ReactNode }): JSX.Element => { +export const AppContextController = ({ + children, +}: { + children: React.ReactNode; +}): JSX.Element => { const [workspaces, setWorkspaces] = useState([]); const [labels, setLabels] = useState>(new Map()); - const [searchProps, setSearchProps] = useState(undefined); + const [searchProps, setSearchProps] = useState( + undefined, + ); const [gitBlameInfo, setGitBlameInfo] = useState([]); - const [workspaceId, setWorkspaceId] = useUrlState('workspaceId', ''); - const [projectFileId, setProjectFileId] = useUrlState('projectFileId', ''); - const [metricsGenId, setMetricsGenId] = useUrlState('metricsGenId', ''); - const [diagramGenId, setDiagramGenId] = useUrlState('diagramGenId', ''); - const [diagramTypeId, setDiagramTypeId] = useUrlState('diagramTypeId', ''); - const [diagramType, setDiagramType] = useUrlState('diagramType', 'file'); - const [languageNodeId, setLanguageNodeId] = useUrlState('languageNodeId', ''); - const [editorSelection, setEditorSelection] = useUrlState('editorSelection', ''); - const [gitRepoId, setGitRepoId] = useUrlState('gitRepoId', ''); - const [gitBranch, setGitBranch] = useUrlState('gitBranch', ''); - const [gitCommitId, setGitCommitId] = useUrlState('gitCommitId', ''); - const [activeAccordion, setActiveAccordion] = useUrlState('activeAccordion', AccordionLabel.FILE_MANAGER); - const [activeTab, setActiveTab] = useUrlState('activeTab', TabName.WELCOME.toString()); - const [treeViewOption, setTreeViewOption] = useUrlState('treeViewOption', 'false'); + const [workspaceId, setWorkspaceId] = useUrlState("workspaceId", ""); + const [projectFileId, setProjectFileId] = useUrlState("projectFileId", ""); + const [metricsGenId, setMetricsGenId] = useUrlState("metricsGenId", ""); + const [diagramGenId, setDiagramGenId] = useUrlState("diagramGenId", ""); + const [diagramTypeId, setDiagramTypeId] = useUrlState("diagramTypeId", ""); + const [diagramType, setDiagramType] = useUrlState("diagramType", "file"); + const [languageNodeId, setLanguageNodeId] = useUrlState("languageNodeId", ""); + const [editorSelection, setEditorSelection] = useUrlState( + "editorSelection", + "", + ); + const [gitRepoId, setGitRepoId] = useUrlState("gitRepoId", ""); + const [gitBranch, setGitBranch] = useUrlState("gitBranch", ""); + const [gitCommitId, setGitCommitId] = useUrlState("gitCommitId", ""); + const [activeAccordion, setActiveAccordion] = useUrlState( + "activeAccordion", + AccordionLabel.FILE_MANAGER, + ); + const [activeTab, setActiveTab] = useUrlState( + "activeTab", + TabName.WELCOME.toString(), + ); + const [treeViewOption, setTreeViewOption] = useUrlState( + "treeViewOption", + "false", + ); const [loadComplete, setLoadComplete] = useState(true); @@ -128,14 +148,16 @@ export const AppContextController = ({ children }: { children: React.ReactNode } const url = window.location; const wHost = url.hostname; const wPort = url.port; - const wHTTPS = url.protocol === 'https:'; + const wHTTPS = url.protocol === "https:"; const wPath = url.pathname; createConfig({ webserver_host: wHost, webserver_port: wHTTPS && !wPort ? 443 : parseInt(wPort), webserver_https: wHTTPS, - webserver_path: wPath.includes('/new') ? wPath.slice(0, wPath.lastIndexOf('/new')) : wPath, + webserver_path: wPath.includes("/new") + ? wPath.slice(0, wPath.lastIndexOf("/new")) + : wPath, }); createWorkspaceClient(); @@ -151,7 +173,6 @@ export const AppContextController = ({ children }: { children: React.ReactNode } const initializeApp = async () => { createProjectClient(workspaceId); createSearchClient(workspaceId); - createCppClient(workspaceId); createCppReparseClient(workspaceId); createMetricsClient(workspaceId); createGitClient(workspaceId); @@ -213,7 +234,14 @@ export const AppContextController = ({ children }: { children: React.ReactNode } return loadComplete ? ( {children} ) : ( - + ); diff --git a/webgui-new/src/service/language-service.ts b/webgui-new/src/service/language-service.ts index 4aa4d8eba..982247392 100644 --- a/webgui-new/src/service/language-service.ts +++ b/webgui-new/src/service/language-service.ts @@ -1,29 +1,41 @@ -import thrift from 'thrift'; -import { LanguageService, FilePosition, Position } from '@thrift-generated'; -import { config } from './config'; -import { toast } from 'react-toastify'; +import thrift from "thrift"; +import { LanguageService, FilePosition, Position } from "@thrift-generated"; +import { config } from "./config"; +import { toast } from "react-toastify"; let client: LanguageService.Client | undefined; -export const createClient = (workspace: string, fileType: string | undefined) => { +export const createClient = ( + workspace: string, + fileType: string | undefined, +) => { if (!config || !fileType) return; - - const service = () => - { - switch(fileType) - { + + const service = () => { + switch (fileType) { case "CPP": return "CppService"; case "PY": return "PythonService"; + case "CS": + return "CsharpService"; + default: + return undefined; } }; - const connection = thrift.createXHRConnection(config.webserver_host, config.webserver_port, { - transport: thrift.TBufferedTransport, - protocol: thrift.TJSONProtocol, - https: config.webserver_https, - path: `${config.webserver_path}/${workspace}/${service()}`, - }); + const serviceName = service(); + if (!serviceName) return; + + const connection = thrift.createXHRConnection( + config.webserver_host, + config.webserver_port, + { + transport: thrift.TBufferedTransport, + protocol: thrift.TJSONProtocol, + https: config.webserver_https, + path: `${config.webserver_path}/${workspace}/${serviceName}`, + }, + ); client = thrift.createXHRClient(LanguageService, connection); return client; }; @@ -51,27 +63,27 @@ export const getFileDiagramTypes = async (fileId: string) => { export const getFileDiagram = async (fileId: string, diagramId: number) => { if (!client) { - return ''; + return ""; } try { return await client.getFileDiagram(fileId, diagramId); } catch (e) { - toast.error('Could not display diagram.'); + toast.error("Could not display diagram."); console.error(e); - return ''; + return ""; } }; export const getFileDiagramLegend = async (diagramId: number) => { if (!client) { - return ''; + return ""; } try { return await client.getFileDiagramLegend(diagramId); } catch (e) { - toast.error('Could not display diagram legend.'); + toast.error("Could not display diagram legend."); console.error(e); - return ''; + return ""; } }; @@ -91,27 +103,27 @@ export const getDiagramTypes = async (astNodeId: string) => { export const getDiagram = async (astNodeId: string, diagramId: number) => { if (!client) { - return ''; + return ""; } try { return await client.getDiagram(astNodeId, diagramId); } catch (e) { - toast.error('Could not display diagram.'); + toast.error("Could not display diagram."); console.error(e); - return ''; + return ""; } }; export const getDiagramLegend = async (diagramId: number) => { if (!client) { - return ''; + return ""; } try { return await client.getDiagramLegend(diagramId); } catch (e) { - toast.error('Could not display diagram legend.'); + toast.error("Could not display diagram legend."); console.error(e); - return ''; + return ""; } }; @@ -129,7 +141,10 @@ export const getFileReferenceTypes = async (fileId: string) => { return resultMap; }; -export const getFileReferences = async (fileId: string, referenceId: number) => { +export const getFileReferences = async ( + fileId: string, + referenceId: number, +) => { if (!client) { return []; } @@ -141,7 +156,10 @@ export const getFileReferences = async (fileId: string, referenceId: number) => } }; -export const getFileReferenceCount = async (fileId: string, referenceId: number) => { +export const getFileReferenceCount = async ( + fileId: string, + referenceId: number, +) => { if (!client) { return 0; } @@ -167,7 +185,11 @@ export const getReferenceTypes = async (astNodeId: string) => { return resultMap; }; -export const getReferences = async (astNodeId: string, referenceId: number, tags: string[]) => { +export const getReferences = async ( + astNodeId: string, + referenceId: number, + tags: string[], +) => { if (!client) { return []; } @@ -179,7 +201,10 @@ export const getReferences = async (astNodeId: string, referenceId: number, tags } }; -export const getReferenceCount = async (astNodeId: string, referenceId: number) => { +export const getReferenceCount = async ( + astNodeId: string, + referenceId: number, +) => { if (!client) { return 0; } @@ -195,13 +220,18 @@ export const getReferencesInFile = async ( astNodeId: string, referenceId: number, fileId: string, - tags: string[] + tags: string[], ) => { if (!client) { return []; } try { - return await client.getReferencesInFile(astNodeId, referenceId, fileId, tags); + return await client.getReferencesInFile( + astNodeId, + referenceId, + fileId, + tags, + ); } catch (e) { console.error(e); return []; @@ -212,13 +242,18 @@ export const getReferencesPage = async ( astNodeId: string, referenceId: number, pageSize: number, - pageNo: number + pageNo: number, ) => { if (!client) { return []; } try { - return await client.getReferencesPage(astNodeId, referenceId, pageSize, pageNo); + return await client.getReferencesPage( + astNodeId, + referenceId, + pageSize, + pageNo, + ); } catch (e) { console.error(e); return []; @@ -227,13 +262,13 @@ export const getReferencesPage = async ( export const getSourceText = async (astNodeId: string) => { if (!client) { - return ''; + return ""; } try { return await client.getSourceText(astNodeId); } catch (e) { console.error(e); - return ''; + return ""; } }; @@ -253,14 +288,14 @@ export const getProperties = async (astNodeId: string) => { export const getDocumentation = async (astNodeId: string) => { if (!client) { - return ''; + return ""; } try { return await client.getDocumentation(astNodeId); } catch (e) { - toast.error('Could not get documentation about this AST node.'); + toast.error("Could not get documentation about this AST node."); console.error(e); - return ''; + return ""; } }; @@ -276,7 +311,11 @@ export const getAstNodeInfo = async (astNodeId: string) => { } }; -export const getAstNodeInfoByPosition = async (fileId: string, line: number, column: number) => { +export const getAstNodeInfoByPosition = async ( + fileId: string, + line: number, + column: number, +) => { if (!client) { return; } @@ -288,7 +327,7 @@ export const getAstNodeInfoByPosition = async (fileId: string, line: number, col line, column, }), - }) + }), ); } catch (e) { return; From 9f84ebc1b532b89fcd3e082c7ab159924ab68f50 Mon Sep 17 00:00:00 2001 From: danimester23 Date: Mon, 11 May 2026 20:38:20 +0200 Subject: [PATCH 2/2] fix issues --- plugins/csharp/service/src/csharpservice.cpp | 27 ++- .../codemirror-editor/codemirror-editor.tsx | 6 +- .../src/components/info-tree/info-tree.tsx | 165 +++++++++++++----- webgui-new/src/service/language-service.ts | 1 + 4 files changed, 146 insertions(+), 53 deletions(-) diff --git a/plugins/csharp/service/src/csharpservice.cpp b/plugins/csharp/service/src/csharpservice.cpp index 7d44b417d..42dfe1ea8 100644 --- a/plugins/csharp/service/src/csharpservice.cpp +++ b/plugins/csharp/service/src/csharpservice.cpp @@ -70,8 +70,13 @@ void CsharpServiceHandler::getAstNodeInfo( return _db->query_one( FileQuery::path == return_.range.file); }); + if (!file) + { + LOG(warning) << "[csharpservice] getAstNodeInfo: file not found for path: " << return_.range.file; + return; + } std::stringstream ss; - ss << file; + ss << file->id; return_.range.file = ss.str(); //LOG(info) << "csharpQuery.getAstNodeInfo: file = " << return_.range.file; } @@ -203,10 +208,14 @@ void CsharpServiceHandler::getReferences( for (AstNodeInfo nodeinfo : return_) { model::FilePtr file = _transaction([&, this](){ - return _db->query_one( - FileQuery::path == nodeinfo.range.file); + return _db->query_one( + FileQuery::path == nodeinfo.range.file); }); - + if (!file) + { + LOG(warning) << "[csharpservice] getReferences: file not found for path: " << nodeinfo.range.file; + continue; + } std::stringstream ss; ss << file->id; nodeinfo.range.file = ss.str(); @@ -254,6 +263,11 @@ std::int32_t CsharpServiceHandler::getFileReferenceCount( return _db->query_one( FileQuery::id == std::stoull(fileId_)); }); + if (!file) + { + LOG(warning) << "[csharpservice] getFileReferenceCount: file not found for id: " << fileId_; + return 0; + } return _csharpQueryHandler.getFileReferenceCount(file->path, referenceId_); } @@ -267,6 +281,11 @@ void CsharpServiceHandler::getFileReferences( return _db->query_one( FileQuery::id == std::stoull(fileId_)); }); + if (!file) + { + LOG(warning) << "[csharpservice] getFileReferences: file not found for id: " << fileId_; + return; + } _csharpQueryHandler.getFileReferences(return_, file->path, referenceId_); } diff --git a/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx b/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx index ffabb4c2f..9f0bd5c8c 100644 --- a/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx +++ b/webgui-new/src/components/codemirror-editor/codemirror-editor.tsx @@ -101,11 +101,13 @@ export const CodeMirrorEditor = (): JSX.Element => { ); }, [theme]); + useEffect(() => { + createClient(appCtx.workspaceId, fileInfo?.type); + }, [appCtx.workspaceId, fileInfo]); + useEffect(() => { if (!editorRef.current || !editorRef.current.view) return; setHighlightRanges([]); - - createClient(appCtx.workspaceId, fileInfo?.type); }, [appCtx.workspaceId, fileInfo, fileContent]); const createHighlightDecoration = ( diff --git a/webgui-new/src/components/info-tree/info-tree.tsx b/webgui-new/src/components/info-tree/info-tree.tsx index 618f963b9..fd3233f08 100644 --- a/webgui-new/src/components/info-tree/info-tree.tsx +++ b/webgui-new/src/components/info-tree/info-tree.tsx @@ -1,7 +1,7 @@ -import { alpha, Box, CircularProgress } from '@mui/material'; -import { Code } from '@mui/icons-material'; -import { ExpandMore, ChevronRight } from '@mui/icons-material'; -import React, { SyntheticEvent, useContext, useEffect, useState } from 'react'; +import { alpha, Box, CircularProgress } from "@mui/material"; +import { Code } from "@mui/icons-material"; +import { ExpandMore, ChevronRight } from "@mui/icons-material"; +import React, { SyntheticEvent, useContext, useEffect, useState } from "react"; import { createClient, getReferenceTypes, @@ -9,59 +9,80 @@ import { getProperties, getReferenceCount, getAstNodeInfo, -} from 'service/language-service'; -import { AstNodeInfo, FileInfo, Range } from '@thrift-generated'; -import { FileIcon, RefIcon } from 'components/custom-icon/custom-icon'; -import { TabName } from 'enums/tab-enum'; -import { AppContext } from 'global-context/app-context'; -import { getFileInfo } from 'service/project-service'; -import * as SC from './styled-components'; -import { convertSelectionRangeToString } from 'utils/utils'; -import { useRouter } from 'next/router'; -import { RouterQueryType } from 'utils/types'; -import { useTranslation } from 'react-i18next'; -import { referenceTypeArray } from 'enums/entity-types'; +} from "service/language-service"; +import { AstNodeInfo, FileInfo, Range } from "@thrift-generated"; +import { FileIcon, RefIcon } from "components/custom-icon/custom-icon"; +import { TabName } from "enums/tab-enum"; +import { AppContext } from "global-context/app-context"; +import { getFileInfo } from "service/project-service"; +import * as SC from "./styled-components"; +import { convertSelectionRangeToString } from "utils/utils"; +import { useRouter } from "next/router"; +import { RouterQueryType } from "utils/types"; +import { useTranslation } from "react-i18next"; +import { referenceTypeArray } from "enums/entity-types"; export const InfoTree = (): JSX.Element => { const { t } = useTranslation(); const router = useRouter(); const appCtx = useContext(AppContext); - const [astNodeInfo, setAstNodeInfo] = useState(undefined); + const [astNodeInfo, setAstNodeInfo] = useState( + undefined, + ); const [properties, setProperties] = useState>(new Map()); const [refTypes, setRefTypes] = useState>(new Map()); const [refCounts, setRefCounts] = useState>(new Map()); const [refs, setRefs] = useState>(new Map()); - const [fileUsages, setFileUsages] = useState>(new Map()); + const [fileUsages, setFileUsages] = useState>( + new Map(), + ); const [loadComplete, setLoadComplete] = useState(false); const [expandedTreeNodes, setExpandedTreeNodes] = useState([]); useEffect(() => { if (!appCtx.languageNodeId) return; setLoadComplete(false); + setAstNodeInfo(undefined); + let cancelled = false; const init = async () => { const fileInfo = await getFileInfo(appCtx.projectFileId); + if (cancelled) return; createClient(appCtx.workspaceId, fileInfo?.type); - const initAstNodeInfo = await getAstNodeInfo(appCtx.languageNodeId as string); - if (!initAstNodeInfo) return; + const initAstNodeInfo = await getAstNodeInfo( + appCtx.languageNodeId as string, + ); + if (!initAstNodeInfo || cancelled) return; const initProps = await getProperties(initAstNodeInfo.id as string); - const initRefTypes = await getReferenceTypes(initAstNodeInfo.id as string); + const initRefTypes = await getReferenceTypes( + initAstNodeInfo.id as string, + ); const initRefCounts: typeof refCounts = new Map(); const initRefs: typeof refs = new Map(); const initFileUsages: typeof fileUsages = new Map(); for (const [rType, rId] of initRefTypes) { - const refCount = await getReferenceCount(initAstNodeInfo.id as string, rId); + if (cancelled) return; + const refCount = await getReferenceCount( + initAstNodeInfo.id as string, + rId, + ); initRefCounts.set(rType, refCount); - const refsForType = await getReferences(initAstNodeInfo.id as string, rId, initAstNodeInfo.tags ?? []); + const refsForType = await getReferences( + initAstNodeInfo.id as string, + rId, + initAstNodeInfo.tags ?? [], + ); initRefs.set(rType, refsForType); - if (rType === 'Caller' || rType === 'Usage') { + if (rType === "Caller" || rType === "Usage") { const fileInfos: FileInfo[] = []; - for (const fId of [...new Set(refsForType.map((aInfo) => aInfo.range?.file as string))]) { + for (const fId of [ + ...new Set(refsForType.map((aInfo) => aInfo.range?.file as string)), + ]) { const fInfo = await getFileInfo(fId); fileInfos.push(fInfo as FileInfo); } @@ -71,6 +92,7 @@ export const InfoTree = (): JSX.Element => { } } + if (cancelled) return; setAstNodeInfo(initAstNodeInfo); setProperties(initProps); setRefTypes(initRefTypes); @@ -78,17 +100,24 @@ export const InfoTree = (): JSX.Element => { setRefs(initRefs); setFileUsages(initFileUsages); }; - init().then(() => setLoadComplete(true)); + init().finally(() => { + if (!cancelled) setLoadComplete(true); + }); + return () => { + cancelled = true; + }; }, [appCtx.languageNodeId]); const jumpToRef = async (astNodeInfo: AstNodeInfo) => { const fileId = astNodeInfo.range?.file as string; router.push({ - pathname: '/project', + pathname: "/project", query: { ...router.query, projectFileId: fileId, - editorSelection: convertSelectionRangeToString(astNodeInfo.range?.range as Range), + editorSelection: convertSelectionRangeToString( + astNodeInfo.range?.range as Range, + ), activeTab: TabName.CODE.toString(), } as RouterQueryType, }); @@ -97,25 +126,39 @@ export const InfoTree = (): JSX.Element => { return appCtx.languageNodeId && astNodeInfo ? ( loadComplete ? ( - + jumpToRef(astNodeInfo)} sx={{ - fontWeight: 'bold', - cursor: 'pointer', - ':hover': { - backgroundColor: (theme) => alpha(theme.backgroundColors?.secondary as string, 0.3), + fontWeight: "bold", + cursor: "pointer", + ":hover": { + backgroundColor: (theme) => + alpha(theme.backgroundColors?.secondary as string, 0.3), }, }} >{`${astNodeInfo.symbolType}: ${astNodeInfo.astNodeValue}`} {Array.from(properties.keys()).map((name, idx) => ( - + - {name}: {properties.get(name)} + + {name}: + {" "} + {properties.get(name)} ))} @@ -124,9 +167,12 @@ export const InfoTree = (): JSX.Element => { defaultExpandIcon={} defaultEndIcon={} defaultCollapseIcon={} - sx={{ width: 'max-content', marginTop: '5px' }} + sx={{ width: "max-content", marginTop: "5px" }} expanded={expandedTreeNodes} - onNodeSelect={(_e: SyntheticEvent, nodeIds: string | string[]) => { + onNodeSelect={( + _e: SyntheticEvent, + nodeIds: string | string[], + ) => { // Handle both single string and array of strings const nodeId = Array.isArray(nodeIds) ? nodeIds[0] : nodeIds; if (!nodeId) return; @@ -149,8 +195,9 @@ export const InfoTree = (): JSX.Element => { key={refTypeIdx} icon={} label={ - - {referenceTypeArray[refTypes.get(type) as number]} ({refCounts.get(type)}) + + {referenceTypeArray[refTypes.get(type) as number]} ( + {refCounts.get(type)}) } > @@ -161,9 +208,16 @@ export const InfoTree = (): JSX.Element => { key={fileInfo.id} icon={} label={ - + {fileInfo.name} ( - {refs.get(type)?.filter((aInfo) => aInfo.range?.file === fileInfo.id).length}) + { + refs + .get(type) + ?.filter( + (aInfo) => aInfo.range?.file === fileInfo.id, + ).length + } + ) } > @@ -171,8 +225,11 @@ export const InfoTree = (): JSX.Element => { .get(type) ?.filter((aInfo) => aInfo.range?.file === fileInfo.id) .map((aInfo) => ( - jumpToRef(aInfo)}> - + jumpToRef(aInfo)} + > + {`${aInfo.range?.range?.startpos?.line}:${aInfo.range?.range?.startpos?.column}: ${aInfo.astNodeValue}`} ))} @@ -180,7 +237,7 @@ export const InfoTree = (): JSX.Element => { )) : refs.get(type)?.map((aInfo) => ( jumpToRef(aInfo)}> - + {`${aInfo.range?.range?.startpos?.line}:${aInfo.range?.range?.startpos?.column}: ${aInfo.astNodeValue}`} ))} @@ -189,13 +246,27 @@ export const InfoTree = (): JSX.Element => { ) : ( - + ) ) : ( - - {t('infoTree.noNode')} + + {t("infoTree.noNode")} ); }; diff --git a/webgui-new/src/service/language-service.ts b/webgui-new/src/service/language-service.ts index 982247392..7377960a9 100644 --- a/webgui-new/src/service/language-service.ts +++ b/webgui-new/src/service/language-service.ts @@ -34,6 +34,7 @@ export const createClient = ( protocol: thrift.TJSONProtocol, https: config.webserver_https, path: `${config.webserver_path}/${workspace}/${serviceName}`, + timeout: 10000, }, ); client = thrift.createXHRClient(LanguageService, connection);