Source code for PyXA.apps.TextEdit

""".. versionadded:: 0.0.1

Control the macOS TextEdit application using JXA-like syntax.
"""

from time import sleep
from typing import Any, Union
from enum import Enum

import AppKit

from PyXA import XABase
from PyXA import XABaseScriptable
from ..XAProtocols import (
    XACanOpenPath,
    XACanPrintPath,
    XAClipboardCodable,
    XACloseable,
    XAPrintable,
)


[docs] class XATextEditApplication( XABaseScriptable.XASBApplication, XACanOpenPath, XACanPrintPath ): """A class for managing and interacting with TextEdit.app. .. versionadded:: 0.0.1 """
[docs] class ObjectType(Enum): """Types of objects that can be created with :func:`make`.""" DOCUMENT = "document" WINDOW = "window"
def __init__(self, properties): super().__init__(properties) self.xa_wcls = XATextEditWindow @property def frontmost(self) -> bool: """Whether TextEdit is the active application.""" return self.xa_scel.frontmost() @frontmost.setter def frontmost(self, frontmost: bool): self.set_property("frontmost", frontmost) @property def name(self) -> str: """The name of the application.""" return self.xa_scel.name() @property def version(self) -> str: """The version of the TextEdit application.""" return self.xa_scel.version()
[docs] def open(self, path: str) -> "XATextEditDocument": super().open(path) return self.front_window.document
[docs] def print( self, file: Union[str, AppKit.NSURL, "XATextEditDocument"], print_properties: dict = None, show_prompt: bool = True, ): """Prints a TextEdit document. :param file: The document or path to a document to print :type file: Union[str, AppKit.NSURL, XATextEditDocument] :param print_properties: Settings to print with or to preset in the print dialog, defaults to None :type print_properties: dict, optional :param show_prompt: Whether to show the print dialog, defaults to True :type show_prompt: bool, optional :Example 1: Printing a document with print properties >>> import PyXA >>> from datetime import datetime, timedelta >>> app = PyXA.Application("TextEdit") >>> doc = app.documents()[0] >>> print_time = datetime.now() + timedelta(minutes=1) >>> properties = { >>> "copies": 3, >>> "collating": False, >>> "startingPage": 1, >>> "endingPage": 10, >>> "pagesAcross": 3, >>> "pagesDown": 3, >>> "requestedPrintTime": print_time, >>> "errorHandling": app.PrintErrorHandling.DETAILED.value, >>> "faxNumber": "", >>> "targetPrinter": "" >>> } >>> app.print(doc, print_properties=properties) .. versionadded:: 0.0.3 """ if isinstance(file, str): file = AppKit.NSURL.alloc().initFileURLWithPath_(file) elif isinstance(file, XATextEditDocument): file = file.path.xa_elem self.xa_scel.print_printDialog_withProperties_( file, show_prompt, print_properties )
[docs] def documents(self, filter: dict = None) -> "XATextEditDocumentList": """Returns a list of documents matching the filter. :param filter: A dictionary specifying property-value pairs that all returned documents will have :type filter: dict :return: The list of documents :rtype: list[XATextEditDocument] :Example 1: Listing all documents >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> print(list(app.documents())) [<<class 'PyXA.apps.TextEdit.XATextEditDocument'>Current Document.txt>, <<class 'PyXA.apps.TextEdit.XATextEditDocument'>Another Document.txt>, ...] :Example 2: List documents after applying a filter >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> print(list(app.documents({"name": "Another Document.txt"}))) [<<class 'PyXA.apps.TextEdit.XATextEditDocument'>Another Document.txt>] :Example 3: List all paragraphs, words, and characters in all currently open documents >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> documents = app.documents() >>> print("Paragraphs:", documents.paragraphs()) >>> print("Words:", documents.words()) >>> print("Characters:", documents.characters()) Paragraphs: [This is note 1 , This is note 2 , This is note 3 ] Words: [This, is, note, 1, This, is, note, 2, This, is, note, 3] Characters: [T, h, i, s, , i, s, , n, o, t, e, , 1, , T, h, i, s, , i, s, , n, o, t, e, , 2, , T, h, i, s, , i, s, , n, o, t, e, , 3, ] .. versionchanged:: 0.0.4 Now returns an object of :class:`XATextEditDocumentList` instead of a default list. .. versionadded:: 0.0.1 """ return self._new_element( self.xa_scel.documents(), XATextEditDocumentList, filter )
[docs] def new_document( self, name: Union[str, None] = "Untitled.txt", text: Union[str, None] = "", location: Union[str, None] = None, ) -> "XATextEditDocument": """Creates a new document with the given name and initializes it with the supplied text. If no location is provided, the document file is created in the user's Documents folder. :param name: The name (including file extension) of the document, defaults to "Untitled.txt" :type name: Union[str, None], optional :param text: The initial text of the document, defaults to "" :type text: Union[str, None], optional :param location: The containing folder of the new document, defaults to None. :type location: Union[str, None] :return: A reference to the newly created document. :rtype: XATextEditDocument :Example 1: Create a new document with a name and initial body content >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> doc = app.new_document("New.txt", "Example text") >>> print(doc.properties) { modified = 0; name = "New.txt"; objectClass = "<NSAppleEventDescriptor: 'docu'>"; path = "/Users/exampleuser/Documents/New.txt"; text = "Example text"; } .. seealso:: :class:`XATextEditDocument` .. versionadded:: 0.0.1 """ if location is None: location = ( AppKit.NSFileManager.alloc() .homeDirectoryForCurrentUser() .relativePath() + "/Documents/" + name ) else: if not location.endswith("/"): location = location + "/" location = location + name new_doc = self.make("document", {"name": name, "text": text, "path": location}) doc = self.documents().push(new_doc) return doc
[docs] def make( self, specifier: Union[str, "XATextEditApplication.ObjectType"], properties: dict, data: Any = None, ): """Creates a new element of the given specifier class without adding it to any list. Use :func:`XABase.XAList.push` to push the element onto a list. :param specifier: The classname of the object to create :type specifier: Union[str, XATextEditApplication.ObjectType] :param properties: The properties to give the object :type properties: dict :param data: The data to initialize the object with, defaults to None :type data: Any, optional :return: A PyXA wrapped form of the object :rtype: XABase.XAObject :Example 1: Make a new document and push it onto the list of documents >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> properties = { >>> "name": "Example.txt", >>> "path": "/Users/exampleuser/Downloads/Example.txt", >>> "text": "Some example text" >>> } >>> new_doc = app.make("document", properties) >>> app.documents().push(new_doc) .. versionadded:: 0.0.3 """ if isinstance(specifier, XATextEditApplication.ObjectType): specifier = specifier.value if data is None: camelized_properties = {} if properties is None: properties = {} for key, value in properties.items(): if key == "url": key = "URL" camelized_properties[XABase.camelize(key)] = value obj = ( self.xa_scel.classForScriptingClass_(specifier) .alloc() .initWithProperties_(camelized_properties) ) else: obj = ( self.xa_scel.classForScriptingClass_(specifier) .alloc() .initWithData_(data) ) if specifier == "document": return self._new_element(obj, XATextEditDocument) elif specifier == "window": return self._new_element(obj, XATextEditWindow)
[docs] class XATextEditWindow(XABaseScriptable.XASBWindow, XABaseScriptable.XASBPrintable): """A class for managing and interacting with TextEdit windows. .. versionadded:: 0.0.1 """ def __init__(self, properties): super().__init__(properties) @property def document(self) -> "XATextEditDocument": """The active document.""" doc_obj = self.xa_elem.document() return self._new_element(doc_obj, XATextEditDocument) @document.setter def document(self, document: "XATextEditDocument"): self.set_property("document", document.xa_elem) @property def floating(self) -> bool: """Whether the window floats.""" return self.xa_elem.floating() @property def modal(self) -> bool: """Whether the window is a modal window.""" return self.xa_elem.modal() @property def titled(self) -> bool: """Whether the window has a title bar.""" return self.xa_elem.titled()
[docs] class XATextEditDocumentList(XABase.XATextDocumentList, XAClipboardCodable): """A wrapper around lists of documents that employs fast enumeration techniques. All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. .. versionadded:: 0.0.3 """ def __init__(self, properties: dict, filter: Union[dict, None] = None): super().__init__(properties, filter, XATextEditDocument)
[docs] def properties(self) -> list[dict]: raw_dicts = list(self.xa_elem.arrayByApplyingSelector_("properties") or []) return [ { "modified": raw_dict["modified"], "name": raw_dict["name"], "class": "document", "path": XABase.XAPath(raw_dict["path"]) if raw_dict["path"] is not None else None, "text": raw_dict["text"], } for raw_dict in raw_dicts ]
[docs] def path(self) -> list[XABase.XAPath]: ls = self.xa_elem.arrayByApplyingSelector_("path") or [] return [XABase.XAPath(x) for x in ls]
[docs] def name(self) -> list[str]: return list(self.xa_elem.arrayByApplyingSelector_("name") or [])
[docs] def modified(self) -> list[str]: return list(self.xa_elem.arrayByApplyingSelector_("modified") or [])
[docs] def by_properties(self, properties: dict) -> Union["XABase.XATextDocument", None]: for document in self.xa_elem: doc_props = document.properties() conditions = [ doc_props["modified"] == properties["modified"], doc_props["name"] == properties["name"], doc_props["path"] == ( properties["path"].path if isinstance(properties["path"], XABase.XAPath) else properties["path"] ), doc_props["text"] == properties["text"], ] if all(conditions): return self._new_element(document, self.xa_ocls)
[docs] def by_path( self, path: Union[str, XABase.XAPath] ) -> Union["XATextEditDocument", None]: if isinstance(path, XABase.XAPath): path = path.path return self.by_property("path", path)
[docs] def by_name(self, name: str) -> Union["XATextEditDocument", None]: return self.by_property("name", name)
[docs] def by_modified(self, modified: bool) -> Union["XATextEditDocument", None]: return self.by_property("modified", modified)
[docs] def prepend(self, text: str) -> "XATextEditDocumentList": """Inserts the provided text at the beginning of every document in the list. :param text: The text to insert. :type text: str :return: A reference to the document object. :rtype: XATextDocument :Example 1: Prepend a string at the beginning of every open document >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> documents = app.documents() >>> documents.prepend("-- PyXA Notes --\\n\\n") .. seealso:: :func:`append` .. versionadded:: 0.0.4 """ for doc in self.xa_elem: old_text = doc.text().get() doc.setValue_forKey_(text + old_text, "text") return self
[docs] def append(self, text: str) -> "XATextEditDocumentList": """Appends the provided text to the end of every document in the list. :param text: The text to append. :type text: str :return: A reference to the document object. :rtype: XATextDocument :Example 1: Append a string at the end of every open document >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> documents = app.documents() >>> documents.append("\\n\\n-- End Of Notes --") .. seealso:: :func:`prepend` .. versionadded:: 0.0.4 """ for doc in self.xa_elem: old_text = doc.text().get() doc.setValue_forKey_(old_text + text, "text") return self
[docs] def reverse(self) -> "XATextEditDocumentList": """Reverses the text of every document in the list. :return: A reference to the document object. :rtype: XATextDocument :Example 1: Reverse the text of every open document >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> documents = app.documents() >>> documents.reverse() .. versionadded:: 0.0.4 """ for doc in self.xa_elem: doc.setValue_forKey_(doc.text().get()[::-1], "text") return self
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: """Gets a clipboard-codable representation of each document in the list. When the clipboard content is set to a list of documents, each documents's file URL and name are added to the clipboard. :return: A list of each document's file URL and name :rtype: list[Union[str, AppKit.NSURL]] .. versionadded:: 0.0.8 """ items = [] texts = self.text() paths = self.path() for index, text in enumerate(texts): items.append(str(text)) items.append(paths[index].xa_elem) return items
[docs] def push( self, *documents: list["XATextEditDocument"] ) -> Union["XATextEditDocument", list["XATextEditDocument"], None]: """Appends the document to the list. .. versionadded:: 0.1.1 """ objects = [] num_added = 0 for document in documents: len_before = len(self.xa_elem) self.xa_elem.addObject_(document.xa_elem) len_after = len(self.xa_elem) if len_after == len_before: # Document wasn't added -- try force-getting the list before adding self.xa_elem.get().addObject_(document.xa_elem) if len_after > len_before: num_added += 1 objects.append( self._new_element( self.xa_elem.objectAtIndex_(0).get(), self.xa_ocls ) ) if num_added == 1: return objects[0] if num_added == 0: return None return objects
def __repr__(self): return "<" + str(type(self)) + str(self.name()) + ">"
[docs] class XATextEditDocument( XABase.XATextDocument, XAPrintable, XAClipboardCodable, XACloseable ): """A class for managing and interacting with TextEdit documents. .. versionchanged:: 0.0.2 Added :func:`close`, :func:`save`, and :func:`copy` .. versionadded:: 0.0.1 """ def __init__(self, properties): super().__init__(properties) @property def properties(self) -> dict: """All properties of the document.""" raw_dict = self.xa_elem.properties() return { "path": XABase.XAPath(raw_dict["path"]), "name": raw_dict["name"], "modified": raw_dict["modified"], "text": self._new_element(raw_dict["text"], XABase.XAText), } @properties.setter def properties(self, properties: dict[str, Any]): if "path" in properties: path = properties["path"] if isinstance(path, str): path = XABase.XAPath(path) self.set_property("path", path.xa_elem) if "name" in properties: self.set_property("name", properties["name"]) if "text" in properties: # Assume provided text is a string text = properties["text"] if isinstance(properties["text"], XABase.XAText): # Provided text is actually an XAObject text = text.xa_elem self.set_property("text", text) @property def path(self) -> XABase.XAPath: """The path at which the document is stored.""" return XABase.XAPath(self.xa_elem.path()) @path.setter def path(self, path: XABase.XAPath): self.set_property("path", path.xa_elem) @property def name(self) -> str: """The name of the document, including the file extension.""" return self.xa_elem.name() @name.setter def name(self, name: str): self.set_property("name", name) @property def modified(self) -> bool: """Whether the document has been modified since the last save.""" return self.xa_elem.modified()
[docs] def print( self, print_properties: Union[dict, None] = None, show_dialog: bool = True ) -> "XATextEditDocument": """Prints the document. :param print_properties: Properties to set for printing, defaults to None :type print_properties: Union[dict, None], optional :param show_dialog: Whether to show the print dialog, defaults to True :type show_dialog: bool, optional :return: The document object :rtype: XATextEditDocument .. versionadded:: 0.0.8 """ if print_properties is None: print_properties = {} self.xa_elem.print_printDialog_withProperties_( self.xa_elem, show_dialog, print_properties ) return self
[docs] def save(self, file_path: Union[str, XABase.XAPath, None] = None): """Saves the document. If a file path is provided, TextEdit will attempt to create a new file at the target location and of the specified file extension. If no file path is provided, and the document does not have a current path on the disk, a save dialog for the document will open. :param file_path: The path to save the document at, defaults to None :type file_path: str, optional :Example 1: Save all currently open documents >>> import PyXA >>> app = PyXA.Application("TextEdit") >>> for doc in app.documents(): >>> doc.save() .. versionadded:: 0.0.2 """ if file_path is not None: if isinstance(file_path, str): file_path = XABase.XAPath(file_path) self.xa_elem.saveAs_in_(None, file_path.xa_elem) else: url = self.path.xa_elem print(url) self.xa_elem.saveAs_in_(None, url)
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: """Gets a clipboard-codable representation of the document. When the clipboard content is set to a document, the documents's file URL and body text are added to the clipboard. :return: The document's file URL and body text :rtype: list[Union[str, AppKit.NSURL]] .. versionadded:: 0.0.8 """ return [str(self.text), self.path.xa_elem]
def __repr__(self): try: return "<" + str(type(self)) + self.name + ">" except AttributeError: return "<" + str(type(self)) + str(self.xa_elem) + ">"