Як редагувати elisp, не гублячись у дужках


13

Я змінюю код elisp з linum.el:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

Мені вдалося виправити помилку, коли відступ було відмінено за допомогою зміни (count-lines (point-min) (point-max))до (+ (count-lines (point-min) (point-max)) 1). Це було легко.

Але тепер я хочу змінити його так, щоб мінімальна ширина була 2, додавши if-умовно, якщо, (concat " %" (number-to-string w) "2d ")якщо число рядків дорівнює <10.

Це повинно бути легко! Додайте один умовний і скопіюйте / вставте накладки. Шматок пирога, правда? Я маю на увазі, я знаю, що я повинен робити, але я рідко торкаюся елісп, і мені завжди спантеличено, коли мені доводиться щось змінювати за допомогою багатьох дужок.

"Правильний" стиль, наскільки я розумію, полягає в структурі коду на основі відступу та загортанні кінцевих дужок в кінці рядка, а не самостійно. Походячи з інших мов стилю "С", я борюся з читанням та написанням коду таким чином. Тож моє запитання: що я роблю неправильно? Як я повинен редагувати elisp і орієнтуватися навколо коду таким чином, що мені не доведеться сидіти там і рахувати всі дужки?

Коли я працюю з чимось глибоким глибиною, я повинен зачинити двері, натягнути жалюзі та почати розміщувати круглі дужки у стилі K&R, щоб я міг не тільки читати, але й змінювати чортову річ, не вигадуючи.

Очевидно, я роблю це все неправильно. Як я можу так торкнутися Елісп без страху?

Зауважте, що моє питання полягає в тому, як орієнтуватися та редагувати elisp не як питання про стиль. Я вже використовую таке керівництво як стиль: https://github.com/bbatsov/emacs-lisp-style-guide

Оновлення:

Як правильно відформатувати elisp перед тим, як посоромити себе на emacs.stackexchange:

Познач свій елісп і виконай M-x indent-region.

Рішення проблеми:

Для тих, хто хоче знати, як виконати право-виправдання для підшивки з мінімальною шириною в два, ось таке рішення:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1)
                           (concat " %" (number-to-string w) "d ")
                         " %2d ")))
    ad-do-it))

1
Кожна людина бореться з читаннями, що надходять від C. Це просто потребує часу, але з часом це стає надзвичайно природним. Також ви, ймовірно, можете вирішити свою проблему, змінивши linum-формат на такий рядок формату, як "% 2d".
Джордон Біондо

Я не можу просто використовувати, %2dтому що, як тільки ширина перекидається на 3 і більше символів, тоді вона переходить від привести право-виправдане до ліво-виправданого.
Жро

Є кілька бібліотек, які виділяють цілі дужки, які є безцінними (на мою думку), наприклад highlight-parentheses,; rainbow-delimiters; і т. д. Ось моя власна спрощена версія, highlight-parenthesesяка дозволяє прокручувати, не видаляючи дужки, які були остаточно забарвлені: stackoverflow.com/a/23998965/2112489 Надалі це одне запитання на кожного клієнта / нитку.
законник

1
Це звучить дуже схоже на те, що ваша проблема справді читає , а не редагує код Elisp. Вам здається, що у вас проблеми з розумінням структури Lisp, і це погіршує ваше редагування.
PythonNut

1
На ваш захист, це досить погано відрізаний фрагмент коду. Я читав це неправильно перші 3 рази через це. Також паритет .
Малабарба

Відповіді:


12

Існує ряд додаткових пакетів, які можуть допомогти, такі як паредіювання, розумний одяг та ліпі. Ці пакети полегшують навігацію та маніпулювання кодом lisp, щоб ви могли обдумувати s-вирази та дозволяти редактору турбуватися про балансування паролів.

