Skip to content

Commit e5479ab

Browse files
authored
[emoji] iid:0.5 v1.0
1 parent bfdf1ac commit e5479ab

File tree

4 files changed

+5242
-0
lines changed

4 files changed

+5242
-0
lines changed

emoji/__init__.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""Find emojis by name.
4+
5+
Synopsis: <trigger> <emoji name>"""
6+
7+
import json
8+
import re
9+
import subprocess
10+
from concurrent.futures import ThreadPoolExecutor
11+
from itertools import islice
12+
from pathlib import Path
13+
14+
from albert import Action, Item, QueryHandler, cacheLocation, setClipboardText
15+
16+
md_iid = "0.5"
17+
md_version = "1.0"
18+
md_name = "Emoji Picker"
19+
md_description = "Find emojis by name"
20+
md_license = "GPL-3.0"
21+
md_url = "https://github.com/albertlauncher/python/tree/master/emoji"
22+
md_maintainers = "@tyilo"
23+
md_bin_dependencies = ["convert"]
24+
25+
26+
EXTENSION_DIR = Path(__file__).parent
27+
ALIASES_PATH = EXTENSION_DIR / "aliases.json"
28+
EMOJI_PATH = EXTENSION_DIR / "emoji-test.txt"
29+
ICON_DIR = Path(cacheLocation()) / "emojis"
30+
31+
32+
ICON_DIR.mkdir(exist_ok=True, parents=True)
33+
34+
35+
def icon_path(emoji):
36+
return ICON_DIR / f"{emoji}.png"
37+
38+
39+
def convert_to_png(emoji, output_path):
40+
subprocess.run(
41+
[
42+
"convert",
43+
"-pointsize",
44+
"64",
45+
"-background",
46+
"transparent",
47+
f"pango:{emoji}",
48+
output_path,
49+
]
50+
)
51+
52+
53+
def schedule_create_missing_icons(emojis):
54+
executor = ThreadPoolExecutor()
55+
for emoji in emojis:
56+
path = icon_path(emoji["emoji"])
57+
if not path.exists():
58+
executor.submit(convert_to_png, emoji["emoji"], path)
59+
60+
return executor
61+
62+
63+
class Plugin(QueryHandler):
64+
def id(self):
65+
return __name__
66+
67+
def name(self):
68+
return md_name
69+
70+
def description(self):
71+
return md_description
72+
73+
def defaultTrigger(self):
74+
return ": "
75+
76+
def synopsis(self):
77+
return "<emoji name>"
78+
79+
def initialize(self):
80+
line_re = re.compile(
81+
r"""
82+
^
83+
(?P<codepoints> .*\S)
84+
\s*;\s*
85+
(?P<status> \S+)
86+
\s*\#\s*
87+
(?P<emoji> \S+)
88+
\s*
89+
(?P<version> E\d+.\d+)
90+
\s*
91+
(?P<name> [^:]+)
92+
(?: : \s* (?P<modifiers> .+))?
93+
\n
94+
$
95+
""",
96+
re.VERBOSE,
97+
)
98+
99+
with ALIASES_PATH.open("r") as f:
100+
aliases = json.load(f)
101+
102+
self.emojis = []
103+
with EMOJI_PATH.open("r") as f:
104+
for line in f:
105+
if m := line_re.match(line):
106+
e = m.groupdict()
107+
if e["status"] == "fully-qualified":
108+
search_tokens = [e["name"]]
109+
if e["modifiers"]:
110+
search_tokens.append(e["modifiers"])
111+
e["aliases"] = [a.lower() for a in aliases.get(e["name"], [])]
112+
search_tokens += e["aliases"]
113+
e["search_tokens"] = search_tokens
114+
self.emojis.append(e)
115+
116+
self.icon_executor = schedule_create_missing_icons(self.emojis)
117+
118+
def finalize(self):
119+
self.icon_executor.shutdown(wait=True, cancel_futures=True)
120+
121+
def matched_emojis(self, query_tokens):
122+
for emoji in self.emojis:
123+
for w in query_tokens:
124+
if w not in " ".join(emoji["search_tokens"]):
125+
break
126+
127+
yield emoji
128+
129+
def handleQuery(self, query):
130+
query_tokens = query.string.strip().lower().split()
131+
if not query_tokens:
132+
return
133+
134+
for emoji in islice(self.matched_emojis(query_tokens), 100):
135+
query.add(
136+
Item(
137+
id=f"emoji_{emoji['emoji']}",
138+
text=f"{emoji['emoji']} {emoji['name']}",
139+
subtext=emoji["modifiers"] or "",
140+
icon=[str(icon_path(emoji["emoji"]))],
141+
actions=[
142+
Action(
143+
"copy",
144+
"Copy to clipboard",
145+
lambda r=emoji["emoji"]: setClipboardText(r),
146+
),
147+
],
148+
)
149+
)

emoji/aliases.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)