Просмотр исходного кода

Fix intermittent Xcode scope errors for word limits and email writer.

Consolidate word-limit helpers into WordLimit.swift and colocate EmailWriterServicing with its models so the indexer reliably resolves types across ViewModels.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 месяц назад
Родитель
Сommit
870d3f9e36

+ 9 - 0
gramora/Models/EmailWriterModels.swift

@@ -1,5 +1,14 @@
 import Foundation
 
+protocol EmailWriterServicing: Sendable {
+    func generateEmail(
+        prompt: String,
+        subject: String,
+        emailType: EmailType,
+        tone: EmailTone
+    ) async throws -> String
+}
+
 enum EmailType: String, CaseIterable, Identifiable, Codable {
     case followUp
     case thankYou

+ 0 - 9
gramora/Services/EmailWriterService.swift

@@ -1,14 +1,5 @@
 import Foundation
 
-protocol EmailWriterServicing {
-    func generateEmail(
-        prompt: String,
-        subject: String,
-        emailType: EmailType,
-        tone: EmailTone
-    ) async throws -> String
-}
-
 struct EmailWriterService: EmailWriterServicing {
     private let openAI: OpenAIChatServicing
 

+ 23 - 21
gramora/Utilities/String+WordCount.swift → gramora/Utilities/WordLimit.swift

@@ -1,7 +1,28 @@
 import Foundation
 
+enum WordLimit: Sendable {
+    nonisolated static func message(maxWords: Int) -> String {
+        "Maximum \(maxWords) words allowed."
+    }
+
+    nonisolated static func apply(_ text: String, maxWords: Int) -> (text: String, exceeded: Bool) {
+        guard text.wordCount > maxWords else { return (text, false) }
+        return (text.truncated(toMaxWords: maxWords), true)
+    }
+
+    nonisolated static func applyImported(
+        _ text: String,
+        maxWords: Int,
+        setError: (String?) -> Void
+    ) -> String {
+        let (limited, exceeded) = apply(text, maxWords: maxWords)
+        setError(exceeded ? message(maxWords: maxWords) : nil)
+        return limited
+    }
+}
+
 extension String {
-    var wordCount: Int {
+    nonisolated var wordCount: Int {
         let trimmed = trimmingCharacters(in: .whitespacesAndNewlines)
         guard !trimmed.isEmpty else { return 0 }
         return trimmed
@@ -10,7 +31,7 @@ extension String {
             .count
     }
 
-    func truncated(toMaxWords maxWords: Int) -> String {
+    nonisolated func truncated(toMaxWords maxWords: Int) -> String {
         guard maxWords > 0 else { return "" }
         guard wordCount > maxWords else { return self }
 
@@ -35,23 +56,4 @@ extension String {
 
         return self
     }
-
-    static func wordLimitExceededMessage(maxWords: Int) -> String {
-        "Maximum \(maxWords) words allowed."
-    }
-
-    func applyingWordLimit(maxWords: Int) -> (text: String, exceeded: Bool) {
-        guard wordCount > maxWords else { return (self, false) }
-        return (truncated(toMaxWords: maxWords), true)
-    }
-
-    static func importedApplyingWordLimit(
-        _ text: String,
-        maxWords: Int,
-        setError: (String?) -> Void
-    ) -> String {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: maxWords)
-        setError(exceeded ? wordLimitExceededMessage(maxWords: maxWords) : nil)
-        return limited
-    }
 }

+ 5 - 5
gramora/ViewModels/EmailWriterViewModel.swift

@@ -39,20 +39,20 @@ final class EmailWriterViewModel: ObservableObject {
 
     func pastePrompt() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        prompt = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        prompt = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         generatedEmail = ""
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handlePromptChange() {
-        let (limited, exceeded) = prompt.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(prompt, maxWords: Self.maxWords)
         if exceeded {
             prompt = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if !generatedEmail.isEmpty {

+ 6 - 6
gramora/ViewModels/GrammarCheckerViewModel.swift

@@ -42,7 +42,7 @@ final class GrammarCheckerViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         resetResults()
     }
 
@@ -50,7 +50,7 @@ final class GrammarCheckerViewModel: ObservableObject {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
                 resetResults()
             } catch {
                 errorMessage = error.localizedDescription
@@ -59,15 +59,15 @@ final class GrammarCheckerViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleTextChange() {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
         if exceeded {
             text = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if hasResults {

+ 5 - 5
gramora/ViewModels/JournalFinderViewModel.swift

@@ -54,20 +54,20 @@ final class JournalFinderViewModel: ObservableObject {
 
     func pasteAbstract() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        abstractText = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxAbstractWords, setError: { errorMessage = $0 })
+        abstractText = WordLimit.applyImported(clipboard, maxWords: Self.maxAbstractWords, setError: { errorMessage = $0 })
         resetResults()
     }
 
     func notifyAbstractWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxAbstractWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxAbstractWords)
     }
 
     func handleAbstractTextChange() {
-        let (limited, exceeded) = abstractText.applyingWordLimit(maxWords: Self.maxAbstractWords)
+        let (limited, exceeded) = WordLimit.apply(abstractText, maxWords: Self.maxAbstractWords)
         if exceeded {
             abstractText = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxAbstractWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxAbstractWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxAbstractWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxAbstractWords) {
             errorMessage = nil
         }
         if hasResults {

+ 6 - 6
gramora/ViewModels/LanguageTranslatorViewModel.swift

@@ -45,7 +45,7 @@ final class LanguageTranslatorViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        sourceText = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        sourceText = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         translatedText = ""
     }
 
@@ -53,7 +53,7 @@ final class LanguageTranslatorViewModel: ObservableObject {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                sourceText = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                sourceText = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
                 translatedText = ""
             } catch {
                 errorMessage = error.localizedDescription
@@ -62,15 +62,15 @@ final class LanguageTranslatorViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleSourceTextChange() {
-        let (limited, exceeded) = sourceText.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(sourceText, maxWords: Self.maxWords)
         if exceeded {
             sourceText = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if !translatedText.isEmpty {

+ 6 - 6
gramora/ViewModels/ParaphrasingViewModel.swift

@@ -39,7 +39,7 @@ final class ParaphrasingViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         paraphrasedText = ""
     }
 
@@ -47,7 +47,7 @@ final class ParaphrasingViewModel: ObservableObject {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
                 paraphrasedText = ""
             } catch {
                 errorMessage = error.localizedDescription
@@ -56,15 +56,15 @@ final class ParaphrasingViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleTextChange() {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
         if exceeded {
             text = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if !paraphrasedText.isEmpty {

+ 6 - 6
gramora/ViewModels/PunctuationCheckerViewModel.swift

@@ -37,7 +37,7 @@ final class PunctuationCheckerViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         resetResults()
     }
 
@@ -45,7 +45,7 @@ final class PunctuationCheckerViewModel: ObservableObject {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
                 resetResults()
             } catch {
                 errorMessage = error.localizedDescription
@@ -54,15 +54,15 @@ final class PunctuationCheckerViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleTextChange() {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
         if exceeded {
             text = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if hasResults {

+ 6 - 6
gramora/ViewModels/SpellCheckerViewModel.swift

@@ -42,7 +42,7 @@ final class SpellCheckerViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         resetResults()
     }
 
@@ -50,7 +50,7 @@ final class SpellCheckerViewModel: ObservableObject {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
                 resetResults()
             } catch {
                 errorMessage = error.localizedDescription
@@ -59,15 +59,15 @@ final class SpellCheckerViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleTextChange() {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
         if exceeded {
             text = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if hasResults {

+ 6 - 6
gramora/ViewModels/SummarizerViewModel.swift

@@ -39,7 +39,7 @@ final class SummarizerViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
         summary = ""
     }
 
@@ -47,7 +47,7 @@ final class SummarizerViewModel: ObservableObject {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
                 summary = ""
             } catch {
                 errorMessage = error.localizedDescription
@@ -56,15 +56,15 @@ final class SummarizerViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleTextChange() {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
         if exceeded {
             text = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
         if !summary.isEmpty {

+ 6 - 6
gramora/ViewModels/WordsCountViewModel.swift

@@ -48,14 +48,14 @@ final class WordsCountViewModel: ObservableObject {
 
     func pasteText() {
         guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
-        text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+        text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
     }
 
     func uploadFile() {
         Task {
             do {
                 guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
-                text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
+                text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
             } catch {
                 errorMessage = error.localizedDescription
             }
@@ -63,15 +63,15 @@ final class WordsCountViewModel: ObservableObject {
     }
 
     func notifyWordLimitReached() {
-        errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
+        errorMessage = WordLimit.message(maxWords: Self.maxWords)
     }
 
     func handleTextChange() {
-        let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
+        let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
         if exceeded {
             text = limited
-            errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
-        } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
+            errorMessage = WordLimit.message(maxWords: Self.maxWords)
+        } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
             errorMessage = nil
         }
     }