Trying to create an extension for bullets on/off

Hi all,

Just for fun, I tried to create a popclip extension that would add bullets if none and remove them if present.

I got the extension to load into popclip, but nothing happens when I click the extension. I have Python 3 installed on the Mac.

I’m also not sure how to create an icon instead of the title being displayed.

Plist

bullet.py:

#!/usr/bin/env python3

import sys
import os
import re

Get input (PopClip env OR stdin fallback)

text = os.environ.get(“POPCLIP_TEXT”)
if not text:
text = sys.stdin.read()

BULLET = “•”
USE_NUMBERS = False

lines = text.splitlines()

def is_bulleted(line):
return re.match(r’^\s*(?:[•-*]\s+|\d+.\s+)', line)

def strip_bullet(line):
return re.sub(r’^(\s*)(?:[•-*]|\d+.)\s+‘, r’\1’, line)

def add_bullet(line, index):
if not line.strip():
return line

indent = re.match(r'^(\s*)', line).group(1)
content = line.strip()

if USE_NUMBERS:
    return f"{indent}{index + 1}. {content}"
else:
    return f"{indent}{BULLET} {content}"

non_empty = [l for l in lines if l.strip()]
all_bulleted = all(is_bulleted(l) for l in non_empty)

result =

if all_bulleted:
for line in lines:
result.append(strip_bullet(line))
else:
counter = 0
for line in lines:
if line.strip():
result.append(add_bullet(line, counter))
counter += 1
else:
result.append(line)

print(“\n”.join(result))

Thanks in advance for any suggestions,

Hi Mike,

Awesome to be experimenting with extensions!

You are also using quite an old style of extension with the plist and the separate Python script in a package.
Because you posted it as quoted text rather than code, it was difficult for me to read the Python directly.
I won’t engage with the Python since I’m crap at Python anyway.
There is an easier and more modern way of doing things, which is to use a javascript snippet, which I’ll demonstrate below:

// #popclip
// name: Toggle Bullets
// icon: symbol:list.bullet
// requirements: [text, paste]
// language: javascript
// after: paste-result

const marker = /^(\s*)(?:[•*-]|\d+\.)\s+/;
const lines = popclip.input.text.split(/\r?\n/);
const nonBlank = lines.filter((line) => line.trim());

const removeBullets =
  nonBlank.length > 0 && nonBlank.every((line) => marker.test(line));

return lines
  .map((line) => {
    if (!line.trim()) return line;

    if (removeBullets) {
      return line.replace(marker, "$1");
    }

    if (marker.test(line)) {
      return line;
    }

    return line.replace(/^(\s*)/, "$1• ");
  })
  .join("\n");

(The above block is an extension snippet — select it then click “Install Extension” in PopClip.)

I can’t claim credit for the actual code above because I had Codex 5.5 write it. It’s a nice bit of code though!

1 Like

Thanks Nick for your incredible help. :grinning_face:

When I copied your extension above to a text file (Named “BulletList.popclipext”), popclip didn’t recognize it when I double clicked it, or single clicked it with “Open With”.

I got: “There is no application set to open the document “BulletList.popclipext”.”

Confusingly, however, the extension file does get the popclip icon.

What am I doing wrong?

Thanks again,