├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── ask-a-question.md │ ├── bug_report.md │ ├── feature_request.md │ └── positive-feedback.md ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── ci.yaml ├── .gitignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── Core ├── Bookmarking │ ├── Bookmark.cs │ ├── BookmarkRepository.cs │ ├── BookmarkSet.cs │ ├── BookmarkType.cs │ ├── EnvVariables.cs │ ├── JumpFsConfiguration.cs │ ├── PathConverter.cs │ ├── PathOperations.cs │ └── RegexTranslator.cs ├── Core.csproj ├── EnvironmentAccess │ ├── IJumpfsEnvironment.cs │ ├── JumpfsEnvironment.cs │ └── ShellGuesser.cs ├── Extensions │ ├── EnumerableExtensions.cs │ └── StringExtensions.cs └── ShellType.cs ├── DriveProvider ├── BookmarkContentReader.cs ├── DriveProvider.csproj ├── JumpfsBookmarkVirtualDrive.cs └── Properties │ └── PublishProfiles │ └── windows.pubxml ├── LICENSE ├── README.md ├── Tests ├── BasicApplicationTests.cs ├── BookMarkTests.cs ├── CommandParserTests.cs ├── CrossShellTests.cs ├── FullPathTests.cs ├── PathConverterTests.cs ├── PathOperationTests.cs ├── ShellGuesserTests.cs ├── SupportClasses │ └── MockJumpfsEnvironment.cs └── Tests.csproj ├── build.ps1 ├── build.sh ├── doc ├── buildFromSource.md ├── changelist.json ├── cmd-installation.md ├── contributions.md ├── download.md ├── faq.md ├── jumpfs-exe.md ├── linux-installation.md ├── powershell-installation.md ├── psdrive.md ├── revisionHistory.md ├── troubleshooting.md └── wsl-installation.md ├── img └── jumpfs.gif ├── jumpfs.nuspec ├── jumpfs.sln ├── jumpfs.sln.DotSettings ├── jumpfs.v3.ncrunchsolution ├── jumpfs ├── CommandLineParsing │ ├── ArgumentDescriptor.cs │ ├── CommandDescriptor.cs │ ├── CommandLineParser.cs │ └── ParseResults.cs ├── Commands │ ├── ApplicationContext.cs │ ├── BookmarkTypeParser.cs │ ├── CmdCheckVersion.cs │ ├── CmdDebug.cs │ ├── CmdFind.cs │ ├── CmdInfo.cs │ ├── CmdList.cs │ ├── CmdMark.cs │ ├── CmdRemove.cs │ ├── CmdShowArgs.cs │ ├── FullPathCalculator.cs │ ├── GitVersionInformation.cs │ └── JumpFs.cs ├── Names.cs ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ ├── linux.pubxml │ │ └── windows.pubxml │ └── launchSettings.json ├── UpgradeManager.cs ├── jumpfs.csproj └── jumpfs.v3.ncrunchproject └── scripts ├── bash └── bash-jumpfs.sh ├── cmd ├── codego.bat ├── go.bat ├── jumpfs_info.bat ├── lst.bat ├── mark.bat └── x.bat └── powershell └── ps-jumpfs.psm1 /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Windows-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = crlf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | # 4 space indentation 11 | [*.{cs, cshtml, razor}] 12 | indent_style = space 13 | indent_size = 4 14 | # Default severity for analyzer diagnostics with category 'Style' 15 | dotnet_analyzer_diagnostic.category-Style.severity = none 16 | # CA1031: Do not catch general exception types 17 | dotnet_diagnostic.CA1031.severity = none 18 | 19 | # 2 space indentation 20 | [*.csproj] 21 | indent_style = space 22 | indent_size = 2 23 | charset = utf-8-bom 24 | 25 | # 2 space indentation 26 | [*.{xaml, wxs, config, yml}] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | # Matches the exact files package.json 31 | [{packages.config}] 32 | indent_style = space 33 | indent_size = 2 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-a-question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask a question 3 | about: If you're not sure about something feel free to ask 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Oh dear - tell me what didn't work 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Description of the problem** 11 | What happened and why do you think it's a bug? 12 | 13 | **TextrudeInteractive project** 14 | If you are able to reproduce the issue in TextrudeInteractive then the best way to help us get to the bottom of the issue is to post a project file with the bug-report. Failing that, as much detail as possible about the models and templates you used would be helpful. 15 | 16 | **Desktop (please complete the following information):** 17 | - OS: [if you think it is OS specific] 18 | - Version [I'll assume you're using the latest release unless you say otherwise] 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Got an idea? Tell me about it 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Tell me what you think would be a good addition! I can't promise to add it but you never know...** 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/positive-feedback.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Send a smile 3 | about: Tell me what you like - it keeps me motivated! 4 | title: I like Textrude because.... 5 | labels: smile 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Don't be shy - tell me why you like Textrude!** 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | name: CD 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "docs/**" 7 | - "*.md" 8 | 9 | jobs: 10 | package: 11 | env: 12 | DisableGitVersionTask: true 13 | runs-on: windows-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 9.0.x 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - "docs/**" 7 | - "*.md" 8 | pull_request: 9 | paths-ignore: 10 | - "docs/**" 11 | - "*.md" 12 | 13 | jobs: 14 | test: 15 | env: 16 | DisableGitVersionTask: true 17 | strategy: 18 | matrix: 19 | include: 20 | - os: windows-latest 21 | build_config: Debug 22 | - os: ubuntu-latest 23 | build_config: Linux-Debug 24 | runs-on: ${{ matrix.os }} 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Setup .NET 28 | uses: actions/setup-dotnet@v1 29 | with: 30 | dotnet-version: 9.0.x 31 | - name: Restore 32 | run: dotnet restore 33 | - name: Test 34 | run: dotnet test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /act 2 | /tools 3 | /TestReports 4 | 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.rsuser 12 | *.suo 13 | *.user 14 | *.userosscache 15 | *.sln.docstates 16 | 17 | # User-specific files (MonoDevelop/Xamarin Studio) 18 | *.userprefs 19 | 20 | # Mono auto generated files 21 | mono_crash.* 22 | 23 | # Build results 24 | [Dd]ebug/ 25 | [Dd]ebugPublic/ 26 | [Rr]elease/ 27 | [Rr]eleases/ 28 | x64/ 29 | x86/ 30 | [Ww][Ii][Nn]32/ 31 | [Aa][Rr][Mm]/ 32 | [Aa][Rr][Mm]64/ 33 | bld/ 34 | [Bb]in/ 35 | [Oo]bj/ 36 | [Oo]ut/ 37 | [Ll]og/ 38 | [Ll]ogs/ 39 | 40 | # Visual Studio 2015/2017 cache/options directory 41 | .vs/ 42 | # Uncomment if you have tasks that create the project's static files in wwwroot 43 | #wwwroot/ 44 | 45 | # Visual Studio 2017 auto generated files 46 | Generated\ Files/ 47 | 48 | # MSTest test Results 49 | [Tt]est[Rr]esult*/ 50 | [Bb]uild[Ll]og.* 51 | 52 | # NUnit 53 | *.VisualState.xml 54 | TestResult.xml 55 | nunit-*.xml 56 | 57 | # Build Results of an ATL Project 58 | [Dd]ebugPS/ 59 | [Rr]eleasePS/ 60 | dlldata.c 61 | 62 | # Benchmark Results 63 | BenchmarkDotNet.Artifacts/ 64 | 65 | # .NET Core 66 | project.lock.json 67 | project.fragment.lock.json 68 | artifacts/ 69 | 70 | # ASP.NET Scaffolding 71 | ScaffoldingReadMe.txt 72 | 73 | # StyleCop 74 | StyleCopReport.xml 75 | 76 | # Files built by Visual Studio 77 | *_i.c 78 | *_p.c 79 | *_h.h 80 | *.ilk 81 | *.meta 82 | *.obj 83 | *.iobj 84 | *.pch 85 | *.pdb 86 | *.ipdb 87 | *.pgc 88 | *.pgd 89 | *.rsp 90 | *.sbr 91 | *.tlb 92 | *.tli 93 | *.tlh 94 | *.tmp 95 | *.tmp_proj 96 | *_wpftmp.csproj 97 | *.log 98 | *.vspscc 99 | *.vssscc 100 | .builds 101 | *.pidb 102 | *.svclog 103 | *.scc 104 | 105 | # Chutzpah Test files 106 | _Chutzpah* 107 | 108 | # Visual C++ cache files 109 | ipch/ 110 | *.aps 111 | *.ncb 112 | *.opendb 113 | *.opensdf 114 | *.sdf 115 | *.cachefile 116 | *.VC.db 117 | *.VC.VC.opendb 118 | 119 | # Visual Studio profiler 120 | *.psess 121 | *.vsp 122 | *.vspx 123 | *.sap 124 | 125 | # Visual Studio Trace Files 126 | *.e2e 127 | 128 | # TFS 2012 Local Workspace 129 | $tf/ 130 | 131 | # Guidance Automation Toolkit 132 | *.gpState 133 | 134 | # ReSharper is a .NET coding add-in 135 | _ReSharper*/ 136 | *.[Rr]e[Ss]harper 137 | *.DotSettings.user 138 | 139 | # TeamCity is a build add-in 140 | _TeamCity* 141 | 142 | # DotCover is a Code Coverage Tool 143 | *.dotCover 144 | 145 | # AxoCover is a Code Coverage Tool 146 | .axoCover/* 147 | !.axoCover/settings.json 148 | 149 | # Coverlet is a free, cross platform Code Coverage Tool 150 | coverage*.json 151 | coverage*.xml 152 | coverage*.info 153 | 154 | # Visual Studio code coverage results 155 | *.coverage 156 | *.coveragexml 157 | 158 | # NCrunch 159 | _NCrunch_* 160 | .*crunch*.local.xml 161 | nCrunchTemp_* 162 | 163 | # MightyMoose 164 | *.mm.* 165 | AutoTest.Net/ 166 | 167 | # Web workbench (sass) 168 | .sass-cache/ 169 | 170 | # Installshield output folder 171 | [Ee]xpress/ 172 | 173 | # DocProject is a documentation generator add-in 174 | DocProject/buildhelp/ 175 | DocProject/Help/*.HxT 176 | DocProject/Help/*.HxC 177 | DocProject/Help/*.hhc 178 | DocProject/Help/*.hhk 179 | DocProject/Help/*.hhp 180 | DocProject/Help/Html2 181 | DocProject/Help/html 182 | 183 | # Click-Once directory 184 | publish/ 185 | 186 | # Publish Web Output 187 | *.[Pp]ublish.xml 188 | *.azurePubxml 189 | # Note: Comment the next line if you want to checkin your web deploy settings, 190 | # but database connection strings (with potential passwords) will be unencrypted 191 | *.publishproj 192 | 193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 194 | # checkin your Azure Web App publish settings, but sensitive information contained 195 | # in these scripts will be unencrypted 196 | PublishScripts/ 197 | 198 | # NuGet Packages 199 | *.nupkg 200 | # NuGet Symbol Packages 201 | *.snupkg 202 | # The packages folder can be ignored because of Package Restore 203 | **/[Pp]ackages/* 204 | # except build/, which is used as an MSBuild target. 205 | !**/[Pp]ackages/build/ 206 | # Uncomment if necessary however generally it will be regenerated when needed 207 | #!**/[Pp]ackages/repositories.config 208 | # NuGet v3's project.json files produces more ignorable files 209 | *.nuget.props 210 | *.nuget.targets 211 | 212 | # Microsoft Azure Build Output 213 | csx/ 214 | *.build.csdef 215 | 216 | # Microsoft Azure Emulator 217 | ecf/ 218 | rcf/ 219 | 220 | # Windows Store app package directories and files 221 | AppPackages/ 222 | BundleArtifacts/ 223 | Package.StoreAssociation.xml 224 | _pkginfo.txt 225 | *.appx 226 | *.appxbundle 227 | *.appxupload 228 | 229 | # Visual Studio cache files 230 | # files ending in .cache can be ignored 231 | *.[Cc]ache 232 | # but keep track of directories ending in .cache 233 | !?*.[Cc]ache/ 234 | 235 | # Others 236 | ClientBin/ 237 | ~$* 238 | *~ 239 | *.dbmdl 240 | *.dbproj.schemaview 241 | *.jfm 242 | *.pfx 243 | *.publishsettings 244 | orleans.codegen.cs 245 | 246 | # Including strong name files can present a security risk 247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 248 | #*.snk 249 | 250 | # Since there are multiple workflows, uncomment next line to ignore bower_components 251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 252 | #bower_components/ 253 | 254 | # RIA/Silverlight projects 255 | Generated_Code/ 256 | 257 | # Backup & report files from converting an old project file 258 | # to a newer Visual Studio version. Backup files are not needed, 259 | # because we have git ;-) 260 | _UpgradeReport_Files/ 261 | Backup*/ 262 | UpgradeLog*.XML 263 | UpgradeLog*.htm 264 | ServiceFabricBackup/ 265 | *.rptproj.bak 266 | 267 | # SQL Server files 268 | *.mdf 269 | *.ldf 270 | *.ndf 271 | 272 | # Business Intelligence projects 273 | *.rdl.data 274 | *.bim.layout 275 | *.bim_*.settings 276 | *.rptproj.rsuser 277 | *- [Bb]ackup.rdl 278 | *- [Bb]ackup ([0-9]).rdl 279 | *- [Bb]ackup ([0-9][0-9]).rdl 280 | 281 | # Microsoft Fakes 282 | FakesAssemblies/ 283 | 284 | # GhostDoc plugin setting file 285 | *.GhostDoc.xml 286 | 287 | # Node.js Tools for Visual Studio 288 | .ntvs_analysis.dat 289 | node_modules/ 290 | 291 | # Visual Studio 6 build log 292 | *.plg 293 | 294 | # Visual Studio 6 workspace options file 295 | *.opt 296 | 297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 298 | *.vbw 299 | 300 | # Visual Studio LightSwitch build output 301 | **/*.HTMLClient/GeneratedArtifacts 302 | **/*.DesktopClient/GeneratedArtifacts 303 | **/*.DesktopClient/ModelManifest.xml 304 | **/*.Server/GeneratedArtifacts 305 | **/*.Server/ModelManifest.xml 306 | _Pvt_Extensions 307 | 308 | # Paket dependency manager 309 | .paket/paket.exe 310 | paket-files/ 311 | 312 | # FAKE - F# Make 313 | .fake/ 314 | 315 | # CodeRush personal settings 316 | .cr/personal 317 | 318 | # Python Tools for Visual Studio (PTVS) 319 | __pycache__/ 320 | *.pyc 321 | 322 | # Cake - Uncomment if you are using it 323 | # tools/** 324 | # !tools/packages.config 325 | 326 | # Tabs Studio 327 | *.tss 328 | 329 | # Telerik's JustMock configuration file 330 | *.jmconfig 331 | 332 | # BizTalk build output 333 | *.btp.cs 334 | *.btm.cs 335 | *.odx.cs 336 | *.xsd.cs 337 | 338 | # OpenCover UI analysis results 339 | OpenCover/ 340 | 341 | # Azure Stream Analytics local run output 342 | ASALocalRun/ 343 | 344 | # MSBuild Binary and Structured Log 345 | *.binlog 346 | 347 | # NVidia Nsight GPU debugger configuration file 348 | *.nvuser 349 | 350 | # MFractors (Xamarin productivity tool) working folder 351 | .mfractor/ 352 | 353 | # Local History for Visual Studio 354 | .localhistory/ 355 | 356 | # BeatPulse healthcheck temp database 357 | healthchecksdb 358 | 359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 360 | MigrationBackup/ 361 | 362 | # Ionide (cross platform F# VS Code tools) working folder 363 | .ionide/ 364 | 365 | # Fody - auto-generated XML schema 366 | FodyWeavers.xsd 367 | .vscode/tasks.json 368 | .vscode/launch.json 369 | 370 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureOnOpen": false 3 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at neil.macmullen@outlook.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /Core/Bookmarking/Bookmark.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Bookmarking 2 | { 3 | /// 4 | /// A bookmark entry 5 | /// 6 | public class Bookmark 7 | { 8 | public string Name { get; set; } = string.Empty; 9 | public string Path { get; set; } = string.Empty; 10 | public int Line { get; set; } 11 | public int Column { get; set; } 12 | public BookmarkType Type { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Core/Bookmarking/BookmarkRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text.Json; 5 | using System.Text.RegularExpressions; 6 | using Core.EnvironmentAccess; 7 | using Core.Extensions; 8 | 9 | namespace Core.Bookmarking 10 | { 11 | /// 12 | /// A repository for bookmarks 13 | /// 14 | /// 15 | /// The repository makes certain assumptions about the persistence mechanism 16 | /// 17 | public class BookmarkRepository 18 | { 19 | public readonly string BookmarkFile; 20 | public readonly IJumpfsEnvironment JumpfsEnvironment; 21 | 22 | public BookmarkRepository(IJumpfsEnvironment jumpfsEnvironment) 23 | { 24 | JumpfsEnvironment = jumpfsEnvironment; 25 | BookmarkFile = Path.Combine(Folder, "jumpfs", "bookmarks.json"); 26 | } 27 | 28 | public string Folder => 29 | (JumpfsEnvironment.ShellType == ShellType.Wsl) 30 | ? JumpfsEnvironment.GetEnvironmentVariable(EnvVariables.WslEnvVar).Trim() 31 | : JumpfsEnvironment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 32 | 33 | public Bookmark[] Load() 34 | { 35 | var text = !JumpfsEnvironment.FileExists(BookmarkFile) ? "[]" : JumpfsEnvironment.ReadAllText(BookmarkFile); 36 | return JsonSerializer.Deserialize(text); 37 | } 38 | 39 | 40 | public static Bookmark[] Match(Bookmark[] bookmarks, string search) 41 | { 42 | var regex = RegexTranslator.RegexFromPowershell(search); 43 | bookmarks = bookmarks.Where(b => Regex.IsMatch(b.Name, regex)).ToArray(); 44 | return bookmarks; 45 | } 46 | 47 | public Bookmark[] List(string match) 48 | { 49 | var all = Load(); 50 | var matches = all.Where(m => m.Name.Contains(match) || m.Path.Contains(match)).ToArray(); 51 | return matches; 52 | } 53 | 54 | 55 | public void Save(Bookmark[] bookmarks) 56 | { 57 | var text = JsonSerializer.Serialize(bookmarks, new JsonSerializerOptions {WriteIndented = true}); 58 | JumpfsEnvironment.WriteAllText(BookmarkFile, text); 59 | } 60 | 61 | public void Mark(BookmarkType type, string name, string path) => Mark(type, name, path, 0, 0); 62 | 63 | public void Mark(BookmarkType type, string name, string path, int line, int column) 64 | { 65 | var marks = Load(); 66 | var existing = marks.SingleOrDefault(m => m.Name == name); 67 | if (existing == null) 68 | { 69 | existing = new Bookmark {Path = path}; 70 | marks = marks.Append(existing).ToArray(); 71 | } 72 | 73 | existing.Type = type; 74 | existing.Path = path; 75 | existing.Name = name; 76 | existing.Line = line; 77 | existing.Column = column; 78 | Save(marks); 79 | } 80 | 81 | public Bookmark Find(string name) 82 | { 83 | var marks = Load(); 84 | return marks.SingleOr(m => m.Name == name, new Bookmark()); 85 | } 86 | 87 | public Bookmark[] Remove(string name) 88 | { 89 | var marks = Load(); 90 | var victim = marks.SingleOr(m => m.Name == name, new Bookmark()); 91 | marks = marks.Where(m => m != victim).ToArray(); 92 | Save(marks); 93 | return new[] {victim}.Where(v => v.Name.Length > 0).ToArray(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Core/Bookmarking/BookmarkSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Bookmarking 4 | { 5 | public class BookmarkSet 6 | { 7 | public JumpFsConfiguration Configuration { get; set; } = JumpFsConfiguration.Empty; 8 | public Bookmark[] Bookmarks { get; set; } = Array.Empty(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Core/Bookmarking/BookmarkType.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Bookmarking 2 | { 3 | public enum BookmarkType 4 | { 5 | Unknown, 6 | File, 7 | Folder, 8 | Url, 9 | 10 | //command types 11 | PsCmd, 12 | BashCmd, 13 | DosCmd 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Core/Bookmarking/EnvVariables.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Bookmarking 2 | { 3 | public static class EnvVariables 4 | { 5 | public const string WslEnvVar = "JUMPFS_FOLDER"; 6 | public const string WslRootVar = "JUMPFS_WSL_ROOT"; 7 | public const string ShellOveride = "JUMPFS_SHELL"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Core/Bookmarking/JumpFsConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Bookmarking 2 | { 3 | public class JumpFsConfiguration 4 | { 5 | public static readonly JumpFsConfiguration Empty = new(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Core/Bookmarking/PathConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using Core.Extensions; 3 | 4 | namespace Core.Bookmarking 5 | { 6 | /// 7 | /// Converts paths between Windows and Linux/WSL 8 | /// 9 | /// 10 | /// Requires some extra logic to cope with WSL 11 | /// 12 | public class PathConverter 13 | { 14 | private readonly string _unc; 15 | 16 | public PathConverter(string unc) => _unc = unc; 17 | 18 | public string ToWsl(string path) 19 | { 20 | if (_unc.Length > 0 && path.StartsWith(_unc)) 21 | return path.Substring(_unc.Length - 1).UnixSlash(); 22 | 23 | var m = Regex.Match(path, @"^(\w):(.*)"); 24 | 25 | if (m.Success) 26 | { 27 | var root = m.Groups[1].Value; 28 | path = $"/mnt/{root.ToLowerInvariant()}" + m.Groups[2].Value; 29 | } 30 | 31 | return path.UnixSlash(); 32 | } 33 | 34 | public string ToUnc(string path) 35 | { 36 | if (path.StartsWith("/")) 37 | { 38 | var m = Regex.Match(path, @"^/mnt/(\w)/(.*)"); 39 | if (m.Success) 40 | { 41 | var drv = m.Groups[1].Value; 42 | var subPath = m.Groups[2].Value.WinSlash(); 43 | return $@"{drv}:\{subPath}"; 44 | } 45 | 46 | return $"{_unc}{path.Substring(1).WinSlash()}"; 47 | } 48 | 49 | return path; 50 | } 51 | 52 | public string ToShell(ShellType environment, string path) 53 | { 54 | switch (environment) 55 | { 56 | case ShellType.Wsl: 57 | return ToWsl(path); 58 | default: return ToUnc(path); 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Core/Bookmarking/PathOperations.cs: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Core/Bookmarking/RegexTranslator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Core.Bookmarking 4 | { 5 | public static class RegexTranslator 6 | { 7 | public static string RegexFromPowershell(string search) 8 | { 9 | if (string.IsNullOrWhiteSpace(search)) 10 | search = "*"; 11 | //we need to be a little careful about how we perform the 12 | //substitutions 13 | search = search 14 | .Replace("?", "!SINGLE!") 15 | .Replace("]*", "!RANGE!") 16 | .Replace("*", "!GLOB!") 17 | .Replace("[", "!LBRACE!"); 18 | search = Regex.Escape(search) 19 | .Replace("!LBRACE!", "[") 20 | .Replace("!GLOB!", ".*") 21 | .Replace("!SINGLE!", ".") 22 | .Replace("!RANGE!", "]*"); 23 | search = $"^{search}$"; 24 | return search; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Core/Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Core/EnvironmentAccess/IJumpfsEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.EnvironmentAccess 4 | { 5 | /// 6 | /// Provides a way of abstracting the file system and environment which is handy for testing 7 | /// 8 | public interface IJumpfsEnvironment 9 | { 10 | ShellType ShellType { get; } 11 | bool DirectoryExists(string path); 12 | bool FileExists(string path); 13 | string ReadAllText(string path); 14 | void WriteAllText(string location, string text); 15 | public string GetEnvironmentVariable(string name); 16 | string GetFolderPath(Environment.SpecialFolder folderName); 17 | public string Cwd(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Core/EnvironmentAccess/JumpfsEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Core.EnvironmentAccess 5 | { 6 | public class JumpfsEnvironment : IJumpfsEnvironment 7 | { 8 | public JumpfsEnvironment() => ShellType = ShellGuesser.GuessShell(this); 9 | 10 | public ShellType ShellType { get; } 11 | public bool DirectoryExists(string path) => Directory.Exists(path); 12 | 13 | public bool FileExists(string path) => File.Exists(path); 14 | public string ReadAllText(string path) => File.ReadAllText(path); 15 | 16 | public void WriteAllText(string location, string text) 17 | { 18 | var jumpsFolder = Path.GetDirectoryName(location); 19 | Directory.CreateDirectory(jumpsFolder); 20 | File.WriteAllText(location, text); 21 | } 22 | 23 | public string GetEnvironmentVariable(string name) => 24 | Environment.GetEnvironmentVariable(name) ?? string.Empty; 25 | 26 | public string GetFolderPath(Environment.SpecialFolder folderName) => 27 | Environment.GetFolderPath(folderName); 28 | 29 | public string Cwd() => Directory.GetCurrentDirectory(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Core/EnvironmentAccess/ShellGuesser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Core.Bookmarking; 4 | 5 | namespace Core.EnvironmentAccess 6 | { 7 | public static class ShellGuesser 8 | { 9 | public static ShellType GuessShell(IJumpfsEnvironment env) 10 | { 11 | //if the user has not specified the shell, try to guess it from environmental information 12 | var forcedEnv = env.GetEnvironmentVariable(EnvVariables.ShellOveride); 13 | return Enum.TryParse(typeof(ShellType), forcedEnv, true, out var shell) 14 | // ReSharper disable once PossibleNullReferenceException 15 | ? (ShellType) shell 16 | : RuntimeInformation.OSDescription.Contains("Linux") 17 | ? ShellType.Wsl 18 | : ShellType.PowerShell; 19 | } 20 | 21 | public static bool IsUnixy() => 22 | ( 23 | RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || 24 | RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Core/Extensions/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Core.Extensions 6 | { 7 | public static class EnumerableExtensions 8 | { 9 | /// 10 | /// Attempts to get the one and only item in a set 11 | /// 12 | public static bool TryGetSingle(this IEnumerable items, Func selector, out T selected) 13 | { 14 | selected = default; 15 | var matches = items.Where(selector).ToArray(); 16 | if (matches.Length != 1) 17 | return false; 18 | selected = matches.First(); 19 | return true; 20 | } 21 | 22 | public static T SingleOr(this IEnumerable items, Func selector, T fallback) 23 | => items.TryGetSingle(selector, out var s) ? s : fallback; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Core/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Extensions 4 | { 5 | public static class StringExtensions 6 | { 7 | //split a string by spaces - useful for testing 8 | public static string[] Tokenise(this string str) => str.Split(' ', 9 | StringSplitOptions.TrimEntries | 10 | StringSplitOptions.RemoveEmptyEntries 11 | ); 12 | 13 | public static string WinSlash(this string str) => str.Replace("/", @"\"); 14 | public static string UnixSlash(this string str) => str.Replace(@"\", "/"); 15 | 16 | public static bool EqualsCI(this string str, string other) 17 | => str.Equals(other, StringComparison.InvariantCultureIgnoreCase); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Core/ShellType.cs: -------------------------------------------------------------------------------- 1 | namespace Core 2 | { 3 | /// 4 | /// These are the environments we currently know how to run under 5 | /// 6 | public enum ShellType 7 | { 8 | PowerShell, 9 | Cmd, 10 | Wsl, 11 | 12 | //currently Linux is treated the same as WSL 13 | Linux 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DriveProvider/BookmarkContentReader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Management.Automation.Provider; 5 | 6 | namespace DriveProvider 7 | { 8 | public class BookmarkContentReader : IContentReader 9 | { 10 | private readonly string _path; 11 | private long pos; 12 | 13 | public BookmarkContentReader(string path) => _path = path; 14 | 15 | public void Dispose() 16 | { 17 | } 18 | 19 | public void Close() 20 | { 21 | } 22 | 23 | public IList Read(long readCount) 24 | { 25 | //some operations such as get-content may make repeated calls 26 | //and it appears the way to signal that we are at the end of the 27 | //stream is to return an empty list 28 | if (pos == 0) 29 | { 30 | pos++; 31 | return new[] {_path}.ToList(); 32 | } 33 | 34 | return new string[0].ToList(); 35 | } 36 | 37 | public void Seek(long offset, SeekOrigin origin) 38 | { 39 | switch (origin) 40 | { 41 | case SeekOrigin.Begin: 42 | pos = offset; 43 | break; 44 | case SeekOrigin.Current: 45 | pos += offset; 46 | break; 47 | case SeekOrigin.End: 48 | pos -= offset; 49 | break; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /DriveProvider/DriveProvider.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /DriveProvider/JumpfsBookmarkVirtualDrive.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using System.Management.Automation; 5 | using System.Management.Automation.Provider; 6 | using System.Runtime.CompilerServices; 7 | using Core.Bookmarking; 8 | using Core.EnvironmentAccess; 9 | using Core.Extensions; 10 | 11 | namespace DriveProvider 12 | { 13 | [CmdletProvider("jumpfs", ProviderCapabilities.ExpandWildcards)] 14 | public class JumpfsBookmarkVirtualDrive : NavigationCmdletProvider, IContentCmdletProvider 15 | { 16 | //TODO - possibly this could be cached - it's a little unclear what the lifecycle of this class is 17 | //By NOT caching we can,at least, avoid problems with cross-over reads and writes from different sessions 18 | private BookmarkRepository Repo => new BookmarkRepository(new JumpfsEnvironment()); 19 | 20 | 21 | protected override string[] ExpandPath(string path) 22 | { 23 | return DebugR(path, () => 24 | { 25 | path ??= ""; 26 | path = TranslatePath(path); 27 | var bmk = Repo.Load(); 28 | return BookmarkRepository.Match(bmk, path) 29 | .Select(b => b.Name).ToArray(); 30 | }); 31 | } 32 | 33 | protected override bool HasChildItems(string path) 34 | { 35 | return DebugR(path, 36 | () => false); 37 | } 38 | 39 | protected override void RemoveItem(string path, bool recurse) 40 | { 41 | DebugR(path, () => Repo.Remove(TranslatePath(path))); 42 | } 43 | 44 | 45 | protected override void GetChildNames(string path, ReturnContainers returnContainers) 46 | { 47 | DebugR(path, () => 48 | { 49 | var bookmarks = Repo.Load() 50 | .OrderBy(b => b.Name) 51 | .Select(b => b.Name) 52 | .ToArray(); 53 | foreach (var b in bookmarks) 54 | WriteItemObject(b, path, false); 55 | }); 56 | } 57 | 58 | protected override void GetChildItems(string path, bool recurse) 59 | { 60 | DebugR($"{path} {recurse}", 61 | () => 62 | { 63 | var bookmarks = Repo.Load() 64 | .OrderBy(b => b.Name); 65 | foreach (var b in bookmarks) 66 | { 67 | WriteItemObject(b, path, false); 68 | } 69 | }); 70 | } 71 | 72 | //we are an item container if at the root 73 | protected override bool IsItemContainer(string path) 74 | { 75 | return DebugR($"{path}", () => TranslatePath(path) == ""); 76 | } 77 | 78 | 79 | //just say that all paths are valid 80 | protected override bool IsValidPath(string path) => true; 81 | 82 | private string TranslatePath(string path) => path == null ? "*" : path.Replace(PSDriveInfo.Root, ""); 83 | 84 | protected override bool ItemExists(string path) 85 | { 86 | return DebugR(path, 87 | () => 88 | { 89 | if (path == null) 90 | return true; 91 | 92 | path = TranslatePath(path); 93 | var bookmarks = Repo.Load(); 94 | return string.IsNullOrEmpty(path) || bookmarks.Any(b => b.Name == path); 95 | }); 96 | } 97 | 98 | 99 | //for testing, just return a string 100 | protected override void GetItem(string path) 101 | { 102 | DebugR(path, 103 | () => 104 | { 105 | var bookmarks = Repo.Load(); 106 | foreach (var bookmark in bookmarks.Where(b => b.Name == TranslatePath(path))) 107 | { 108 | WriteItemObject(bookmark, path, false); 109 | } 110 | }); 111 | } 112 | 113 | protected override PSDriveInfo NewDrive(PSDriveInfo drive) 114 | { 115 | return DebugR("", () => base.NewDrive(drive)); 116 | } 117 | 118 | 119 | #region CONTENT 120 | 121 | public IContentReader GetContentReader(string path) 122 | { 123 | return DebugR(path, 124 | () => 125 | { 126 | var bookmarks = Repo.Load(); 127 | path = TranslatePath(path); 128 | if (bookmarks.TryGetSingle(b => b.Name == path, out var hit)) 129 | { 130 | return new BookmarkContentReader(hit.Path); 131 | } 132 | 133 | throw new ArgumentException($"{path} is not a valid bookmark name"); 134 | }); 135 | } 136 | 137 | 138 | public object GetContentReaderDynamicParameters(string path) 139 | { 140 | return DebugR(path, () => null); 141 | } 142 | 143 | #region CONTENT notimplemented 144 | 145 | public void ClearContent(string path) 146 | { 147 | DebugR(path, () => throw new NotImplementedException()); 148 | } 149 | 150 | public object ClearContentDynamicParameters(string path) 151 | { 152 | return DebugR(path, () => throw new NotImplementedException()); 153 | } 154 | 155 | public IContentWriter GetContentWriter(string path) 156 | { 157 | return DebugR(path, () => throw new NotImplementedException()); 158 | } 159 | 160 | public object GetContentWriterDynamicParameters(string path) 161 | { 162 | return DebugR(path, () => throw new NotImplementedException()); 163 | } 164 | 165 | #endregion CONTENT notimplemented 166 | 167 | #endregion CONTENT 168 | 169 | #region tracing - no overridden code here 170 | 171 | protected override object GetChildItemsDynamicParameters(string path, bool recurse) 172 | { 173 | return DebugR($"p:{path} r:{recurse}", () => base.GetChildItemsDynamicParameters(path, recurse)); 174 | } 175 | 176 | protected override void ClearItem(string path) 177 | { 178 | DebugR("", () => 179 | base.ClearItem(path)); 180 | } 181 | 182 | protected override object ClearItemDynamicParameters(string path) 183 | { 184 | return DebugR("", () => base.ClearItemDynamicParameters(path)); 185 | } 186 | 187 | protected override bool ConvertPath(string path, string filter, ref string updatedPath, 188 | ref string updatedFilter) 189 | { 190 | Debug(""); 191 | _debugLevel++; 192 | var r = base.ConvertPath(path, filter, ref updatedPath, ref updatedFilter); 193 | _debugLevel--; 194 | Debug($"ret:{r}"); 195 | return r; 196 | } 197 | 198 | 199 | protected override void CopyItem(string path, string copyPath, bool recurse) 200 | { 201 | DebugR("", () => base.CopyItem(path, copyPath, recurse)); 202 | } 203 | 204 | protected override object CopyItemDynamicParameters(string path, string destination, bool recurse) 205 | { 206 | return DebugR("", () => base.CopyItemDynamicParameters(path, destination, recurse)); 207 | } 208 | 209 | protected override void GetChildItems(string path, bool recurse, uint depth) 210 | { 211 | DebugR("", 212 | () => base.GetChildItems(path, recurse, depth)); 213 | } 214 | 215 | protected override string GetChildName(string path) 216 | { 217 | return DebugR(path, () => base.GetChildName(path)); 218 | } 219 | 220 | protected override object GetChildNamesDynamicParameters(string path) 221 | { 222 | return DebugR(path, () => base.GetChildNamesDynamicParameters(path)); 223 | } 224 | 225 | protected override object GetItemDynamicParameters(string path) 226 | { 227 | return DebugR(path, () => base.GetItemDynamicParameters(path)); 228 | } 229 | 230 | protected override string GetParentPath(string path, string root) 231 | { 232 | return DebugR($"p:{path} r:{root}", () => base.GetParentPath(path, root)); 233 | } 234 | 235 | public override string GetResourceString(string baseName, string resourceId) 236 | { 237 | return DebugR("", () => base.GetResourceString(baseName, resourceId)); 238 | } 239 | 240 | protected override Collection InitializeDefaultDrives() 241 | { 242 | return DebugR("", () => base.InitializeDefaultDrives()); 243 | } 244 | 245 | protected override void InvokeDefaultAction(string path) 246 | { 247 | DebugR("", () => base.InvokeDefaultAction(path)); 248 | } 249 | 250 | protected override object InvokeDefaultActionDynamicParameters(string path) 251 | { 252 | return DebugR("", () => base.InvokeDefaultActionDynamicParameters(path)); 253 | } 254 | 255 | 256 | protected override object ItemExistsDynamicParameters(string path) 257 | { 258 | return DebugR("", () => base.ItemExistsDynamicParameters(path)); 259 | } 260 | 261 | 262 | protected override string MakePath(string parent, string child) 263 | { 264 | return DebugR($"p:{parent} c:{child}", () => base.MakePath(parent, child)); 265 | } 266 | 267 | protected override void MoveItem(string path, string destination) 268 | { 269 | DebugR("", () => base.MoveItem(path, destination)); 270 | } 271 | 272 | protected override object MoveItemDynamicParameters(string path, string destination) 273 | { 274 | return DebugR("", () => base.MoveItemDynamicParameters(path, destination)); 275 | } 276 | 277 | protected override object NewDriveDynamicParameters() 278 | { 279 | return DebugR("", () => base.NewDriveDynamicParameters()); 280 | } 281 | 282 | protected override void NewItem(string path, string itemTypeName, object newItemValue) 283 | { 284 | DebugR("", () => base.NewItem(path, itemTypeName, newItemValue)); 285 | } 286 | 287 | protected override object NewItemDynamicParameters(string path, string itemTypeName, object newItemValue) 288 | { 289 | return DebugR("", () => base.NewItemDynamicParameters(path, itemTypeName, newItemValue)); 290 | } 291 | 292 | protected override string NormalizeRelativePath(string path, string basePath) 293 | { 294 | return DebugR("", () => base.NormalizeRelativePath(path, basePath)); 295 | } 296 | 297 | protected override PSDriveInfo RemoveDrive(PSDriveInfo drive) 298 | { 299 | return DebugR("", () => base.RemoveDrive(drive)); 300 | } 301 | 302 | protected override object RemoveItemDynamicParameters(string path, bool recurse) 303 | { 304 | return DebugR("", () => base.RemoveItemDynamicParameters(path, recurse)); 305 | } 306 | 307 | protected override void RenameItem(string path, string newName) 308 | { 309 | DebugR("", () => base.RenameItem(path, newName)); 310 | } 311 | 312 | protected override object RenameItemDynamicParameters(string path, string newName) 313 | { 314 | return DebugR("", () => base.RenameItemDynamicParameters(path, newName)); 315 | } 316 | 317 | protected override void SetItem(string path, object value) 318 | { 319 | DebugR("", () => base.SetItem(path, value)); 320 | } 321 | 322 | protected override object SetItemDynamicParameters(string path, object value) 323 | { 324 | return DebugR("", () => base.SetItemDynamicParameters(path, value)); 325 | } 326 | 327 | protected override ProviderInfo Start(ProviderInfo providerInfo) 328 | { 329 | return DebugR("", () => base.Start(providerInfo)); 330 | } 331 | 332 | protected override object StartDynamicParameters() 333 | { 334 | return DebugR("", () => base.StartDynamicParameters()); 335 | } 336 | 337 | protected override void Stop() 338 | { 339 | DebugR("", () => base.Stop()); 340 | } 341 | 342 | protected override void StopProcessing() 343 | { 344 | DebugR("", () => base.StopProcessing()); 345 | } 346 | 347 | #endregion 348 | 349 | #region debug 350 | 351 | private int _debugLevel; 352 | 353 | private void DebugIndented(string msg) 354 | { 355 | var pad = "".PadRight(_debugLevel * 4); 356 | WriteDebug($"{pad}{msg}"); 357 | } 358 | 359 | 360 | private void Debug(string message, [CallerMemberName] string caller = "none") 361 | { 362 | DebugIndented($"{caller}: {message}"); 363 | } 364 | 365 | private T DebugR(string message, Func act, [CallerMemberName] string caller = "none") 366 | { 367 | DebugIndented($"{caller}: {message}"); 368 | _debugLevel++; 369 | var r = act(); 370 | _debugLevel--; 371 | DebugIndented($"{caller}: returned {r}"); 372 | return r; 373 | } 374 | 375 | private void DebugR(string message, Action act, [CallerMemberName] string caller = "none") 376 | { 377 | DebugIndented($"{caller}: {message}"); 378 | _debugLevel++; 379 | act(); 380 | _debugLevel--; 381 | DebugIndented($"{caller}: returned (void)"); 382 | } 383 | 384 | #endregion debug 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /DriveProvider/Properties/PublishProfiles/windows.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | ..\publish\driveProviders 10 | FileSystem 11 | net9.0 12 | win-x64 13 | false 14 | True 15 | 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 NeilMacMullen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/jumpfs?label=Latest-version)](https://www.nuget.org/packages/jumpfs/) 4 | [![GitHub release (latest by SemVer including pre-releases)](https://img.shields.io/github/downloads-pre/NeilMacmullen/jumpfs/total?label=Local-downloads)](https://github.com/NeilMacMullen/jumpfs/releases) 5 | [![NuGet](https://img.shields.io/nuget/dt/jumpfs?label=Nuget-downloads)](https://www.nuget.org/packages/jumpfs/) 6 | [![Join the chat at https://gitter.im/jumpfs/community](https://badges.gitter.im/jumpfs/community.svg)](https://gitter.im/jumpfs/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | ## Give a Star! :star: 9 | 10 | If you like, or are using this project please give it a star - thanks! 11 |
12 | 13 | ## What's new? 14 | ### v1.5.0 (Feb 2024) 15 | - migrate to Net8 16 | 17 | See [full revision history](doc/revisionHistory.md) 18 |
19 | 20 | **jumpfs** is a simple cross-platform exe and collection of scripts that allow you bookmark locations in your file system, jump between them, or open them in explorer or VS Code. 21 | 22 | Jumps knows how to translate between WSL paths and Windows paths so you can use Bash bookmarks from PowerShell or PowerShell bookmarks from Bash. 23 | 24 | You can also "bookmark" Urls and shell commands which can make it a bit easier to reuse that complicated pipeline you just spent half an hour putting together. 25 | 26 | It's easiest to demonstrate with a picture.. 27 | 28 | ![jumpfs in action](img/jumpfs.gif) 29 | 30 | ## I want it! 31 | 32 | Great! You can get [prebuilt binaries](doc/download.md) or [build it yourself from source](doc/buildFromSource.md). 33 | There is also a [nuget package](https://www.nuget.org/packages/jumpfs/) 34 | 35 | 36 | Follow the instructions to 37 | - [install for PowerShell](doc/powershell-installation.md) 38 | - [install for WSL (Linux on Windows)](doc/wsl-installation.md) 39 | - [install for native Linux](doc/linux-installation.md) 40 | - [install for DOS/CMD](doc/cmd-installation.md) 41 | 42 | ## Basic use 43 | 44 | The main commands are listed below but you can create your own quite easily by reading the [advanced usage](doc/advanced.md) guide. 45 | 46 | Commands have both long function names and shorter aliases, shown in brackets below. Aliases are defined at the top of *ps-jumpfs.psm1* and *bash-jumpfs.sh* 47 | 48 | ### jumpfs_mark (mark) - create a bookmark 49 | **mark** can take up to 4 arguments. The first is the name you want to use for the bookmark. The remainder are *path*, *line-number* and *column-number*. The latter two are useful when specifying a bookmark to a particular position within a file. 50 | 51 | - `mark name` creates a bookmark at the current working directory 52 | - `mark name path` creates a bookmark at the supplied folder or file 53 | - `mark name path 10 5` creates a bookmark with line number and column 54 | 55 | *path* can be a file, folder, or URL starting *http:* or *https:* 56 | 57 | jumpfs will silently overwrite an existing bookmark of the same name. This is by design since bookmarks are meant to be lightweight and ephemeral. If you don't like this behaviour, raise an issue and I'll consider adding a way to customise it. 58 | 59 | ### jumpfs_go (go) - go to a bookmark 60 | **go** only takes a single argument which is the name of the bookmark. Note that if you *go* to a file, you will actually be taken to the folder that contains it. 61 | 62 | ### jumpfs_list (lst) - list bookmarks 63 | If no arguments are supplied, **lst** will display all stored bookmarks. If an argument is given, it is used to search within the bookmark names and paths and only those that match are returned. 64 | 65 | ### jumpfs_remove (rmbk) - remove bookmark 66 | Removes a bookmark from the bookmark file 67 | 68 | ### jumpfs_code (codego) - open Visual Studio Code at the bookmark 69 | If the bookmark supplied to **codego** is a file and has a line and column associated with it the file will be opened at that position. 70 | 71 | ### jumpfs_explorer_folder (x) - open Windows File Explorer at the bookmark 72 | Opens Windows File Explorer at the location of the bookmark (or the containing folder if the bookmark is a file). 73 | 74 | ### jumpfs_explorer_run (xr) - pass a file to Windows explorer 75 | Similar to double clicking on a file in Windows Explorer - if a folder, it's opened, if a file, then the extension will be used as a clue to perform the associated action. 76 | 77 | ### jumpfs_value (bp) - get bookmark path 78 | Gets the path of a bookmark so you can use it in a command. For example: 79 | ``` 80 | ls (bp myplace) 81 | ``` 82 | 83 | ### jumpfs_browse (url) - browse to a URL 84 | *currently not implemented for Bash* 85 | Runs explorer with the URL value of the bookmark 86 | 87 | ### jumpfs_remember_last_command (markcmd) 88 | *currently not implemented for Bash* 89 | Stores the last issued command as a bookmark 90 | 91 | ### jumpfs_invoke (jrun) 92 | *currently not implemented for Bash* 93 | Runs the bookmark (assuming it is a command that is suitable for the current shell) 94 | 95 | ### jumpfs_info - display version and other information 96 | **jumpfs_info** will provide version and environment information. 97 | 98 | ## Advanced use and custom scripts 99 | 100 | 101 | How to use the PowerShell [virtual drive](doc/psdrive.md) 102 | 103 | Create custom functions and scripts by calling jumpfs directly: [jumpfs parameter reference](doc/jumpfs-exe.md) 104 | 105 | 106 | ## *"What's wrong with ZLocation?"* and other questions 107 | Read the [faq](doc/faq.md) 108 | 109 | ## Contributions 110 | PRs are welcome. Particularly to documentation and Linux-side scripts! Please read the 111 | [contributors guide](doc/contributions.md) 112 | 113 | *Big-ticket* items it would be nice to get help with.... 114 | 115 | - Windows Shell extension 116 | - VS Code extension 117 | - a visual bookmark editor (possible as a VS Code extension) 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /Tests/BasicApplicationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using Core; 5 | using Core.Bookmarking; 6 | using Core.EnvironmentAccess; 7 | using Core.Extensions; 8 | using FluentAssertions; 9 | using jumpfs.Commands; 10 | using NUnit.Framework; 11 | using Tests.SupportClasses; 12 | 13 | namespace Tests 14 | { 15 | [TestFixture] 16 | public class BasicApplicationTests 17 | { 18 | [SetUp] 19 | public void Setup() 20 | { 21 | _sb = new StringBuilder(); 22 | _stdout = new StringWriter(_sb); 23 | _stderr = new StringWriter(); 24 | _env = new MockJumpfsEnvironment(ShellType.PowerShell, new MockFileSystem()); 25 | if (ShellGuesser.IsUnixy()) 26 | { 27 | _env.SetCwd(@"/usr"); 28 | } 29 | else 30 | { 31 | _env.SetCwd(@"D:\"); 32 | } 33 | 34 | _repo = new BookmarkRepository(_env); 35 | _context = new ApplicationContext(_repo, _stdout, _stderr, Array.Empty()); 36 | } 37 | 38 | private ApplicationContext _context; 39 | private BookmarkRepository _repo; 40 | private StringWriter _stderr; 41 | 42 | private StringWriter _stdout; 43 | 44 | private MockJumpfsEnvironment _env; 45 | 46 | private void Execute(string cmd) 47 | { 48 | JumpFs.ExecuteWithContext(cmd.Tokenise(), _context); 49 | } 50 | 51 | 52 | private StringBuilder _sb; 53 | private string GetStdOut() => _sb.ToString(); 54 | 55 | private void CheckOutput(string expected) 56 | { 57 | GetStdOut().Trim().Should().EndWith(expected); 58 | _sb.Clear(); 59 | } 60 | 61 | [Test] 62 | public void SimpleBookmarking() 63 | { 64 | Execute("mark --name here --path apath --literal"); 65 | Execute("find --name here"); 66 | CheckOutput(@"apath"); 67 | } 68 | 69 | [Test] 70 | public void FindOfMissingBookmarkReturnsEmptyLine() 71 | { 72 | Execute("find --name here"); 73 | GetStdOut().Should().Be(EmptyLine); 74 | } 75 | 76 | 77 | [Test] 78 | public void FindOfMissingBookmarkReturnsEmptyLineEvenWhenFormatSpecified() 79 | { 80 | Execute("find --name here --format \"asdfasdf\""); 81 | GetStdOut().Should().Be(EmptyLine); 82 | } 83 | 84 | private static readonly string EmptyLine = "" + Environment.NewLine; 85 | 86 | [Test] 87 | public void RemoveWorks() 88 | { 89 | Execute("mark --name here --path apath --literal"); 90 | Execute("remove --name here"); 91 | CheckOutput(@"apath"); 92 | Execute("remove --name here"); 93 | GetStdOut().Should().Be(EmptyLine); 94 | } 95 | 96 | 97 | [Test] 98 | public void FormattedBookmarking() 99 | { 100 | Execute("mark --name here --path apath --line 15 --column 10 --literal"); 101 | Execute("find --name here --format %p:%l:%c"); 102 | CheckOutput(@"apath:15:10"); 103 | } 104 | 105 | 106 | [Test] 107 | public void UrlNotReturnedUnlessTypeSpecified() 108 | { 109 | Execute("mark --name here --path http://atest"); 110 | Execute("find --name here"); 111 | GetStdOut().Trim().Should().BeEmpty(); 112 | } 113 | 114 | [Test] 115 | public void UrlReturnedWhenTypeSpecified() 116 | { 117 | Execute("mark --name here --path http://atest"); 118 | Execute("find --name here --type Url"); 119 | CheckOutput(@"http://atest"); 120 | } 121 | 122 | 123 | [Test] 124 | public void ScriptNotReturnedUnlessTypeSpecified() 125 | { 126 | Execute("mark --name here --path 'a_script' --type PsCmd"); 127 | Execute("find --name here"); 128 | GetStdOut().Trim().Should().BeEmpty(); 129 | } 130 | 131 | [Test] 132 | public void ScriptReturnedWhenTypeSpecified() 133 | { 134 | Execute("mark --name here --path a_script --type PsCmd"); 135 | Execute("find --name here --type PsCmd"); 136 | CheckOutput(@"a_script"); 137 | } 138 | 139 | [Test] 140 | public void ScriptNotReturnedInWrongShell() 141 | { 142 | Execute("mark --name here --path 'a_script' --type BashCmd"); 143 | Execute("find --name here --type BashCmd"); 144 | GetStdOut().Trim().Should().BeEmpty(); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Tests/BookMarkTests.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.Bookmarking; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | using Tests.SupportClasses; 6 | 7 | namespace Tests 8 | { 9 | [TestFixture] 10 | public class BookMarkTests 11 | { 12 | [SetUp] 13 | public void Setup() 14 | { 15 | _jumpfsEnvironment = new MockJumpfsEnvironment(ShellType.PowerShell, 16 | new MockFileSystem()); 17 | _repo = new BookmarkRepository(_jumpfsEnvironment); 18 | } 19 | 20 | private BookmarkRepository _repo; 21 | private MockJumpfsEnvironment _jumpfsEnvironment; 22 | 23 | [Test] 24 | public void BookMarkCanBeWritten() 25 | { 26 | _repo.Mark(BookmarkType.Folder, "a", "a path"); 27 | var b = _repo.Find("a"); 28 | b.Name.Should().Be("a"); 29 | b.Type.Should().Be(BookmarkType.Folder); 30 | } 31 | 32 | [Test] 33 | public void BookMarkCanBeChanged() 34 | { 35 | _repo.Mark(BookmarkType.Folder, "home", @"path\1"); 36 | _repo.Mark(BookmarkType.Folder, "home", @"path\2"); 37 | 38 | var b = _repo.Find("home"); 39 | b.Name.Should().Be("home"); 40 | b.Path.Should().Be(@"path\2"); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/CommandParserTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Extensions; 3 | using FluentAssertions; 4 | using jumpfs.CommandLineParsing; 5 | using jumpfs.Commands; 6 | using NUnit.Framework; 7 | 8 | namespace Tests 9 | { 10 | [TestFixture] 11 | public class CommandParserTests 12 | { 13 | private static void Noop(ParseResults p, ApplicationContext context) 14 | { 15 | } 16 | 17 | [Test] 18 | public void ParsingCommandSucceeds() 19 | { 20 | var command = new CommandDescriptor(Noop, "test"); 21 | 22 | var parser = new CommandLineParser(command); 23 | var results = parser.Parse("test".Tokenise()); 24 | results.IsSuccess.Should().BeTrue("because we recognised the command"); 25 | results.CommandDescriptor.Name.Should().Be("test"); 26 | } 27 | 28 | [Test] 29 | public void ParsingUnrecognisedCommandFails() 30 | { 31 | var parser = new CommandLineParser(); 32 | var results = parser.Parse(Array.Empty()); 33 | results.IsSuccess.Should().BeFalse("because there are no commands to parse"); 34 | } 35 | 36 | [Test] 37 | public void ParsingRecognisesArguments() 38 | { 39 | var command = new CommandDescriptor(Noop, "test") 40 | .WithArguments(ArgumentDescriptor.Create("foo") 41 | ); 42 | 43 | var parser = new CommandLineParser(command); 44 | var results = parser.Parse("test --foo xyz".Tokenise()); 45 | results.IsSuccess.Should().BeTrue("because we recognised the command"); 46 | results.CommandDescriptor.Name.Should().Be("test"); 47 | results.ValueOf("foo").Should().Be("xyz"); 48 | } 49 | 50 | [Test] 51 | public void MissingCommandProvidesListInErrorOutput() 52 | { 53 | var command = new CommandDescriptor(Noop, "testcommand") 54 | .WithArguments(ArgumentDescriptor.Create("foo") 55 | ); 56 | 57 | var parser = new CommandLineParser(command); 58 | var results = parser.Parse(Array.Empty()); 59 | results.IsSuccess.Should().BeFalse(); 60 | results.Message.Should().Contain("testcommand"); 61 | } 62 | 63 | [Test] 64 | public void UnrecognisedArgumentFails() 65 | { 66 | var command = new CommandDescriptor(Noop, "test") 67 | .WithArguments(ArgumentDescriptor.Create("foo")); 68 | 69 | var parser = new CommandLineParser(command); 70 | var results = parser.Parse("test --fo xyz".Tokenise()); 71 | results.IsSuccess.Should().BeFalse(); 72 | } 73 | 74 | [Test] 75 | public void MissingValueFails() 76 | { 77 | var command = new CommandDescriptor(Noop, "test") 78 | .WithArguments(ArgumentDescriptor.Create("foo")); 79 | 80 | var parser = new CommandLineParser(command); 81 | var results = parser.Parse("test --foo ".Tokenise()); 82 | results.IsSuccess.Should().BeFalse(); 83 | } 84 | 85 | [Test] 86 | public void OptionalValueReturnsEmptyString() 87 | { 88 | var command = new CommandDescriptor(Noop, "test") 89 | .WithArguments( 90 | ArgumentDescriptor.Create("foo") 91 | .AllowEmpty()); 92 | 93 | var parser = new CommandLineParser(command); 94 | var results = parser.Parse("test -foo ".Tokenise()); 95 | results.IsSuccess.Should().BeTrue(); 96 | results.ValueOf("foo").Should().Be(string.Empty); 97 | } 98 | 99 | [Test] 100 | public void OptionalIntReturns0() 101 | { 102 | var command = new CommandDescriptor(Noop, "test") 103 | .WithArguments( 104 | ArgumentDescriptor.Create("foo")); 105 | 106 | var parser = new CommandLineParser(command); 107 | var results = parser.Parse("test".Tokenise()); 108 | results.IsSuccess.Should().BeTrue(); 109 | results.ValueOf("foo").Should().Be(0); 110 | } 111 | 112 | 113 | [Test] 114 | public void IntParsesCorrectly() 115 | { 116 | var command = new CommandDescriptor(Noop, "test") 117 | .WithArguments( 118 | ArgumentDescriptor.Create("foo")); 119 | 120 | var parser = new CommandLineParser(command); 121 | var results = parser.Parse("test --foo 123".Tokenise()); 122 | results.IsSuccess.Should().BeTrue(); 123 | results.ValueOf("foo").Should().Be(123); 124 | } 125 | 126 | [Test] 127 | public void SwitchWorksAsExpected() 128 | { 129 | var command = new CommandDescriptor(Noop, "test") 130 | .WithArguments( 131 | ArgumentDescriptor 132 | .CreateSwitch("foo") 133 | ); 134 | 135 | var parser = new CommandLineParser(command); 136 | 137 | var results = parser.Parse("test".Tokenise()); 138 | results.IsSuccess.Should().BeTrue(); 139 | results.ValueOf("foo").Should().Be(false); 140 | 141 | var results2 = parser.Parse("test --foo".Tokenise()); 142 | results2.IsSuccess.Should().BeTrue(); 143 | results2.ValueOf("foo").Should().Be(true); 144 | } 145 | 146 | 147 | [Test] 148 | public void MissingParameterFails() 149 | { 150 | var command = new CommandDescriptor(Noop, "test") 151 | .WithArguments( 152 | ArgumentDescriptor.Create("foo") 153 | .Mandatory() 154 | ); 155 | 156 | var parser = new CommandLineParser(command); 157 | var results = parser.Parse("test".Tokenise()); 158 | results.IsSuccess.Should().BeFalse(); 159 | } 160 | 161 | [Test] 162 | public void OptionalValuesDontElideSuccessiveTokens() 163 | { 164 | var command = new CommandDescriptor(Noop, "test") 165 | .WithArguments( 166 | ArgumentDescriptor.Create("foo") 167 | .AllowEmpty(), 168 | ArgumentDescriptor.Create("bar") 169 | .AllowEmpty() 170 | ); 171 | 172 | var parser = new CommandLineParser(command); 173 | var results = parser.Parse("test --foo --bar xyz".Tokenise()); 174 | results.IsSuccess.Should().BeTrue(); 175 | results.ValueOf("foo").Should().Be(string.Empty); 176 | results.ValueOf("bar").Should().Be("xyz"); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Tests/CrossShellTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Core; 4 | using Core.Bookmarking; 5 | using Core.EnvironmentAccess; 6 | using Core.Extensions; 7 | using FluentAssertions; 8 | using jumpfs.Commands; 9 | using NUnit.Framework; 10 | using Tests.SupportClasses; 11 | 12 | namespace Tests 13 | { 14 | [TestFixture] 15 | public class CrossShellTests 16 | { 17 | [SetUp] 18 | public void Setup() 19 | { 20 | _stdout = new StringWriter(); 21 | _stderr = new StringWriter(); 22 | _fs = new MockFileSystem(); 23 | var winMock = new MockJumpfsEnvironment(ShellType.PowerShell, _fs); 24 | winMock.SetCwd(@"C:\"); 25 | var winRepo = new BookmarkRepository(winMock); 26 | _win = new ApplicationContext(winRepo, _stdout, _stderr, Array.Empty()); 27 | var wslMock = new MockJumpfsEnvironment(ShellType.Wsl, _fs); 28 | wslMock.SetCwd("/usr"); 29 | wslMock.SetEnvironmentVariable( 30 | EnvVariables.WslEnvVar, 31 | winRepo.Folder 32 | ); 33 | wslMock.SetEnvironmentVariable( 34 | EnvVariables.WslRootVar, 35 | @"\\wsl$\Ubuntu\" 36 | ); 37 | var wslRepo = new BookmarkRepository(wslMock); 38 | 39 | _wsl = new ApplicationContext(wslRepo, _stdout, _stderr, Array.Empty()); 40 | } 41 | 42 | private StringWriter _stdout; 43 | private StringWriter _stderr; 44 | 45 | private ApplicationContext _win; 46 | private MockFileSystem _fs; 47 | private ApplicationContext _wsl; 48 | 49 | 50 | private void Execute(ApplicationContext context, string cmd) 51 | { 52 | JumpFs.ExecuteWithContext(cmd.Tokenise(), context); 53 | } 54 | 55 | private string GetStdOut() => _stdout.ToString(); 56 | private void CheckOutput(string expected) => GetStdOut().Trim().Should().Be(expected); 57 | 58 | [Test] 59 | public void SimpleBookmarking() 60 | { 61 | Execute(_win, @"mark --name here --path C:/thepath --literal"); 62 | Execute(_wsl, "find --name here"); 63 | CheckOutput("/mnt/c/thepath"); 64 | } 65 | 66 | [Test] 67 | public void Win2WslNameTranslation() 68 | { 69 | Execute(_win, "mark --name here --path D:/a/b --literal"); 70 | Execute(_wsl, "find --name here"); 71 | CheckOutput("/mnt/d/a/b"); 72 | } 73 | 74 | [Test] 75 | public void WslToWinNameTranslation() 76 | { 77 | Execute(_wsl, "mark --name here --path /mnt/d/a/b --literal"); 78 | Execute(_win, "find --name here"); 79 | CheckOutput(@"d:\a\b"); 80 | } 81 | 82 | [Test] 83 | public void WslToWinNameTranslation2() 84 | { 85 | Execute(_wsl, @"mark --name here --path \\wsl$\Ubuntu\etc\x --literal"); 86 | Execute(_win, "find --name here"); 87 | CheckOutput(@"\\wsl$\Ubuntu\etc\x"); 88 | } 89 | 90 | [Test] 91 | public void TrueUnixWslToWinNameTranslation() 92 | { 93 | if (ShellGuesser.IsUnixy()) 94 | { 95 | Execute(_wsl, "mark --name here --path /etc/x"); 96 | Execute(_win, "find --name here"); 97 | CheckOutput(@"\\wsl$\Ubuntu\etc\x"); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Tests/FullPathTests.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.EnvironmentAccess; 3 | using FluentAssertions; 4 | using jumpfs.Commands; 5 | using NUnit.Framework; 6 | using Tests.SupportClasses; 7 | 8 | namespace Tests 9 | { 10 | [TestFixture] 11 | public class FullPathTests 12 | { 13 | [Test] 14 | public void FromWindows() 15 | { 16 | //These tests only run on windows 17 | if (ShellGuesser.IsUnixy()) 18 | return; 19 | var env = new MockJumpfsEnvironment(ShellType.PowerShell, new MockFileSystem()); 20 | env.SetCwd(@"C:\a\b"); 21 | var p = new FullPathCalculator(env); 22 | p.ToAbsolute(@"d:\x").Should().Be(@"d:\x"); 23 | p.ToAbsolute(@"..\x").Should().Be(@"C:\a\x"); 24 | } 25 | 26 | [Test] 27 | public void FromWslVirtualDrive() 28 | { 29 | //These tests only run on *nix 30 | if (!ShellGuesser.IsUnixy()) 31 | return; 32 | var env = new MockJumpfsEnvironment(ShellType.Wsl, new MockFileSystem()); 33 | env.SetCwd(@"/mnt/d/a"); 34 | var p = new FullPathCalculator(env); 35 | p.ToAbsolute(@"../x").Should().Be(@"/mnt/d/x"); 36 | p.ToAbsolute(@"/x").Should().Be(@"/x"); 37 | p.ToAbsolute(@"D:\x").Should().Be(@"D:\x"); 38 | p.ToAbsolute(@"\\wsl$Ubuntu\var").Should().Be(@"\\wsl$Ubuntu\var"); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/PathConverterTests.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.Bookmarking; 3 | using FluentAssertions; 4 | using NUnit.Framework; 5 | 6 | namespace Tests 7 | { 8 | [TestFixture] 9 | public class PathConverterTests 10 | { 11 | [SetUp] 12 | public void Setup() 13 | { 14 | } 15 | 16 | private readonly string _unc = @"\\wsl$Ubuntu\"; 17 | 18 | [Test] 19 | public void RootedPath() 20 | { 21 | var p = new PathConverter(string.Empty); 22 | var src = @"C:\a\b\c"; 23 | p.ToShell(ShellType.Wsl, src).Should().Be("/mnt/c/a/b/c"); 24 | p.ToShell(ShellType.PowerShell, src).Should().Be(src); 25 | } 26 | 27 | [Test] 28 | public void InternalWslPath() 29 | { 30 | var p = new PathConverter(_unc); 31 | var src = @"/var/etc"; 32 | p.ToShell(ShellType.Wsl, src).Should().Be("/var/etc"); 33 | } 34 | 35 | [Test] 36 | public void InternalPsPath() 37 | { 38 | var p = new PathConverter(_unc); 39 | var src = @"/var/etc"; 40 | p.ToShell(ShellType.PowerShell, src).Should().Be($@"{_unc}var\etc"); 41 | } 42 | 43 | [Test] 44 | public void UncPath() 45 | { 46 | var p = new PathConverter(_unc); 47 | var src = @$"{_unc}var\etc"; 48 | p.ToShell(ShellType.Wsl, src).Should().Be("/var/etc"); 49 | p.ToShell(ShellType.PowerShell, src).Should().Be(src); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Tests/PathOperationTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeilMacMullen/jumpfs/5a8f0efa1ae5e1fc0b1815c56d2520406b23030f/Tests/PathOperationTests.cs -------------------------------------------------------------------------------- /Tests/ShellGuesserTests.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.Bookmarking; 3 | using Core.EnvironmentAccess; 4 | using FluentAssertions; 5 | using NUnit.Framework; 6 | using Tests.SupportClasses; 7 | 8 | namespace Tests 9 | { 10 | [TestFixture] 11 | public class ShellGuesserTests 12 | { 13 | [Test] 14 | public void ShouldUseVarIfPresent() 15 | { 16 | var initialEnv = new MockJumpfsEnvironment(ShellType.PowerShell, new MockFileSystem()); 17 | initialEnv.SetEnvironmentVariable(EnvVariables.ShellOveride, "cmd"); 18 | ShellGuesser 19 | .GuessShell(initialEnv) 20 | .Should() 21 | .Be(ShellType.Cmd); 22 | } 23 | 24 | [Test] 25 | public void ShouldGuessIfVarNotPresent() 26 | { 27 | var initialEnv = new MockJumpfsEnvironment(ShellType.PowerShell, new MockFileSystem()); 28 | ShellGuesser 29 | .GuessShell(initialEnv) 30 | .Should() 31 | .NotBe(ShellType.Cmd); 32 | } 33 | } 34 | 35 | [TestFixture] 36 | public class RegexTests 37 | { 38 | [Test] 39 | public void Translation() 40 | { 41 | RegexTranslator 42 | .RegexFromPowershell("abc") 43 | .Should().Be("^abc$"); 44 | 45 | RegexTranslator 46 | .RegexFromPowershell("") 47 | .Should().Be("^.*$"); 48 | 49 | RegexTranslator 50 | .RegexFromPowershell("a*b*c") 51 | .Should().Be("^a.*b.*c$"); 52 | 53 | RegexTranslator 54 | .RegexFromPowershell("?a*") 55 | .Should().Be("^.a.*$"); 56 | 57 | RegexTranslator 58 | .RegexFromPowershell("[a-b]*") 59 | .Should().Be("^[a-b]*$"); 60 | 61 | RegexTranslator 62 | .RegexFromPowershell("a[a-b]*c?d") 63 | .Should().Be("^a[a-b]*c.d$"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Tests/SupportClasses/MockJumpfsEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Core; 5 | using Core.EnvironmentAccess; 6 | 7 | namespace Tests.SupportClasses 8 | { 9 | public class MockFileSystem 10 | { 11 | private readonly Dictionary _files = new(); 12 | private readonly HashSet _folders = new(); 13 | public bool DirectoryExists(string path) => _folders.Contains(path); 14 | public bool FileExists(string path) => _files.ContainsKey(path); 15 | 16 | public string ReadAllText(string path) 17 | { 18 | if (!FileExists(path)) 19 | throw new IOException("File not found"); 20 | return _files[path]; 21 | } 22 | 23 | public void WriteAllText(string path, string text) 24 | { 25 | var folder = Path.GetDirectoryName(path); 26 | _folders.Add(folder); 27 | 28 | _files[path] = text; 29 | } 30 | } 31 | 32 | //Mocks up the environment so we can test with a fake bookmark file 33 | public class MockJumpfsEnvironment : IJumpfsEnvironment 34 | { 35 | private readonly Dictionary _env = new(); 36 | private readonly MockFileSystem _fileSystem; 37 | private string _cwd = string.Empty; 38 | 39 | 40 | public MockJumpfsEnvironment(ShellType shellType, MockFileSystem fs) 41 | { 42 | ShellType = shellType; 43 | _fileSystem = fs; 44 | } 45 | 46 | public ShellType ShellType { get; } 47 | public bool DirectoryExists(string path) => _fileSystem.DirectoryExists(path); 48 | 49 | public bool FileExists(string path) => _fileSystem.FileExists(path); 50 | 51 | public string ReadAllText(string path) => _fileSystem.ReadAllText(path); 52 | 53 | public void WriteAllText(string location, string text) 54 | { 55 | _fileSystem.WriteAllText(location, text); 56 | } 57 | 58 | 59 | public string GetEnvironmentVariable(string name) => _env.TryGetValue(name, out var v) ? v : string.Empty; 60 | 61 | public string GetFolderPath(Environment.SpecialFolder folderName) => 62 | Environment.GetFolderPath(folderName); 63 | 64 | public string Cwd() => _cwd; 65 | 66 | 67 | public string SetEnvironmentVariable(string name, string val) => _env[name] = val; 68 | 69 | public void SetCwd(string s) 70 | { 71 | _cwd = s; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | mkdir publish -Force 2 | Remove-Item publish\* -Recurse -Force 3 | dotnet publish .\jumpfs\jumpfs.csproj -p:PublishProfile=linux 4 | dotnet publish .\jumpfs\jumpfs.csproj -p:PublishProfile=windows 5 | dotnet publish .\DriveProvider\DriveProvider.csproj -p:PublishProfile=windows 6 | Remove-Item publish\*.pdb -Recurse 7 | 8 | Copy-Item scripts\bash\*.sh publish 9 | Copy-Item scripts\powershell\*.ps* publish 10 | Copy-Item scripts\cmd\*.bat publish 11 | 12 | 13 | Write-Host "Artefacts are in the ./publish folder" -ForegroundColor Green 14 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | mkdir publish -p 2 | rm publish/* -rf 3 | dotnet publish -p:PublishProfile=linux 4 | echo "" 5 | echo "SORRY - windows exe can't be build under Linux" 6 | echo "Build from PowerShell if you want this" 7 | #dotnet publish -p:PublishProfile=windows 8 | 9 | rm publish/*.pdb 10 | 11 | cp scripts/bash/*.sh publish 12 | cp scripts/powershell/*.ps* publish 13 | cp scripts/cmd/*.bat publish 14 | 15 | echo "Artefacts are in the ./publish folder" 16 | -------------------------------------------------------------------------------- /doc/buildFromSource.md: -------------------------------------------------------------------------------- 1 | # Building from source 2 | 3 | You can build jumpfs from either Windows or Linux; if you are intending to use it from both environments on the same machine, you only need to build it once. 4 | 5 | 6 | First, ensure that you have the [.NET 5 SDK](https://dotnet.microsoft.com/download/dotnet/5.0) installed. 7 | 8 | 9 | If you want to build the *bleeding-edge* checkout *main* 10 | 11 | `git checkout main` 12 | 13 | If you want to build one of the stable releases, checkout one of the release tags 14 | 15 | `git checkout tags/v1.2.0` 16 | 17 | ## PowerShell 18 | Run the **build.ps** script in the top *jumps* folder. 19 | 20 | Note that if you already have the [virtual drive](psdrive.md) installed, the build will fail. You will need to close down all sessions that have the virtual drive loaded and then build from a shell that doesn't load it at startup. This may include VS-Code terminal sessions. 21 | 22 | In practice, the easiest way to do this is to build from an older shell such as PowerShell 5. 23 | 24 | ## Linux 25 | Run the **build.sh** script in the top *jumps* folder. 26 | *Note that it is currently not possible to build the Windows executable from Linux. If you use both PowerShell and Linux, you should build from PowerShell* 27 | 28 | 29 | ## Artefacts 30 | 31 | Binaries and scripts are available in the *publish* folder. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /doc/changelist.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Version": "1.5.0", 4 | "Date": "2024-02-11", 5 | "Summary": "Upgrade to .Net 8", 6 | "Detail": [] 7 | }, 8 | { 9 | "Version": "1.4.0", 10 | "Date": "2022-03-25", 11 | "Summary": "Upgrade to .Net 7", 12 | "Detail": [] 13 | }, 14 | { 15 | "Version": "1.3.0", 16 | "Date": "2021-03-24", 17 | "Summary": "Fix bug in Powershell drive provider", 18 | "Detail": [] 19 | }, 20 | { 21 | "Version": "1.2.0", 22 | "Date": "2021-03-22", 23 | "Summary": "Powershell drive provider", 24 | "Detail": [] 25 | }, 26 | { 27 | "Version": "1.0.0", 28 | "Date": "2021-03-16", 29 | "Summary": "First public release", 30 | "Detail": [] 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /doc/cmd-installation.md: -------------------------------------------------------------------------------- 1 | # Installing for DOS/CMD 2 | 3 | Ensure you have [.NET 5.0 for Windows](https://dotnet.microsoft.com/download/dotnet/5.0) installed. You only need the *.NET Runtime* unless you are planning to [build from source](buildFromSource.md). 4 | 5 | Next, [download](download.md) or [build](buildFromSource.md) the tool and copy the files to a folder such as `C:\tools\jumpfs`. 6 | 7 | You'll need to ensure that the folder is on the PATH. Add this to your autoexec.bat file or change it in system settings.... 8 | 9 | ``` 10 | @PATH=%PATH%;c:\tools\jumpfs 11 | ``` 12 | 13 | That's it! Jumpfs should be available when you start your next CMD session. You can confirm this by running `jumpfs_info`. 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /doc/contributions.md: -------------------------------------------------------------------------------- 1 | # Contributors guide 2 | 3 | Pull requests are welcome! A few guidelines... 4 | 5 | ## Source code 6 | 7 | If you're planning a big change please let me know first via the Discussion forum or by raising an Feature issue. 8 | 9 | Generally, please avoid NULLs - prefer string.Empty or other equivalent no-op values. 10 | 11 | The main codebase (currently) deliberately avoids dependencies on external packages - hence why there is a home-built command-line parser. Unless there's a compelling reason I'd prefer to keep the number of dependencies to zero. 12 | 13 | Unit tests are always appreciated ! 14 | 15 | If you're adding a feature, it would be great if you could update the relevant documentation so people know about it. 16 | 17 | ## Scripts 18 | 19 | If you're adding an extra nifty command for one shell please consider also adding it in the other shells if that's practical (DOS is pretty limited but it would be nice to keep PowerShell and Bash on an even footing). 20 | 21 | ## Documentatation 22 | 23 | Yes please - more is better ! -------------------------------------------------------------------------------- /doc/download.md: -------------------------------------------------------------------------------- 1 | # Dowloading 2 | 3 | Pre-built binaries and scripts are provided for both Linux and Windows in a single zip file in the [Releases](https://github.com/NeilMacMullen/jumpfs/releases) section. 4 | 5 | Just download the latest release and unzip it into a folder such as **c:\tools\jumpfs** (You can refer to this from WSL as **/mnt/c/tools/jumpfs**) 6 | 7 | 8 | -------------------------------------------------------------------------------- /doc/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## Why not just use [ZLocation](https://github.com/vors/ZLocation) ? 4 | 5 | I wasn't aware of this when writing jumpfs. It looks great! If your day-to-day usage is just getting around folders in PowerShell it's probably a better solution. 6 | 7 | There are some differences of approach between ZLocation and jumps 8 | 9 | - ZLocation requires you to use it as a replacement to cd and stores a searchable history of all the locations you've been. jumpfs is a bookmark system. (A long, long time ago it was a set of Perl scripts to allow me to share Emacs bookmarks with the bash and cmd shells.) 10 | 11 | - ZLocation is pure powershell, hence adds nice features such as tab-completion and simple installation. jumpfs uses a common .net exe 'engine' with simple PS/Bash functions/BAT scripts as the front end, does not (yet) offer tab-completion and requires a bit of profile fiddling for each shell you want to use it in. Jumpfs is conceived as a family of related scripts and utilities; hence it includes simple functions to open a code-editor or file-explorer and may conceivable grow to include a shell-extension and VS Code plugin. 12 | 13 | - zlocation is aimed at getting around folders. jumpfs allows you get around folders and files. I.e you can bookmark a line within a file and either 'go' to it (in which case you cd to the containing folder) or 'codego' to it to open your editor at that line. Jumpfs *may* (no promises) also be able to bookmark other things in future such as URLs. 14 | 15 | - jumpfs is aimed at a problem I suspect many developers and sysadmins face since the introduction of WSL; i.e. being able to get around in both filesystems on the same box and refer to the same items regardless of shell. I.e. you can bookmark a folder or file in WSL then get to it easily in PowerShell or vice-versa 16 | 17 | As far as I can see, you could use both ZLocation and jumps together (and I probably will). 18 | 19 | ### Wouldn't it be better if it was 100% PowerShell? 20 | 21 | Yes, that would certainly be better if I didn't want it to support Bash and Cmd bookmarking. That requires some kind of common backend (even if just an agreed file format) and being a C# developer it's easiest to do that in C# and keep the script layering thin. Other functional partitioning would certainly be possible and might even be superior but this is the current one. 22 | 23 | ### Why do some of the command names clash with things I have installed 24 | 25 | *go* and *mark* in particular are popular verbs. Apologies if you are a golang developer. You're free to edit the scripts to change them to suit yourself ! 26 | 27 | ### How can I find out if there's a newer version with better features? 28 | 29 | Run the `jumpfs_info` command - it will check for new versions. 30 | 31 | ### Where is the bookmark file? 32 | 33 | Run the `jumpfs_info` command - it will tell you. 34 | 35 | ### Why did you use JSON for the bookmark file? 36 | 37 | Because it's a reasonable compromise between human and machine-readable and .Net has a Json serializer built in. I wanted to be able to hack the file by hand if necessary and to allow (at least, in principle) for other client applications to use the same bookmark file. 38 | 39 | ### Why isn't there a way to delete bookmarks 40 | Ooops - forgot to include this in the first release. Coming soon.... 41 | -------------------------------------------------------------------------------- /doc/jumpfs-exe.md: -------------------------------------------------------------------------------- 1 | # Jumpfs commands and parameters 2 | 3 | Jumpfs is normally called via scripts. If you want to add a custom command you'll need to know about the commands and parameters. 4 | 5 | The basic syntax is 6 | 7 | ``` 8 | jumpfs *command* [--*parameter* [*value*]] 9 | ``` 10 | 11 | It's generally ok to omit the value for a parameter - this makes it easier to write scripts. For example, this is a perfectly acceptable way of creating a bookmark in the current directory 12 | 13 | ``` 14 | jumpfs mark --name h --path --line --column 15 | ``` 16 | 17 | ## mark 18 | The mark command creates or replaces a bookmark. 19 | ### --name 20 | Required. The name of the bookmark 21 | ### --path 22 | Optional path to the bookmark (defaults to current working directory) 23 | ### --line 24 | Optional line (defaults to 0) 25 | ### --columns 26 | Optional column (defaults to 0) 27 | 28 | ## find 29 | The find command locates a bookmark and returns its value in a specified format 30 | ### --name 31 | Required. The name of the bookmark 32 | ### --winpath 33 | A flag which forces the path to be returned in Windows format which can be useful if trying to pass paths to Windows executables from within WSL. 34 | ### --format 35 | Optional format descriptor (defaults to "%f") 36 | - %f expands to the bookmark value when a folder or the containing folder when a file 37 | - %p - expands to the full bookmark path 38 | %l - expands to the bookmark line number 39 | %c - expands to the bookmark column number 40 | %N - expands to the a newline 41 | %D - expands to the drive letter for a windows path 42 | Format specifiers can be combined. E.g. `--format "%p@(%l,%c)"` 43 | 44 | ## list 45 | The list command lists all bookmarks that match the specified criteria 46 | ### --match 47 | Optional match string. If supplied, all bookmarks whose name or path contain the substring will be displayed, otherwise all stored bookmarks will be displayed 48 | 49 | ## info 50 | The info command displays version and environmental information and will check for updates. 51 | 52 | ## showargs 53 | The showargs command simply echoes back a list of arguments that have been supplied. This can be useful when debugging script interpolation issues. 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /doc/linux-installation.md: -------------------------------------------------------------------------------- 1 | # Installing for Linux 2 | 3 | If you are installing for Linux runing on Windows, please follow the [WSL installation guide](wsl-installation.md) 4 | 5 | If you are truly installing on "native" Linux then most of the installation flow for WSL also applies. The differences are: 6 | 7 | - you should obviously use a folder in the native filesystem to store the files! 8 | - at the beginning of the **bash-jumpfs.sh** script you should replace the calls to wslvar and wslpath with simple assignments. 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /doc/powershell-installation.md: -------------------------------------------------------------------------------- 1 | # Installing for PowerShell 2 | 3 | Ensure you have [.NET 7.0 for Windows](https://dotnet.microsoft.com/download/dotnet/7.0) installed. You only need the *.NET Runtime* unless you are planning to [build from source](buildFromSource.md). 4 | 5 | Next, [download](download.md) or [build](buildFromSource.md) *jumpfs* and copy the files to a folder such as `C:\tools\jumpfs`. 6 | 7 | *You can share a single "installation" of jumpfs between Windows and Linux. If you do this, it's best to use a folder in the Windows file-system.* 8 | 9 | You'll need to add a few lines to your profile startup script. If you're not sure where to find your profile, you can look at the `$profile` variable. Most likely you'll see something like this. 10 | 11 | 12 | 13 | ``` 14 | PS> $profile 15 | C:\Users\neilm\Documents\PowerShell\Microsoft.PowerShell_profile.ps1 16 | ``` 17 | 18 | ** IMPORTANT ** the ps-jumpfs.psm1 module is not signed so PowerShell will refuse to execute it unless using an Unrestricted execution policy (not advisable!). The easiest workaround is to unblock the file manually (after you've reviewed the contents of course!) ... 19 | 20 | ``` 21 | PS> unblock-file C:\tools\jumpfs\ps-jumpfs.psm1 22 | ``` 23 | 24 | You *may* also have to unblock the *jumpfs/driverProviders/DriveProvider.dll* file although that hasn't been necessary in my testing. 25 | 26 | You'll then need to add these lines to the end of the script. Modify the `jumpfs_path` line to point to the folder you stored the files. 27 | 28 | ``` 29 | # change this line... 30 | $jumpfs_path = "C:\tools\jumpfs" 31 | $env:Path = $env:Path+";$jumpfs_path" 32 | Import-Module "$jumpfs_path\ps-jumpfs.psm1" 33 | 34 | # OPTIONAL - install virtual drive (PS 7.x only, has no effect on earlier versions) 35 | jumpfs_install_drive $jumpfs_path "jfs" 36 | ``` 37 | 38 | 39 | That's it! Jumpfs should be available when you start your next PowerShell session. You can confirm this by running `jumpfs_info`. 40 | 41 | IF you chose to install the [virtual drive](doc/../psdrive.md) you can list bookmarks with `ls jfs:` 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /doc/psdrive.md: -------------------------------------------------------------------------------- 1 | # PowerShell virtual drive (PowerShell 7.x only) 2 | 3 | jumpfs comes with a virtual drive which is installed with the `jumpfs_install_drive $jumpfs_path "jfs"` command in your profile script. You are free to change the name to something other than "jfs" - a single letter such as "b" makes it a little quicker to get around! 4 | 5 | It can be used in two different ways. 6 | 7 | ## As a bookmark container 8 | 9 | ``` 10 | 11 | # list bookmarks 12 | ls jfs: 13 | 14 | # get all the paths 15 | ls jfs: | % Path 16 | 17 | # a clumsy way of performing 'go example' 18 | cd (get-item jfs:example).Path 19 | ``` 20 | ## As a content container 21 | 22 | By using the `$` variable prefix, the drive can be coerced to return the Path member for a bookmark 23 | 24 | ``` 25 | # show contents of the file pointed to by the bookmark 'myfile' 26 | get-content $jfs:myfile 27 | 28 | ``` 29 | ## Drive features 30 | 31 | Tab-completion and standard PS wild-cards (`*,?,[]`) are supported. 32 | 33 | ## Restrictions 34 | It is possible to `remove-item` a bookmark via the drive but not yet to add or edit items 35 | -------------------------------------------------------------------------------- /doc/revisionHistory.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ### v1.5.0 4 | - Migrate to Net8 5 | 6 | ### v1.4.0 7 | - Migrate to Net7 8 | - Make version checking less intrusive by only checking on 1 in 10 startups 9 | 10 | ### v1.3.0 11 | - fixes an issue where `get-content jfs:x` would never terminate 12 | - fixed nuspec file 13 | 14 | # v1.2.0 15 | - Implemented 'remove' 16 | - powershell and bash modules now better organised 17 | - better handling for missing bookmarks 18 | - If you pass an actual path instead of a bookmark, jumpfs now does the "right thing" in most cases. 19 | - Bookmarks can be exposed via [virtual drive](doc/psdrive.md) (Powershell 7 only) 20 | - It is now possible to bookmark Urls and shell commands 21 | 22 | See [full revision history](doc/revisionHistory.md) 23 | 24 | # v1.1.0 25 | - Initial public release 26 | -------------------------------------------------------------------------------- /doc/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | There's not a lot to go wrong but if things don't work as expected try running 4 | 5 | jumpfs env 6 | 7 | -------------------------------------------------------------------------------- /doc/wsl-installation.md: -------------------------------------------------------------------------------- 1 | # Installing for WSL 2 | Ensure you have [.NET 5.0 for Linux](https://dotnet.microsoft.com/download/dotnet/5.0) installed. You only need the *.NET Runtime* unless you are planning to [build from source](buildFromSource.md). 3 | 4 | Next, [download](download.md) or [build](buildFromSource.md) the tool and copy the files to a folder such as `/mnt/c/tools/jumpfs`. 5 | 6 | *You can share a single "installation" of jumpfs between Windows and Linux. If you do this, it's best to use a folder in the Windows file-system. **/mnt/c/** is the root of the Windows **C:** drive. 7 | 8 | Assuming you are using Bash 9 | 10 | Edit **~/.profile** to include these lines... 11 | 12 | ``` 13 | #add JumpFs to path.. 14 | jumpfs_path="/mnt/c/work/tools/jumpfs" 15 | PATH="$PATH:$jumpfs_path" 16 | . "$jumpfs_path/bash-jumpfs.sh" 17 | ``` 18 | 19 | That's it! Jumpfs should be available when you start your next PowerShell session. You can confirm this by running `jumpfs_info`. 20 | 21 | 22 | ## Faster startup 23 | 24 | When running under WSL, jumpfs needs to be told how to find the Windows APPLOCALDATA folder so that it can share the bookmarks file. It uses the **wslvar** command to do this which appear to be quite slow and which can add to shell startup time. 25 | 26 | Since the value is constant, you can get slightly faster startup by determining this value on your system and hardcoding it at the beginning of the **bash-jumpfs.sh** script. 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /img/jumpfs.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NeilMacMullen/jumpfs/5a8f0efa1ae5e1fc0b1815c56d2520406b23030f/img/jumpfs.gif -------------------------------------------------------------------------------- /jumpfs.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | jumpfs 4 | 1.3.0.0 5 | jumpfs - cross-shell bookmarking 6 | NeilMacMullen 7 | NeilMacMullen 8 | MIT 9 | https://github.com/NeilMacMullen/jumpfs 10 | false 11 | Jump around your filesystem with bookmarks that can be shared between WSL, Powershell and DOS. Bookmark URLs, shell commands and more... 12 | First nuget release 13 | Copyright 2021 14 | wsl powershell bookmark navigation wsl powershell 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /jumpfs.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33513.286 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jumpfs", "jumpfs\jumpfs.csproj", "{E9E6F99B-8455-40F9-BD52-61987D8D8A36}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerShell", "PowerShell", "{06C2B334-C4DB-4EE4-9E41-6B96BDD5EC64}" 9 | ProjectSection(SolutionItems) = preProject 10 | scripts\powershell\ps-jumpfs.psm1 = scripts\powershell\ps-jumpfs.psm1 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7E47A2B1-0B76-4D5E-A646-C38BC1405E07}" 14 | ProjectSection(SolutionItems) = preProject 15 | .editorconfig = .editorconfig 16 | README.md = README.md 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{1D00CA0A-2EA0-4592-BD3A-789640C821AC}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bash", "bash", "{71679E9C-4289-45AB-83E2-45891796DB9C}" 22 | ProjectSection(SolutionItems) = preProject 23 | scripts\bash\bash-jumpfs.sh = scripts\bash\bash-jumpfs.sh 24 | EndProjectSection 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cmd", "cmd", "{61E0D795-C342-4852-A92B-09D4452A36B5}" 27 | ProjectSection(SolutionItems) = preProject 28 | codego.bat = codego.bat 29 | scripts\cmd\go.bat = scripts\cmd\go.bat 30 | scripts\cmd\jumpfs_info.bat = scripts\cmd\jumpfs_info.bat 31 | scripts\cmd\lst.bat = scripts\cmd\lst.bat 32 | scripts\cmd\x.bat = scripts\cmd\x.bat 33 | EndProjectSection 34 | EndProject 35 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{AE4E31E8-F8F8-4918-B848-C8523941989B}" 36 | EndProject 37 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{C3EA9AA4-7757-416A-858C-F85E64689852}" 38 | ProjectSection(SolutionItems) = preProject 39 | doc\buildFromSource.md = doc\buildFromSource.md 40 | doc\changelist.json = doc\changelist.json 41 | doc\cmd-installation.md = doc\cmd-installation.md 42 | doc\contributions.md = doc\contributions.md 43 | doc\download.md = doc\download.md 44 | doc\faq.md = doc\faq.md 45 | doc\jumpfs-exe.md = doc\jumpfs-exe.md 46 | doc\linux-installation.md = doc\linux-installation.md 47 | doc\powershell-installation.md = doc\powershell-installation.md 48 | doc\psdrive.md = doc\psdrive.md 49 | doc\revisionHistory.md = doc\revisionHistory.md 50 | doc\troubleshooting.md = doc\troubleshooting.md 51 | doc\wsl-installation.md = doc\wsl-installation.md 52 | EndProjectSection 53 | EndProject 54 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DriveProvider", "DriveProvider\DriveProvider.csproj", "{0363DE4C-0569-4C5C-8A9F-0B3EF821D7E1}" 55 | EndProject 56 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core.csproj", "{3E33E98C-240B-403A-A1D6-FA809B4DEB4F}" 57 | EndProject 58 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{AF5E4254-F60A-47A1-B9BE-D7F4492C71B1}" 59 | ProjectSection(SolutionItems) = preProject 60 | .github\workflows\cd.yaml = .github\workflows\cd.yaml 61 | .github\workflows\ci.yaml = .github\workflows\ci.yaml 62 | EndProjectSection 63 | EndProject 64 | Global 65 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 66 | Debug|Any CPU = Debug|Any CPU 67 | Release|Any CPU = Release|Any CPU 68 | EndGlobalSection 69 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 70 | {E9E6F99B-8455-40F9-BD52-61987D8D8A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {E9E6F99B-8455-40F9-BD52-61987D8D8A36}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {E9E6F99B-8455-40F9-BD52-61987D8D8A36}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {E9E6F99B-8455-40F9-BD52-61987D8D8A36}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {1D00CA0A-2EA0-4592-BD3A-789640C821AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {1D00CA0A-2EA0-4592-BD3A-789640C821AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {1D00CA0A-2EA0-4592-BD3A-789640C821AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {1D00CA0A-2EA0-4592-BD3A-789640C821AC}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {0363DE4C-0569-4C5C-8A9F-0B3EF821D7E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {0363DE4C-0569-4C5C-8A9F-0B3EF821D7E1}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {0363DE4C-0569-4C5C-8A9F-0B3EF821D7E1}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {0363DE4C-0569-4C5C-8A9F-0B3EF821D7E1}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {3E33E98C-240B-403A-A1D6-FA809B4DEB4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {3E33E98C-240B-403A-A1D6-FA809B4DEB4F}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {3E33E98C-240B-403A-A1D6-FA809B4DEB4F}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {3E33E98C-240B-403A-A1D6-FA809B4DEB4F}.Release|Any CPU.Build.0 = Release|Any CPU 86 | EndGlobalSection 87 | GlobalSection(SolutionProperties) = preSolution 88 | HideSolutionNode = FALSE 89 | EndGlobalSection 90 | GlobalSection(NestedProjects) = preSolution 91 | {06C2B334-C4DB-4EE4-9E41-6B96BDD5EC64} = {AE4E31E8-F8F8-4918-B848-C8523941989B} 92 | {71679E9C-4289-45AB-83E2-45891796DB9C} = {AE4E31E8-F8F8-4918-B848-C8523941989B} 93 | {61E0D795-C342-4852-A92B-09D4452A36B5} = {AE4E31E8-F8F8-4918-B848-C8523941989B} 94 | EndGlobalSection 95 | GlobalSection(ExtensibilityGlobals) = postSolution 96 | SolutionGuid = {CADCDD24-864F-420B-9E27-DD613740F461} 97 | EndGlobalSection 98 | EndGlobal 99 | -------------------------------------------------------------------------------- /jumpfs.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /jumpfs.v3.ncrunchsolution: -------------------------------------------------------------------------------- 1 |  2 | 3 | False 4 | 5 | 6 | Legacy 7 | %LOCALAPPDATA%\NCrunchCache 8 | True 9 | 10 | -------------------------------------------------------------------------------- /jumpfs/CommandLineParsing/ArgumentDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace jumpfs.CommandLineParsing 4 | { 5 | [Flags] 6 | public enum ArgumentFlags 7 | { 8 | None, 9 | AllowNoValue = (1 << 1), 10 | Mandatory = (1 << 2) 11 | } 12 | 13 | /// 14 | /// Describes an argument that can be passed to a particular command 15 | /// 16 | public readonly struct ArgumentDescriptor 17 | { 18 | public readonly string Name; 19 | public readonly Type Type; 20 | public readonly string HelpText; 21 | 22 | private ArgumentDescriptor(string name, Type type, string helpText, ArgumentFlags flags) 23 | { 24 | Name = name; 25 | Type = type; 26 | HelpText = helpText; 27 | Flags = flags; 28 | } 29 | 30 | public readonly ArgumentFlags Flags; 31 | public bool IsMandatory => Flags.HasFlag(ArgumentFlags.Mandatory); 32 | public bool CanBeEmpty => Flags.HasFlag(ArgumentFlags.AllowNoValue); 33 | 34 | /// 35 | /// Creates a string argument that doesn't necessarily need to be supplied 36 | /// 37 | public static ArgumentDescriptor Create(string name) => 38 | new(name, typeof(T), string.Empty, ArgumentFlags.None); 39 | 40 | public ArgumentDescriptor WithHelpText(string helpText) => 41 | new(Name, Type, helpText, Flags); 42 | 43 | 44 | public ArgumentDescriptor WithFlags(ArgumentFlags flags) => 45 | new(Name, Type, HelpText, flags); 46 | 47 | public ArgumentDescriptor Mandatory() => 48 | WithFlags(Flags | ArgumentFlags.Mandatory); 49 | 50 | public ArgumentDescriptor AllowEmpty() => WithFlags(Flags | ArgumentFlags.AllowNoValue); 51 | 52 | public object DefaultValue() 53 | { 54 | if (Type == typeof(string)) return string.Empty; 55 | if (Type == typeof(int)) return 0; 56 | if (Type == typeof(bool)) return false; 57 | throw new NotImplementedException($"Unable to provide default value for type {Type.Name}"); 58 | } 59 | 60 | public object DefaultValueWhenFlagPresent() => Type == typeof(bool) ? true : DefaultValue(); 61 | 62 | public static ArgumentDescriptor CreateSwitch(string name) => Create(name).AllowEmpty(); 63 | 64 | public bool TryConvert(string valStr, out object o) 65 | { 66 | if (Type == typeof(string)) 67 | { 68 | o = valStr; 69 | return true; 70 | } 71 | 72 | if (Type == typeof(int)) 73 | { 74 | if (int.TryParse(valStr, out var i)) 75 | { 76 | o = i; 77 | return true; 78 | } 79 | } 80 | 81 | if (Type == typeof(bool)) 82 | { 83 | if (bool.TryParse(valStr, out var b)) 84 | { 85 | o = b; 86 | return true; 87 | } 88 | } 89 | 90 | o = null; 91 | return false; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /jumpfs/CommandLineParsing/CommandDescriptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Core.Extensions; 4 | using jumpfs.Commands; 5 | 6 | namespace jumpfs.CommandLineParsing 7 | { 8 | /// 9 | /// A command that can be supported by the command line parser 10 | /// 11 | public readonly struct CommandDescriptor 12 | { 13 | /// 14 | /// The action to be taken when this command is run 15 | /// 16 | public readonly Action Action; 17 | 18 | /// 19 | /// Name of the command (string used to invoke it) 20 | /// 21 | public readonly string Name; 22 | 23 | /// 24 | /// List of Argument Descriptors for this command 25 | /// 26 | public readonly ArgumentDescriptor[] Arguments; 27 | 28 | private CommandDescriptor(Action action, string name, 29 | ArgumentDescriptor[] arguments, 30 | string helpText) 31 | { 32 | Action = action; 33 | //ensure the name is always lower-cased 34 | Name = name.ToLowerInvariant(); 35 | Arguments = arguments.ToArray(); 36 | HelpText = helpText; 37 | } 38 | 39 | public readonly string HelpText; 40 | 41 | public CommandDescriptor(Action action, string name) : this(action, name, 42 | Array.Empty(), string.Empty) 43 | { 44 | } 45 | 46 | public CommandDescriptor WithArguments(params ArgumentDescriptor[] args) => 47 | new(Action, Name, args, HelpText); 48 | 49 | public CommandDescriptor WithHelpText(string helpText) => 50 | new(Action, Name, Arguments, helpText); 51 | 52 | public static readonly CommandDescriptor Empty = new((_, _) => { }, string.Empty); 53 | 54 | public bool TryArgument(string name, out ArgumentDescriptor arg) 55 | { 56 | return Arguments.TryGetSingle(a => a.Name.EqualsCI(name), out arg); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /jumpfs/CommandLineParsing/CommandLineParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Core.Extensions; 6 | 7 | namespace jumpfs.CommandLineParsing 8 | { 9 | /// 10 | /// Parses an array of strings passed on the command line 11 | /// 12 | /// 13 | /// No action is taken by the parser - it's up to the caller to use 14 | /// the returned ParseResult 15 | /// 16 | public class CommandLineParser 17 | { 18 | private const string CommandPrefix = "--"; 19 | private readonly CommandDescriptor[] _commands; 20 | 21 | public CommandLineParser(params CommandDescriptor[] args) => _commands = args.ToArray(); 22 | 23 | public string ConstructHelp() 24 | { 25 | return 26 | @"Missing/unrecognised command. 27 | Use one of the following: 28 | " + string.Join(Environment.NewLine, _commands.Select(c => $" {c.Name} - {c.HelpText}")) 29 | + @" 30 | "; 31 | } 32 | 33 | public ParseResults Parse(string[] suppliedArguments) 34 | { 35 | if (!suppliedArguments.Any()) 36 | return ParseResults.Error(CommandDescriptor.Empty, ConstructHelp()); 37 | 38 | if (!_commands.TryGetSingle(c => c.Name.EqualsCI(suppliedArguments[0]), out var requestedCommand)) 39 | return ParseResults.Error(CommandDescriptor.Empty, ConstructHelp()); 40 | 41 | bool IsValue(int i) => i < suppliedArguments.Length && !suppliedArguments[i].StartsWith(CommandPrefix); 42 | 43 | var assignedArguments = new Dictionary(); 44 | 45 | //allow for "args" command 46 | if (requestedCommand.Arguments.Any()) 47 | for (var i = 1; i < suppliedArguments.Length; i++) 48 | { 49 | var token = suppliedArguments[i]; 50 | if (!token.StartsWith(CommandPrefix)) continue; 51 | var p = token.Substring(CommandPrefix.Length); 52 | if (!requestedCommand.TryArgument(p, out var arg)) 53 | { 54 | return ParseResults.Error(requestedCommand, 55 | ConstructHelpForCommand(requestedCommand, 56 | $"unrecognised argument '{p}'" 57 | )); 58 | } 59 | 60 | //move on to the next token 61 | i++; 62 | 63 | if (IsValue(i)) 64 | { 65 | if (!arg.TryConvert(suppliedArguments[i], out var v)) 66 | return ParseResults.Error(requestedCommand, $"Parameter '{arg.Name}' invalid value"); 67 | assignedArguments[arg.Name] = v; 68 | } 69 | else 70 | { 71 | if (arg.CanBeEmpty) 72 | { 73 | assignedArguments[arg.Name] = arg.DefaultValueWhenFlagPresent(); 74 | //rewind since we didn't actually consume 75 | i--; 76 | } 77 | else 78 | return ParseResults.Error(requestedCommand, $"Parameter '{arg.Name}' missing value"); 79 | } 80 | } 81 | 82 | var missingParameters = requestedCommand 83 | .Arguments 84 | .Where(p => p.IsMandatory) 85 | .Where(req => !assignedArguments.ContainsKey(req.Name)) 86 | .ToArray(); 87 | if (missingParameters.Any()) 88 | return ParseResults.Error(requestedCommand, ConstructHelpForCommand(requestedCommand, 89 | $"missing arguments: {string.Join(" ", missingParameters.Select(p => p.Name))}" 90 | )); 91 | 92 | return ParseResults.Success(requestedCommand, assignedArguments); 93 | } 94 | 95 | public static string ConstructHelpForCommand(CommandDescriptor cmd, string additional) 96 | { 97 | var sb = new StringBuilder(); 98 | if (additional.Length != 0) 99 | sb.AppendLine(additional); 100 | foreach (var a in cmd.Arguments) 101 | { 102 | sb.AppendLine($" {CommandPrefix}{a.Name} {a.Type} {a.HelpText}"); 103 | } 104 | 105 | return sb.ToString(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /jumpfs/CommandLineParsing/ParseResults.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using jumpfs.Commands; 4 | 5 | namespace jumpfs.CommandLineParsing 6 | { 7 | public class ParseResults 8 | { 9 | private Dictionary _argumentValues = new(); 10 | 11 | public CommandDescriptor CommandDescriptor { get; private set; } 12 | 13 | public string Message { get; private set; } = string.Empty; 14 | public bool IsSuccess { get; private set; } 15 | 16 | public static ParseResults Error(CommandDescriptor commandDescriptor, string error) => 17 | new() 18 | { 19 | Message = error, 20 | CommandDescriptor = commandDescriptor 21 | }; 22 | 23 | public static ParseResults Success(CommandDescriptor commandDescriptor, Dictionary vals) => 24 | new() 25 | { 26 | CommandDescriptor = commandDescriptor, 27 | IsSuccess = true, 28 | _argumentValues = vals, 29 | }; 30 | 31 | public T ValueOf(string argName) 32 | { 33 | if (!CommandDescriptor.TryArgument(argName, out var arg)) 34 | throw new ArgumentException($"attempt to retrieve invalid argument {argName}"); 35 | 36 | return _argumentValues.TryGetValue(arg.Name, out var v) 37 | ? (T) v 38 | : (T) arg.DefaultValue(); 39 | } 40 | 41 | public void Execute(ApplicationContext context) 42 | { 43 | CommandDescriptor.Action(this, context); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /jumpfs/Commands/ApplicationContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Core; 5 | using Core.Bookmarking; 6 | 7 | namespace jumpfs.Commands 8 | { 9 | public class ApplicationContext 10 | { 11 | private readonly PathConverter _pathConverter; 12 | public readonly string[] Args; 13 | public readonly TextWriter ErrorStream; 14 | public readonly TextWriter OutputStream; 15 | public readonly BookmarkRepository Repo; 16 | 17 | public ApplicationContext( 18 | BookmarkRepository repo, 19 | TextWriter outputStream, 20 | TextWriter errorStream, 21 | IEnumerable args) 22 | { 23 | Repo = repo; 24 | Args = args.ToArray(); 25 | OutputStream = outputStream; 26 | ErrorStream = errorStream; 27 | _pathConverter = new PathConverter(repo.JumpfsEnvironment.GetEnvironmentVariable(EnvVariables.WslRootVar)); 28 | } 29 | 30 | public void WriteLine(string str) => OutputStream.WriteLine(str); 31 | public string ToNative(string path) => _pathConverter.ToShell(Repo.JumpfsEnvironment.ShellType, path); 32 | public string ToWindows(string path) => _pathConverter.ToShell(ShellType.PowerShell, path); 33 | 34 | public string ToAbsolutePath(string path) 35 | { 36 | var pm = new FullPathCalculator(Repo.JumpfsEnvironment); 37 | var abs = pm.ToAbsolute(path); 38 | return _pathConverter.ToShell(ShellType.PowerShell, abs); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jumpfs/Commands/BookmarkTypeParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Core.Bookmarking; 4 | 5 | namespace jumpfs.Commands 6 | { 7 | public static class BookmarkTypeParser 8 | { 9 | public static readonly BookmarkType[] FilesystemTypes = 10 | { 11 | BookmarkType.File, BookmarkType.Folder 12 | }; 13 | 14 | public static BookmarkType Parse(string bookmarkType) 15 | { 16 | if (Enum.TryParse(typeof(BookmarkType), bookmarkType, true, out var parsedType)) 17 | { 18 | return (BookmarkType) parsedType; 19 | } 20 | 21 | return BookmarkType.Unknown; 22 | } 23 | 24 | public static bool IsFileSystem(BookmarkType markType, string name) 25 | { 26 | var wantedType = Parse(name); 27 | return FilesystemTypes.Contains(markType) && Matches(markType, wantedType); 28 | } 29 | 30 | private static bool Matches(BookmarkType markType, BookmarkType wantedType) => 31 | wantedType == BookmarkType.Unknown || (markType == wantedType); 32 | 33 | public static bool Match(BookmarkType markType, string type, BookmarkType requiredType) 34 | { 35 | var wantedType = Parse(type); 36 | return markType == wantedType && wantedType == requiredType; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdCheckVersion.cs: -------------------------------------------------------------------------------- 1 | using jumpfs.CommandLineParsing; 2 | 3 | namespace jumpfs.Commands 4 | { 5 | public class CmdCheckVersion 6 | { 7 | public static readonly CommandDescriptor Descriptor = 8 | new CommandDescriptor(Run, "checkVersion") 9 | .WithArguments( 10 | ArgumentDescriptor.CreateSwitch("quiet") 11 | ) 12 | .WithHelpText("checks for a new version"); 13 | 14 | private static void Run(ParseResults results, ApplicationContext context) 15 | { 16 | var quiet = results.ValueOf(Names.Quiet); 17 | UpgradeManager.CheckAndWarnOfNewVersion(context.OutputStream, quiet); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdDebug.cs: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdFind.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Core; 5 | using Core.Bookmarking; 6 | using jumpfs.CommandLineParsing; 7 | 8 | namespace jumpfs.Commands 9 | { 10 | public class CmdFind 11 | { 12 | public static readonly CommandDescriptor Descriptor = new CommandDescriptor(Run, "find") 13 | .WithArguments( 14 | ArgumentDescriptor.Create(Names.Name) 15 | .Mandatory() 16 | .WithHelpText("name of the bookmark"), 17 | ArgumentDescriptor.CreateSwitch(Names.Win) 18 | .WithHelpText("use windows path"), 19 | ArgumentDescriptor.Create(Names.Format) 20 | .WithHelpText(@"custom output format: 21 | %f - (default) containing folder when this is a file 22 | %p - full path 23 | %l - line number 24 | %c - column number 25 | %N - newline 26 | %D - Drive specifier (assuming windows path) 27 | 28 | specifiers can be combined and separated . For example: 29 | 30 | --format %p:%l:%c 31 | 32 | "), ArgumentDescriptor.Create(Names.Type) 33 | .AllowEmpty() 34 | .WithHelpText("type (default restricts output to filesystem)") 35 | ) 36 | .WithHelpText("locates a bookmark with the specified name and outputs the associated path"); 37 | 38 | public static void Run(ParseResults results, ApplicationContext context) 39 | { 40 | var type = results.ValueOf(Names.Type); 41 | 42 | 43 | var name = results.ValueOf(Names.Name); 44 | var mark = context.Repo.Find(name); 45 | 46 | var format = results.ValueOf(Names.Format); 47 | 48 | if (BookmarkTypeParser.IsFileSystem(mark.Type, type)) 49 | { 50 | if (EmitFilesystem(results, context, mark, name, format)) 51 | return; 52 | } 53 | 54 | if (BookmarkTypeParser.Match(mark.Type, type, BookmarkType.Url)) 55 | { 56 | //no translation needed for URLs 57 | context.WriteLine(mark.Path); 58 | return; 59 | } 60 | 61 | //ensure we only emit scripts for the correct shell 62 | var scriptTypes = new Dictionary 63 | { 64 | [ShellType.PowerShell] = BookmarkType.PsCmd, 65 | [ShellType.Linux] = BookmarkType.BashCmd, 66 | [ShellType.Wsl] = BookmarkType.BashCmd, 67 | [ShellType.Cmd] = BookmarkType.DosCmd, 68 | }; 69 | 70 | if (scriptTypes.TryGetValue(context.Repo.JumpfsEnvironment.ShellType, out var scriptType)) 71 | { 72 | if (BookmarkTypeParser.Match(mark.Type, type, scriptType)) 73 | { 74 | //no translation needed for Script 75 | context.WriteLine(mark.Path); 76 | return; 77 | } 78 | } 79 | 80 | context.WriteLine(""); 81 | } 82 | 83 | private static bool EmitFilesystem(ParseResults results, ApplicationContext context, Bookmark mark, string name, 84 | string format) 85 | { 86 | var path = results.ValueOf(Names.Win) 87 | ? context.ToWindows(mark.Path) 88 | : context.ToNative(mark.Path); 89 | 90 | 91 | var folder = (mark.Type == BookmarkType.File) 92 | ? Path.GetDirectoryName(path) 93 | : path; 94 | 95 | 96 | if (mark.Name.Length == 0) 97 | { 98 | //if we couldn't find the bookmark there is a chance the user gave us an actual path to use 99 | if (context.Repo.JumpfsEnvironment.FileExists(name)) 100 | { 101 | path = name; 102 | folder = Path.GetDirectoryName(path); 103 | } 104 | else if (context.Repo.JumpfsEnvironment.DirectoryExists(name)) 105 | { 106 | path = folder = name; 107 | } 108 | else 109 | { 110 | return false; 111 | } 112 | } 113 | 114 | 115 | var drive = path.Length > 0 ? path.Substring(0, 1) : string.Empty; 116 | if (format.Length == 0) 117 | { 118 | context.WriteLine(folder); 119 | } 120 | else 121 | { 122 | format = format 123 | .Replace("%f", folder) 124 | .Replace("%p", path) 125 | .Replace("%l", mark.Line.ToString()) 126 | .Replace("%c", mark.Column.ToString()) 127 | .Replace("%N", Environment.NewLine) 128 | .Replace("%D", drive) 129 | ; 130 | context.WriteLine(format); 131 | } 132 | 133 | return true; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using jumpfs.CommandLineParsing; 4 | 5 | namespace jumpfs.Commands 6 | { 7 | public class CmdInfo 8 | { 9 | public static readonly CommandDescriptor Descriptor = 10 | new CommandDescriptor(Run, "info") 11 | .WithHelpText("shows information about the application and environment it is running in"); 12 | 13 | private static void Run(ParseResults results, ApplicationContext context) 14 | { 15 | var exeFolder = AppContext.BaseDirectory; 16 | 17 | context.WriteLine( 18 | @$" 19 | Jumpfs v{GitVersionInformation.SemVer}: 20 | - Executable location: {exeFolder} 21 | - Bookmark file: {context.Repo.BookmarkFile} 22 | 23 | Runtime Info: 24 | - System: {RuntimeEnvironment.GetSystemVersion()} 25 | - Architecture: {RuntimeInformation.OSArchitecture} 26 | - OS: {RuntimeInformation.OSDescription} 27 | - Shell: {context.Repo.JumpfsEnvironment.ShellType} 28 | 29 | Useful links: 30 | - jumpfs homepage: https://github.com/NeilMacMullen/jumpfs 31 | - Chat and questions: https://gitter.im/jumpfs/community 32 | "); 33 | 34 | 35 | context.WriteLine("Checking for new version..."); 36 | UpgradeManager.CheckAndWarnOfNewVersion(context.OutputStream, false); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdList.cs: -------------------------------------------------------------------------------- 1 | using jumpfs.CommandLineParsing; 2 | 3 | namespace jumpfs.Commands 4 | { 5 | public class CmdList 6 | { 7 | public static readonly CommandDescriptor Descriptor 8 | = new CommandDescriptor(Run, "list") 9 | .WithArguments( 10 | ArgumentDescriptor.Create(Names.Match) 11 | .WithHelpText("restrict the output to items where the name or path matches the supplied string") 12 | .AllowEmpty() 13 | ) 14 | .WithHelpText("lists all or a subset of stored bookmarks"); 15 | 16 | private static void Run(ParseResults results, ApplicationContext context) 17 | { 18 | var name = results.ValueOf(Names.Match); 19 | var marks = context.Repo.List(name); 20 | foreach (var mark in marks) 21 | { 22 | context.WriteLine($"{mark.Name} --> {context.ToNative(mark.Path)}"); 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdMark.cs: -------------------------------------------------------------------------------- 1 | using Core.Bookmarking; 2 | using jumpfs.CommandLineParsing; 3 | 4 | namespace jumpfs.Commands 5 | { 6 | public class CmdMark 7 | { 8 | public static readonly CommandDescriptor Descriptor = new CommandDescriptor(Run, "mark") 9 | .WithArguments( 10 | ArgumentDescriptor.Create(Names.Name) 11 | .WithHelpText("name of the bookmark"), 12 | ArgumentDescriptor.Create(Names.Path) 13 | .AllowEmpty() 14 | .WithHelpText("file or folder name"), 15 | ArgumentDescriptor.Create(Names.Line) 16 | .AllowEmpty() 17 | .WithHelpText("line number"), 18 | ArgumentDescriptor.Create(Names.Column) 19 | .AllowEmpty() 20 | .WithHelpText("column number"), 21 | ArgumentDescriptor.CreateSwitch(Names.Literal) 22 | .WithHelpText("use the path as provided rather than trying to turn it into an absolute path"), 23 | ArgumentDescriptor.Create(Names.Type) 24 | .AllowEmpty() 25 | .WithHelpText("type (default is file/folder autodetect)") 26 | ) 27 | .WithHelpText("adds a bookmark. By default this is considered to be a folder"); 28 | 29 | private static void Run(ParseResults results, ApplicationContext context) 30 | { 31 | var name = results.ValueOf(Names.Name); 32 | var path = results.ValueOf(Names.Path); 33 | var line = results.ValueOf(Names.Line); 34 | var column = results.ValueOf(Names.Column); 35 | var bookmarkType = results.ValueOf(Names.Type); 36 | 37 | var type = BookmarkTypeParser.Parse(bookmarkType); 38 | 39 | if (type == BookmarkType.Unknown) 40 | { 41 | //autodetect 42 | if (path.StartsWith("http:") || path.StartsWith("https:")) 43 | type = BookmarkType.Url; 44 | else 45 | { 46 | if (!results.ValueOf(Names.Literal)) 47 | path = context.ToAbsolutePath(path); 48 | type = context.Repo.JumpfsEnvironment.FileExists(path) ? BookmarkType.File : BookmarkType.Folder; 49 | } 50 | } 51 | 52 | context.Repo.Mark(type, name, path, line, column); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdRemove.cs: -------------------------------------------------------------------------------- 1 | using jumpfs.CommandLineParsing; 2 | 3 | namespace jumpfs.Commands 4 | { 5 | public class CmdRemove 6 | { 7 | public static readonly CommandDescriptor Descriptor 8 | = new CommandDescriptor(Run, "remove") 9 | .WithArguments( 10 | ArgumentDescriptor.Create(Names.Name) 11 | .WithHelpText("removes the named bookmark") 12 | ) 13 | .WithHelpText("removes a bookmark"); 14 | 15 | private static void Run(ParseResults results, ApplicationContext context) 16 | { 17 | var name = results.ValueOf(Names.Name); 18 | var victims = context.Repo.Remove(name); 19 | foreach (var v in victims) 20 | { 21 | context.WriteLine($"Removed {v.Name} --> {v.Path}"); 22 | } 23 | 24 | context.WriteLine(""); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jumpfs/Commands/CmdShowArgs.cs: -------------------------------------------------------------------------------- 1 | using jumpfs.CommandLineParsing; 2 | 3 | namespace jumpfs.Commands 4 | { 5 | public class CmdShowArgs 6 | { 7 | public static readonly CommandDescriptor Descriptor 8 | = new CommandDescriptor(Run, "args") 9 | .WithHelpText( 10 | @"writes out the arguments supplied to the program 11 | This can be useful when debugging interpolation issues."); 12 | 13 | private static void Run(ParseResults results, ApplicationContext context) 14 | { 15 | context.WriteLine("Received arguments..."); 16 | foreach (var a in context.Args) 17 | context.WriteLine($" '{a}'"); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /jumpfs/Commands/FullPathCalculator.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Core.EnvironmentAccess; 3 | 4 | namespace jumpfs.Commands 5 | { 6 | /// 7 | /// Allows conversion between absolute and relative paths 8 | /// 9 | public class FullPathCalculator 10 | { 11 | public FullPathCalculator(IJumpfsEnvironment env) => Env = env; 12 | public IJumpfsEnvironment Env { get; set; } 13 | 14 | /// 15 | /// Returns an absolute (rooted) path 16 | /// 17 | /// 18 | /// If a relative path is passed in we assume it is relative to the root 19 | /// 20 | public string ToAbsolute(string path) 21 | { 22 | var root = Env.Cwd(); 23 | if (path.Length == 0) return root; 24 | //Path.GetFullPath does different things under unix and windows 25 | //so we need to do some run-time adjustment! 26 | if (ShellGuesser.IsUnixy()) 27 | { 28 | //under wls we have want to be able to cope with the relative path 29 | //being either.... 30 | // a relative path ../x 31 | // an absolute path /var 32 | // a wsl path such as c:\users\aaa 33 | // a wsl path to internal drive such \\wsl$\Ubuntu\var 34 | 35 | 36 | if (path.StartsWith("/")) 37 | return path; 38 | 39 | 40 | if (path.StartsWith(@"\\")) 41 | return path; 42 | if (path.Length > 1 && path[1] == ':') 43 | return path; 44 | return Path.GetFullPath(path, root); 45 | } 46 | 47 | //there's less to worry about on windows! 48 | return Path.GetFullPath(path, root); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /jumpfs/Commands/GitVersionInformation.cs: -------------------------------------------------------------------------------- 1 | namespace jumpfs.Commands 2 | { 3 | public static class GitVersionInformation 4 | { 5 | public const string SemVer = "1.5.0"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jumpfs/Commands/JumpFs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Core.Bookmarking; 4 | using Core.EnvironmentAccess; 5 | using jumpfs.CommandLineParsing; 6 | 7 | namespace jumpfs.Commands 8 | { 9 | public class JumpFs 10 | { 11 | public static CommandLineParser CreateParser() 12 | { 13 | var parser = new CommandLineParser( 14 | CmdShowArgs.Descriptor, 15 | CmdInfo.Descriptor, 16 | CmdMark.Descriptor, 17 | CmdFind.Descriptor, 18 | CmdList.Descriptor, 19 | CmdRemove.Descriptor, 20 | CmdCheckVersion.Descriptor 21 | ); 22 | return parser; 23 | } 24 | 25 | public static ApplicationContext CliContext(IEnumerable args) 26 | { 27 | var outputStream = Console.Out; 28 | var errorStream = Console.Error; 29 | 30 | var repo = new BookmarkRepository(new JumpfsEnvironment()); 31 | return new ApplicationContext(repo, outputStream, errorStream, args); 32 | } 33 | 34 | 35 | public static void ExecuteWithContext(string[] args, ApplicationContext context) 36 | { 37 | var parser = CreateParser(); 38 | var results = parser.Parse(args); 39 | if (results.IsSuccess) 40 | results.Execute(context); 41 | else 42 | { 43 | if (results.CommandDescriptor.Name.Length != 0) 44 | context.ErrorStream.WriteLine($"Command: {results.CommandDescriptor.Name}"); 45 | context.ErrorStream.WriteLine($"Error: {results.Message}"); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /jumpfs/Names.cs: -------------------------------------------------------------------------------- 1 | namespace jumpfs 2 | { 3 | /// 4 | /// These are stored here to ensure some consistency between commands 5 | /// 6 | public static class Names 7 | { 8 | public const string Name = "name"; 9 | public const string Path = "path"; 10 | public const string Container = "container"; 11 | public const string Help = "help"; 12 | public const string Line = "line"; 13 | public const string Column = "column"; 14 | public const string Format = "format"; 15 | public const string Literal = "literal"; 16 | public const string Match = "match"; 17 | public const string Win = "winpath"; 18 | public const string Type = "type"; 19 | public const string Quiet = "quiet"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jumpfs/Program.cs: -------------------------------------------------------------------------------- 1 | using jumpfs.Commands; 2 | 3 | namespace jumpfs 4 | { 5 | internal class Program 6 | { 7 | private static void Main(string[] args) 8 | { 9 | var context = JumpFs.CliContext(args); 10 | JumpFs.ExecuteWithContext(args, context); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jumpfs/Properties/PublishProfiles/linux.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | ..\publish 10 | FileSystem 11 | net9.0 12 | linux-x64 13 | false 14 | True 15 | 16 | 17 | -------------------------------------------------------------------------------- /jumpfs/Properties/PublishProfiles/windows.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | ..\publish 10 | FileSystem 11 | net9.0 12 | win-x64 13 | false 14 | True 15 | True 16 | 17 | 18 | -------------------------------------------------------------------------------- /jumpfs/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "info": { 4 | "commandName": "Project", 5 | "commandLineArgs": "info" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /jumpfs/UpgradeManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Text.Json; 6 | using System.Threading.Tasks; 7 | using jumpfs.Commands; 8 | 9 | namespace jumpfs 10 | { 11 | public static class UpgradeManager 12 | { 13 | public const string ReleaseSite = "https://github.com/NeilMacMullen/jumpfs"; 14 | 15 | private 16 | static readonly Uri ChangeList = 17 | new("https://raw.githubusercontent.com/NeilMacMullen/jumpfs/master/doc/changelist.json"); 18 | 19 | public static async Task GetLatestVersion() 20 | { 21 | try 22 | { 23 | using var client = new HttpClient(); 24 | var raw = await client.GetStringAsync(ChangeList); 25 | var infos = JsonSerializer.Deserialize(raw); 26 | // ReSharper disable once AssignNullToNotNullAttribute 27 | return infos.OrderByDescending(i => i.Date).First(); 28 | } 29 | catch 30 | { 31 | return VersionInfo.Default; 32 | } 33 | } 34 | 35 | public static void CheckAndWarnOfNewVersion(TextWriter writer, bool suppressUpToDate) 36 | { 37 | var t = GetLatestVersion(); 38 | t.Wait(); 39 | var latestVersion = t.Result; 40 | if (latestVersion.Supersedes(GitVersionInformation.SemVer)) 41 | writer.WriteLine(@$" 42 | An Upgrade to jumpfs version {latestVersion.Version} is available. 43 | Please visit {ReleaseSite} for download. 44 | "); 45 | else if (!suppressUpToDate) 46 | 47 | writer.WriteLine(@" 48 | You are running the latest version." 49 | ); 50 | } 51 | 52 | public static (int major, int minor, int patch) DecomposeVersion(string v) 53 | { 54 | try 55 | { 56 | var elements = v.Split(".") 57 | .Select(int.Parse) 58 | .ToArray(); 59 | return (elements[0], elements[1], elements[2]); 60 | } 61 | catch 62 | { 63 | return (0, 0, 0); 64 | } 65 | } 66 | 67 | public static int CompareVersions(string a, string b) 68 | { 69 | var av = DecomposeVersion(a); 70 | var bv = DecomposeVersion(b); 71 | var d = av.major - bv.major; 72 | if (d != 0) return d; 73 | 74 | d = av.minor - bv.minor; 75 | if (d != 0) return d; 76 | d = av.patch - bv.patch; 77 | if (d != 0) return d; 78 | 79 | return 0; 80 | } 81 | 82 | public record VersionInfo 83 | { 84 | /// 85 | /// The default value with sensible fields 86 | /// 87 | public static readonly VersionInfo Default = new(); 88 | 89 | public string Version { get; init; } = "0.0.0"; 90 | 91 | 92 | public DateTime Date { get; init; } 93 | public string Summary { get; init; } = string.Empty; 94 | 95 | public bool Supersedes(string currentVersion) 96 | => CompareVersions(Version, currentVersion) > 0; 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /jumpfs/jumpfs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /jumpfs/jumpfs.v3.ncrunchproject: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | NetCoreNetStandardLocalSystem 5 | 6 | 7 | -------------------------------------------------------------------------------- /scripts/bash/bash-jumpfs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # configuration 4 | 5 | # use standard script aliases. If you want to set up custom ones 6 | # you can edit those below 7 | jumpfs_use_standard_alias="1" 8 | 9 | ### Standard aliases - feel free to change 10 | if [ "$jumpfs_use_standard_alias" -eq "1" ] ; 11 | then 12 | alias mark="jumpfs_mark" 13 | alias go="jumpfs_go" 14 | alias lst="jumpfs_list" 15 | alias rmbk="jumpfs_remove" 16 | alias codego="jumpfs_code" 17 | alias x="jumpfs_explorer_folder" 18 | alias xr="jumpfs_explorer_run" 19 | alias bp="jumpfs_value" 20 | fi 21 | 22 | 23 | # this should already be on the path 24 | JumpFsExe="jumpfs" 25 | 26 | # JumpFs needs to know where to look for the bookmarks file 27 | # wslvar appears to be quite slow so you can speed up the startup 28 | # by hardcoding the result rather than looking it up on every startup 29 | # For 'bare' LINUX, you should just set this to a folder that will be used 30 | # to contain the bookmark file 31 | loc="$(wslvar LOCALAPPDATA)" 32 | JUMPFS_FOLDER="$(wslpath $loc)" 33 | export JUMPFS_FOLDER 34 | 35 | # JumpFs needs to know the UNC path for the root of this WSL installation 36 | # to allow access from windows. This is a fast operation and not worth 37 | # skipping 38 | # For 'bare' LINUX you should just set this to "/" 39 | JUMPFS_WSL_ROOT="$(wslpath -w /)" 40 | export JUMPFS_WSL_ROOT 41 | 42 | 43 | ### Functions ####################### 44 | 45 | 46 | # jumpfs info 47 | jumpfs_info() { 48 | info=`$JumpFsExe info` 49 | echo "$info" 50 | } 51 | 52 | 53 | # create a bookmark 54 | jumpfs_mark() { 55 | `$JumpFsExe mark --name $1 --path $2 --line $3 --column $4` 56 | } 57 | 58 | #go to a bookmark 59 | jumpfs_go() { 60 | d=`$JumpFsExe find --name $1` 61 | if [ -n "$d" ]; then 62 | cd $d 63 | fi 64 | } 65 | 66 | # list bookmarks 67 | jumpfs_list() { 68 | matches=`$JumpFsExe list --match $1` 69 | echo "$matches" 70 | } 71 | 72 | # list bookmarks 73 | jumpfs_remove() { 74 | `$JumpFsExe remove --name $1` 75 | } 76 | 77 | #open VS Code at a bookmark 78 | jumpfs_code() { 79 | d=`$JumpFsExe find --name $1 --format %p:%l:%c` 80 | if [ -n "$d" ]; then 81 | `code --goto "$d"` 82 | fi 83 | } 84 | 85 | # open file-explorer at bookmark 86 | jumpfs_explorer_folder() { 87 | d=`$JumpFsExe find --name $1 --winpath` 88 | if [ -n "$d" ]; then 89 | explorer.exe "$d" 90 | fi 91 | } 92 | 93 | 94 | # open file-explorer at bookmark 95 | jumpfs_explorer_run() { 96 | d=`$JumpFsExe find --name $1 --format %p --winpath` 97 | if [ -n "$d" ]; then 98 | explorer.exe "$d" 99 | fi 100 | } 101 | 102 | # get the path of a bookmark - useful for building command lines 103 | jumpfs_value() { 104 | d=`$JumpFsExe find --name $1 --format %p` 105 | echo "$d" 106 | } 107 | 108 | 109 | -------------------------------------------------------------------------------- /scripts/cmd/codego.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM extrememly hacky way to cd by constructing a batch file 3 | REM which contains the command we want to run! 4 | SET scr="%TEMP%\codego.bat" 5 | echo|set /p="code --goto " > %scr% 6 | REM we need to use this odd format to ensure that we change directory 7 | jumpfs.exe find --name %1 --format "%%p:%%l%%c" >> %scr% 8 | call %scr% 9 | -------------------------------------------------------------------------------- /scripts/cmd/go.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM extrememly hacky way to cd by constructing a batch file 3 | REM which contains the CD command! 4 | SET scr="%TEMP%\go.bat" 5 | echo|set /p="cd " > %scr% 6 | REM we need to use this odd format to ensure that we change drive as well as directory 7 | jumpfs.exe find --name %1 --format "%%f%%N%%D:" >> %scr% 8 | call %scr% 9 | -------------------------------------------------------------------------------- /scripts/cmd/jumpfs_info.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | jumpfs.exe info 3 | -------------------------------------------------------------------------------- /scripts/cmd/lst.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | jumpfs.exe list --match %1 %2 %3 %4 %5 3 | -------------------------------------------------------------------------------- /scripts/cmd/mark.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | jumpfs.exe mark --name %1 --path %2 --line %3 --column %4 3 | -------------------------------------------------------------------------------- /scripts/cmd/x.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM extrememly hacky way to call explorer by constructing a batch file 3 | REM which contains the command! 4 | SET scr="%TEMP%\x.bat" 5 | echo|set /p="explorer.exe " > %scr% 6 | REM we need to use this odd format to ensure that we change directory 7 | jumpfs.exe find --name %1 >> %scr% 8 | call %scr% 9 | -------------------------------------------------------------------------------- /scripts/powershell/ps-jumpfs.psm1: -------------------------------------------------------------------------------- 1 | 2 | ### Configuration 3 | 4 | # change to true if you want to be explicitly told about invalid bookmark names 5 | $jumpfs_warn = $false 6 | $jumpfs_use_standard_alias = $true 7 | 8 | 9 | ### Standard aliases - feel free to change 10 | if ($jumpfs_use_standard_alias) { 11 | Set-Alias -name mark -value jumpfs_mark 12 | Set-Alias -name go -value jumpfs_go 13 | Set-Alias -name lst -value jumpfs_list 14 | Set-Alias -name rmbk -value jumpfs_remove 15 | Set-Alias -name codego -value jumpfs_code 16 | Set-Alias -name x -value jumpfs_explorer_folder 17 | Set-Alias -name xr -value jumpfs_explorer_run 18 | Set-Alias -name bp -value jumpfs_value 19 | Set-Alias -name url -value jumpfs_browse 20 | Set-Alias -name markcmd -value jumpfs_remember_last_cmd 21 | Set-Alias -name jrun -value jumpfs_invoke 22 | } 23 | 24 | ## EXPERIMENTAL - expose bookmarks as a virtual drive 25 | function jumpfs_install_drive($publish, $name) { 26 | #virtual drive only supported on PS 7 and later 27 | if ($PSVersionTable.PSVersion.Major -ge 7) { 28 | Import-Module "$publish\driveProviders\DriveProvider.dll" 29 | New-PSDrive -Name "$name" -PSProvider "jumpfs" -Root "$($name):\" -Scope Global 30 | } 31 | } 32 | 33 | ################################ 34 | # Functions: 35 | ############################### 36 | 37 | # jumpfs info 38 | function jumpfs_info() { 39 | jumpfs.exe info 40 | } 41 | 42 | function jumpfs_warn($warning) { 43 | if ($jumpfs_warn) { 44 | write-host $warning 45 | } 46 | } 47 | 48 | function jumpfs_do_or_warn_if_empty($path, $action, $warning) { 49 | if ($path -eq "") { 50 | jumpfs_warn $warning 51 | } 52 | else { 53 | if ($action -ne "") { 54 | invoke-expression $action 55 | } 56 | } 57 | } 58 | 59 | # create a bookmark 60 | function jumpfs_mark($p, $r, $l, $c) { 61 | 62 | jumpfs.exe mark --name $p --path $r --line $l --column $c 63 | } 64 | 65 | 66 | function jumpfs_remember_last_cmd($p) { 67 | $cmd = (Get-History | Select-Object -Last 1).CommandLine 68 | #ensure that quotes are translated so we pass the entire CommandLine 69 | #as a single token 70 | $cmd = $cmd.Replace("""","'") 71 | jumpfs.exe mark --name $p --path $cmd --type Pscmd 72 | } 73 | 74 | #go to a bookmark 75 | function jumpfs_go($p) { 76 | $path = (jumpfs.exe find --name $p) ; 77 | jumpfs_do_or_warn_if_empty $path "set-location '$path'" "No bookmark '$p'" 78 | } 79 | 80 | 81 | function jumpfs_browse($p) { 82 | $path = (jumpfs.exe find --name $p --type Url) ; 83 | jumpfs_do_or_warn_if_empty $path "explorer.exe '$path'" "No URL bookmark '$p'" 84 | } 85 | 86 | function jumpfs_invoke($p) { 87 | $path = (jumpfs.exe find --name $p --type Pscmd) ; 88 | jumpfs_do_or_warn_if_empty $path $path "No script bookmark '$p'" 89 | } 90 | 91 | 92 | # list bookmarks 93 | function jumpfs_list($p) { jumpfs.exe list --match $p } 94 | 95 | #open VS Code at a bookmark 96 | function jumpfs_code($p) { 97 | $path = (jumpfs.exe find --name $p --format "%p:%l:%c") ; 98 | jumpfs_do_or_warn_if_empty $path "code --goto '$path'" "No bookmark '$p'" 99 | 100 | } 101 | 102 | # open file-explorer at bookmark 103 | function jumpfs_explorer_folder($p) { 104 | $path = (jumpfs.exe find --name $p) 105 | jumpfs_do_or_warn_if_empty $path "explorer '$path'" "No bookmark '$p'" 106 | } 107 | 108 | # runs the value of a bookmark 109 | function jumpfs_explorer_run($p) { 110 | $path = (jumpfs.exe find --name $p --format "%p") ; 111 | jumpfs_do_or_warn_if_empty $path "explorer '$path'" "No bookmark '$p'" 112 | } 113 | 114 | # get the path of a bookmark - useful for building command lines 115 | function jumpfs_value($p) { 116 | $path = (jumpfs.exe find --name $p --format "%p" ) ; 117 | write-host $path 118 | } 119 | 120 | # get the path of a bookmark - useful for building command lines 121 | function jumpfs_remove($p) { 122 | $path = (jumpfs.exe remove --name $p) ; 123 | jumpfs_do_or_warn_if_empty $path "" "No bookmark '$p'" 124 | } 125 | 126 | ## run version check approximately 1 in 10 startups to prevent it being too intrusive 127 | if (($(Get-date).Second % 10) -eq 0) { 128 | jumpfs.exe checkVersion --quiet 129 | } 130 | 131 | ### ensure functions and aliases are visible 132 | Export-ModuleMember -alias * -function * 133 | 134 | --------------------------------------------------------------------------------