У Emacs також є безліч вбудованих команд для роботи зі сексопами та списками, які варто вивчити, і вони можуть допомогти вам більше звикнути до структури Lisp-коду. Зазвичай вони пов'язані з C-M-префіксом, наприклад:

  • C-M-f/ C-M-bрухатися вперед / назад за сексом
  • C-M-n/ C-M-pдля переміщення вперед / назад за списком
  • C-M-u/ C-M-dдля переміщення вгору / вниз на один рівень
  • C-M-t поміняти два підлоги навколо точки
  • C-M-k вбити сексп
  • C-M-SPC позначати сексом
  • C-M-q для повторного відступу сексу

Наведений вами приклад код може бути зрозумілішим, якщо ви виправите відступ: поставте крапку на початку (defadvice...та натисніть, C-M-qщоб повторно відступити весь вираз. Для заміни (concat...я б почав, поставивши крапку на початку цього сексопу і натиснувши, C-M-oщоб розділити лінію, зберігаючи відступ. Потім додайте своє (if...та використовуйте команди вище, щоб перейти до кінця списку, щоб додати ще один закриваючий батьків, назад до початку, щоб повторно відступити тощо.

Ви також можете увімкнути show-paren-mode, що виділить відповідний батьківський параметр, коли точка знаходиться в початку або в кінці списку. Ви можете скоріше виділити цілий вираз, а не відповідний батьків, тому спробуйте налаштувати show-paren-style.

Як згадував @Tyler у коментарях до іншої відповіді, вам слід ознайомитися з посібником, щоб дізнатися більше про ці та пов’язані з ними команди.


Зауважте, що C-M-qможна зателефонувати за допомогою аргументу префікса ( C-u C-M-q), щоб сильно роздрукувати вираз у точці. Це розділить все на окремі рядки та відступи, щоб гніздування було дуже очевидним. Як правило, ви не хочете, щоб ваш lisp-код виглядав так, але може бути корисним зрозуміти трохи коду, не збираючи на ньому повну K&R. (І ви завжди C-x uможете скасувати).
глюкас

6

Хороший спосіб редагувати LISP - це маніпулювання AST, а не окремими символами чи рядками. Я робив це з моїм пакетом Lispy , що я почав 2 роки тому. Я думаю, що насправді може знадобитися небагато практики, щоб навчитися робити це добре, але навіть використання лише основ вже повинно вам допомогти.

Я можу пояснити, як би я змінив цю зміну:

Крок 1

Вихідне положення, як правило, має точку перед сексом верхнього рівня. Тут |справа.

|(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

Вам потрібно буде правильно відрізати код, тому натисніть iодин раз. Це гарно відреагує.

Крок 2

Ви хочете позначити "d "з регіоном, оскільки об'єкт, який може маніпулювати, - lispyце або список, який не потрібно маркувати, або послідовність символів або / та списки, які необхідно позначити регіоном.

Натисніть, atщоб зробити це. aКоманда дозволяє помітити будь-який одиночний символ в межах батьківського списку, і tє показником цього конкретного символу.

Крок 3

  1. Щоб обернути поточну область (), натисніть (.
  2. Натисніть, C-fщоб перенести одну табличку вперед і вставити if.
  3. Натисніть (ще раз, щоб вставити ().
  4. Вставити > w 10.
  5. C-f C-m щоб перемістити рядок до нового рядка.

Крок 4

Щоб клонувати рядок:

  1. Позначте символ-або-рядок у точці за допомогою M-m.
  2. Натисніть c.

Якщо ви хочете деактивувати регіон на вигадливий спосіб і поставити крапку на самому початку струни, ви можете натиснути idm:

  1. i вибере внутрішній вміст рядка.
  2. d поміняється пунктом та позначкою.
  3. m відключить регіон

Або ви могли зробити m C-b C-b C-bв цьому випадку, або C-g C-b C-b C-b. Я думаю idm, що краще, оскільки це однаково незалежно від довжини струни. Я думаю, C-x C-x C-f C-g що також буде працювати незалежно від довжини струни.

Нарешті, вставити, 2щоб отримати кінцевий код:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) (if (> w 10)
                                                             "2|d "
                                                           "d "))))
    ad-do-it))

