Skip to content

Commit 1781483

Browse files
committed
Implement VCS ignoring in Swift and use fzy to fuzzy match
1 parent 6045c88 commit 1781483

File tree

75 files changed

+992
-1590
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+992
-1590
lines changed

Diff for: .github/workflows/vimr-deps-release-on-tag.yml

-58
This file was deleted.

Diff for: Commons/Sources/Commons/FileUtils.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ private let iconsCache = NSCache<NSURL, NSImage>()
1010

1111
public final class FileUtils {
1212
private static let keysToGet: [URLResourceKey] = [
13+
.isRegularFileKey,
1314
.isDirectoryKey,
15+
.isPackageKey,
1416
.isHiddenKey,
15-
.isAliasFileKey,
16-
.isSymbolicLinkKey,
1717
]
1818

1919
private static let scanOptions: FileManager.DirectoryEnumerationOptions = [
@@ -56,7 +56,7 @@ public final class FileUtils {
5656
let result = pathCompsOnlyMin[0]
5757
let possibleParent = NSURL.fileURL(withPathComponents: Array(result[0...commonIdx]))!
5858

59-
return possibleParent.isDir ? possibleParent : possibleParent.parent
59+
return possibleParent.hasDirectoryPath ? possibleParent : possibleParent.parent
6060
}
6161

6262
public static func icon(forType type: String) -> NSImage { workspace.icon(forFileType: type) }

Diff for: Commons/Sources/Commons/FoundationCommons.swift

+5-26
Original file line numberDiff line numberDiff line change
@@ -125,34 +125,13 @@ public extension URL {
125125

126126
var shellEscapedPath: String { self.path.shellEscapedPath }
127127

128-
var isDir: Bool { self.resourceValue(URLResourceKey.isDirectoryKey.rawValue) }
129-
130-
var isHidden: Bool { self.resourceValue(URLResourceKey.isHiddenKey.rawValue) }
131-
132-
var isPackage: Bool { self.resourceValue(URLResourceKey.isPackageKey.rawValue) }
133-
134-
/// Wrapper function for NSURL.getResourceValue for Bool values.
135-
/// Returns also `false` when
136-
/// - there is no value for the given `key` or
137-
/// - the value cannot be converted to `NSNumber`.
138-
///
139-
/// - parameters:
140-
/// - key: The `key`-parameter of `NSURL.getResourceValue`.
141-
private func resourceValue(_ key: String) -> Bool {
142-
var rsrc: AnyObject?
143-
144-
do {
145-
try (self as NSURL).getResourceValue(&rsrc, forKey: URLResourceKey(rawValue: key))
146-
} catch let error as NSError {
147-
// FIXME: How to error handle?
148-
log.error("ERROR while getting \(key): \(error)")
149-
return false
150-
}
128+
var isRegularFile: Bool {
129+
(try? self.resourceValues(forKeys: [.isRegularFileKey]))?.isRegularFile ?? false
130+
}
151131

152-
if let result = rsrc as? NSNumber { return result.boolValue }
132+
var isHidden: Bool { (try? self.resourceValues(forKeys: [.isHiddenKey]))?.isHidden ?? false }
153133

154-
return false
155-
}
134+
var isPackage: Bool { (try? self.resourceValues(forKeys: [.isPackageKey]))?.isPackage ?? false }
156135
}
157136

158137
public extension ValueTransformer {

Diff for: DEVELOP.md

-8
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,6 @@ then continuously invoke the `build_nvimserver_for_local_dev` script.
5353

5454
### VimR
5555

56-
#### Dependencies
57-
58-
* Tag with the name `vimr-deps-yyyy-mm-dd`. GitHub actions will build the universal version,
59-
create a release and upload it.
60-
* Update `resources/vimr-deps_version.txt` and push.
61-
62-
#### Executable
63-
6456
* Set a new version of VimR via
6557
```bash
6658
is_snapshot=true ./bin/set_new_versions.sh # for snapshot or

Diff for: Ignore/Package.swift

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// swift-tools-version:5.6
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "Ignore",
7+
platforms: [.macOS(.v10_13)],
8+
products: [
9+
.library(name: "Ignore", targets: ["Ignore"]),
10+
],
11+
dependencies: [
12+
.package(url: "https://github.com/qvacua/misc.swift", exact: "0.0.1"),
13+
.package(url: "https://github.com/Quick/Nimble", from: "10.0.0"),
14+
],
15+
targets: [
16+
.target(name: "Ignore", dependencies: [.product(name: "WildmatchC", package: "misc.swift")]),
17+
.testTarget(
18+
name: "IgnoreTests",
19+
dependencies: ["Ignore", "Nimble"],
20+
resources: [.copy("Resources")]
21+
),
22+
]
23+
)

Diff for: Ignore/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Ignore
2+
3+
A description of this package.

Diff for: Ignore/Sources/Ignore/FileLineReader.swift

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/// Tae Won Ha - http://taewon.de - @hataewon
2+
/// See LICENSE
3+
4+
import Foundation
5+
6+
/// Reads the file at the given ``URL`` line by line.
7+
/// The Unix line ending `LF` is used to determine lines.
8+
/// Thus, it supports `LF` and `CRLF` files. It does not support the legacy Mac line ending `CR`.
9+
public final class FileLineReader: Sequence {
10+
public static let defaultLineBufferCount = 1024
11+
12+
public let url: URL
13+
14+
public var lineBufferCount: Int
15+
public var encoding: String.Encoding
16+
17+
/// - Parameters:
18+
/// - url: URL of the file.
19+
/// - encoding: Encoding of the file. It's mutable.
20+
/// After mutating, the next iterator will use the new value.
21+
/// - lineBufferCount: The initial size of the buffer for reading lines. It's mutable.
22+
/// After mutating, the next iterator will use the new value.
23+
/// The default is ``FileLineReader/defaultLineBufferCount``.
24+
public init(url: URL, encoding: String.Encoding, lineBufferCount: Int = defaultLineBufferCount) {
25+
self.lineBufferCount = lineBufferCount
26+
self.url = url
27+
self.encoding = encoding
28+
}
29+
30+
public func makeIterator() -> AnyIterator<String> {
31+
guard let file = fopen(url.path, "r") else { return AnyIterator { nil } }
32+
33+
let iterator = LfLineIterator(
34+
file: file,
35+
encoding: self.encoding,
36+
lineBufferCount: self.lineBufferCount
37+
)
38+
return AnyIterator { iterator.next() }
39+
}
40+
}
41+
42+
private class LfLineIterator: IteratorProtocol {
43+
init(
44+
file: UnsafeMutablePointer<FILE>,
45+
encoding: String.Encoding,
46+
lineBufferCount: Int
47+
) {
48+
self.file = file
49+
self.encoding = encoding
50+
self.buffer = Array(repeating: 0, count: lineBufferCount)
51+
}
52+
53+
deinit { fclose(self.file) }
54+
55+
func next() -> String? {
56+
var readCharCount = 0
57+
while true {
58+
let nextChar = getc(self.file)
59+
60+
if nextChar == EOF {
61+
if readCharCount == 0 { return nil }
62+
return String(
63+
data: Data(
64+
bytesNoCopy: self.buffer[0..<readCharCount].unsafeMutableRawPointer,
65+
count: readCharCount,
66+
deallocator: .none
67+
), encoding: self.encoding
68+
)
69+
}
70+
71+
if readCharCount >= self.buffer.count {
72+
// Array.append()
73+
// https://developer.apple.com/documentation/swift/array/3126937-append
74+
// "Complexity: O(1) on average, over many calls to append(_:) on the same array."
75+
self.buffer.append(UInt8(nextChar))
76+
} else {
77+
self.buffer[readCharCount] = UInt8(nextChar)
78+
}
79+
readCharCount += 1
80+
81+
if nextChar == Self.unixLineEnding {
82+
return String(
83+
data: Data(
84+
bytesNoCopy: self.buffer[0..<readCharCount].unsafeMutableRawPointer,
85+
count: readCharCount,
86+
deallocator: .none
87+
), encoding: self.encoding
88+
)
89+
}
90+
}
91+
}
92+
93+
private let encoding: String.Encoding
94+
private var buffer: [UInt8]
95+
private let file: UnsafeMutablePointer<FILE>
96+
97+
private static let unixLineEnding = "\n".utf8.first!
98+
}
99+
100+
private extension ArraySlice {
101+
@inline(__always)
102+
var unsafeMutableRawPointer: UnsafeMutableRawPointer {
103+
UnsafeMutableRawPointer(mutating: self.withUnsafeBytes { $0.baseAddress! })
104+
}
105+
}

Diff for: Ignore/Sources/Ignore/Filter.swift

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/// Tae Won Ha - http://taewon.de - @hataewon
2+
/// See LICENSE
3+
4+
import Foundation
5+
import WildmatchC
6+
7+
public struct Filter: CustomStringConvertible {
8+
public let base: URL
9+
public let pattern: String
10+
11+
public let isAllow: Bool
12+
public let isOnlyForDirectories: Bool
13+
public let isRelativeToBase: Bool
14+
15+
public init(base: URL, pattern: String) {
16+
self.base = base
17+
18+
var effectivePattern: String
19+
20+
self.isAllow = pattern.first == "!"
21+
effectivePattern = self.isAllow ? String(pattern.dropFirst()) : pattern
22+
23+
self.isOnlyForDirectories = effectivePattern.last == "/"
24+
effectivePattern =
25+
self.isOnlyForDirectories ? String(effectivePattern.dropLast(1)) : effectivePattern
26+
27+
self.isRelativeToBase = effectivePattern.contains("/")
28+
if self.isRelativeToBase {
29+
effectivePattern = base.path
30+
+ (effectivePattern.first == "/" ? effectivePattern : "/" + effectivePattern)
31+
}
32+
33+
self.pattern = effectivePattern
34+
self.patternCstr = Array(self.pattern.utf8CString)
35+
}
36+
37+
public func disallows(_ url: URL) -> Bool {
38+
if self.isOnlyForDirectories {
39+
guard url.hasDirectoryPath else { return false }
40+
}
41+
42+
if self.isRelativeToBase {
43+
let matches = self.matches(url.path)
44+
if self.isAllow { return !matches } else { return matches }
45+
}
46+
47+
let matches = self.matches(url.lastPathComponent)
48+
if self.isAllow { return false } else { return matches }
49+
}
50+
51+
public func explicitlyAllows(_ url: URL) -> Bool {
52+
if self.isOnlyForDirectories {
53+
guard url.hasDirectoryPath else { return false }
54+
}
55+
56+
if self.isRelativeToBase {
57+
if self.isAllow { return self.matches(url.path) }
58+
return false
59+
}
60+
61+
if self.isAllow { return self.matches(url.lastPathComponent) } else { return false }
62+
}
63+
64+
/// Ignores whether the pattern is only applicable for directories.
65+
public func disallows(_ string: String) -> Bool {
66+
if self.isAllow { return false } else { return self.matches(string) }
67+
}
68+
69+
/// Ignores whether the pattern is only applicable for directories.
70+
public func explicitlyAllows(_ string: String) -> Bool {
71+
if self.isAllow { return self.matches(string) }
72+
return false
73+
}
74+
75+
public func matches(_ url: URL) -> Bool {
76+
if self.isOnlyForDirectories {
77+
guard url.hasDirectoryPath else { return false }
78+
}
79+
80+
if self.isRelativeToBase {
81+
return url.path.withCString { stringCstr in
82+
wildmatch(patternCstr, stringCstr, WM_WILDSTAR) == WM_MATCH
83+
}
84+
}
85+
86+
return url.lastPathComponent.withCString { stringCstr in
87+
wildmatch(patternCstr, stringCstr, WM_WILDSTAR) == WM_MATCH
88+
}
89+
}
90+
91+
/// Ignores whether the pattern is only applicable for directories.
92+
public func matches(_ string: String) -> Bool {
93+
string.withCString {
94+
stringCstr in
95+
wildmatch(patternCstr, stringCstr, WM_WILDSTAR) == WM_MATCH
96+
}
97+
}
98+
99+
public var description: String {
100+
"Ignore(isAllow: \(self.isAllow), isOnlyDirectory: \(self.isOnlyForDirectories), "
101+
+ "isAbsolute: \(self.isRelativeToBase), pattern: \(self.pattern), baseUrl: \(self.base))"
102+
}
103+
104+
private let patternCstr: [CChar]
105+
}

0 commit comments

Comments
 (0)