├── .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