Skip to content

Commit a0be3de

Browse files
authored
hls: open last rpt (#1015)
* hls: open last rpt * stream new data with start offset * dispose onDidClose
1 parent 8d4d243 commit a0be3de

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

hls/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@
223223
"title": "Convert to wss",
224224
"category": "HEMTT Audio",
225225
"enablement": "resourceExtname == '.wav' || resourceExtname == '.ogg' || resourceExtname == '.mp3'"
226+
},
227+
{
228+
"command": "hemtt.openLastRPT",
229+
"title": "Open Last RPT",
230+
"category": "HEMTT"
226231
}
227232
],
228233
"menus": {

hls/vscode-src/extension.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import * as audio from "./audio";
1212
import * as p3d from "./p3d";
1313
import * as paa from "./paa";
1414
import * as preprocessor from "./preprocessor";
15+
import * as rpt from "./rpt";
1516

1617
import { getPortPromise } from "portfinder";
1718

@@ -77,6 +78,7 @@ export async function activate(context: vscode.ExtensionContext) {
7778
p3d.init(client, channel, context);
7879
paa.init(client, channel, context);
7980
preprocessor.init(client, channel, context);
81+
rpt.init(client, channel, context);
8082
channel.appendLine("HEMTT initialized");
8183
}
8284

hls/vscode-src/rpt/index.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { existsSync, lstatSync } from "fs";
2+
import { readdir } from "fs/promises";
3+
import path = require("path");
4+
import * as vscode from "vscode";
5+
import { LanguageClient } from "vscode-languageclient/node";
6+
import { LogFileViewer } from "./viewer";
7+
8+
export function init(client: LanguageClient, channel: vscode.OutputChannel, context: vscode.ExtensionContext) {
9+
LogFileViewer.setupRptStatusBarVisibility();
10+
context.subscriptions.push(vscode.commands.registerCommand("hemtt.openLastRPT", async () => {
11+
let rptPath = "";
12+
switch (process.platform) {
13+
case "win32":
14+
if (process.env.LOCALAPPDATA) {
15+
rptPath = path.join(process.env.LOCALAPPDATA, "Arma 3");
16+
} else {
17+
return vscode.window.showErrorMessage("Could not find local app data path.");
18+
}
19+
break;
20+
case "linux":
21+
rptPath = linuxRptPath();
22+
break;
23+
case "darwin":
24+
rptPath = path.join(process.env.HOME || "", "Library", "Application Support", "Steam", "steamapps", "common", "Arma 3");
25+
break;
26+
}
27+
if (rptPath === "") {
28+
return vscode.window.showErrorMessage("Could not determine Arma 3 rpt path for your platform.");
29+
}
30+
if (!existsSync(rptPath)) {
31+
return vscode.window.showErrorMessage(`Could not find Arma 3 rpt path: ${rptPath}`);
32+
}
33+
const rptFiles = await readdir(rptPath);
34+
const rptFile = rptFiles
35+
.filter(file => file.endsWith(".rpt"))
36+
.map(file => {
37+
return {
38+
path: path.join(rptPath, file),
39+
mtime: lstatSync(path.join(rptPath, file)).mtime,
40+
}
41+
})
42+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())[0];
43+
if (!rptFile) {
44+
return vscode.window.showErrorMessage("No rpt files found in Arma 3 directory.");
45+
}
46+
if (!existsSync(rptFile.path)) {
47+
return vscode.window.showErrorMessage(`Could not find rpt file: ${rptFile.path}`);
48+
}
49+
LogFileViewer.watch(rptFile.path);
50+
}));
51+
}
52+
53+
function linuxRptPath(): string {
54+
if (process.env.HOME) {
55+
let rptPath = path.join(process.env.HOME, ".var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/compatdata/107410/pfx/drive_c/users/steamuser/AppData/Local/Arma 3/");
56+
if (existsSync(rptPath)) {
57+
return rptPath;
58+
}
59+
rptPath = path.join(process.env.HOME, "/.steam/steam/steamapps/compatdata/107410/pfx/drive_c/users/steamuser/AppData/Local/Arma 3/");
60+
if (existsSync(rptPath)) {
61+
return rptPath;
62+
}
63+
}
64+
return "";
65+
}

