├── .gitignore ├── img ├── icon.png ├── icon.psd └── Code_fKwUDaApe9.gif ├── .vscodeignore ├── src ├── model │ ├── PragmaParseResult.ts │ ├── PragmaTreeItem.ts │ ├── ALObjectType.ts │ ├── PragmaInstance.ts │ ├── Pragma.ts │ ├── PragmaFile.ts │ └── PragmaWorkspace.ts ├── updater │ ├── ALApp.ts │ └── updater.ts ├── parser │ ├── getFiles.ts │ └── parse.ts ├── test │ ├── suite │ │ ├── extension.test.ts │ │ └── index.ts │ └── runTest.ts ├── commands │ └── toggle.ts ├── extension.ts └── ui │ ├── PragmaFileDecorationsProvider.ts │ └── PragmaViewProvider.ts ├── .vscode ├── extensions.json ├── tasks.json ├── settings.json └── launch.json ├── CHANGELOG.md ├── README.md ├── tsconfig.json ├── .eslintrc.json ├── LICENSE.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjekob/al-pragma-explorer/HEAD/img/icon.png -------------------------------------------------------------------------------- /img/icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjekob/al-pragma-explorer/HEAD/img/icon.psd -------------------------------------------------------------------------------- /img/Code_fKwUDaApe9.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vjekob/al-pragma-explorer/HEAD/img/Code_fKwUDaApe9.gif -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/.eslintrc.json 9 | **/*.map 10 | **/*.ts 11 | -------------------------------------------------------------------------------- /src/model/PragmaParseResult.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range, Uri } from 'vscode'; 2 | 3 | export interface PragmaParseResult { 4 | uri: Uri; 5 | id: string; 6 | positions: Position[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/model/PragmaTreeItem.ts: -------------------------------------------------------------------------------- 1 | import { TreeItem } from "vscode"; 2 | 3 | export interface PragmaTreeItem extends TreeItem { 4 | getChildren: () => PragmaTreeItem[] | Promise; 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/updater/ALApp.ts: -------------------------------------------------------------------------------- 1 | import { Uri, WorkspaceFolder } from 'vscode'; 2 | 3 | export interface ALApp { 4 | workspace: WorkspaceFolder; 5 | uri: Uri; 6 | id: string; 7 | name: string; 8 | publisher: string; 9 | preprocessorSymbols?: string[]; 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "al-pragma-explorer" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /src/parser/getFiles.ts: -------------------------------------------------------------------------------- 1 | import { workspace, WorkspaceFolder } from 'vscode'; 2 | import { ALObjectType } from '../model/ALObjectType'; 3 | import { Pragma } from '../model/Pragma'; 4 | import { PragmaFile } from '../model/PragmaFile'; 5 | 6 | export async function getFiles() { 7 | const alFiles = await workspace.findFiles('**/*.al'); 8 | return alFiles; 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": true, // set this to true to hide the "out" folder with the compiled JS files 5 | "node_modules": true 6 | }, 7 | "search.exclude": { 8 | "out": true // set this to false to include "out" folder in search results 9 | }, 10 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 11 | "typescript.tsc.autoDetect": "off", 12 | } -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/commands/toggle.ts: -------------------------------------------------------------------------------- 1 | import { Pragma } from '../model/Pragma'; 2 | import { addPragma, getALApp, removePragma } from '../updater/updater'; 3 | 4 | export async function toggle(pragma: Pragma) { 5 | if (!pragma) { 6 | return; 7 | } 8 | 9 | const app = await getALApp(pragma.folder); 10 | if (!app) { 11 | return; 12 | } 13 | 14 | if (app.preprocessorSymbols?.includes(pragma.name)) { 15 | removePragma(pragma.folder, pragma.name); 16 | } else { 17 | addPragma(pragma.folder, pragma.name); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AL Pragma Explorer README 2 | 3 | ## Features 4 | 5 | This extension enables a view in the Explorer. 6 | 7 | The view shows a treeview of 8 | - workspaces 9 | - pragmas 10 | - files 11 | - Line `where-used` 12 | 13 | 14 | ![al-Pragme-View](https://i.imgur.com/WCWAdqx.gif) 15 | 16 | ## Requirements 17 | 18 | Extension gets activate on AL language detection 19 | 20 | ## Extension Settings 21 | 22 | None (in progress) 23 | 24 | ## Release Notes 25 | 26 | ### 0.0.1 27 | 28 | > September 14, 2022 29 | > 30 | >- Initial release of AL Pragma Explorer 31 | 32 | -------------------------------------------------------------------------------- /src/model/ALObjectType.ts: -------------------------------------------------------------------------------- 1 | export enum ALObjectType { 2 | Table = 'Table', 3 | TableExtension = 'TableExtension', 4 | Report = 'Report', 5 | ReportExtension = 'ReportExtension', 6 | Codeunit = 'Codeunit', 7 | XMLport = 'XMLport', 8 | MenuSuite = 'MenuSuite', 9 | Page = 'Page', 10 | PageExtension = 'PageExtension', 11 | Query = 'Query', 12 | Enum = 'Enum', 13 | EnumExtension = 'EnumExtension', 14 | Profile = 'Profile', 15 | ProfileExtension = 'ProfileExtension', 16 | PermissionSet = 'PermissionSet', 17 | PermissionSetExtension = 'PermissionSetExtension' 18 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ 7 | "ES2020" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { toggle } from './commands/toggle'; 3 | import { PragmaViewProvider } from './ui/PragmaViewProvider'; 4 | 5 | export function activate(context: vscode.ExtensionContext) { 6 | context.subscriptions.push(vscode.window.registerTreeDataProvider("al-pragma-explorer.view", new PragmaViewProvider)); 7 | context.subscriptions.push(vscode.commands.registerCommand("al-pragma-explorer.toggle", toggle)); 8 | 9 | vscode.commands.executeCommand("setContext", "al-pragma-explorer.showPragmaExplorer", true); 10 | } 11 | 12 | export function deactivate() { 13 | vscode.commands.executeCommand("setContext", "al-pragma-explorer.showPragmaExplorer", false); 14 | } 15 | -------------------------------------------------------------------------------- /src/model/PragmaInstance.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; 2 | import { PragmaTreeItem } from './PragmaTreeItem'; 3 | 4 | export class PragmaInstance extends TreeItem implements PragmaTreeItem { 5 | public position: Position; 6 | 7 | constructor(uri: Uri, position: Position) { 8 | super(`Line ${position.line + 1}`, TreeItemCollapsibleState.None); 9 | this.position = position; 10 | this.command = { 11 | command: 'vscode.open', 12 | arguments: [uri, { selection: new Range(position, position) }], 13 | title: '', 14 | }; 15 | } 16 | 17 | getChildren() { 18 | return []; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "${defaultBuildTask}" 19 | }, 20 | { 21 | "name": "Extension Tests", 22 | "type": "extensionHost", 23 | "request": "launch", 24 | "args": [ 25 | "--extensionDevelopmentPath=${workspaceFolder}", 26 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 27 | ], 28 | "outFiles": [ 29 | "${workspaceFolder}/out/test/**/*.js" 30 | ], 31 | "preLaunchTask": "${defaultBuildTask}" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Vjekoslav Babić 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ui/PragmaFileDecorationsProvider.ts: -------------------------------------------------------------------------------- 1 | import { Event, EventEmitter, FileDecorationProvider, ThemeColor, Uri, workspace } from 'vscode'; 2 | import { getALApp } from '../updater/updater'; 3 | 4 | export class PragmaFileDecorationsProvider implements FileDecorationProvider { 5 | private _onDidChangeFileDecorations: EventEmitter = new EventEmitter< 6 | Uri | Uri[] | undefined 7 | >(); 8 | onDidChangeFileDecorations?: Event = this._onDidChangeFileDecorations.event; 9 | 10 | async provideFileDecoration(uri: Uri) { 11 | if (uri.scheme !== 'alpragmas') { 12 | return; 13 | } 14 | 15 | const folder = workspace.workspaceFolders?.find((f) => f.name === uri.authority); 16 | if (!folder) { 17 | return; 18 | } 19 | 20 | const app = await getALApp(folder); 21 | 22 | const active = app?.preprocessorSymbols?.includes(uri.path.substring(1)); 23 | return { 24 | color: new ThemeColor(active ? 'gitDecoration.addedResourceForeground' : 'disabledForeground'), 25 | badge: active ? 'ON' : '', 26 | }; 27 | } 28 | 29 | public refresh() { 30 | this._onDidChangeFileDecorations.fire(undefined); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/model/Pragma.ts: -------------------------------------------------------------------------------- 1 | import { ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri, workspace, WorkspaceFolder } from 'vscode'; 2 | import * as vscode from 'vscode'; 3 | import { ALObjectType } from './ALObjectType'; 4 | import { PragmaFile } from './PragmaFile'; 5 | import { PragmaParseResult } from './PragmaParseResult'; 6 | import { PragmaTreeItem } from './PragmaTreeItem'; 7 | 8 | export class Pragma extends TreeItem implements PragmaTreeItem { 9 | public folder: WorkspaceFolder; 10 | 11 | public name: string; 12 | public files?: PragmaFile[]; 13 | 14 | constructor(id: string, pragmas: PragmaParseResult[] | undefined, parent: WorkspaceFolder) { 15 | super(id, TreeItemCollapsibleState.Collapsed); 16 | this.name = id; 17 | const settings = vscode.workspace.getConfiguration('al-pragma-explorer'); 18 | this.iconPath = new ThemeIcon(settings.pragmaIcon); 19 | this.folder = parent; 20 | this.resourceUri = Uri.from({ scheme: 'alpragmas', authority: this.folder.name, path: `/${id}` }); 21 | 22 | if (pragmas?.length) { 23 | this.files = pragmas.map((pragma) => new PragmaFile(pragma.uri, pragma.positions)); 24 | } 25 | this.contextValue = "ispragma"; 26 | } 27 | 28 | async getChildren(): Promise { 29 | if (!this.files) { 30 | return []; 31 | } 32 | return this.files; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/model/PragmaFile.ts: -------------------------------------------------------------------------------- 1 | import { getVSCodeDownloadUrl } from '@vscode/test-electron/out/util'; 2 | import path = require('path'); 3 | import { Position, Range, ThemeIcon, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode'; 4 | import { parse } from '../parser/parse'; 5 | import { ALObjectType } from './ALObjectType'; 6 | import { PragmaInstance } from './PragmaInstance'; 7 | import { PragmaTreeItem } from './PragmaTreeItem'; 8 | import * as vscode from 'vscode'; 9 | 10 | export class PragmaFile extends TreeItem implements PragmaTreeItem { 11 | private _positions: vscode.Position[]; 12 | 13 | public objectType: ALObjectType; 14 | public instances?: PragmaInstance[]; 15 | 16 | constructor(uri: Uri, positions: vscode.Position[]) { 17 | const filename = path.basename(uri.fsPath); 18 | super(filename, TreeItemCollapsibleState.Collapsed); 19 | 20 | this._positions = positions; 21 | this.command = { command: 'vscode.open', title: '', arguments: [uri] }; 22 | 23 | this.resourceUri = uri; 24 | this.objectType = ALObjectType.Codeunit; 25 | this.iconPath = ThemeIcon.File; 26 | } 27 | 28 | getChildren(): PragmaInstance[] { 29 | if (this.instances) { 30 | return this.instances; 31 | } 32 | 33 | this.instances = this._positions.map((position) => new PragmaInstance(this.resourceUri!, position)); 34 | return this.instances; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/model/PragmaWorkspace.ts: -------------------------------------------------------------------------------- 1 | import { Position, TreeItem, TreeItemCollapsibleState, Uri, workspace, WorkspaceFolder } from 'vscode'; 2 | import { getFiles } from '../parser/getFiles'; 3 | import { parse } from '../parser/parse'; 4 | import { Pragma } from './Pragma'; 5 | import { PragmaParseResult } from './PragmaParseResult'; 6 | import { PragmaTreeItem } from './PragmaTreeItem'; 7 | 8 | export class PragmaWorkspace extends TreeItem implements PragmaTreeItem { 9 | public workspace: WorkspaceFolder; 10 | public pragmas?: Pragma[]; 11 | 12 | constructor(folder: WorkspaceFolder) { 13 | super(folder.name, TreeItemCollapsibleState.Collapsed); 14 | 15 | this.workspace = folder; 16 | this.id = folder.uri.fsPath; 17 | this.resourceUri = folder.uri; 18 | } 19 | 20 | async getChildren(): Promise { 21 | if (this.pragmas) { 22 | return this.pragmas; 23 | } 24 | 25 | const alFiles = await getFiles(); 26 | const myFiles = alFiles.filter((uri) => workspace.getWorkspaceFolder(uri) === this.workspace); 27 | if (!myFiles.length) { 28 | return []; 29 | } 30 | 31 | const parseResults: PragmaParseResult[] = []; 32 | const uniqueIds: string[] = []; 33 | for (let file of myFiles) { 34 | const results = parse(file); 35 | for (let result of results) { 36 | if (uniqueIds.includes(result.id)) { 37 | continue; 38 | } 39 | uniqueIds.push(result.id); 40 | } 41 | parseResults.push(...results); 42 | } 43 | 44 | this.pragmas = uniqueIds.map((id) => { 45 | const uris: Uri[] = []; 46 | 47 | return new Pragma( 48 | id, 49 | parseResults.filter((result) => result.id === id), 50 | this.workspace 51 | ); 52 | }); 53 | 54 | return this.pragmas.sort((a, b) => (a.name! > b.name! ? 1 : -1)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/parser/parse.ts: -------------------------------------------------------------------------------- 1 | import { Position, Range, Uri } from 'vscode'; 2 | import { PragmaParseResult } from '../model/PragmaParseResult'; 3 | import * as fs from 'fs'; 4 | 5 | export function parse(uri: Uri): PragmaParseResult[] { 6 | const contents = fs.readFileSync(uri.fsPath).toString(); 7 | const lines = contents.split('\n'); 8 | 9 | const results: PragmaParseResult[] = []; 10 | 11 | for (let line = 0; line < lines.length; line++) { 12 | let lineStr = lines[line]; 13 | const lineStrTrimmed = lineStr.trim(); 14 | if (!lineStrTrimmed.startsWith('#')) { 15 | continue; 16 | } 17 | 18 | let lineUppercase = lineStr.toUpperCase(); 19 | let character = lineUppercase[0] === '#' ? 0 : lineUppercase.indexOf('#'); 20 | const ifPos = lineUppercase.indexOf('#IF '); 21 | let pos = ifPos + 3; 22 | if (ifPos !== character) { 23 | const elifPos = lineUppercase.indexOf('#ELIF '); 24 | if (elifPos !== character) { 25 | continue; 26 | } 27 | pos = elifPos + 5; 28 | } 29 | 30 | lineUppercase = lineUppercase.substring(pos); 31 | lineUppercase = lineUppercase.split('//')[0]; //get rid off comments 32 | lineUppercase = lineUppercase.replace(/[(]/g, ' '); 33 | lineUppercase = lineUppercase.replace(/[)]/g, ' '); 34 | 35 | const parts = lineUppercase.trim().split(' '); 36 | for (let part of parts) { 37 | part = part.trim(); 38 | switch(part) { 39 | case 'AND': 40 | case 'OR': 41 | case 'NOT': 42 | continue; 43 | } 44 | const result = results.find((result) => result.id === part); 45 | if (result) { 46 | result.positions.push(new Position(line, character)); 47 | } else { 48 | results.push({ 49 | uri, 50 | id: part, 51 | positions: [new Position(line, character)], 52 | }); 53 | } 54 | } 55 | } 56 | 57 | return results; 58 | } 59 | -------------------------------------------------------------------------------- /src/updater/updater.ts: -------------------------------------------------------------------------------- 1 | import { Uri, WorkspaceFolder } from 'vscode'; 2 | import * as fs from 'fs'; 3 | import { ALApp } from './ALApp'; 4 | 5 | function getPath(folder: WorkspaceFolder) { 6 | return `${folder.uri?.fsPath}/app.json`; 7 | } 8 | 9 | export async function getALApp(folder: WorkspaceFolder): Promise { 10 | if (!folder) { 11 | return; 12 | } 13 | const uri = Uri.file(getPath(folder)); 14 | 15 | try { 16 | const appRaw = await new Promise((resolve) => { 17 | fs.readFile(uri.fsPath, (err, data) => { 18 | resolve(err || data); 19 | }); 20 | }); 21 | if (!(appRaw instanceof Buffer)) { 22 | return; 23 | } 24 | return JSON.parse(appRaw.toString()); 25 | } catch (err) { 26 | return; 27 | } 28 | } 29 | 30 | export async function addPragma(folder: WorkspaceFolder, pragma: string) { 31 | if (!folder) { 32 | return; 33 | } 34 | const uri = Uri.file(getPath(folder)); 35 | const app = await getALApp(folder); 36 | 37 | if (!app) { 38 | return; 39 | } 40 | 41 | if (!app.preprocessorSymbols) { 42 | app.preprocessorSymbols = []; 43 | } 44 | 45 | if (app.preprocessorSymbols.indexOf(pragma) === -1) { 46 | app.preprocessorSymbols.push(pragma); 47 | fs.writeFile(uri.fsPath, JSON.stringify(app, null, 2), (err) => { 48 | if (err) return console.log(err); 49 | }); 50 | } 51 | } 52 | 53 | export async function removePragma(folder: WorkspaceFolder, pragma: string) { 54 | if (!folder) { 55 | return; 56 | } 57 | const uri = Uri.file(getPath(folder)); 58 | const app = await getALApp(folder); 59 | 60 | if (!app) { 61 | return; 62 | } 63 | 64 | if (!app.preprocessorSymbols) { 65 | return; 66 | } 67 | 68 | let updateFile = false; 69 | 70 | let index = -1; 71 | while ((index = app.preprocessorSymbols.indexOf(pragma)) > -1) { 72 | app.preprocessorSymbols.splice(index, 1); 73 | if (!updateFile) { 74 | updateFile = true; 75 | } 76 | } 77 | 78 | if (updateFile) { 79 | fs.writeFile(uri.fsPath, JSON.stringify(app, null, 2), (err) => { 80 | if (err) return console.log(err); 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/ui/PragmaViewProvider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CancellationToken, 3 | Disposable, 4 | Event, 5 | EventEmitter, 6 | ProviderResult, 7 | TreeDataProvider, 8 | TreeItem, 9 | Uri, 10 | window, 11 | workspace, 12 | } from 'vscode'; 13 | import { PragmaTreeItem } from '../model/PragmaTreeItem'; 14 | import { PragmaWorkspace } from '../model/PragmaWorkspace'; 15 | import { PragmaFileDecorationsProvider } from './PragmaFileDecorationsProvider'; 16 | 17 | export class PragmaViewProvider implements Disposable, TreeDataProvider { 18 | private _decorationsProvider: PragmaFileDecorationsProvider; 19 | 20 | private _onDidChangeTreeData: EventEmitter = new EventEmitter(); 21 | readonly onDidChangeTreeData: Event = this._onDidChangeTreeData.event; 22 | 23 | constructor() { 24 | this._disposables.push(workspace.onDidChangeWorkspaceFolders((change) => this.refresh())); 25 | 26 | const watcher = workspace.createFileSystemWatcher('{**/*.al,**/app.json}', false, false, false); 27 | this._disposables.push(watcher); 28 | 29 | watcher.onDidChange((e) => this.refresh()); 30 | watcher.onDidCreate((e) => this.refresh()); 31 | watcher.onDidDelete((e) => this.refresh()); 32 | 33 | this._decorationsProvider = new PragmaFileDecorationsProvider(); 34 | this._disposables.push(window.registerFileDecorationProvider(this._decorationsProvider)); 35 | } 36 | 37 | private getPragmaUriFromUri(uri: Uri) { 38 | const folder = workspace.getWorkspaceFolder(uri); 39 | return Uri.from({ scheme: 'alpragmas', authority: folder?.name }); 40 | } 41 | 42 | private refresh() { 43 | this._onDidChangeTreeData.fire(null); 44 | this._decorationsProvider.refresh(); 45 | } 46 | 47 | getTreeItem(element: PragmaTreeItem): TreeItem | Thenable { 48 | return element; 49 | } 50 | 51 | getChildren(element?: PragmaTreeItem | undefined): ProviderResult { 52 | // Sub-children 53 | if (element?.getChildren) { 54 | return element.getChildren(); 55 | } 56 | 57 | // Top-level children 58 | if (!workspace.workspaceFolders?.length) { 59 | return []; 60 | } 61 | 62 | const results: PragmaTreeItem[] = []; 63 | 64 | for (let folder of workspace.workspaceFolders) { 65 | results.push(new PragmaWorkspace(folder)); 66 | } 67 | 68 | return results; 69 | } 70 | getParent?(element: PragmaTreeItem): ProviderResult { 71 | throw new Error('Method not implemented.'); 72 | } 73 | 74 | resolveTreeItem?( 75 | item: PragmaTreeItem, 76 | element: PragmaTreeItem, 77 | token: CancellationToken 78 | ): ProviderResult { 79 | throw new Error('Method not implemented.'); 80 | } 81 | 82 | //#region Disposable 83 | private _disposed: Boolean = false; 84 | private _disposables: Disposable[] = []; 85 | 86 | dispose() { 87 | if (this._disposed) { 88 | return; 89 | } 90 | 91 | for (let disposable of this._disposables) { 92 | disposable.dispose(); 93 | } 94 | 95 | this._disposed = true; 96 | } 97 | //#endregion 98 | } 99 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "al-pragma-explorer", 3 | "displayName": "AL Pragma Explorer", 4 | "description": "Presents and manages precompiler conditional directives for AL Language workspaces", 5 | "publisher": "vjeko", 6 | "version": "0.0.1", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/vjekob/al-pragma-explorer" 10 | }, 11 | "homepage": "https://github.com/vjekob/al-pragma-explorer", 12 | "bugs": "https://github.com/vjekob/al-pragma-explorer/issues", 13 | "icon": "img/icon.png", 14 | "engines": { 15 | "vscode": "^1.71.0" 16 | }, 17 | "categories": [ 18 | "Other" 19 | ], 20 | "activationEvents": [ 21 | "onLanguage:al" 22 | ], 23 | "main": "./out/extension.js", 24 | "contributes": { 25 | "commands": [ 26 | { 27 | "category": "Pragma", 28 | "command": "al-pragma-explorer.toggle", 29 | "title": "Toggle preprocessor directive" 30 | } 31 | ], 32 | "views": { 33 | "explorer": [ 34 | { 35 | "id": "al-pragma-explorer.view", 36 | "name": "Pragma Explorer", 37 | "contextualTitle": "Pragma Explorer", 38 | "when": "al-pragma-explorer.showPragmaExplorer" 39 | } 40 | ] 41 | }, 42 | "menus": { 43 | "commandPalette": [ 44 | { 45 | "command": "al-pragma-explorer.toggle", 46 | "when": "false" 47 | } 48 | ], 49 | "view/item/context": [ 50 | { 51 | "group": "navigation", 52 | "command": "al-pragma-explorer.toggle", 53 | "when": "view == al-pragma-explorer.view && viewItem == ispragma" 54 | } 55 | ] 56 | }, 57 | "configuration": [ 58 | { 59 | "title": "AL Pragma Icon", 60 | "properties": { 61 | "al-pragma-explorer.pragmaIcon": { 62 | "type": "string", 63 | "default": "symbol-constant", 64 | "enum": [ 65 | "symbol-constant", 66 | "octoface", 67 | "symbol-class", 68 | "open-editors-view-icon" 69 | ], 70 | "description": "Shows the selected icon in the treeview pragmas" 71 | } 72 | } 73 | } 74 | ] 75 | }, 76 | "prettier": { 77 | "tabWidth": 4, 78 | "printWidth": 120, 79 | "singleQuote": true, 80 | "semi": true 81 | }, 82 | "scripts": { 83 | "vscode:prepublish": "npm run compile", 84 | "compile": "tsc -p ./", 85 | "watch": "tsc -watch -p ./", 86 | "pretest": "npm run compile && npm run lint", 87 | "lint": "eslint src --ext ts", 88 | "test": "node ./out/test/runTest.js" 89 | }, 90 | "devDependencies": { 91 | "@types/vscode": "^1.71.0", 92 | "@types/glob": "^7.2.0", 93 | "@types/mocha": "^9.1.1", 94 | "@types/node": "16.x", 95 | "@typescript-eslint/eslint-plugin": "^5.31.0", 96 | "@typescript-eslint/parser": "^5.31.0", 97 | "eslint": "^8.20.0", 98 | "glob": "^8.0.3", 99 | "mocha": "^10.0.0", 100 | "typescript": "^4.7.4", 101 | "@vscode/test-electron": "^2.1.5" 102 | } 103 | } 104 | --------------------------------------------------------------------------------