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
49 changes: 45 additions & 4 deletions src/lib/openFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import fileTypeHandler from "./fileTypeHandler";
import recents from "./recents";
import appSettings from "./settings";

let loadingFileCount = 0;

/**
* @typedef {object} FileOptions
* @property {string} text
Expand All @@ -24,6 +26,7 @@ import appSettings from "./settings";
* @property {string} uri
* @property {string} paneId
* @property {boolean} persistInSession
* @property {AbortSignal} signal Discard an obsolete open before activating its file.
*/

/**
Expand All @@ -33,6 +36,9 @@ import appSettings from "./settings";
*/

export default async function openFile(file, options = {}) {
const { signal } = options;
if (signal?.aborted) return;
let releaseTitleLoader;
try {
let uri = typeof file === "string" ? file : file.uri;
if (!uri) return;
Expand Down Expand Up @@ -112,13 +118,15 @@ export default async function openFile(file, options = {}) {
return;
}

loader.showTitleLoader();
releaseTitleLoader = acquireTitleLoader(signal);
const settings = appSettings.value;
const fs = fsOperation(uri);
const fileInfo = await fs.stat();
if (signal?.aborted) return;
const name = fileInfo.name || file.filename || uri;
const readOnly = fileInfo.canWrite === false;
const createEditor = (isUnsaved, text, detectedEncoding) => {
if (signal?.aborted) return;
new EditorFile(name, {
uri,
text,
Expand Down Expand Up @@ -152,10 +160,12 @@ export default async function openFile(file, options = {}) {
encoding,
mode,
createEditor,
signal,
},
});
return;
} catch (error) {
if (signal?.aborted) return;
console.error(`File handler '${customHandler.id}' failed:`, error);
// Continue with default handling if custom handler fails
}
Expand All @@ -173,6 +183,10 @@ export default async function openFile(file, options = {}) {

if (videoRegex.test(name)) {
const objectUrl = await fileToDataUrl(uri);
if (signal?.aborted) {
URL.revokeObjectURL(objectUrl);
return;
}
const videoContainer = (
<div
style={{
Expand Down Expand Up @@ -212,6 +226,10 @@ export default async function openFile(file, options = {}) {

if (imageRegex.test(name)) {
const objectUrl = await fileToDataUrl(uri);
if (signal?.aborted) {
URL.revokeObjectURL(objectUrl);
return;
}
const imageContainer = (
<div
className="image-container"
Expand Down Expand Up @@ -381,6 +399,10 @@ export default async function openFile(file, options = {}) {

if (audioRegex.test(name)) {
const objectUrl = await fileToDataUrl(uri);
if (signal?.aborted) {
URL.revokeObjectURL(objectUrl);
return;
}
const audioContainer = (
<div
style={{
Expand Down Expand Up @@ -425,10 +447,11 @@ export default async function openFile(file, options = {}) {

if (helpers.isBinary(uri)) {
const confirmation = await confirm(strings.info, strings["binary file"]);
if (!confirmation) return;
if (!confirmation || signal?.aborted) return;
}

const binData = await fs.readFile();
if (signal?.aborted) return;

// Determine encoding: if explicit provided use it, otherwise
// if settings.defaultFileEncoding === 'auto' then detect; else use the default as-is
Expand All @@ -448,18 +471,36 @@ export default async function openFile(file, options = {}) {
}
}

if (signal?.aborted) return;
const fileContent = await decode(binData, detectedEncoding);
if (signal?.aborted) return;

createEditor(false, fileContent, detectedEncoding);
if (mode !== "single") recents.addFile(uri);
return;
} catch (error) {
console.error(error);
if (!signal?.aborted) console.error(error);
} finally {
loader.removeTitleLoader();
releaseTitleLoader?.();
}
}

/** Keep the shared indicator visible while any file open still needs it. */
function acquireTitleLoader(signal) {
if (loadingFileCount++ === 0) loader.showTitleLoader();
let released = false;
const release = () => {
// An aborted filesystem call may settle much later. Release immediately
// on abort, and make its eventual finally block a no-op.
if (released) return;
released = true;
signal?.removeEventListener("abort", release);
if (--loadingFileCount === 0) loader.removeTitleLoader();
};
signal?.addEventListener("abort", release, { once: true });
return release;
}

/**
* Converts file to data url
* @param {string} file file url
Expand Down
22 changes: 4 additions & 18 deletions src/sidebarApps/searchInFiles/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import "./styles.scss";
import fsOperation from "fileSystem";
import { EditorView } from "@codemirror/view";
import autosize from "autosize";
import { getDocText } from "cm/editorUtils";
import Checkbox from "components/checkbox";
Expand All @@ -15,6 +14,7 @@ import { addedFolder } from "lib/openFolder";
import settings from "lib/settings";
import helpers from "utils/helpers";
import { createSearchResultView } from "./cmResultView";
import navigateToResult from "./navigateToResult";

// Local highlight sources
const words = [];
Expand Down Expand Up @@ -1121,27 +1121,13 @@ async function onCursorChange(line) {
const result = results[line];
if (!result) return;
const { file, position } = result;
if (!position) {
// header line clicked; CM view folding not implemented yet
return;
}
const url = filesSearched[file]?.url;
if (!position || !url) return;

rememberResultScroll();
Sidebar.hide();
const { url } = filesSearched[file];
await openFile(url, { render: true });
const { editor } = editorManager;
try {
// Compute offsets from row/column (rows from worker are 0-based)
const doc = editor.state.doc;
const startLine = doc.line(position.start.row + 1);
const endLine = doc.line(position.end.row + 1);
const from = Math.min(startLine.from + position.start.column, startLine.to);
const to = Math.min(endLine.from + position.end.column, endLine.to);
editor.dispatch({
selection: { anchor: from, head: to },
effects: EditorView.scrollIntoView(from, { y: "center" }),
});
await navigateToResult(url, position);
} catch (error) {
console.warn(`Failed to focus search result at line ${line}.`, error);
}
Expand Down
59 changes: 59 additions & 0 deletions src/sidebarApps/searchInFiles/navigateToResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import openFile from "lib/openFile";

let currentNavigation;

/** Open a search match and reveal its zero-based row/column range. */
export default async function navigateToResult(url, position) {
currentNavigation?.abort();
const navigation = new AbortController();
currentNavigation = navigation;
const { signal } = navigation;

try {
// Start immediately even if an obsolete filesystem operation is stalled.
// openFile checks the signal before creating/activating a late result.
await openFile(url, { render: true, signal });
Comment thread
bajrangCoder marked this conversation as resolved.
const file = editorManager.getFile(url, "uri");
if (
signal.aborted ||
file?.type !== "editor" ||
editorManager.activeFile !== file
) {
return false;
}

// load() reuses a restored tab's in-flight load. Do not cancel that shared
// load; only discard this request's reveal if a newer result is selected.
await file.load();
if (
signal.aborted ||
!file.loaded ||
file.loading ||
editorManager.activeFile !== file ||
editorManager.getFile(url, "uri") !== file
) {
return false;
}

const doc = editorManager.editor.state.doc;
const from = positionToOffset(doc, position.start);
const to = positionToOffset(doc, position.end);
// Cancel delayed tab scroll restoration and scrollbar locks before reveal.
return editorManager.revealRange(from, to, {
y: "center",
userEvent: "select.search",
});
} catch (error) {
if (signal.aborted) return false;
throw error;
} finally {
if (currentNavigation === navigation) currentNavigation = undefined;
}
}

function positionToOffset(doc, { row, column }) {
// Search results can outlive edits to the file. Clamp both coordinates so
// an older result still navigates to the nearest available position.
const line = doc.line(Math.max(1, Math.min(row + 1, doc.lines)));
return line.from + Math.max(0, Math.min(column, line.length));
}
Loading