diff --git a/SwiftNeoVim/NeoVimView.swift b/SwiftNeoVim/NeoVimView.swift index 2e6a60ab..0a7768f6 100644 --- a/SwiftNeoVim/NeoVimView.swift +++ b/SwiftNeoVim/NeoVimView.swift @@ -1439,13 +1439,14 @@ extension NeoVimView { } public func ipcBecameInvalid(_ reason: String) { - NSLog("ERROR \(#function): force-quitting") DispatchUtils.gui { if self.agent.neoVimIsQuitting { return } self.delegate?.ipcBecameInvalid(reason: reason) + + NSLog("ERROR \(#function): force-quitting") self.agent.quit() } } diff --git a/VimR/Matcher.swift b/VimR/Matcher.swift index 6073a6f5..26eabcd3 100644 --- a/VimR/Matcher.swift +++ b/VimR/Matcher.swift @@ -6,7 +6,7 @@ import Foundation class Matcher { - + static let uppercaseCharSet = CharacterSet.uppercaseLetters enum ExactMatchResult { @@ -20,6 +20,7 @@ class Matcher { static func exactMatchIgnoringCase(_ target: String, pattern: String) -> ExactMatchResult { let ltarget = target.lowercased() let lpattern = pattern.lowercased() + if ltarget == lpattern { return .exact } @@ -40,77 +41,49 @@ class Matcher { } static func numberOfUppercaseMatches(_ target: String, pattern: String) -> Int { - var tscalars = target.unicodeScalars.filter { uppercaseCharSet.contains(UnicodeScalar($0.value)!) } - + var tscalars = target.unicodeScalars.filter { self.uppercaseCharSet.contains($0) } + let count = tscalars.count guard count > 0 else { return 0 } - + let pscalars = pattern.uppercased().unicodeScalars - - pscalars.forEach { scalar in - if let idx = tscalars.index(of: scalar) { + + pscalars.forEach { + if let idx = tscalars.index(of: $0) { tscalars.remove(at: idx) } } - + return count - tscalars.count } - + /// Matches `pattern` to `target` in a fuzzy way. - /// - returns: `Array` of `Range` - static func fuzzyIgnoringCase(_ target: String, - pattern: String) -> (matches: Int, ranges: [CountableClosedRange]) { + /// - returns: number of matched characters where first character match gets a bonus of 5 + static func fuzzyIgnoringCase(_ target: String, pattern: String) -> Int { let tlower = target.lowercased() let plower = pattern.lowercased() - + let tchars = tlower.unicodeScalars let pchars = plower.unicodeScalars - - var flags = Array(repeating: false, count: tchars.count) - + + var result = 0 var pidx = pchars.startIndex - for (i, tchar) in tchars.enumerated() { + for tchar in tchars { if pchars[pidx] == tchar { - flags[i] = true + result += 1 pidx = pchars.index(after: pidx) } } - - var ranges: [CountableClosedRange] = [] - var matches = 0 - - var lastTrue = -1 - var curTrue = -1 - - for (i, isTrue) in flags.enumerated() { - if isTrue { - matches = matches &+ 1 - if lastTrue == -1 { - lastTrue = i - } - curTrue = i - - if i == flags.count &- 1 { - if lastTrue > -1 && curTrue > -1 { - ranges.append(lastTrue...curTrue) - lastTrue = -1 - curTrue = -1 - } - } - } else { - if lastTrue > -1 && curTrue > -1 { - ranges.append(lastTrue...curTrue) - lastTrue = -1 - curTrue = -1 - } - } + + if tchars.first == pchars.first { + result += 5 } - - return (matches, ranges) + + return result } - + /// Wagner-Fischer algorithm. /// We use the 32 bit representation (`String.unicodeScalars`) of both parameters to compare them. /// @@ -119,19 +92,19 @@ class Matcher { static func wagnerFisherDistance(_ target: String, pattern: String) -> Int { let s = target.unicodeScalars let t = pattern.unicodeScalars - + let m = s.count - + var prevRow = Array(repeating: 0, count: m &+ 1) var curRow = Array(repeating: 0, count: m &+ 1) - - for i in 0...m { + + for i in 0 ... m { prevRow[i] = i } - + for (j, tchar) in t.enumerated() { curRow[0] = j &+ 1 - + for (i, schar) in s.enumerated() { if schar == tchar { curRow[i &+ 1] = prevRow[i] @@ -139,10 +112,10 @@ class Matcher { curRow[i &+ 1] = min(curRow[i] &+ 1, prevRow[i &+ 1] &+ 1, prevRow[i] &+ 1) } } - + prevRow = curRow } - + return curRow[m] } } diff --git a/VimR/Scorer.swift b/VimR/Scorer.swift index 031352eb..9e2d19ed 100644 --- a/VimR/Scorer.swift +++ b/VimR/Scorer.swift @@ -6,28 +6,26 @@ import Foundation class Scorer { - + static func score(_ target: String, pattern: String) -> Int { - let wf = Matcher.wagnerFisherDistance(target, pattern: pattern) let fuzzy = Matcher.fuzzyIgnoringCase(target, pattern: pattern) let upper = Matcher.numberOfUppercaseMatches(target, pattern: pattern) let exactMatch = Matcher.exactMatchIgnoringCase(target, pattern: pattern) + let wf = Matcher.wagnerFisherDistance(target, pattern: pattern) let exactScore: Int switch exactMatch { case .none: exactScore = 0 case .exact: - return 100 + return 1000 case .prefix, .contains, .suffix: exactScore = 5 } - let wfScore = 0 - (wf / 10) - return exactScore - + wfScore - + fuzzy.matches - + 2 * upper + + 10 * fuzzy + + 5 * upper + - wf } } diff --git a/VimRTests/MatcherTests.swift b/VimRTests/MatcherTests.swift index b9ad5066..d1a8b315 100644 --- a/VimRTests/MatcherTests.swift +++ b/VimRTests/MatcherTests.swift @@ -19,7 +19,7 @@ class MatcherTest: XCTestCase { expect(Matcher.exactMatchIgnoringCase(self.target, pattern: "swIFt")).to(equal(Matcher.ExactMatchResult.suffix)) expect(Matcher.exactMatchIgnoringCase(self.target, pattern: "userdecon")).to(equal(Matcher.ExactMatchResult.none)) } - + func testUppercaseMatcher() { expect(Matcher.numberOfUppercaseMatches("SwiftNeoVimNeoVimView.swift", pattern: "swnvv")).to(equal(4)) expect(Matcher.numberOfUppercaseMatches(self.target, pattern: "xct")).to(equal(2)) @@ -29,12 +29,12 @@ class MatcherTest: XCTestCase { expect(Matcher.numberOfUppercaseMatches(self.target, pattern: "ut")).to(equal(2)) expect(Matcher.numberOfUppercaseMatches(self.target, pattern: "de")).to(equal(1)) } - + func testFuzzyMatcher() { - expect(Matcher.fuzzyIgnoringCase(self.target, pattern: "ucotft").matches).to(equal(6)) - expect(Matcher.fuzzyIgnoringCase(self.target, pattern: "uco-tft").matches).to(equal(3)) + expect(Matcher.fuzzyIgnoringCase(self.target, pattern: "ucotft")).to(equal(6 + 5)) + expect(Matcher.fuzzyIgnoringCase(self.target, pattern: "uco-tft")).to(equal(3 + 5)) } - + func testWagerFischerAlgo() { expect(Matcher.wagnerFisherDistance("sitting", pattern: "kitten")).to(equal(3)) expect(Matcher.wagnerFisherDistance("saturday", pattern: "sunday")).to(equal(3)) diff --git a/VimRTests/ScorerTest.swift b/VimRTests/ScorerTest.swift index a3b3ee46..ffb461b8 100644 --- a/VimRTests/ScorerTest.swift +++ b/VimRTests/ScorerTest.swift @@ -24,7 +24,7 @@ class ScorerTest: XCTestCase { let targets = [ "NeoVimView.swift", "NeoVimViewDelegate.swift", - "NeoVimServer", + "NeoVimAgent", ] expect(Scorer.score(targets[0], pattern: pattern)).to(beGreaterThan(Scorer.score(targets[1], pattern: pattern)))