refactor: Linus-style code quality improvements across Elisp and C modules
- Extract shared binary search from duplicate ekp--hyphenate-p/ekp--flagged-p - Remove duplicate ekp-root-dir definition (shadowed ekp-utils.el version) - Remove dead ekp-clear-caches from ekp-utils.el (cleared non-existent var) - Replace hardcoded 'zsh' with shell-file-name for portability - Deduplicate module reload: shared ekp--module-reload for Rust/C - Add NULL guard for ekp_global in ekp_paragraph.c (crash prevention) - Track actual thread count in pool to fix destroy joining wrong count - Normalize line endings (CRLF→LF) in ekp-utils.el Co-authored-by: Kinneyzhang <38454496+Kinneyzhang@users.noreply.github.com>
This commit is contained in:
parent
ddfa26fafc
commit
af855e3394
754
ekp-utils.el
754
ekp-utils.el
@ -1,379 +1,375 @@
|
|||||||
;;; ekp-utils.el --- Utility functions for EKP -*- lexical-binding: t; -*-
|
;;; ekp-utils.el --- Utility functions for EKP -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
;; Copyright (C) 2024
|
;; Copyright (C) 2024
|
||||||
;; Author: emacs-kp contributors
|
;; Author: emacs-kp contributors
|
||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
|
|
||||||
;; Utilities for the Emacs Knuth-Plass (EKP) typesetting package.
|
;; Utilities for the Emacs Knuth-Plass (EKP) typesetting package.
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(defconst ekp-utils--load-file (or load-file-name (buffer-file-name))
|
(defconst ekp-utils--load-file (or load-file-name (buffer-file-name))
|
||||||
"Path to this file, for locating module directories.")
|
"Path to this file, for locating module directories.")
|
||||||
|
|
||||||
(defun ekp-root-dir ()
|
(defun ekp-root-dir ()
|
||||||
"Return directory containing ekp files."
|
"Return directory containing ekp files."
|
||||||
(when ekp-utils--load-file
|
(when ekp-utils--load-file
|
||||||
(file-name-directory ekp-utils--load-file)))
|
(file-name-directory ekp-utils--load-file)))
|
||||||
|
|
||||||
;;;; Font Detection
|
;;;; Font Detection
|
||||||
|
|
||||||
(defsubst ekp-cjk-char-p (char)
|
(defsubst ekp-cjk-char-p (char)
|
||||||
"Return non-nil if CHAR is a CJK character."
|
"Return non-nil if CHAR is a CJK character."
|
||||||
(let ((entry (aref (category-table) char)))
|
(let ((entry (aref (category-table) char)))
|
||||||
;; Use ‘describe-categories’ for a full list of categories.
|
;; Use ‘describe-categories’ for a full list of categories.
|
||||||
;; Another way is to use ‘char-script-table’ (see
|
;; Another way is to use ‘char-script-table’ (see
|
||||||
;; ‘script-representative-chars’ for possible scripts), which is
|
;; ‘script-representative-chars’ for possible scripts), which is
|
||||||
;; not as convenient.
|
;; not as convenient.
|
||||||
(or (aref entry ?c) ; Chinese
|
(or (aref entry ?c) ; Chinese
|
||||||
(aref entry ?h) ; Korean
|
(aref entry ?h) ; Korean
|
||||||
(aref entry ?j) ; Japanese
|
(aref entry ?j) ; Japanese
|
||||||
)))
|
)))
|
||||||
|
|
||||||
(defun ekp-font-family (string &optional position)
|
(defun ekp-font-family (string &optional position)
|
||||||
(format "%s" (font-get (font-at (or position 0) nil string) :family)))
|
(format "%s" (font-get (font-at (or position 0) nil string) :family)))
|
||||||
|
|
||||||
(defun ekp-font-monospace-p (font-family)
|
(defun ekp-font-monospace-p (font-family)
|
||||||
(let* ((font (find-font (font-spec :family font-family)))
|
(let* ((font (find-font (font-spec :family font-family)))
|
||||||
(font-name (font-xlfd-name font))
|
(font-name (font-xlfd-name font))
|
||||||
(type (nth 10 (split-string font-name "-" t))))
|
(type (nth 10 (split-string font-name "-" t))))
|
||||||
;; 'c' used in terminal
|
;; 'c' used in terminal
|
||||||
(or (or (string= "m" type) (string= "c" type))
|
(or (or (string= "m" type) (string= "c" type))
|
||||||
(let ((info (font-info font-name)))
|
(let ((info (font-info font-name)))
|
||||||
(and info (> (length info) 4)
|
(and info (> (length info) 4)
|
||||||
;; 等宽字体的核心标志: 最大宽度等于平均宽度
|
;; 等宽字体的核心标志: 最大宽度等于平均宽度
|
||||||
(= (aref info 7) (aref info 11)))))))
|
(= (aref info 7) (aref info 11)))))))
|
||||||
|
|
||||||
(defun ekp-get-latin-letter (string)
|
(defun ekp-get-latin-letter (string)
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(insert string)
|
(insert string)
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(while (and (< (point) (point-max))
|
(while (and (< (point) (point-max))
|
||||||
(let ((char (char-after)))
|
(let ((char (char-after)))
|
||||||
(not (or (and (>= char ?a) (<= char ?z))
|
(not (or (and (>= char ?a) (<= char ?z))
|
||||||
(and (>= char ?A) (<= char ?Z))))))
|
(and (>= char ?A) (<= char ?Z))))))
|
||||||
(forward-char 1))
|
(forward-char 1))
|
||||||
(unless (eobp)
|
(unless (eobp)
|
||||||
(buffer-substring (point) (1+ (point))))))
|
(buffer-substring (point) (1+ (point))))))
|
||||||
|
|
||||||
(defun ekp-get-cjk-letter (string)
|
(defun ekp-get-cjk-letter (string)
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(insert string)
|
(insert string)
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(while (and (< (point) (point-max))
|
(while (and (< (point) (point-max))
|
||||||
(let* ((char (char-after))
|
(let* ((char (char-after))
|
||||||
(width (char-width char)))
|
(width (char-width char)))
|
||||||
(or (or (= 1 width) (= 0 width))
|
(or (or (= 1 width) (= 0 width))
|
||||||
(not (ekp-cjk-char-p char)))))
|
(not (ekp-cjk-char-p char)))))
|
||||||
(forward-char 1))
|
(forward-char 1))
|
||||||
(unless (eobp)
|
(unless (eobp)
|
||||||
(buffer-substring (point) (1+ (point))))))
|
(buffer-substring (point) (1+ (point))))))
|
||||||
|
|
||||||
(defun ekp-monospace-p (string)
|
(defun ekp-monospace-p (string)
|
||||||
"判断字符串中的拉丁字母的字体是否等宽,返回字体名称"
|
"判断字符串中的拉丁字母的字体是否等宽,返回字体名称"
|
||||||
(if-let* ((letter (ekp-get-latin-letter string))
|
(if-let* ((letter (ekp-get-latin-letter string))
|
||||||
(font-family (ekp-font-family letter)))
|
(font-family (ekp-font-family letter)))
|
||||||
;; return monospace font family
|
;; return monospace font family
|
||||||
(when (ekp-font-monospace-p font-family)
|
(when (ekp-font-monospace-p font-family)
|
||||||
font-family)
|
font-family)
|
||||||
;; no latin letter in string, use default
|
;; no latin letter in string, use default
|
||||||
(face-attribute 'default :family)))
|
(face-attribute 'default :family)))
|
||||||
|
|
||||||
(defun ekp-word-spacing-pixel (string)
|
(defun ekp-word-spacing-pixel (string)
|
||||||
;; font is monospace, use the pixel of blank
|
;; font is monospace, use the pixel of blank
|
||||||
;; as word spacing pixel
|
;; as word spacing pixel
|
||||||
(if-let ((font-family (ekp-monospace-p string)))
|
(if-let ((font-family (ekp-monospace-p string)))
|
||||||
(string-pixel-width
|
(string-pixel-width
|
||||||
(propertize " " 'face `(:family ,font-family)))
|
(propertize " " 'face `(:family ,font-family)))
|
||||||
(let* ((letter (ekp-get-latin-letter string))
|
(let* ((letter (ekp-get-latin-letter string))
|
||||||
(font-family (ekp-font-family letter)))
|
(font-family (ekp-font-family letter)))
|
||||||
(string-pixel-width
|
(string-pixel-width
|
||||||
(propertize
|
(propertize
|
||||||
" " 'face `(:family ,font-family))))))
|
" " 'face `(:family ,font-family))))))
|
||||||
|
|
||||||
(defun ekp-latin-font (string)
|
(defun ekp-latin-font (string)
|
||||||
(if-let ((letter (ekp-get-latin-letter string)))
|
(if-let ((letter (ekp-get-latin-letter string)))
|
||||||
(ekp-font-family letter)
|
(ekp-font-family letter)
|
||||||
(face-attribute 'default :family)))
|
(face-attribute 'default :family)))
|
||||||
|
|
||||||
(defun ekp-cjk-font (string)
|
(defun ekp-cjk-font (string)
|
||||||
(if-let ((letter (ekp-get-cjk-letter string)))
|
(if-let ((letter (ekp-get-cjk-letter string)))
|
||||||
(ekp-font-family letter)
|
(ekp-font-family letter)
|
||||||
(ekp-font-family "牛")))
|
(ekp-font-family "牛")))
|
||||||
|
|
||||||
(defun ekp-pixel-spacing (pixel)
|
(defun ekp-pixel-spacing (pixel)
|
||||||
"Return a pixel spacing with a PIXEL pixel width."
|
"Return a pixel spacing with a PIXEL pixel width."
|
||||||
(if (= pixel 0)
|
(if (= pixel 0)
|
||||||
""
|
""
|
||||||
(propertize " " 'display `(space :width (,pixel)))))
|
(propertize " " 'display `(space :width (,pixel)))))
|
||||||
|
|
||||||
(defun ekp-cjk-fw-punct-p (str)
|
(defun ekp-cjk-fw-punct-p (str)
|
||||||
"Return if CHAR is CJK full-width punctuation."
|
"Return if CHAR is CJK full-width punctuation."
|
||||||
(let ((char (seq-first str)))
|
(let ((char (seq-first str)))
|
||||||
(or (equal (char-syntax char) ?.)
|
(or (equal (char-syntax char) ?.)
|
||||||
(and (>= char #x3000) (<= char #x303F))
|
(and (>= char #x3000) (<= char #x303F))
|
||||||
(and (>= char #xFF00) (<= char #xFF60)))))
|
(and (>= char #xFF00) (<= char #xFF60)))))
|
||||||
|
|
||||||
(defun ekp--flush-latin-word (word boxes)
|
(defun ekp--flush-latin-word (word boxes)
|
||||||
"Push latin WORD to BOXES if non-nil. Return updated boxes."
|
"Push latin WORD to BOXES if non-nil. Return updated boxes."
|
||||||
(if word (cons word boxes) boxes))
|
(if word (cons word boxes) boxes))
|
||||||
|
|
||||||
(defun ekp--flush-cjk-char (char boxes)
|
(defun ekp--flush-cjk-char (char boxes)
|
||||||
"Push CJK CHAR to BOXES if non-nil. Return updated boxes."
|
"Push CJK CHAR to BOXES if non-nil. Return updated boxes."
|
||||||
(if char (cons char boxes) boxes))
|
(if char (cons char boxes) boxes))
|
||||||
|
|
||||||
(defun ekp--flush-spaces (spaces boxes prev-state next-width)
|
(defun ekp--flush-spaces (spaces boxes prev-state next-width)
|
||||||
"Push SPACES to BOXES based on context.
|
"Push SPACES to BOXES based on context.
|
||||||
PREV-STATE: 1=latin, 2=CJK (previous content type).
|
PREV-STATE: 1=latin, 2=CJK (previous content type).
|
||||||
NEXT-WIDTH: width of next character (1=latin, 2=CJK).
|
NEXT-WIDTH: width of next character (1=latin, 2=CJK).
|
||||||
Rules:
|
Rules:
|
||||||
- Leading spaces (boxes is nil): preserve all spaces
|
- Leading spaces (boxes is nil): preserve all spaces
|
||||||
- CJK involved (prev or next is CJK): preserve all spaces
|
- CJK involved (prev or next is CJK): preserve all spaces
|
||||||
- Latin-Latin with single space: let glue handle it
|
- Latin-Latin with single space: let glue handle it
|
||||||
- Latin-Latin with multiple spaces: preserve all but last"
|
- Latin-Latin with multiple spaces: preserve all but last"
|
||||||
(when (and spaces (not (string-empty-p spaces)))
|
(when (and spaces (not (string-empty-p spaces)))
|
||||||
(let ((cjk-involved (or (= prev-state 2) (= next-width 2))))
|
(let ((cjk-involved (or (= prev-state 2) (= next-width 2))))
|
||||||
(cond
|
(cond
|
||||||
;; Leading spaces (no previous boxes): preserve all
|
;; Leading spaces (no previous boxes): preserve all
|
||||||
((null boxes)
|
((null boxes)
|
||||||
(setq boxes (cons spaces boxes)))
|
(setq boxes (cons spaces boxes)))
|
||||||
;; CJK involved: preserve all spaces
|
;; CJK involved: preserve all spaces
|
||||||
(cjk-involved
|
(cjk-involved
|
||||||
(setq boxes (cons spaces boxes)))
|
(setq boxes (cons spaces boxes)))
|
||||||
;; Latin-Latin with multiple spaces: preserve all but last
|
;; Latin-Latin with multiple spaces: preserve all but last
|
||||||
((> (length spaces) 1)
|
((> (length spaces) 1)
|
||||||
(setq boxes (cons (substring spaces 0 -1) boxes)))
|
(setq boxes (cons (substring spaces 0 -1) boxes)))
|
||||||
;; Latin-Latin with single space: let glue handle it
|
;; Latin-Latin with single space: let glue handle it
|
||||||
(t nil))))
|
(t nil))))
|
||||||
boxes)
|
boxes)
|
||||||
|
|
||||||
(defun ekp--flush-trailing-spaces (spaces boxes)
|
(defun ekp--flush-trailing-spaces (spaces boxes)
|
||||||
"Push all trailing SPACES to BOXES (for end of string)."
|
"Push all trailing SPACES to BOXES (for end of string)."
|
||||||
(if (and spaces (not (string-empty-p spaces)))
|
(if (and spaces (not (string-empty-p spaces)))
|
||||||
(cons spaces boxes)
|
(cons spaces boxes)
|
||||||
boxes))
|
boxes))
|
||||||
|
|
||||||
(defun ekp--handle-latin-char (str state latin-word cjk-char boxes)
|
(defun ekp--handle-latin-char (str state latin-word cjk-char boxes)
|
||||||
"Handle a latin (width=1) character.
|
"Handle a latin (width=1) character.
|
||||||
Return (new-state new-latin-word new-cjk-char new-boxes)."
|
Return (new-state new-latin-word new-cjk-char new-boxes)."
|
||||||
(if (= state 1)
|
(if (= state 1)
|
||||||
;; Already in latin mode: accumulate
|
;; Already in latin mode: accumulate
|
||||||
(list 1 (concat latin-word str) nil boxes)
|
(list 1 (concat latin-word str) nil boxes)
|
||||||
;; Was in CJK mode: flush CJK char, switch to latin
|
;; Was in CJK mode: flush CJK char, switch to latin
|
||||||
(list 1 str nil (ekp--flush-cjk-char cjk-char boxes))))
|
(list 1 str nil (ekp--flush-cjk-char cjk-char boxes))))
|
||||||
|
|
||||||
(defun ekp--handle-cjk-char (str state latin-word cjk-char boxes)
|
(defun ekp--handle-cjk-char (str state latin-word cjk-char boxes)
|
||||||
"Handle a CJK (width=2) character.
|
"Handle a CJK (width=2) character.
|
||||||
Return (new-state new-latin-word new-cjk-char new-boxes)."
|
Return (new-state new-latin-word new-cjk-char new-boxes)."
|
||||||
(if (= state 1)
|
(if (= state 1)
|
||||||
;; Was in latin mode: flush latin word, push CJK directly
|
;; Was in latin mode: flush latin word, push CJK directly
|
||||||
(list 2 nil nil (cons str (ekp--flush-latin-word latin-word boxes)))
|
(list 2 nil nil (cons str (ekp--flush-latin-word latin-word boxes)))
|
||||||
;; Already in CJK mode
|
;; Already in CJK mode
|
||||||
(if (ekp-cjk-fw-punct-p str)
|
(if (ekp-cjk-fw-punct-p str)
|
||||||
;; Punctuation attaches to previous CJK char
|
;; Punctuation attaches to previous CJK char
|
||||||
(list 2 nil nil (cons (concat cjk-char str) boxes))
|
(list 2 nil nil (cons (concat cjk-char str) boxes))
|
||||||
;; Regular CJK char: flush previous, hold current
|
;; Regular CJK char: flush previous, hold current
|
||||||
(list 2 nil str (ekp--flush-cjk-char cjk-char boxes)))))
|
(list 2 nil str (ekp--flush-cjk-char cjk-char boxes)))))
|
||||||
|
|
||||||
(defun ekp-split-to-boxes (string)
|
(defun ekp-split-to-boxes (string)
|
||||||
"Split STRING into typographic boxes.
|
"Split STRING into typographic boxes.
|
||||||
Latin words become single boxes; CJK chars are individual boxes.
|
Latin words become single boxes; CJK chars are individual boxes.
|
||||||
Whitespace runs are preserved as separate boxes; CJK punctuation attaches to preceding char."
|
Whitespace runs are preserved as separate boxes; CJK punctuation attaches to preceding char."
|
||||||
(if (string-blank-p string)
|
(if (string-blank-p string)
|
||||||
(vector string)
|
(vector string)
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(insert string)
|
(insert string)
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(let ((state (char-width (seq-first string))) ; 1=latin, 2=CJK
|
(let ((state (char-width (seq-first string))) ; 1=latin, 2=CJK
|
||||||
(prev-state 1) ; track previous content state for space handling
|
(prev-state 1) ; track previous content state for space handling
|
||||||
latin-word ; accumulator for latin characters
|
latin-word ; accumulator for latin characters
|
||||||
cjk-char ; holds previous CJK char (for punct attachment)
|
cjk-char ; holds previous CJK char (for punct attachment)
|
||||||
spaces ; accumulator for whitespace runs
|
spaces ; accumulator for whitespace runs
|
||||||
boxes) ; result list (built in reverse)
|
boxes) ; result list (built in reverse)
|
||||||
(while (not (eobp))
|
(while (not (eobp))
|
||||||
(let* ((str (buffer-substring (point) (1+ (point))))
|
(let* ((str (buffer-substring (point) (1+ (point))))
|
||||||
(width (string-width str)))
|
(width (string-width str)))
|
||||||
(cond
|
(cond
|
||||||
;; Whitespace or zero-width: flush content, accumulate spaces
|
;; Whitespace or zero-width: flush content, accumulate spaces
|
||||||
((or (string-blank-p str) (= 0 width))
|
((or (string-blank-p str) (= 0 width))
|
||||||
(setq boxes (ekp--flush-cjk-char cjk-char boxes))
|
(setq boxes (ekp--flush-cjk-char cjk-char boxes))
|
||||||
(when cjk-char (setq prev-state 2))
|
(when cjk-char (setq prev-state 2))
|
||||||
(setq cjk-char nil)
|
(setq cjk-char nil)
|
||||||
(setq boxes (ekp--flush-latin-word latin-word boxes))
|
(setq boxes (ekp--flush-latin-word latin-word boxes))
|
||||||
(when latin-word (setq prev-state 1))
|
(when latin-word (setq prev-state 1))
|
||||||
(setq latin-word nil)
|
(setq latin-word nil)
|
||||||
(setq spaces (concat spaces str)))
|
(setq spaces (concat spaces str)))
|
||||||
;; Non-whitespace: flush spaces first, then handle char
|
;; Non-whitespace: flush spaces first, then handle char
|
||||||
(t
|
(t
|
||||||
(setq boxes (ekp--flush-spaces spaces boxes prev-state width))
|
(setq boxes (ekp--flush-spaces spaces boxes prev-state width))
|
||||||
(setq spaces nil)
|
(setq spaces nil)
|
||||||
(cond
|
(cond
|
||||||
;; Latin character (width = 1)
|
;; Latin character (width = 1)
|
||||||
((= 1 width)
|
((= 1 width)
|
||||||
(pcase-let ((`(,s ,lw ,cc ,bx)
|
(pcase-let ((`(,s ,lw ,cc ,bx)
|
||||||
(ekp--handle-latin-char
|
(ekp--handle-latin-char
|
||||||
str state latin-word cjk-char boxes)))
|
str state latin-word cjk-char boxes)))
|
||||||
(setq state s latin-word lw cjk-char cc boxes bx)))
|
(setq state s latin-word lw cjk-char cc boxes bx)))
|
||||||
;; CJK character (width = 2)
|
;; CJK character (width = 2)
|
||||||
((= 2 width)
|
((= 2 width)
|
||||||
(pcase-let ((`(,s ,lw ,cc ,bx)
|
(pcase-let ((`(,s ,lw ,cc ,bx)
|
||||||
(ekp--handle-cjk-char
|
(ekp--handle-cjk-char
|
||||||
str state latin-word cjk-char boxes)))
|
str state latin-word cjk-char boxes)))
|
||||||
(setq state s latin-word lw cjk-char cc boxes bx)))))))
|
(setq state s latin-word lw cjk-char cc boxes bx)))))))
|
||||||
(forward-char 1))
|
(forward-char 1))
|
||||||
;; Flush remaining content
|
;; Flush remaining content
|
||||||
(setq boxes (ekp--flush-cjk-char cjk-char boxes))
|
(setq boxes (ekp--flush-cjk-char cjk-char boxes))
|
||||||
(setq boxes (ekp--flush-latin-word latin-word boxes))
|
(setq boxes (ekp--flush-latin-word latin-word boxes))
|
||||||
(setq boxes (ekp--flush-trailing-spaces spaces boxes))
|
(setq boxes (ekp--flush-trailing-spaces spaces boxes))
|
||||||
(vconcat (nreverse boxes))))))
|
(vconcat (nreverse boxes))))))
|
||||||
|
|
||||||
(defun ekp-clear-caches ()
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(interactive)
|
|
||||||
(setq ekp-caches
|
(defun ekp-start-process-with-callback
|
||||||
(make-hash-table
|
(process-name command-args callback
|
||||||
:test 'equal :size 100 :rehash-size 1.5 :weakness nil)))
|
&optional output-buffer)
|
||||||
|
"执行命令(带参数)并在完成后调用回调"
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
(let* ((buffer-name (generate-new-buffer-name
|
||||||
|
(or output-buffer "*EKP Process Output*")))
|
||||||
(defun ekp-start-process-with-callback
|
(process (apply #'start-process process-name
|
||||||
(process-name command-args callback
|
buffer-name command-args)))
|
||||||
&optional output-buffer)
|
(set-process-sentinel
|
||||||
"执行命令(带参数)并在完成后调用回调"
|
process
|
||||||
(let* ((buffer-name (generate-new-buffer-name
|
`(lambda (proc event)
|
||||||
(or output-buffer "*EKP Process Output*")))
|
(if (string-match-p "finished" event)
|
||||||
(process (apply #'start-process process-name
|
(when (memq (process-status proc) '(exit signal))
|
||||||
buffer-name command-args)))
|
(unwind-protect
|
||||||
(set-process-sentinel
|
(funcall ',callback proc (process-buffer proc))
|
||||||
process
|
(when (buffer-live-p (process-buffer proc))
|
||||||
`(lambda (proc event)
|
(kill-buffer (process-buffer proc)))))
|
||||||
(if (string-match-p "finished" event)
|
(message "%s, please check %s" (string-trim event)
|
||||||
(when (memq (process-status proc) '(exit signal))
|
,buffer-name))))
|
||||||
(unwind-protect
|
process))
|
||||||
(funcall ',callback proc (process-buffer proc))
|
|
||||||
(when (buffer-live-p (process-buffer proc))
|
(defun ekp--module-reload (module)
|
||||||
(kill-buffer (process-buffer proc)))))
|
"Load MODULE from a temp copy to allow rebuilding."
|
||||||
(message "%s, please check %s" (string-trim event)
|
(let ((tmpfile (make-temp-file
|
||||||
,buffer-name))))
|
(file-name-nondirectory module))))
|
||||||
process))
|
(copy-file module tmpfile t)
|
||||||
|
(module-load tmpfile)))
|
||||||
(defun ekp-rust-module-reload (module)
|
|
||||||
(let ((tmpfile (make-temp-file
|
;;; Rust Module Support (currently unused — ekp_rust/ directory does not exist)
|
||||||
(file-name-nondirectory module))))
|
|
||||||
(copy-file module tmpfile t)
|
(defun ekp-rust-module-reload (module)
|
||||||
(module-load tmpfile)))
|
(ekp--module-reload module))
|
||||||
|
|
||||||
(defun ekp-module-dir ()
|
(defun ekp-module-dir ()
|
||||||
(when-let ((root-dir (ekp-root-dir)))
|
(when-let ((root-dir (ekp-root-dir)))
|
||||||
(expand-file-name "ekp_rust" root-dir)))
|
(expand-file-name "ekp_rust" root-dir)))
|
||||||
|
|
||||||
(defun ekp-module-file ()
|
(defun ekp-module-file ()
|
||||||
(when-let* ((module-dir (ekp-module-dir))
|
(when-let* ((module-dir (ekp-module-dir))
|
||||||
(filename (cond ((eq system-type 'darwin) "libekp.dylib")
|
(filename (cond ((eq system-type 'darwin) "libekp.dylib")
|
||||||
((eq system-type 'windows-nt) "ekp.dll")
|
((eq system-type 'windows-nt) "ekp.dll")
|
||||||
(t "libekp.so"))))
|
(t "libekp.so"))))
|
||||||
(expand-file-name (concat "target/release/" filename) module-dir)))
|
(expand-file-name (concat "target/release/" filename) module-dir)))
|
||||||
|
|
||||||
(defun ekp-module-load ()
|
(defun ekp-module-load ()
|
||||||
"Load rust module of ekp."
|
"Load rust module of ekp."
|
||||||
(if (executable-find "cargo")
|
(if (executable-find "cargo")
|
||||||
(let ((file (ekp-module-file)))
|
(let ((file (ekp-module-file)))
|
||||||
(if file
|
(if file
|
||||||
(ekp-rust-module-reload file)
|
(ekp--module-reload file)
|
||||||
(ekp-module-build)))
|
(ekp-module-build)))
|
||||||
(error "Please install cargo and add it to executable path!")))
|
(error "Please install cargo and add it to executable path!")))
|
||||||
|
|
||||||
(defun ekp-module-build ()
|
(defun ekp-module-build ()
|
||||||
"Reload ekp rust module."
|
"Reload ekp rust module."
|
||||||
(interactive)
|
(interactive)
|
||||||
(if (executable-find "cargo")
|
(if (executable-find "cargo")
|
||||||
(ekp-start-process-with-callback
|
(ekp-start-process-with-callback
|
||||||
"ekp-build"
|
"ekp-build"
|
||||||
(cond
|
(cond
|
||||||
((eq system-type 'windows-nt)
|
((eq system-type 'windows-nt)
|
||||||
`("cmd.exe" "/c" ,(format "cd %s && cargo build -r"
|
`("cmd.exe" "/c" ,(format "cd %s && cargo build -r"
|
||||||
(ekp-module-dir))))
|
(ekp-module-dir))))
|
||||||
(t `("zsh" "-c" ,(format "cd %s && cargo build -r"
|
(t `(,shell-file-name "-c" ,(format "cd %s && cargo build -r"
|
||||||
(ekp-module-dir)))))
|
(ekp-module-dir)))))
|
||||||
(lambda (proc buffer)
|
(lambda (proc buffer)
|
||||||
(ekp-rust-module-reload (ekp-module-file))
|
(ekp--module-reload (ekp-module-file))
|
||||||
(message "ekp rust module reload success!")))
|
(message "ekp rust module reload success!")))
|
||||||
(error "Please install cargo and add it to executable path!")))
|
(error "Please install cargo and add it to executable path!")))
|
||||||
|
|
||||||
;;; C Module Support
|
;;; C Module Support
|
||||||
;; Parallel C implementation using pthreads
|
;; Parallel C implementation using pthreads
|
||||||
|
|
||||||
(defvar ekp-c-module-loaded nil
|
(defvar ekp-c-module-loaded nil
|
||||||
"Non-nil if C module is loaded.")
|
"Non-nil if C module is loaded.")
|
||||||
|
|
||||||
(defvar ekp-c-hyphenator-index nil
|
(defvar ekp-c-hyphenator-index nil
|
||||||
"Index of the loaded hyphenator in C module.")
|
"Index of the loaded hyphenator in C module.")
|
||||||
|
|
||||||
(defun ekp-c-module-dir ()
|
(defun ekp-c-module-dir ()
|
||||||
"Return the C module directory."
|
"Return the C module directory."
|
||||||
(when-let ((root-dir (ekp-root-dir)))
|
(when-let ((root-dir (ekp-root-dir)))
|
||||||
(expand-file-name "ekp_c" root-dir)))
|
(expand-file-name "ekp_c" root-dir)))
|
||||||
|
|
||||||
(defun ekp-c-module-file ()
|
(defun ekp-c-module-file ()
|
||||||
"Return path to compiled C module."
|
"Return path to compiled C module."
|
||||||
(when-let* ((module-dir (ekp-c-module-dir))
|
(when-let* ((module-dir (ekp-c-module-dir))
|
||||||
(filename (cond ((eq system-type 'darwin) "ekp.dylib")
|
(filename (cond ((eq system-type 'darwin) "ekp.dylib")
|
||||||
((eq system-type 'windows-nt) "ekp.dll")
|
((eq system-type 'windows-nt) "ekp.dll")
|
||||||
(t "ekp.so"))))
|
(t "ekp.so"))))
|
||||||
(expand-file-name filename module-dir)))
|
(expand-file-name filename module-dir)))
|
||||||
|
|
||||||
(defun ekp-c-module-reload (module)
|
(defalias 'ekp-c-module-reload #'ekp--module-reload
|
||||||
"Load MODULE from a temp copy to allow rebuilding."
|
"Load MODULE from a temp copy to allow rebuilding.")
|
||||||
(let ((tmpfile (make-temp-file
|
|
||||||
(file-name-nondirectory module))))
|
(defun ekp-c-module-load ()
|
||||||
(copy-file module tmpfile t)
|
"Load EKP C module if available."
|
||||||
(module-load tmpfile)))
|
(interactive)
|
||||||
|
(let ((file (ekp-c-module-file)))
|
||||||
(defun ekp-c-module-load ()
|
(if (and file (file-exists-p file))
|
||||||
"Load EKP C module if available."
|
(progn
|
||||||
(interactive)
|
(ekp-c-module-reload file)
|
||||||
(let ((file (ekp-c-module-file)))
|
(when (fboundp 'ekp-c-init)
|
||||||
(if (and file (file-exists-p file))
|
(ekp-c-init)
|
||||||
(progn
|
(setq ekp-c-module-loaded t)
|
||||||
(ekp-c-module-reload file)
|
(message "ekp-c module loaded (version %s, %d threads)"
|
||||||
(when (fboundp 'ekp-c-init)
|
(ekp-c-version) (ekp-c-thread-count))))
|
||||||
(ekp-c-init)
|
(message "C module not found. Run 'make' in ekp_c/ directory."))))
|
||||||
(setq ekp-c-module-loaded t)
|
|
||||||
(message "ekp-c module loaded (version %s, %d threads)"
|
(defun ekp-c-load-dictionary (lang)
|
||||||
(ekp-c-version) (ekp-c-thread-count))))
|
"Load hyphenation dictionary for LANG into C module."
|
||||||
(message "C module not found. Run 'make' in ekp_c/ directory."))))
|
(when ekp-c-module-loaded
|
||||||
|
(let* ((root-dir (ekp-root-dir))
|
||||||
(defun ekp-c-load-dictionary (lang)
|
(dict-file (expand-file-name
|
||||||
"Load hyphenation dictionary for LANG into C module."
|
(format "dictionaries/hyph_%s.dic" lang)
|
||||||
(when ekp-c-module-loaded
|
root-dir)))
|
||||||
(let* ((root-dir (ekp-root-dir))
|
(when (file-exists-p dict-file)
|
||||||
(dict-file (expand-file-name
|
(setq ekp-c-hyphenator-index
|
||||||
(format "dictionaries/hyph_%s.dic" lang)
|
(ekp-c-load-hyphenator dict-file))
|
||||||
root-dir)))
|
(when ekp-c-hyphenator-index
|
||||||
(when (file-exists-p dict-file)
|
(message "Loaded hyphenator for %s (index %d)"
|
||||||
(setq ekp-c-hyphenator-index
|
lang ekp-c-hyphenator-index))))))
|
||||||
(ekp-c-load-hyphenator dict-file))
|
|
||||||
(when ekp-c-hyphenator-index
|
(defun ekp-c-module-build ()
|
||||||
(message "Loaded hyphenator for %s (index %d)"
|
"Build the C module using make."
|
||||||
lang ekp-c-hyphenator-index))))))
|
(interactive)
|
||||||
|
(let ((module-dir (ekp-c-module-dir)))
|
||||||
(defun ekp-c-module-build ()
|
(if (and module-dir (file-exists-p
|
||||||
"Build the C module using make."
|
(expand-file-name "Makefile" module-dir)))
|
||||||
(interactive)
|
(ekp-start-process-with-callback
|
||||||
(let ((module-dir (ekp-c-module-dir)))
|
"ekp-c-build"
|
||||||
(if (and module-dir (file-exists-p
|
(cond
|
||||||
(expand-file-name "Makefile" module-dir)))
|
((eq system-type 'windows-nt)
|
||||||
(ekp-start-process-with-callback
|
`("cmd.exe" "/c" ,(format "cd %s && make" module-dir)))
|
||||||
"ekp-c-build"
|
(t `(,shell-file-name "-c" ,(format "cd %s && make" module-dir))))
|
||||||
(cond
|
(lambda (proc buffer)
|
||||||
((eq system-type 'windows-nt)
|
(ekp-c-module-load)
|
||||||
`("cmd.exe" "/c" ,(format "cd %s && make" module-dir)))
|
(message "ekp C module build success!")))
|
||||||
(t `("zsh" "-c" ,(format "cd %s && make" module-dir))))
|
(error "Makefile not found in ekp_c/ directory"))))
|
||||||
(lambda (proc buffer)
|
|
||||||
(ekp-c-module-load)
|
(provide 'ekp-utils)
|
||||||
(message "ekp C module build success!")))
|
|
||||||
(error "Makefile not found in ekp_c/ directory"))))
|
;;; ekp-utils.el ends here
|
||||||
|
|
||||||
(provide 'ekp-utils)
|
|
||||||
|
|
||||||
;;; ekp-utils.el ends here
|
|
||||||
|
|||||||
42
ekp.el
42
ekp.el
@ -124,11 +124,7 @@ Used for explicit line breaks in poetry, code blocks, etc.")
|
|||||||
"Internal flag for parameter initialization.")
|
"Internal flag for parameter initialization.")
|
||||||
|
|
||||||
;;;; Initialization
|
;;;; Initialization
|
||||||
|
;; ekp-root-dir is provided by ekp-utils.el
|
||||||
(defun ekp-root-dir ()
|
|
||||||
"Return directory containing ekp.el."
|
|
||||||
(when ekp--load-file
|
|
||||||
(file-name-directory ekp--load-file)))
|
|
||||||
|
|
||||||
(defun ekp--load-dicts ()
|
(defun ekp--load-dicts ()
|
||||||
"Load hyphenation dictionaries."
|
"Load hyphenation dictionaries."
|
||||||
@ -530,35 +526,25 @@ Returns (:badness NUM :fitness NUM :gaps LIST :adjustment NUM :flexibility NUM).
|
|||||||
:adjustment adjustment
|
:adjustment adjustment
|
||||||
:flexibility flexibility)))
|
:flexibility flexibility)))
|
||||||
|
|
||||||
(defun ekp--hyphenate-p (hyphen-positions n)
|
(defun ekp--sorted-vector-member-p (vec n)
|
||||||
"Return non-nil if position N ends with hyphenation.
|
"Return non-nil if N exists in sorted vector VEC.
|
||||||
HYPHEN-POSITIONS is a sorted vector of indices where hyphenation can occur.
|
|
||||||
Uses binary search for O(log n) lookup instead of O(n) linear search."
|
|
||||||
(and hyphen-positions
|
|
||||||
(> (length hyphen-positions) 0)
|
|
||||||
(let ((lo 0)
|
|
||||||
(hi (1- (length hyphen-positions))))
|
|
||||||
(while (< lo hi)
|
|
||||||
(let ((mid (/ (+ lo hi) 2)))
|
|
||||||
(if (< (aref hyphen-positions mid) n)
|
|
||||||
(setq lo (1+ mid))
|
|
||||||
(setq hi mid))))
|
|
||||||
(= (aref hyphen-positions lo) n))))
|
|
||||||
|
|
||||||
(defun ekp--flagged-p (flagged-positions n)
|
|
||||||
"Return non-nil if position N is a flagged (forced) break.
|
|
||||||
FLAGGED-POSITIONS is a sorted vector of indices where forced breaks occur.
|
|
||||||
Uses binary search for O(log n) lookup."
|
Uses binary search for O(log n) lookup."
|
||||||
(and flagged-positions
|
(and vec
|
||||||
(> (length flagged-positions) 0)
|
(> (length vec) 0)
|
||||||
(let ((lo 0)
|
(let ((lo 0)
|
||||||
(hi (1- (length flagged-positions))))
|
(hi (1- (length vec))))
|
||||||
(while (< lo hi)
|
(while (< lo hi)
|
||||||
(let ((mid (/ (+ lo hi) 2)))
|
(let ((mid (/ (+ lo hi) 2)))
|
||||||
(if (< (aref flagged-positions mid) n)
|
(if (< (aref vec mid) n)
|
||||||
(setq lo (1+ mid))
|
(setq lo (1+ mid))
|
||||||
(setq hi mid))))
|
(setq hi mid))))
|
||||||
(= (aref flagged-positions lo) n))))
|
(= (aref vec lo) n))))
|
||||||
|
|
||||||
|
(defalias 'ekp--hyphenate-p #'ekp--sorted-vector-member-p
|
||||||
|
"Return non-nil if position N ends with hyphenation.")
|
||||||
|
|
||||||
|
(defalias 'ekp--flagged-p #'ekp--sorted-vector-member-p
|
||||||
|
"Return non-nil if position N is a flagged (forced) break.")
|
||||||
|
|
||||||
;;;; Dynamic Programming Line Breaking
|
;;;; Dynamic Programming Line Breaking
|
||||||
|
|
||||||
|
|||||||
@ -155,6 +155,7 @@ typedef struct {
|
|||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
pthread_t threads[EKP_THREAD_POOL_SIZE];
|
pthread_t threads[EKP_THREAD_POOL_SIZE];
|
||||||
|
size_t thread_count;
|
||||||
pthread_mutex_t queue_lock;
|
pthread_mutex_t queue_lock;
|
||||||
pthread_cond_t queue_cond;
|
pthread_cond_t queue_cond;
|
||||||
pthread_cond_t done_cond;
|
pthread_cond_t done_cond;
|
||||||
|
|||||||
@ -311,6 +311,11 @@ ekp_paragraph_t *ekp_para_create(const char *text, size_t len,
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ekp_global) {
|
||||||
|
ekp_para_destroy(p);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
ekp_spacing_t *sp = &ekp_global->spacing;
|
ekp_spacing_t *sp = &ekp_global->spacing;
|
||||||
|
|
||||||
for (size_t i = 0; i < final_count; i++) {
|
for (size_t i = 0; i < final_count; i++) {
|
||||||
|
|||||||
@ -92,6 +92,7 @@ ekp_thread_pool_t *ekp_pool_create(size_t num_threads)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pool->thread_count = num_threads;
|
||||||
return pool;
|
return pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,9 +106,8 @@ void ekp_pool_destroy(ekp_thread_pool_t *pool)
|
|||||||
pthread_cond_broadcast(&pool->queue_cond);
|
pthread_cond_broadcast(&pool->queue_cond);
|
||||||
pthread_mutex_unlock(&pool->queue_lock);
|
pthread_mutex_unlock(&pool->queue_lock);
|
||||||
|
|
||||||
for (size_t i = 0; i < EKP_THREAD_POOL_SIZE; i++) {
|
for (size_t i = 0; i < pool->thread_count; i++) {
|
||||||
if (pool->threads[i])
|
pthread_join(pool->threads[i], NULL);
|
||||||
pthread_join(pool->threads[i], NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_destroy(&pool->queue_lock);
|
pthread_mutex_destroy(&pool->queue_lock);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user