Підсумок

Повна послідовність була: at( C-f, if, (, > w 10, C-f C-m M-m cidm, 2.

Зауважте, що якщо ви рахуєте вставлення коду, єдиними клавішами, не пов'язаними з маніпуляцією AST, були дві C-fта одна C-m.


3
Ліспі звучить цікаво! До ОП: ви можете розробити подібні робочі процеси за допомогою paredit: emacsrocks.com/e14.html та розширити регіон: github.com/magnars/expand-region.el , або просто пограти з вбудованими дужками, що відповідають командам: gnu. org / software / emacs / manual / html_node / emacs / Parentheses.html
Tyler

Я впевнений, що ви здогадувались, коли зв’язувались, щоб вирішити проблему для мене; дякую за спробу. Мені вдалося заплутатися через це, використовуючи ваш приклад як приклад. Дивіться моє оновлення щодо правильного рішення.
Жро

6

Ви звикнете до нього з часом, але звичайно, що ви можете зробити, щоб прискорити його:

Відступ

Існує метод цього безумства. У lisp ви можете зробити це:

(a-fun (another-fun (magic-macro 1)))

Припустимо, що 1це дійсно великий вираз, і ви хочете відступити його на власному рядку.

(a-fun (another-fun (magic-macro 
  1)))

Це вводить в оману. 1має відступ лише на одне гніздо, хоча це на три рівні гніздування вище! Краще кажучи, її батько.

(a-fun (another-fun (magic-macro 
                      1)))

Якщо ми використовуємо цю схему у власному коді, ми отримуємо наступне:

(custom-set-variables '(linum-format 'dynamic))
(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                      (+ (count-lines (point-min) (point-max)) 1))))       
          (linum-format 
            (concat " %" (number-to-string w) "d ")))
     ad-do-it))

Зверніть увагу на сильну кореляцію між відступом та рівнем гніздування. Лінії з більшим рівнем гніздування завжди відступили більше, ніж лінії з меншою.

Це одне дуже допоможе. Ви можете легко побачити "форму" коду, і більшість інших мов навчили вас пов'язувати відступи з блоками та блоками з якоюсь формою вкладення (або явною, або мається на увазі).

Як вправу я видалив із коду несуттєві дужки. Ви повинні мати змогу як прочитати код, так і подати пропущені дужки, враховуючи базові знання Elisp (а саме, що є функціями та значеннями та структурою let*).

custom-set-variables '(linum-format 'dynamic)
defadvice linum-update-window (around linum-dynamic activate)
  let* w length number-to-string
                  + 
                    count-lines (point-min) (point-max) 
                    1       
         linum-format 
           concat " %" (number-to-string w) "d "
     ad-do-it

1
Зауважте, що в Emacs, на вкладці натискання майже завжди буде встановлено відступ правильно, як описано тут - ніколи цього не слід робити, додаючи пробіли вручну!
Тайлер

Прив’яжіть "Cm" до newline-and-indentрежиму emacs-lisp і режиму Lisp-взаємодії вирішує ваш перший приклад, PythonNut. І TAB буде виконувати цю роботу решту часу.
Давор Кубраніч

5

Я включаю це як іншу відповідь.

Іноді відступ відмовить вас. Тоді ваш звернення полягає в тому, щоб використовувати щось на кшталт rainbow-delimiters:

введіть тут опис зображення

Це робить рівень вкладення будь-якого з батьків явним і легко скануваним.

Однак є одна досить велика проблема: парени смішно відволікають. Тут є баланс акцентів. rainbow-delimitersзазвичай буде робити великий акцент на паронах за рахунок решти вашого коду! Але якщо тонізувати веселку обличчям вниз, їх стає прогресивно важче сканувати.

