Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion editors/code/src/commands/sandboxCommands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: MIT
// Copyright © 2025 TON Studio
import * as vscode from "vscode"
import vscode from "vscode"

import {ContractAbi} from "@shared/abi"

Expand Down Expand Up @@ -491,3 +491,14 @@ export function registerSandboxCommands(

return disposables
}

export function openFileAtPosition(uri: string, row: number, column: number): void {
const fileUri = vscode.Uri.parse(uri)
const position = new vscode.Position(row, column)

vscode.workspace.openTextDocument(fileUri).then(document => {
void vscode.window.showTextDocument(document, {
selection: new vscode.Range(position, position),
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import * as vscode from "vscode"

import {TransactionDetailsInfo} from "../../common/types/transaction"
import {openFileAtPosition} from "../../commands/sandboxCommands"
import {OpenFileAtPositionCommand} from "../../webview-ui/src/views/details/transaction-details-types"

export class TransactionDetailsProvider {
public static readonly viewType: string = "tonTransactionDetails"
Expand Down Expand Up @@ -42,8 +44,11 @@ export class TransactionDetailsProvider {

this.panel.webview.html = this.getHtmlForWebview(this.panel.webview)

this.panel.webview.onDidReceiveMessage(() => {
// ...
this.panel.webview.onDidReceiveMessage((message: OpenFileAtPositionCommand) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (message.type === "openFileAtPosition") {
openFileAtPosition(message.uri, message.row, message.column)
}
})

this.panel.onDidDispose(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
import {ContractData} from "../../../../common/types/contract"
import {LoadingSpinner} from "../../components/common"

import {VSCodeTransactionDetailsAPI} from "./transaction-details-types"

import {TransactionTree} from "./components"

import styles from "./TransactionDetails.module.css"
Expand All @@ -27,9 +29,7 @@ interface AddTransactionsMessage {
}

interface Props {
readonly vscode: {
readonly postMessage: (message: unknown) => void
}
readonly vscode: VSCodeTransactionDetailsAPI
}

export default function TransactionDetails({vscode}: Props): JSX.Element {
Expand Down Expand Up @@ -132,7 +132,9 @@ export default function TransactionDetails({vscode}: Props): JSX.Element {

return (
<div className={styles.container}>
{transactions && <TransactionTree transactions={transactions} contracts={contracts} />}
{transactions && (
<TransactionTree vscode={vscode} transactions={transactions} contracts={contracts} />
)}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {ContractABI} from "@ton/core"

import React from "react"
import React, {useState, useEffect, useRef} from "react"
import {FiExternalLink} from "react-icons/fi"

import {ExitCodeInfo} from "@shared/abi"

Expand All @@ -13,9 +14,34 @@ interface ExitCodeViewerProps {
readonly exitCode: number | undefined
readonly abi: ContractABI | undefined
readonly exitCodes?: readonly ExitCodeInfo[]
readonly onOpenFile: (uri: string, row: number, column: number) => void
}

export function ExitCodeChip({exitCode, abi, exitCodes}: ExitCodeViewerProps): React.JSX.Element {
export function ExitCodeChip({
exitCode,
abi,
exitCodes,
onOpenFile,
}: ExitCodeViewerProps): React.JSX.Element {
const [showDropdown, setShowDropdown] = useState(false)
const containerRef = useRef<HTMLDivElement>(null)

useEffect(() => {
const handleClickOutside = (event: MouseEvent): void => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setShowDropdown(false)
}
}

if (showDropdown) {
document.addEventListener("mousedown", handleClickOutside)
}

return () => {
document.removeEventListener("mousedown", handleClickOutside)
}
}, [showDropdown])

if (exitCode === undefined) {
return <span className={styles.exitCode}>—</span>
}
Expand Down Expand Up @@ -55,12 +81,68 @@ export function ExitCodeChip({exitCode, abi, exitCodes}: ExitCodeViewerProps): R
const isSuccess = exitCode === 0 || exitCode === 1
const className = `${styles.exitCode} ${isSuccess ? styles.success : styles.error}`

const usagePositions = exitCodeInfo?.usagePositions ?? []
const hasUsagePositions = usagePositions.length > 0
const hasMultiplePositions = usagePositions.length > 1

const handleFileButtonClick = (e: React.MouseEvent | React.KeyboardEvent): void => {
e.stopPropagation()
if (hasMultiplePositions) {
setShowDropdown(!showDropdown)
} else if (usagePositions.length === 1) {
const position = usagePositions[0]
onOpenFile(position.uri, position.row, position.column)
}
}

return (
<Tooltip content={tooltipContent} variant="hover">
<span className={className}>
{exitCode}
{exitCode !== 0 && <span className={styles.exitCodeName}> ({displayName})</span>}
</span>
</Tooltip>
<div className={styles.container} ref={containerRef}>
<Tooltip content={tooltipContent} variant="hover">
<span
className={className}
onClick={hasUsagePositions ? handleFileButtonClick : undefined}
onKeyDown={
hasUsagePositions
? e => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
handleFileButtonClick(e)
}
}
: undefined
}
role={hasUsagePositions ? "button" : undefined}
tabIndex={hasUsagePositions ? 0 : undefined}
style={hasUsagePositions ? {cursor: "pointer"} : undefined}
>
{exitCode}
{exitCode !== 0 && <span className={styles.exitCodeName}> ({displayName})</span>}
{hasUsagePositions && (
<span className={styles.icon}>
<FiExternalLink size={12} />
</span>
)}
</span>
</Tooltip>
{showDropdown && hasMultiplePositions && (
<div className={styles.dropdown}>
{usagePositions.map((position, index) => (
<button
key={index}
className={styles.dropdownItem}
onClick={() => {
onOpenFile(position.uri, position.row, position.column)
setShowDropdown(false)
}}
>
<span className={styles.dropdownItemText}>
{position.uri.split("/").pop()}:{position.row}:{position.column}
</span>
<FiExternalLink size={12} className={styles.dropdownItemIcon} />
</button>
))}
</div>
)}
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
font-weight: var(--font-weight-medium);
padding: 0 var(--spacing-sm);
border-radius: var(--border-radius-sm);
display: inline-block;
display: flex;
gap: var(--spacing-xs);
cursor: help;
transition: all 0.2s ease;
}
Expand Down Expand Up @@ -40,3 +41,57 @@
.tooltipPhase {
margin-top: var(--spacing-xs);
}

.icon {
color: inherit;
opacity: 0.7;
display: inline-flex;
align-items: center;
justify-content: center;
}

.container {
position: relative;
display: inline-block;
}

.dropdown {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
background: var(--color-background-primary);
border: 1px solid var(--color-border);
border-radius: var(--border-radius-sm);
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
min-width: 200px;
max-width: 300px;
}

.dropdownItem {
display: flex;
align-items: center;
gap: var(--spacing-xs);
width: 100%;
padding: var(--spacing-sm);
background: none;
border: none;
cursor: pointer;
color: var(--color-text-primary);
text-align: left;
font-size: var(--font-size-sm);
transition: background-color 0.2s ease;
}

.dropdownItemText {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: var(--font-size-xs);
}

.dropdownItemIcon {
color: var(--color-text-tertiary);
opacity: 0.6;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {formatCurrency, formatNumber} from "../../../../components/format/format
import {ContractData} from "../../../../../../common/types/contract"
import {computeSendMode, type TransactionInfo} from "../../../../../../common/types/transaction"
import {parseData, ParsedObject} from "../../../../../../common/binary"
import {VSCodeTransactionDetailsAPI} from "../../transaction-details-types"

import {ActionsSummary} from "./ActionsSummary"

Expand Down Expand Up @@ -88,13 +89,15 @@ export interface TransactionShortInfoProps {
readonly transactions: TransactionInfo[]
readonly contracts: Map<string, ContractData>
readonly onContractClick?: (address: string) => void
readonly vscode: VSCodeTransactionDetailsAPI
}

export function TransactionDetails({
tx,
contracts,
onContractClick,
transactions,
vscode,
}: TransactionShortInfoProps): React.JSX.Element {
const [showActions, setShowActions] = useState(false)
const [showParsedData, setShowParsedData] = useState(false)
Expand All @@ -120,6 +123,15 @@ export function TransactionDetails({
const sendMode = computeSendMode(tx, transactions)

const thisAddress = tx.address

const handleOpenFile = (uri: string, row: number, column: number): void => {
vscode.postMessage({
type: "openFileAtPosition",
uri,
row,
column,
})
}
const targetContract = thisAddress ? contracts.get(thisAddress.toString()) : undefined
const typeAbi = targetContract?.abi?.messages.find(it => it.opcode === tx.opcode)
const opcodeNane = typeAbi?.name
Expand Down Expand Up @@ -288,6 +300,7 @@ export function TransactionDetails({
exitCode={computeInfo.exitCode}
abi={undefined}
exitCodes={knownExitCodes}
onOpenFile={handleOpenFile}
/>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {TransactionDetails} from "../index"
import {formatCurrency} from "../../../../components/format/format"
import {ContractData} from "../../../../../../common/types/contract"
import {TransactionInfo} from "../../../../../../common/types/transaction"
import {VSCodeTransactionDetailsAPI} from "../../transaction-details-types"

import {ParsedDataView} from "../ParsedDataView/ParsedDataView"

Expand Down Expand Up @@ -36,6 +37,7 @@ interface TransactionTooltipData {
interface TransactionTreeProps {
readonly transactions: TransactionInfo[]
readonly contracts: ContractData[]
readonly vscode: VSCodeTransactionDetailsAPI
}

const formatAddress = (
Expand Down Expand Up @@ -152,6 +154,7 @@ function NodeTooltipContent({
export function TransactionTree({
transactions,
contracts,
vscode,
}: TransactionTreeProps): React.JSX.Element {
const {
tooltip,
Expand Down Expand Up @@ -678,6 +681,7 @@ export function TransactionTree({
transactions={transactions}
contracts={contractsMap}
onContractClick={handleContractClick}
vscode={vscode}
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ import React from "react"
import {createRoot} from "react-dom/client"

import TransactionDetails from "./TransactionDetails"
import {VSCodeTransactionDetailsAPI} from "./transaction-details-types"

import "../../main.css"
import "../../index.css"

declare function acquireVsCodeApi(): {
readonly postMessage: (msg: unknown) => void
readonly setState: (state: unknown) => void
readonly getState: () => unknown
}
declare function acquireVsCodeApi(): VSCodeTransactionDetailsAPI

const vscode = acquireVsCodeApi()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface OpenFileAtPositionCommand {
readonly type: "openFileAtPosition"
readonly uri: string
readonly row: number
readonly column: number
}

export type TransactionDetailsCommand = OpenFileAtPositionCommand

export interface VSCodeTransactionDetailsAPI {
readonly postMessage: (command: TransactionDetailsCommand) => void
readonly getState: () => unknown
readonly setState: (state: unknown) => void
}
Loading
Loading