Skip to content

Commit

Permalink
Merge pull request #683 from yous/async-hanja
Browse files Browse the repository at this point in the history
Update hanja candidates asynchronously
  • Loading branch information
youknowone committed Nov 11, 2019
2 parents 1398913 + c714813 commit e6feda2
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 49 deletions.
3 changes: 2 additions & 1 deletion GureumTests/GureumTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,8 @@ class GureumTests: XCTestCase {
(SearchSourceConst.emojiKorean, "사과", "사과"),
(SearchSourceConst.hanjaReversed, "물 수", ""),
] {
let candidates = pool.collect(key)
let workItem = DispatchWorkItem {}
let candidates = pool.collect(key, workItem: workItem)
let c = candidates[0]
XCTAssertTrue(c.candidate == test || c.description.contains(test))
}
Expand Down
83 changes: 43 additions & 40 deletions OSXCore/SearchComposer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ final class SearchComposer: Composer {

var showsCandidateWindow = true

// MARK: 한자 전용 프로퍼티

private var _lastString = ""

// MARK: 이모지 전용 프로퍼티

private var _selectedCandidate: NSAttributedString?
Expand Down Expand Up @@ -91,16 +87,7 @@ final class SearchComposer: Composer {
}

var candidates: [NSAttributedString]? {
switch dependentComposerType {
case .hangul:
if _lastString != originalString {
_candidates = buildHanjaCandidates()
_lastString = originalString
}
return _candidates
case .roman:
return _candidates
}
return _candidates
}

var hasCandidates: Bool {
Expand Down Expand Up @@ -142,9 +129,6 @@ final class SearchComposer: Composer {
_commitString = word
delegate.cancelComposition()
delegate.dequeueCommitString()
if dependentComposerType == .hangul {
prepareHanjaCandidates()
}
}

func candidateSelectionChanged(_ candidateString: NSAttributedString) {
Expand Down Expand Up @@ -219,7 +203,7 @@ final class SearchComposer: Composer {

switch dependentComposerType {
case .hangul:
prepareHanjaCandidates()
updateHanjaCandidates()
case .roman:
updateEmojiCandidates()
}
Expand Down Expand Up @@ -262,7 +246,7 @@ extension SearchComposer {
dlog(DEBUG_SEARCH_COMPOSER, "DEBUG 5, DelegatedComposer.update(client:) before update candidates")
switch dependentComposerType {
case .hangul:
prepareHanjaCandidates()
updateHanjaCandidates()
case .roman:
updateEmojiCandidates()
}
Expand Down Expand Up @@ -291,46 +275,65 @@ extension SearchComposer {
// MARK: - SearchComposer 한글 합성기 의존시 전용 메소드

private extension SearchComposer {
/// 한자 입력을 위한 후보를 생성할 준비를 수행한다.
func prepareHanjaCandidates() {
/// 한자 입력을 위한 후보를 만든다.
func updateHanjaCandidates() {
guard dependentComposerType == .hangul else {
dlog(DEBUG_SEARCH_COMPOSER, "INVALID: prepareHanjaCandidates() at emoji mode!")
dlog(DEBUG_SEARCH_COMPOSER, "INVALID: updateHanjaCandidates() at emoji mode!")
return
}

dlog(DEBUG_SEARCH_COMPOSER, "DelegatedComposer.prepareHanjaCandidates()")
dlog(DEBUG_SEARCH_COMPOSER, "SearchComposer.updateHanjaCandidates()")
// step 1: 한글 입력기에서 조합 완료된 글자를 가져옴
let hangulString = delegate.dequeueCommitString()
dlog(DEBUG_SEARCH_COMPOSER, "DelegatedComposer.prepareHanjaCandidates() step1")
dlog(DEBUG_SEARCH_COMPOSER, "SearchComposer.updateHanjaCandidates() step1")
_bufferedString.append(hangulString)
// step 2: 일단 화면에 한글이 표시되도록 조정
dlog(DEBUG_SEARCH_COMPOSER, "DelegatedComposer.prepareHanjaCandidates() step2")
dlog(DEBUG_SEARCH_COMPOSER, "SearchComposer.updateHanjaCandidates() step2")
_composedString = originalString
// step 3: 키가 없거나 검색 결과가 키 prefix와 일치하지 않으면 후보를 보여주지 않는다.
dlog(DEBUG_SEARCH_COMPOSER, "DelegatedComposer.prepareHanjaCandidates() step3")
}
dlog(DEBUG_SEARCH_COMPOSER, "SearchComposer.updateHanjaCandidates() step3")

/// 한자 입력을 위한 후보를 만든다.
///
/// - Returns: 한자 후보의 문자열 배열. `nil`을 반환할 수 있다.
func buildHanjaCandidates() -> [NSAttributedString]? {
guard dependentComposerType == .hangul else {
dlog(DEBUG_SEARCH_COMPOSER, "INVALID: buildHanjaCandidates() at emoji mode!")
return nil
let keyword = originalString.trimmingCharacters(in: .whitespaces)
if !_searchWorkItem.isCancelled {
_searchWorkItem.cancel()
}
_candidates = [NSAttributedString(string: "검색 중...")] // default candidates

let keyword = originalString.trimmingCharacters(in: .whitespaces)
guard !keyword.isEmpty else {
dlog(DEBUG_SEARCH_COMPOSER, "DelegatedComposer.buildHanjaCandidates() has no keywords")
return nil
dlog(DEBUG_SEARCH_COMPOSER, "SearchComposer.updateHanjaCandidates() has no keywords")
_searchLock.lock()
_candidates = nil
_searchLock.unlock()
return
}

dlog(DEBUG_SEARCH_COMPOSER, "DelegatedComposer.buildHanjaCandidates() has candidates")
dlog(DEBUG_SEARCH_COMPOSER, "SearchComposer.updateHanjaCandidates() has candidates")

let pool = keyword.count == 1
? SearchSourceConst.koreanSingle : SearchSourceConst.korean

return pool.search(keyword)
var workItem: DispatchWorkItem!
workItem = DispatchWorkItem {
let newCandidates = pool.search(keyword, workItem: workItem)
guard !workItem.isCancelled else { return }

self._searchLock.lock()
self._candidates = newCandidates
if workItem.isCancelled {
self._searchLock.unlock()
return
}
DispatchQueue.main.async {
if workItem.isCancelled {
self._searchLock.unlock()
return
}
InputMethodServer.shared.showOrHideCandidates(composer: self)
self._searchLock.unlock()
}
}
_searchWorkItem = workItem
_searchQueue.async(execute: _searchWorkItem)
}
}

Expand Down Expand Up @@ -360,7 +363,7 @@ private extension SearchComposer {

var workItem: DispatchWorkItem!
workItem = DispatchWorkItem {
let newCandidates = SearchSourceConst.emoji.search(keyword)
let newCandidates = SearchSourceConst.emoji.search(keyword, workItem: workItem)
guard !workItem.isCancelled else { return }

self._searchLock.lock()
Expand Down
29 changes: 21 additions & 8 deletions OSXCore/SearchPool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import Hangul
/// 후보 검색 풀을 추상화한 프로토콜.
protocol SearchSource {
typealias Candidate = (candidate: String, description: String, score: Double)
func collect(_ keyword: String) -> [Candidate]
func search(_ keyword: String) -> [NSAttributedString]
func collect(_ keyword: String, workItem: DispatchWorkItem!) -> [Candidate]
func search(_ keyword: String, workItem: DispatchWorkItem!) -> [NSAttributedString]
}

extension SearchSource {
func search(_ keyword: String) -> [NSAttributedString] {
func search(_ keyword: String, workItem: DispatchWorkItem!) -> [NSAttributedString] {
// TODO: 중복 제거?
collect(keyword).sorted(by: { $0.score < $1.score }).map {
collect(keyword, workItem: workItem).sorted(by: { $0.score < $1.score }).map {
#if DEBUG
let s = "\($0.candidate): \($0.description) (\($0.score))"
#else
Expand Down Expand Up @@ -50,8 +50,15 @@ struct SearchPool: SearchSource {
self.sources = sources
}

func collect(_ keyword: String) -> [Candidate] {
sources.map { $0.collect(keyword) }.flatMap { $0 }
func collect(_ keyword: String, workItem: DispatchWorkItem!) -> [Candidate] {
var results = [Candidate]()
for source in sources {
guard !workItem.isCancelled else {
break
}
results.append(contentsOf: source.collect(keyword, workItem: workItem))
}
return results
}
}

Expand All @@ -75,7 +82,7 @@ final class HanjaTableSearchSource: SearchSource {
/// - Parameter keyword: 검색 키워드.
///
/// - Returns: 후보 문자열과 검색 점수로 이루어진 튜플.
func collect(_ keyword: String) -> [Candidate] {
func collect(_ keyword: String, workItem: DispatchWorkItem!) -> [Candidate] {
guard let list: HGHanjaList = {
switch method {
case .exact:
Expand All @@ -90,6 +97,9 @@ final class HanjaTableSearchSource: SearchSource {

var candidates: [Candidate] = []
for hanja in list {
guard !workItem.isCancelled else {
break
}
let hanja = hanja as! HGHanja
let score: Double
if method == .exact || keyword == hanja.comment {
Expand Down Expand Up @@ -139,13 +149,16 @@ final class FuseSearchSource: SearchSource {
self.init(source: source, threshold: threshold)
}

func collect(_ keyword: String) -> [Candidate] {
func collect(_ keyword: String, workItem: DispatchWorkItem!) -> [Candidate] {
dlog(DEBUG_SEARCH_COMPOSER, "DEBUG 3, DelegatedComposer.updateEmojiCandidates() before hanjasByPrefixSearching")
dlog(DEBUG_SEARCH_COMPOSER, "DEBUG 4, DelegatedComposer.updateEmojiCandidates() [keyword: %@]", keyword)
dlog(DEBUG_SEARCH_COMPOSER, "DEBUG 14, DelegatedComposer.updateEmojiCandidates() %@", source.debugDescription)
let searchResult = fuse.search(keyword, in: strings)
dlog(DEBUG_SEARCH_COMPOSER, "DEBUG 5, DelegatedComposer.updateEmojiCandidates() after hanjasByPrefixSearching")

guard !workItem.isCancelled else {
return []
}
return searchResult.map {
result in
let word = source[result.index]
Expand Down

0 comments on commit e6feda2

Please sign in to comment.