Якщо ви зможете знайти один набір облич, який це добре врівноважує, то будь-яким чином використовуйте його.

Однак, одне рішення (те, що змушує мене ласувати глею) - це мати два набори обличчя та перемикатися між ними на льоту. Коли ви робите інші речі, обличчя розріджуються і відпадають на другий план:

введіть тут опис зображення

Але коли ви ставите крапку на батьківщину, вона пробиває парони, щоб ви могли бачити рівні гніздування:

введіть тут опис зображення

А ось код для цього:

(defvar rainbow-delimiters-switch nil
  "t if rainbow-delimiters are currently punched")
(defvar rainbow-delimiters-face-cookies nil
  "a list of face-remap-add-relative cookies to reset")

(make-variable-buffer-local 'rainbow-delimiters-switch)
(make-variable-buffer-local 'rainbow-delimiters-face-cookies)

(add-hook 'prog-mode-hook #'rainbow-delimiters-mode)
(add-hook 'text-mode-hook #'rainbow-delimiters-mode)

(with-eval-after-load 'rainbow-delimiters
  (set-face-foreground 'rainbow-delimiters-depth-1-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-2-face "#9b7b6b")
  (set-face-foreground 'rainbow-delimiters-depth-3-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-4-face "#889899")
  (set-face-foreground 'rainbow-delimiters-depth-5-face "#839564")
  (set-face-foreground 'rainbow-delimiters-depth-6-face "#6391aa")
  (set-face-foreground 'rainbow-delimiters-depth-7-face "#9d748f")
  (set-face-foreground 'rainbow-delimiters-depth-8-face "#7b88a5")
  (set-face-foreground 'rainbow-delimiters-depth-9-face "#659896")

  (defun rainbow-delimiters-focus-on ()
    "Punch the rainbow-delimiters"
    (setq rainbow-delimiters-face-cookies
      (list
        (face-remap-add-relative 'rainbow-delimiters-depth-1-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-1-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-2-face
          '((:foreground "#9B471D") rainbow-delimiters-depth-2-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-3-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-3-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-4-face
          '((:foreground "#3B9399") rainbow-delimiters-depth-4-face))
            (face-remap-add-relative 'rainbow-delimiters-depth-5-face
          '((:foreground "#679519") rainbow-delimiters-depth-5-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-6-face
          '((:foreground "#0E73AA") rainbow-delimiters-depth-6-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-7-face
          '((:foreground "#9D2574") rainbow-delimiters-depth-7-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-8-face
          '((:foreground "#284FA5") rainbow-delimiters-depth-8-face))
        (face-remap-add-relative 'rainbow-delimiters-depth-9-face
          '((:foreground "#199893") rainbow-delimiters-depth-9-face)))
      rainbow-delimiters-switch t))

  (defun rainbow-delimiters-focus-off ()
    "Reset the rainbow-delimiters faces"
    (mapc #'face-remap-remove-relative rainbow-delimiters-face-cookies)
    (setq rainbow-delimiters-switch nil))

  (defun rainbow-delimiters-focus-on-maybe ()
    "Punch the rainbow-delimiters if the point is on a paren"
    (when (looking-at "[][(){}]")
      (unless (or rainbow-delimiters-switch (minibufferp))
        (rainbow-delimiters-focus-on))))

  (defun rainbow-delimiters-focus-off-maybe ()
    "Reset the rainbow-delimiters if the point is not on a paren"
    (unless (looking-at "[][(){}]")
      (when rainbow-delimiters-switch
        (rainbow-delimiters-focus-off))))

  (run-with-idle-timer 0.6 t 'rainbow-delimiters-focus-on-maybe)
  (run-with-idle-timer 0.1 t 'rainbow-delimiters-focus-off-maybe))

Це круто! З якої причини ви вибрали райдужні роздільники над круглими дужками для початку?
Тайлер

@Tyler немає особливих причин, хоча я все-таки дотримуюся розмежувачів веселки зараз (але це може бути просто тому, що я звик до цього). Я просто не чув про круглі дужки до недавнього часу.
PythonNut

4

Якщо ви відступаєте свій код, то вам справді потрібно правильно його відступити. Розробники Lisp очікують, як має виглядати правильний відступ. Це не означає, що код виглядає все однаково. Є ще різні способи форматування коду.

  • як довго повинна бути лінія?
  • як розподілити функцію / макро дзвінок по лініях?
  • скільки пробілів повинно бути?
(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
            ad-do-it))

Я б очікував, що відступ певним чином покаже стримування. Але не у вашому коді.

За замовчуванням ваш код може бути з відступом так. Інші відповіді описували, як це можна зробити за допомогою Emacs:

(defadvice linum-update-window (around linum-dynamic activate)
  (let* ((w (length (number-to-string
                     (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")))
    ad-do-it))

Я б очікував, що letзв'язані змінні починаються в одному вертикальному стовпчику. Я хотів би також очікувати , що тіло з letменш відступу. Ми дізнаємось, як letмає бути відступ Після того, як ви трохи навчитесь, це легко розпізнати візуальний візерунок.

Основні візуальні блоки в коді є defadvice, let*і купа викликів функцій. defВ ім'я говорить мені , що це визначення слід ім'я, список аргументів і тіла.

Таким чином я люблю бачити

defadvice name arglist
  body

Це let*буде:, let*список прив’язок і корпус.

Таким чином мені подобається бачити:

let*  list of bindings
  body

Більш детально:

let*   binding1
       binding2
       binding3
  body

Я хочу побачити функціональний дзвінок:

FUNCTIONNAME ARG1 ARG2 ARG3

або

FUNCTIONNAME ARG1
             ARG2
             ARG3

або

FUNCTIONNAME
  ARG1
  ARG2
  ARG3

Який з них звикне, залежить від тривалості аргументів. Ми не хочемо робити рядки занадто довгими, і кожен аргумент у власному рядку дозволяє нам мати більше структури.

Тому вам потрібно написати свій код, використовуючи ці шаблони, і вам потрібно переконатися, що читач може легко визначити ці шаблони у вашому коді.

Дужки повинні безпосередньо укладати свої конструкції.

(sin 10)

(sin
  10)

Уникайте прикладів пробілів:

(  sin 10  )

або

(
   sin
)

або

(sin 10
)

У Ліспі дужки не мають синтаксичної функції мови, вони є частиною синтаксису структури даних списку (s-вирази). Таким чином ми пишемо списки і відформатуємо списки. Для форматування вмісту списку ми використовуємо знання про мову програмування Lisp. letВираз повинен бути відформатований інакше , ніж виклик функції. Все ще обидва є списками, і дужки повинні безпосередньо додавати списки. Є кілька прикладів, коли є сенс мати одну дужку в рядку, але використовувати це рідко.

Використовуйте круглі дужки, щоб допомогти вам знайти межі виразів. Нехай вони допоможуть вам перейти зі списку до списку, редагувати списки, скорочувати та копіювати списки, замінювати списки, вносити списки відступу / форматування, вибирати списки, ...


2

Для основної допомоги з відступом використовуйте TAB та "CMq" ( indent-pp-sexp); прив'язуючи "Cm", щоб newline-and-indentзаощадити вам доведеться натискати TAB після нового рядка.

Ви можете орієнтуватися по sexp за допомогою "CM-ліво / право / вгору / вниз / home / end", що дуже корисно.

Якщо ви переживаєте за збереження балансу в дужках, використовуйте щось на кшталт smartparens (це трохи прощає цей паритет , який спочатку може відчувати себе, як носити косуху). Він також постачається з великою кількістю прив’язок для навігації та редагування на рівні sexp.

Нарешті, для виділення паролів почніть, увімкнувши цю опцію (меню "Параметри"> "Виділити відповідні круглі дужки" show-paren-mode). Ви також можете використовувати один із пакунків, зазначених у списку законів у своєму коментарі, як-от "виділити круглі дужки" чи "веселки-обмежувачі". ".


1

Як додаток до того, що інші дали відповіді, ось мої 2 копійки:

  • Автоматичне відступ, що надається, emacs-lisp-modeє важливим.
  • blink-matching-parennilза замовчуванням увімкнено (не ). Залиште так.
  • Я використовую show-paren-mode, щоб виділити перед курсором лівий батьківський параметр, відповідний правому.
  • Я тимчасово видаляю та повторно вводя праворуч перед курсором, якщо хочу краще бачити, де знаходиться відповідна ліва батьківщина.

Я не використовую електричне парування або веселку нічого або будь-яку іншу подібну «допомогу». Для мене це турбота, а не допомога.

Зокрема, electric-pair-modeце для мене незручне занепокоєння. Але деякі це люблять. І я гадаю, що це може бути корисним у інших контекстах спарювання, окрім дужок Lisp (я так думаю, але і я не переконаний у таких випадках використання).

Ви знайдете те, що найкраще працює для вас. Основні речі, які я хочу сказати: (1) автоматичний відступ - це ваш друг, як і певний спосіб виявити ліву батьківську, яка відповідає правій батьківській перед точкою.

Зокрема, бачення того, що робить автоматичне відступ для вас, вчить вас негайно, що ви більше ніколи не хочете ставити потрібні паролі на лінію самі. Та стиль кодування, який є повсюдним в інших мовах, просто галасливий.

Крім автоматичного відступу ви отримуєте з RETі TAB, освоїтися з допомогою C-M-q. (Використання C-h kв , emacs-lisp-modeщоб дізнатися, що роблять ці ключі.)


1

Я помітив, що деякі люди вже згадували про роздільники веселки, це також мій улюблений режим під час редагування коду lisp.

Насправді я люблю круглі дужки, найкраще в Lisp - це ті круглі дужки, із ними цікаво спілкуватися, якщо ви знаєте, як:

  • встановіть злу-режим і його плагін evil-surround, так що ви можете легко редагувати круглі дужки, наприклад, натисніть, ci(щоб видалити все, що знаходиться всередині (), va(виберіть речі, загорнуті "()" і "()". і ви можете натиснути, %щоб перейти між відповідні дужки

  • використовувати флюхек або макет, неперевершені дужки будуть підкреслені в режимі реального часу


0

Складність тут полягає в тому, що ви хочете додати якийсь код до і після певного sexp. У цій ситуації секспп (concat " %" (number-to-string w) "d "). Ви хочете вставити перед ним оператор if, (if (> w 1) а інший - " %2d ") після нього.

Основна частина труднощів полягає в тому, щоб виділити це sexp, особисто я використовую нижченаведену команду для ізоляції asexp

(defun isolate-sexp () (interactive)
       (save-excursion (forward-sexp) (if (not (eolp-almost))
               (split-line) nil)))
(defun eolp-almost () (interactive)
(save-excursion (while (equal (char-after) ? ) (forward-char 1))
        (if (eolp) t nil )      ))

Просто поставте курсор на початок (concat " %" (number-to-string w) "d "), тобто спочатку (, а потім застосуйте вище isolate-sexp. Ви отримуєте

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (concat " %" (number-to-string w) "d ")
                                  ))
            ad-do-it))

Потім додайте відповідний код до і після цього sexp, тобто

(defadvice linum-update-window (around linum-dynamic activate)
   (let* ((w (length (number-to-string
      (+ (count-lines (point-min) (point-max)) 1))))
         (linum-format (if (> w 1) (concat " %" (number-to-string w) "d ") " %2d ")
                                  ))
            ad-do-it))

І вуаля. Отриманий таким чином код, можливо, не прекрасний, але шанс загубитися менший.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.