hls/vscode-src/rpt/viewer.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import * as vscode from 'vscode';
2+
import * as fs from 'fs';
3+
4+
export class LogFileViewer {
5+
private static readonly scheme = 'logfile';
6+
private static provider: LogFileContentProvider | undefined;
7+
private static autoScroll = true;
8+
private static statusBarItem: vscode.StatusBarItem | undefined;
9+
private static disposables: Map<string, vscode.Disposable> = new Map();
10+
11+
public static async watch(filePath: string) {
12+
const uri = vscode.Uri.from({ scheme: this.scheme, path: filePath });
13+
14+
if (!this.provider) {
15+
this.provider = new LogFileContentProvider();
16+
vscode.workspace.registerTextDocumentContentProvider(this.scheme, this.provider);
17+
}
18+
19+
this.showAutoScrollStatusBar();
20+
21+
const doc = await vscode.workspace.openTextDocument(uri);
22+
await vscode.window.showTextDocument(doc, { preview: false });
23+
vscode.languages.setTextDocumentLanguage(doc, 'log');
24+
25+
let timeout: NodeJS.Timeout | undefined;
26+
const watcher = fs.watch(filePath, {}, () => {
27+
if (timeout) clearTimeout(timeout);
28+
timeout = setTimeout(() => {
29+
this.provider?.update(uri);
30+
}, 100);
31+
});
32+
33+
this.disposables.set(uri.toString(), vscode.workspace.onDidCloseTextDocument(closedDoc => {
34+
if (closedDoc.uri.toString() === uri.toString()) {
35+
watcher.close();
36+
this.disposables.get(uri.toString())?.dispose();
37+
this.disposables.delete(uri.toString());
38+
}
39+
}));
40+
41+
if (this.autoScroll) {
42+
scrollToBottom(vscode.window.activeTextEditor!);
43+
}
44+
}
45+
46+
private static showAutoScrollStatusBar() {
47+
if (!this.statusBarItem) {
48+
this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
49+
this.statusBarItem.command = 'logfile.toggleAutoScroll';
50+
vscode.commands.registerCommand('logfile.toggleAutoScroll', () => {
51+
this.autoScroll = !this.autoScroll;
52+
this.updateStatusBarText();
53+
if (this.autoScroll) {
54+
scrollToBottom(vscode.window.activeTextEditor!);
55+
}
56+
});
57+
}
58+
this.updateStatusBarText();
59+
}
60+
61+
private static updateStatusBarText() {
62+
if (this.statusBarItem) {
63+
this.statusBarItem.text = `$(arrow-down) Auto Scroll: ${this.autoScroll ? 'On' : 'Off'}`;
64+
this.statusBarItem.tooltip = 'Toggle auto scroll to bottom on log update';
65+
}
66+
}
67+
68+
public static isAutoScrollEnabled() {
69+
return this.autoScroll;
70+
}
71+
72+
public static setupRptStatusBarVisibility() {
73+
const updateStatusBarVisibility = () => {
74+
const hasRpt = vscode.window.visibleTextEditors.some(
75+
editor => editor.document.uri.fsPath.endsWith('.rpt')
76+
);
77+
if (hasRpt) {
78+
this.statusBarItem?.show();
79+
} else {
80+
this.statusBarItem?.hide();
81+
}
82+
};
83+
84+
vscode.window.onDidChangeVisibleTextEditors(updateStatusBarVisibility);
85+
vscode.workspace.onDidCloseTextDocument(updateStatusBarVisibility);
86+
vscode.workspace.onDidOpenTextDocument(updateStatusBarVisibility);
87+
updateStatusBarVisibility();
88+
}
89+
}
90+
91+
function scrollToBottom(editor: vscode.TextEditor) {
92+
const lastLine = editor.document.lineCount - 1;
93+
const lastLineRange = editor.document.lineAt(lastLine).range;
94+
editor.revealRange(lastLineRange, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
95+
}
96+
97+
class LogFileContentProvider implements vscode.TextDocumentContentProvider {
98+
private _onDidChange = new vscode.EventEmitter<vscode.Uri>();
99+
public onDidChange = this._onDidChange.event;
100+
private filePositions: Map<string, number> = new Map();
101+
private fileContents: Map<string, string> = new Map();
102+
103+
provideTextDocumentContent(uri: vscode.Uri): Thenable<string> {
104+
const filePath = uri.path;
105+
return new Promise((resolve) => {
106+
const lastPos = this.filePositions.get(filePath) ?? 0;
107+
let start = lastPos;
108+
let data = '';
109+
let bytesRead = 0;
110+
const stream = fs.createReadStream(filePath, { encoding: 'utf8', start });
111+
stream.on('data', chunk => {
112+
data += chunk;
113+
bytesRead += Buffer.byteLength(chunk, 'utf8');
114+
});
115+
stream.on('end', () => {
116+
const prev = start === 0 ? '' : (this.fileContents.get(filePath) ?? '');
117+
const content = prev + data;
118+
this.fileContents.set(filePath, content);
119+
this.filePositions.set(filePath, start + bytesRead);
120+
resolve(content);
121+
});
122+
stream.on('error', error => {
123+
this.filePositions.set(filePath, 0);
124+
this.fileContents.set(filePath, '');
125+
const errorMessage = error instanceof Error ? error.message : String(error);
126+
resolve(`Failed to read file: ${errorMessage}`);
127+
});
128+
});
129+
}
130+
131+
update(uri: vscode.Uri) {
132+
this._onDidChange.fire(uri);
133+
setTimeout(() => {
134+
if (LogFileViewer.isAutoScrollEnabled()) {
135+
const editor = vscode.window.visibleTextEditors.find(e => e.document.uri.toString() === uri.toString());
136+
if (editor) {
137+
scrollToBottom(editor);
138+
}
139+
}
140+
}, 100);
141+
}
142+
}

0 commit comments

Comments
 (0)