Skip to content

Commit c5f45c3

Browse files
committed
merge main
2 parents 8b4c296 + e018e7b commit c5f45c3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1153
-162
lines changed

cmd/wsh/cmd/wshcmd-file.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,14 @@ func fileInfoRun(cmd *cobra.Command, args []string) error {
231231

232232
info, err := wshclient.FileInfoCommand(RpcClient, fileData, &wshrpc.RpcOpts{Timeout: DefaultFileTimeout})
233233
err = convertNotFoundErr(err)
234-
if err == fs.ErrNotExist {
235-
return fmt.Errorf("%s: no such file", path)
236-
}
237234
if err != nil {
238235
return fmt.Errorf("getting file info: %w", err)
239236
}
240237

238+
if info.NotFound {
239+
return fmt.Errorf("%s: no such file", path)
240+
}
241+
241242
WriteStdout("name:\t%s\n", info.Name)
242243
if info.Mode != 0 {
243244
WriteStdout("mode:\t%s\n", info.Mode.String())

frontend/app/block/blockframe.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,7 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
607607
"--magnified-block-blur": `${magnifiedBlockBlur}px`,
608608
} as React.CSSProperties
609609
}
610+
inert={preview ? "1" : undefined}
610611
>
611612
<BlockMask nodeModel={nodeModel} />
612613
{preview || viewModel == null ? null : (

frontend/app/view/preview/directorypreview.tsx

Lines changed: 142 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import dayjs from "dayjs";
2727
import { PrimitiveAtom, atom, useAtom, useAtomValue, useSetAtom } from "jotai";
2828
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
2929
import React, { Fragment, memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
30+
import { useDrag, useDrop } from "react-dnd";
3031
import { quote as shellQuote } from "shell-quote";
3132
import { debounce } from "throttle-debounce";
3233
import "./directorypreview.scss";
@@ -650,34 +651,6 @@ function TableBody({
650651
[setRefreshVersion, conn]
651652
);
652653

653-
const displayRow = useCallback(
654-
(row: Row<FileInfo>, idx: number) => (
655-
<div
656-
ref={(el) => (rowRefs.current[idx] = el)}
657-
className={clsx("dir-table-body-row", { focused: focusIndex === idx })}
658-
key={row.id}
659-
onDoubleClick={() => {
660-
const newFileName = row.getValue("path") as string;
661-
model.goHistory(newFileName);
662-
setSearch("");
663-
}}
664-
onClick={() => setFocusIndex(idx)}
665-
onContextMenu={(e) => handleFileContextMenu(e, row.original)}
666-
>
667-
{row.getVisibleCells().map((cell) => (
668-
<div
669-
className={clsx("dir-table-body-cell", "col-" + cell.column.id)}
670-
key={cell.id}
671-
style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }}
672-
>
673-
{flexRender(cell.column.columnDef.cell, cell.getContext())}
674-
</div>
675-
))}
676-
</div>
677-
),
678-
[setSearch, handleFileContextMenu, setFocusIndex, focusIndex]
679-
);
680-
681654
return (
682655
<div className="dir-table-body" ref={bodyRef}>
683656
{search !== "" && (
@@ -693,13 +666,110 @@ function TableBody({
693666
<div className="dummy dir-table-body-row" ref={dummyLineRef}>
694667
<div className="dir-table-body-cell">dummy-data</div>
695668
</div>
696-
{table.getTopRows().map(displayRow)}
697-
{table.getCenterRows().map((row, idx) => displayRow(row, idx + table.getTopRows().length))}
669+
{table.getTopRows().map((row, idx) => (
670+
<TableRow
671+
model={model}
672+
row={row}
673+
focusIndex={focusIndex}
674+
setFocusIndex={setFocusIndex}
675+
setSearch={setSearch}
676+
idx={idx}
677+
handleFileContextMenu={handleFileContextMenu}
678+
ref={(el) => (rowRefs.current[idx] = el)}
679+
key={idx}
680+
/>
681+
))}
682+
{table.getCenterRows().map((row, idx) => (
683+
<TableRow
684+
model={model}
685+
row={row}
686+
focusIndex={focusIndex}
687+
setFocusIndex={setFocusIndex}
688+
setSearch={setSearch}
689+
idx={idx + table.getTopRows().length}
690+
handleFileContextMenu={handleFileContextMenu}
691+
ref={(el) => (rowRefs.current[idx] = el)}
692+
key={idx}
693+
/>
694+
))}
698695
</div>
699696
</div>
700697
);
701698
}
702699

700+
type TableRowProps = {
701+
model: PreviewModel;
702+
row: Row<FileInfo>;
703+
focusIndex: number;
704+
setFocusIndex: (_: number) => void;
705+
setSearch: (_: string) => void;
706+
idx: number;
707+
handleFileContextMenu: (e: any, finfo: FileInfo) => Promise<void>;
708+
};
709+
710+
const TableRow = React.forwardRef(function (
711+
{ model, row, focusIndex, setFocusIndex, setSearch, idx, handleFileContextMenu }: TableRowProps,
712+
ref: React.RefObject<HTMLDivElement>
713+
) {
714+
const dirPath = useAtomValue(model.normFilePath);
715+
const connection = useAtomValue(model.connection);
716+
const formatRemoteUri = useCallback(
717+
(path: string) => {
718+
let conn: string;
719+
if (!connection) {
720+
conn = "local";
721+
} else {
722+
conn = connection;
723+
}
724+
return `wsh://${conn}/${path}`;
725+
},
726+
[connection]
727+
);
728+
729+
const dragItem: DraggedFile = {
730+
relName: row.getValue("name") as string,
731+
absParent: dirPath,
732+
uri: formatRemoteUri(row.getValue("path") as string),
733+
};
734+
const [{ isDragging }, drag, dragPreview] = useDrag(
735+
() => ({
736+
type: "FILE_ITEM",
737+
canDrag: true,
738+
item: () => dragItem,
739+
collect: (monitor) => {
740+
return {
741+
isDragging: monitor.isDragging(),
742+
};
743+
},
744+
}),
745+
[dragItem]
746+
);
747+
748+
return (
749+
<div
750+
className={clsx("dir-table-body-row", { focused: focusIndex === idx })}
751+
onDoubleClick={() => {
752+
const newFileName = row.getValue("path") as string;
753+
model.goHistory(newFileName);
754+
setSearch("");
755+
}}
756+
onClick={() => setFocusIndex(idx)}
757+
onContextMenu={(e) => handleFileContextMenu(e, row.original)}
758+
ref={drag}
759+
>
760+
{row.getVisibleCells().map((cell) => (
761+
<div
762+
className={clsx("dir-table-body-cell", "col-" + cell.column.id)}
763+
key={cell.id}
764+
style={{ width: `calc(var(--col-${cell.column.id}-size) * 1px)` }}
765+
>
766+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
767+
</div>
768+
))}
769+
</div>
770+
);
771+
});
772+
703773
const MemoizedTableBody = React.memo(
704774
TableBody,
705775
(prev, next) => prev.table.options.data == next.table.options.data
@@ -830,6 +900,48 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
830900
middleware: [offset(({ rects }) => -rects.reference.height / 2 - rects.floating.height / 2)],
831901
});
832902

903+
const [, drop] = useDrop(
904+
() => ({
905+
accept: "FILE_ITEM", //a name of file drop type
906+
canDrop: (_, monitor) => {
907+
const dragItem = monitor.getItem<DraggedFile>();
908+
// drop if not current dir is the parent directory of the dragged item
909+
// requires absolute path
910+
if (monitor.isOver({ shallow: false }) && dragItem.absParent !== dirPath) {
911+
return true;
912+
}
913+
return false;
914+
},
915+
drop: async (draggedFile: DraggedFile, monitor) => {
916+
if (!monitor.didDrop()) {
917+
const timeoutYear = 31536000000; // one year
918+
const opts: FileCopyOpts = {
919+
timeout: timeoutYear,
920+
recursive: true,
921+
};
922+
const desturi = await model.formatRemoteUri(dirPath, globalStore.get);
923+
const data: CommandFileCopyData = {
924+
srcuri: draggedFile.uri,
925+
desturi,
926+
opts,
927+
};
928+
try {
929+
await RpcApi.FileCopyCommand(TabRpcClient, data, { timeout: timeoutYear });
930+
} catch (e) {
931+
console.log("copy failed:", e);
932+
}
933+
model.refreshCallback();
934+
}
935+
},
936+
// TODO: mabe add a hover option?
937+
}),
938+
[dirPath, model.formatRemoteUri, model.refreshCallback]
939+
);
940+
941+
useEffect(() => {
942+
drop(refs.reference);
943+
}, [refs.reference]);
944+
833945
const dismiss = useDismiss(context);
834946
const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);
835947

frontend/layout/lib/TileLayout.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
} from "./types";
3232
import { determineDropDirection } from "./utils";
3333

34+
const tileItemType = "TILE_ITEM";
35+
3436
export interface TileLayoutProps {
3537
/**
3638
* The atom containing the layout tree state.
@@ -59,14 +61,16 @@ function TileLayoutComponent({ tabAtom, contents, getCursorPoint }: TileLayoutPr
5961
const setReady = useSetAtom(layoutModel.ready);
6062
const isResizing = useAtomValue(layoutModel.isResizing);
6163

62-
const { activeDrag, dragClientOffset } = useDragLayer((monitor) => ({
64+
const { activeDrag, dragClientOffset, dragItemType } = useDragLayer((monitor) => ({
6365
activeDrag: monitor.isDragging(),
6466
dragClientOffset: monitor.getClientOffset(),
67+
dragItemType: monitor.getItemType(),
6568
}));
6669

6770
useEffect(() => {
68-
setActiveDrag(activeDrag);
69-
}, [setActiveDrag, activeDrag]);
71+
const activeTileDrag = activeDrag && dragItemType == tileItemType;
72+
setActiveDrag(activeTileDrag);
73+
}, [activeDrag, dragItemType]);
7074

7175
const checkForCursorBounds = useCallback(
7276
debounce(100, (dragClientOffset: XYCoord) => {
@@ -214,8 +218,6 @@ interface DisplayNodeProps {
214218
node: LayoutNode;
215219
}
216220

217-
const dragItemType = "TILE_ITEM";
218-
219221
/**
220222
* The draggable and displayable portion of a leaf node in a layout tree.
221223
*/
@@ -230,7 +232,7 @@ const DisplayNode = ({ layoutModel, node }: DisplayNodeProps) => {
230232

231233
const [{ isDragging }, drag, dragPreview] = useDrag(
232234
() => ({
233-
type: dragItemType,
235+
type: tileItemType,
234236
canDrag: () => !(isEphemeral || isMagnified),
235237
item: () => node,
236238
collect: (monitor) => ({
@@ -358,7 +360,7 @@ const OverlayNode = memo(({ node, layoutModel }: OverlayNodeProps) => {
358360

359361
const [, drop] = useDrop(
360362
() => ({
361-
accept: dragItemType,
363+
accept: tileItemType,
362364
canDrop: (_, monitor) => {
363365
const dragItem = monitor.getItem<LayoutNode>();
364366
if (monitor.isOver({ shallow: true }) && dragItem.id !== node.id) {

frontend/types/custom.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,12 @@ declare global {
380380
}
381381

382382
type SuggestionsFnType = (query: string, reqContext: SuggestionRequestContext) => Promise<FetchSuggestionsResponse>;
383+
384+
type DraggedFile = {
385+
uri: string;
386+
absParent: string;
387+
relName: string;
388+
};
383389
}
384390

385391
export {};

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"vite-plugin-static-copy": "^2.2.0",
9090
"vite-plugin-svgr": "^4.3.0",
9191
"vite-tsconfig-paths": "^5.1.4",
92-
"vitest": "^3.0.4"
92+
"vitest": "^3.0.5"
9393
},
9494
"dependencies": {
9595
"@floating-ui/react": "^0.27.3",

pkg/remote/connparse/connparse.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,11 @@ func ParseURI(uri string) (*Connection, error) {
128128
}
129129
}
130130

131+
addPrecedingSlash := true
132+
131133
if scheme == "" {
132134
scheme = ConnectionTypeWsh
135+
addPrecedingSlash = false
133136
if len(rest) != len(uri) {
134137
// This accounts for when the uri starts with "//", which would get trimmed in the first split.
135138
parseWshPath()
@@ -152,7 +155,7 @@ func ParseURI(uri string) (*Connection, error) {
152155
}
153156
if strings.HasPrefix(remotePath, "/~") {
154157
remotePath = strings.TrimPrefix(remotePath, "/")
155-
} else if len(remotePath) > 1 && !windowsDriveRegex.MatchString(remotePath) && !strings.HasPrefix(remotePath, "/") && !strings.HasPrefix(remotePath, "~") && !strings.HasPrefix(remotePath, "./") && !strings.HasPrefix(remotePath, "../") && !strings.HasPrefix(remotePath, ".\\") && !strings.HasPrefix(remotePath, "..\\") && remotePath != ".." {
158+
} else if addPrecedingSlash && (len(remotePath) > 1 && !windowsDriveRegex.MatchString(remotePath) && !strings.HasPrefix(remotePath, "/") && !strings.HasPrefix(remotePath, "~") && !strings.HasPrefix(remotePath, "./") && !strings.HasPrefix(remotePath, "../") && !strings.HasPrefix(remotePath, ".\\") && !strings.HasPrefix(remotePath, "..\\") && remotePath != "..") {
156159
remotePath = "/" + remotePath
157160
}
158161
}

pkg/remote/connparse/connparse_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,50 @@ func TestParseURI_WSHCurrentPath(t *testing.T) {
212212
if c.GetFullURI() != expected {
213213
t.Fatalf("expected full URI to be %q, got %q", expected, c.GetFullURI())
214214
}
215+
216+
cstr = "path/to/file"
217+
c, err = connparse.ParseURI(cstr)
218+
if err != nil {
219+
t.Fatalf("failed to parse URI: %v", err)
220+
}
221+
expected = "path/to/file"
222+
if c.Path != expected {
223+
t.Fatalf("expected path to be %q, got %q", expected, c.Path)
224+
}
225+
expected = "current"
226+
if c.Host != expected {
227+
t.Fatalf("expected host to be %q, got %q", expected, c.Host)
228+
}
229+
expected = "wsh"
230+
if c.Scheme != expected {
231+
t.Fatalf("expected scheme to be %q, got %q", expected, c.Scheme)
232+
}
233+
expected = "wsh://current/path/to/file"
234+
if c.GetFullURI() != expected {
235+
t.Fatalf("expected full URI to be %q, got %q", expected, c.GetFullURI())
236+
}
237+
238+
cstr = "/etc/path/to/file"
239+
c, err = connparse.ParseURI(cstr)
240+
if err != nil {
241+
t.Fatalf("failed to parse URI: %v", err)
242+
}
243+
expected = "/etc/path/to/file"
244+
if c.Path != expected {
245+
t.Fatalf("expected path to be %q, got %q", expected, c.Path)
246+
}
247+
expected = "current"
248+
if c.Host != expected {
249+
t.Fatalf("expected host to be %q, got %q", expected, c.Host)
250+
}
251+
expected = "wsh"
252+
if c.Scheme != expected {
253+
t.Fatalf("expected scheme to be %q, got %q", expected, c.Scheme)
254+
}
255+
expected = "wsh://current/etc/path/to/file"
256+
if c.GetFullURI() != expected {
257+
t.Fatalf("expected full URI to be %q, got %q", expected, c.GetFullURI())
258+
}
215259
}
216260

217261
func TestParseURI_WSHCurrentPathWindows(t *testing.T) {

pkg/util/tarcopy/tarcopy.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ func TarCopyDest(ctx context.Context, cancel context.CancelCauseFunc, ch <-chan
8282
pipeReader, pipeWriter := io.Pipe()
8383
iochan.WriterChan(ctx, pipeWriter, ch, func() {
8484
gracefulClose(pipeWriter, tarCopyDestName, pipeWriterName)
85-
cancel(nil)
8685
}, cancel)
8786
tarReader := tar.NewReader(pipeReader)
8887
defer func() {

0 commit comments

Comments
 (0)