├── .gitignore ├── .github └── FUNDING.yml ├── resources ├── icon.png ├── Icon.sketch ├── jsonpath.png └── icon.svg ├── .vscodeignore ├── .vscode ├── extensions.json ├── settings.json ├── tasks.json └── launch.json ├── README.md ├── .eslintrc.json ├── tsconfig.json ├── CHANGELOG.md ├── package.json └── src ├── extension.ts └── jsonPathTo.ts /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [richie5um] 4 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richie5um/vscode-statusbar-json-path/HEAD/resources/icon.png -------------------------------------------------------------------------------- /resources/Icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richie5um/vscode-statusbar-json-path/HEAD/resources/Icon.sketch -------------------------------------------------------------------------------- /resources/jsonpath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richie5um/vscode-statusbar-json-path/HEAD/resources/jsonpath.png -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | .gitignore 5 | .yarnrc 6 | **/tsconfig.json 7 | **/.eslintrc.json 8 | **/*.map 9 | **/*.ts 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StatusBar JSONPath 2 | 3 | Shows the path to the selected JSON Property in the StatusBar. 4 | 5 | ![JSONPath](./resources/jsonpath.png) 6 | 7 | ⚠️ Only works for registered JSON files. (json, jsonc, asl, ssm-json) 8 | 9 | If you want to alias a JSON like file, add this to your config.json 10 | 11 | ```json 12 | "files.associations": { 13 | "*.myjson": "json" 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "jsonpath" 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 | ## [2.0.0] 8 | 9 | - Reorganised extension + clean 10 | - Bump dependencies 11 | - Lint with ESLint 12 | - New option to select path separator (dots or indexes) 13 | - Fix issues with unescaped character 14 | 15 | ## [1.0.0] 16 | 17 | - Added an option to change keys separator (dots or indexes) 18 | - Fixed all unescaped characters (again) 19 | - Extended filetypes (Amazon State Language (asl), AWS System Manager Document (ssm-json)) 20 | - Only shows Path for JSON files (and 'JSON with comments', although this may cause path detection issues). 21 | - Status bar item now copies to Clipboard - thanks to Tyderion. 22 | - Switched clipboard package - thanks to floatdrop 23 | - Fixed issue with slashes preceding quotes 24 | - Fixed issue with file detection - thanks to Outrigger047 25 | - Support for more special characters - thanks to Nuaduwodan 26 | -------------------------------------------------------------------------------- /resources/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Icon 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | { 11 | 12 | 13 | .. 14 | 15 | 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-statusbar-json-path", 3 | "displayName": "JSON Path Status Bar", 4 | "description": "Shows the path to the selected JSON property in the status bar", 5 | "version": "2.0.0", 6 | "author": "Rich Somerfield", 7 | "contributors": [ 8 | "Quentin Lienhardt" 9 | ], 10 | "publisher": "richie5um2", 11 | "license": "MIT", 12 | "homepage": "https://github.com/richie5um/vscode-statusbar-json-path", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/richie5um/vscode-statusbar-json-path.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/richie5um/vscode-statusbar-json-path/issues" 19 | }, 20 | "engines": { 21 | "vscode": "^1.70.0" 22 | }, 23 | "activationEvents": [ 24 | "onLanguage:json", 25 | "onLanguage:jsonc", 26 | "onLanguage:asl", 27 | "onLanguage:ssm-json" 28 | ], 29 | "icon": "resources/icon.png", 30 | "main": "./out/extension.js", 31 | "categories": [ 32 | "Other" 33 | ], 34 | "contributes": { 35 | "commands": [ 36 | { 37 | "command": "extension.statusBarJSONPath", 38 | "title": "Copy current JSONPath to clipboard" 39 | } 40 | ], 41 | "configuration": { 42 | "type": "object", 43 | "title": "JSONPath StatusBar", 44 | "properties": { 45 | "statusBarJSONPath.keysSeparators": { 46 | "type": "string", 47 | "default": "dots", 48 | "description": "Choose between `dots` or `indexes` for JSON keys.", 49 | "enum": [ 50 | "dots", 51 | "indexes" 52 | ], 53 | "enumDescriptions": [ 54 | "Use dots (JS friendly) for keys separators", 55 | "Use indexes (Python friendly) for keys separators" 56 | ], 57 | "scope": "resource" 58 | } 59 | } 60 | } 61 | }, 62 | "scripts": { 63 | "vscode:prepublish": "npm run compile", 64 | "compile": "tsc -p ./", 65 | "watch": "tsc -watch -p ./", 66 | "pretest": "npm run compile && npm run lint", 67 | "lint": "eslint src --ext ts", 68 | "test": "node ./out/test/runTest.js" 69 | }, 70 | "devDependencies": { 71 | "@types/node": "^18.7.11", 72 | "@types/vscode": "^1.70.0", 73 | "@typescript-eslint/eslint-plugin": "^5.34.0", 74 | "@typescript-eslint/parser": "^5.34.0", 75 | "eslint": "^8.22.0", 76 | "typescript": "^4.7.4", 77 | "vsce": "^2.10.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { jsonPathTo } from "./jsonPathTo"; 3 | 4 | let currentString: string = ""; 5 | let status: vscode.StatusBarItem; 6 | 7 | export function activate(context: vscode.ExtensionContext) { 8 | status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100); 9 | status.command = "extension.statusBarJSONPath"; 10 | status.show(); 11 | context.subscriptions.push(status); 12 | 13 | context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(() => updateStatus(status))); 14 | context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection(() => updateStatus(status))); 15 | context.subscriptions.push(vscode.window.onDidChangeTextEditorViewColumn(() => updateStatus(status))); 16 | context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(() => updateStatus(status))); 17 | context.subscriptions.push(vscode.workspace.onDidCloseTextDocument(() => updateStatus(status))); 18 | 19 | context.subscriptions.push( 20 | vscode.commands.registerCommand("extension.statusBarJSONPath", async () => { 21 | await vscode.env.clipboard.writeText(currentString); 22 | }) 23 | ); 24 | 25 | updateStatus(status); 26 | } 27 | 28 | function updateStatus(status: vscode.StatusBarItem): void { 29 | currentString = ""; 30 | 31 | const editor = vscode.window.activeTextEditor; 32 | if ( 33 | !editor || 34 | !( 35 | editor.document.languageId.toLowerCase() === "json" || // 36 | editor.document.languageId.toLowerCase() === "jsonc" || 37 | editor.document.languageId.toLowerCase() === "asl" || 38 | editor.document.languageId.toLowerCase() === "ssm-json" 39 | ) 40 | ) { 41 | status.text = ""; 42 | return; 43 | } 44 | 45 | try { 46 | const text = editor.document.getText(); 47 | const config = vscode.workspace.getConfiguration("statusBarJSONPath"); 48 | const sep = config.get("keysSeparators") as string; 49 | const path = jsonPathTo(text, editor.document.offsetAt(editor.selection.active), sep); 50 | currentString = path; 51 | 52 | status.text = "JSONPath: " + path; 53 | status.tooltip = "Click to copy to clipboard"; 54 | } catch (ex) { 55 | if (ex instanceof SyntaxError) { 56 | status.text = `JSONPath: Invalid JSON.`; 57 | } else { 58 | status.text = `JSONPath: Error.`; 59 | } 60 | status.tooltip = undefined; 61 | } 62 | } 63 | 64 | export function deactivate() { } 65 | -------------------------------------------------------------------------------- /src/jsonPathTo.ts: -------------------------------------------------------------------------------- 1 | // Used from https://github.com/nidu/vscode-copy-json-path 2 | 3 | enum ColType { 4 | Object, // eslint-disable-line @typescript-eslint/naming-convention 5 | Array, // eslint-disable-line @typescript-eslint/naming-convention 6 | } 7 | 8 | interface Frame { 9 | colType: ColType; 10 | index?: number; 11 | key?: string; 12 | } 13 | 14 | export function jsonPathTo(text: string, offset: number, separatorType: string) { 15 | let pos = 0; 16 | let stack: Frame[] = []; 17 | let isInKey = false; 18 | 19 | while (pos < offset) { 20 | const startPos = pos; 21 | switch (text[pos]) { 22 | case '"': 23 | const { text: s, pos: newPos } = readString(text, pos); 24 | if (stack.length) { 25 | const frame = stack[stack.length - 1]; 26 | if (frame.colType === ColType.Object && isInKey) { 27 | frame.key = s; 28 | isInKey = false; 29 | } 30 | } 31 | pos = newPos; 32 | break; 33 | case "{": 34 | stack.push({ colType: ColType.Object }); 35 | isInKey = true; 36 | break; 37 | case "[": 38 | stack.push({ colType: ColType.Array, index: 0 }); 39 | break; 40 | case "]": 41 | stack.pop(); 42 | break; 43 | case "}": 44 | stack.pop(); 45 | break; 46 | case ",": 47 | if (stack.length) { 48 | const frame = stack[stack.length - 1]; 49 | if (frame) { 50 | if (frame.colType === ColType.Object) { 51 | isInKey = true; 52 | } else if (frame.index !== undefined) { 53 | frame.index++; 54 | } 55 | } 56 | } 57 | break; 58 | } 59 | if (pos === startPos) { 60 | pos++; 61 | } 62 | } 63 | 64 | if (separatorType === "dots") { 65 | return pathToStringDot(stack); 66 | } else if (separatorType === "indexes") { 67 | return pathToStringIndexes(stack); 68 | } else { 69 | return ""; 70 | } 71 | } 72 | 73 | function pathToStringDot(path: Frame[]): string { 74 | let s = ""; 75 | for (const frame of path) { 76 | if (frame.colType === ColType.Object) { 77 | if (frame.key) { 78 | if (!frame.key.match(/^[a-zA-Z$#@&%~\-_][a-zA-Z\d$#@&%~\-_]*$/)) { 79 | s += `["${frame.key}"]`; 80 | } else { 81 | if (s.length) { 82 | s += "."; 83 | } 84 | s += frame.key; 85 | } 86 | } 87 | } else { 88 | s += `[${frame.index}]`; 89 | } 90 | } 91 | return s; 92 | } 93 | 94 | function pathToStringIndexes(path: Frame[]): string { 95 | let s = ""; 96 | for (const frame of path) { 97 | if (frame.colType === ColType.Object) { 98 | if (frame.key) { 99 | if (!frame.key.match(/^[a-zA-Z$#@&%~\-_][a-zA-Z\d$#@&%~\-_]*$/)) { 100 | s += `["${frame.key}"]`; 101 | } else { 102 | s += '["' + frame.key + '"]'; 103 | } 104 | } 105 | } else { 106 | s += `[${frame.index}]`; 107 | } 108 | } 109 | return s; 110 | } 111 | 112 | function readString(text: string, pos: number): { text: string; pos: number } { 113 | let i = findEndQuote(text, pos + 1); 114 | var textPos = { 115 | text: text.substring(pos + 1, i), 116 | pos: i + 1, 117 | }; 118 | 119 | return textPos; 120 | } 121 | 122 | // Find the next end quote 123 | function findEndQuote(text: string, i: number) { 124 | while (i < text.length) { 125 | // Handle backtracking to find if this quote is escaped 126 | if (text[i] === "\\") { 127 | i += 2; 128 | } 129 | 130 | if (text[i] === '"') { 131 | break; 132 | } 133 | i++; 134 | } 135 | 136 | return i; 137 | } 138 | --------------------------------------------------------------------------------