Skip to content
Open
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
5 changes: 2 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}/lana", "--disable-extensions"],
"args": ["--extensionDevelopmentPath=${workspaceFolder}/lana"],
"outFiles": ["${workspaceFolder}/lana/out/**/*.js"],
"localRoot": "${workspaceFolder}/lana"
},
Expand All @@ -21,8 +21,7 @@
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/${input:worktree}/lana",
"--disable-extensions"
"--extensionDevelopmentPath=${workspaceFolder}/${input:worktree}/lana"
],
"outFiles": ["${workspaceFolder}/${input:worktree}/lana/out/**/*.js"],
"localRoot": "${workspaceFolder}/${input:worktree}/lana"
Expand Down
11 changes: 7 additions & 4 deletions lana/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
"categories": [
"Other"
],
"extensionDependencies": [
"salesforce.salesforcedx-vscode-services"
],
"activationEvents": [
"onLanguage:apexlog",
"onStartupFinished"
Expand Down Expand Up @@ -94,8 +97,7 @@
"menus": {
"commandPalette": [
{
"command": "lana.showLogAnalysis",
"when": "resourceLangId == apexlog || lana.isApexLog"
"command": "lana.showLogAnalysis"
}
],
"editor/context": [
Expand Down Expand Up @@ -382,8 +384,9 @@
},
"dependencies": {
"@apexdevtools/apex-parser": "5.1.0",
"@salesforce/apex-node": "^9.0.0",
"@salesforce/core": "^9.1.0"
"@salesforce/vscode-services": "^67.12.0",
"effect": "^3.22.0",
"vscode-uri": "^3.1.0"
},
"devDependencies": {
"@types/jest": "^30.0.0",
Expand Down
7 changes: 5 additions & 2 deletions lana/src/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import type { ExtensionContext } from 'vscode';

import { Context } from './Context.js';
import { Display } from './display/Display.js';
import { disposeServices, initServices } from './services/servicesRuntime.js';

export let context: Context | null = null;

export function activate(extensionContext: ExtensionContext) {
export async function activate(extensionContext: ExtensionContext) {
await initServices();
context = new Context(extensionContext, new Display());
}

export function deactivate() {
export async function deactivate() {
context = null;
await disposeServices();
}
1 change: 1 addition & 0 deletions lana/src/__tests__/helpers/test-builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export interface MockContext {
context: MockExtensionContext;
display: MockDisplay;
workspaces: { uri: { fsPath: string }; name: string }[];
workspaceManager?: unknown;
}

/**
Expand Down
41 changes: 14 additions & 27 deletions lana/src/__tests__/mocks/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// a drift from `@types/vscode` surfaces as ONE error at the factory, not at
// every call site.
import type { EndOfLine, TextDocument } from 'vscode';
import { URI, Utils } from 'vscode-uri';

// Track subscriptions for cleanup
const subscriptions: { dispose: jest.Mock }[] = [];
Expand Down Expand Up @@ -110,34 +111,11 @@ export const ViewColumn = {
} as const;
export type ViewColumn = (typeof ViewColumn)[keyof typeof ViewColumn];

// Mock Uri class
// Delegate URI semantics to vscode-uri so virtual URI tests match VS Code.
export const Uri = {
file: jest.fn((path: string) => ({
scheme: 'file',
authority: '',
path,
fsPath: path,
query: '',
fragment: '',
with: jest.fn(),
toString: jest.fn(() => `file://${path}`),
toJSON: jest.fn(() => ({ scheme: 'file', path, fsPath: path })),
})),
parse: jest.fn((value: string) => ({
scheme: value.startsWith('file://') ? 'file' : 'unknown',
authority: '',
path: value.replace('file://', ''),
fsPath: value.replace('file://', ''),
query: '',
fragment: '',
with: jest.fn(),
toString: jest.fn(() => value),
})),
joinPath: jest.fn((base, ...pathSegments) => ({
...base,
path: [base.path, ...pathSegments].join('/'),
fsPath: [base.fsPath, ...pathSegments].join('/'),
})),
file: (path: string) => URI.file(path),
parse: (value: string) => URI.parse(value),
joinPath: (base: URI, ...pathSegments: string[]) => Utils.joinPath(base, ...pathSegments),
};

// Mock RelativePattern (constructor used for glob searches)
Expand Down Expand Up @@ -300,6 +278,10 @@ export const workspace = {
},
};

export const extensions = {
getExtension: jest.fn(),
};

// Mock window
export const window = {
showInformationMessage: jest.fn().mockResolvedValue(undefined),
Expand Down Expand Up @@ -342,6 +324,10 @@ export const window = {
replace: jest.fn(),
})),
createWebviewPanel: jest.fn(),
tabGroups: {
activeTabGroup: { activeTab: undefined },
onDidChangeTabs: jest.fn(() => ({ dispose: jest.fn() })),
},
activeTextEditor: undefined as unknown,
visibleTextEditors: [],
onDidChangeActiveTextEditor: jest.fn(() => ({ dispose: jest.fn() })),
Expand Down Expand Up @@ -558,6 +544,7 @@ export default {
ThemeColor,
ConfigurationTarget,
workspace,
extensions,
window,
commands,
languages,
Expand Down
20 changes: 10 additions & 10 deletions lana/src/cache/LogEventCache.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* Copyright (c) 2026 Certinia Inc. All rights reserved.
*/
import { readFile } from 'fs/promises';
import { workspace } from 'vscode';

import { parse, type ApexLog, type LogEvent } from 'apex-log-parser';

import type { Context } from '../Context.js';
import { readFile } from '../services/salesforceServices.js';

export interface EventSearchResult {
event: LogEvent;
Expand All @@ -17,17 +17,17 @@ export class LogEventCache {
private static readonly MAX_CACHE_SIZE = 10;
private static cache = new Map<string, ApexLog>();

static async getApexLog(filePath: string): Promise<ApexLog | null> {
const cached = LogEventCache.cache.get(filePath);
static async getApexLog(uriString: string): Promise<ApexLog | null> {
const cached = LogEventCache.cache.get(uriString);
if (cached) {
// Move to end (most recently used)
LogEventCache.cache.delete(filePath);
LogEventCache.cache.set(filePath, cached);
LogEventCache.cache.delete(uriString);
LogEventCache.cache.set(uriString, cached);
return cached;
}

try {
const content = await readFile(filePath, 'utf-8');
const content = await readFile(uriString);
const apexLog = parse(content);

// Evict oldest if at capacity
Expand All @@ -38,7 +38,7 @@ export class LogEventCache {
}
}

LogEventCache.cache.set(filePath, apexLog);
LogEventCache.cache.set(uriString, apexLog);
return apexLog;
} catch {
return null;
Expand All @@ -49,15 +49,15 @@ export class LogEventCache {
return LogEventCache.searchEvents(apexLog.children, timestamp, 0);
}

static clearCache(filePath: string): void {
LogEventCache.cache.delete(filePath);
static clearCache(uriString: string): void {
LogEventCache.cache.delete(uriString);
}

static apply(context: Context): void {
context.context.subscriptions.push(
workspace.onDidCloseTextDocument((doc) => {
if (doc.languageId === 'apexlog') {
LogEventCache.clearCache(doc.uri.fsPath);
LogEventCache.clearCache(doc.uri.toString());
}
}),
);
Expand Down
24 changes: 11 additions & 13 deletions lana/src/cache/__tests__/LogEventCache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright (c) 2026 Certinia Inc. All rights reserved.
*/
import { beforeEach, describe, expect, it } from '@jest/globals';

import { workspace } from 'vscode';

import {
Expand All @@ -12,18 +11,17 @@ import {
} from '../../__tests__/helpers/test-builders.js';
import { LogEventCache } from '../LogEventCache.js';

// Mock fs/promises
jest.mock('fs/promises', () => ({
readFile: jest.fn(),
}));

// Mock apex-log-parser
jest.mock('apex-log-parser', () => ({
parse: jest.fn(),
}));

import { parse } from 'apex-log-parser';
import { readFile } from 'fs/promises';
import { readFile } from '../../services/salesforceServices.js';

jest.mock('../../services/salesforceServices.js', () => ({
readFile: jest.fn(),
}));

const mockReadFile = readFile as jest.Mock;
const mockParse = parse as jest.Mock;
Expand Down Expand Up @@ -373,8 +371,8 @@ describe('LogEventCache', () => {
await LogEventCache.getApexLog('/test/file.log');

// Capture the callback
let closeCallback: ((doc: { languageId: string; uri: { fsPath: string } }) => void) | null =
null;
let closeCallback:
((doc: { languageId: string; uri: { toString: () => string } }) => void) | null = null;
(workspace.onDidCloseTextDocument as jest.Mock).mockImplementationOnce((cb) => {
closeCallback = cb;
return { dispose: jest.fn() };
Expand All @@ -386,7 +384,7 @@ describe('LogEventCache', () => {
// Simulate closing an apexlog document
closeCallback!({
languageId: 'apexlog',
uri: { fsPath: '/test/file.log' },
uri: { toString: () => '/test/file.log' },
});

// @ts-expect-error - accessing private static for testing
Expand All @@ -401,8 +399,8 @@ describe('LogEventCache', () => {
await LogEventCache.getApexLog('/test/file.log');

// Capture the callback
let closeCallback: ((doc: { languageId: string; uri: { fsPath: string } }) => void) | null =
null;
let closeCallback:
((doc: { languageId: string; uri: { toString: () => string } }) => void) | null = null;
(workspace.onDidCloseTextDocument as jest.Mock).mockImplementationOnce((cb) => {
closeCallback = cb;
return { dispose: jest.fn() };
Expand All @@ -414,7 +412,7 @@ describe('LogEventCache', () => {
// Simulate closing a non-apexlog document
closeCallback!({
languageId: 'javascript',
uri: { fsPath: '/test/file.log' },
uri: { toString: () => '/test/file.log' },
});

// @ts-expect-error - accessing private static for testing
Expand Down
6 changes: 1 addition & 5 deletions lana/src/codelenses/ShowAnalysisCodeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ class ShowAnalysisCodeLens implements CodeLensProvider {
}

static apply(context: Context): void {
const docSelector = [
{ scheme: 'file', language: 'apexlog' },
{ scheme: 'file', pattern: '**/*.log' },
{ scheme: 'file', pattern: '**/*.txt' },
];
const docSelector = [{ language: 'apexlog' }, { pattern: '**/*.log' }, { pattern: '**/*.txt' }];

const codeLensProviderDisposable = languages.registerCodeLensProvider(
docSelector,
Expand Down
Loading