เป็นไปได้หรือไม่ที่จะใช้ผู้รับมอบสิทธิ์หรือส่งผ่านฟังก์ชันเป็นอาร์กิวเมนต์ใน Vimscript


11

ฉันกำลังพยายามสร้างปลั๊กอินขนาดเล็กเพื่อเรียนรู้ vimscript เป้าหมายของฉันคือการสร้างฟังก์ชั่นบางอย่างที่ประมวลผลข้อความที่เลือกและแทนที่ด้วยผลลัพธ์ สคริปต์มีรายการดังต่อไปนี้:

  • สองฟังก์ชั่นการประมวลผลข้อความ:พวกเขาใช้สตริงเป็นพารามิเตอร์ส่งคืนสตริงซึ่งควรใช้เพื่อแทนที่ข้อความต้นฉบับ สำหรับตอนนี้ฉันมีเพียงสองคน แต่อาจมีอีกมากในอีกไม่กี่ครั้ง

  • ฟังก์ชั่นที่ได้รับข้อความที่เลือก:ซึ่งดึงการเลือกล่าสุดและส่งคืน

  • ฟังก์ชั่น wrapper:ซึ่งเรียกฟังก์ชั่นการประมวลผลได้รับผลและแทนที่การเลือกเก่าด้วยผลลัพธ์นี้

ตอนนี้ฟังก์ชั่น wrapper ของฉันดูเหมือนว่า:

function! Wrapper()
    " Get the string to insert
    let @x = Type1ProcessString(GetSelectedText())

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

และฉันต้องสร้างเสื้อคลุมตัวที่สองแทนที่บรรทัด 3 ด้วย

let @x = Type2ProcessString(GetSelectedText())

ฉันต้องการให้ฟังก์ชั่น wrapper ของฉันพารามิเตอร์ที่มีฟังก์ชั่นกระบวนการในการดำเนินการและใช้การโทรทั่วไปในบรรทัดที่ 3 ตอนนี้ฉันได้ลองใช้callวิธีที่แตกต่างเช่นเช่นนี้:

let @x = call('a:functionToExecute', GetSelectedText()) 

แต่ฉันไม่ประสบความสำเร็จจริง ๆ และ:h callไม่ได้มีประโยชน์จริง ๆ ในหัวข้อผู้แทน

เพื่อสรุปโดยรวมนี่คือคำถามของฉัน:

  • ฉันจะสร้างฟังก์ชั่น wrapper เพียงอันเดียวสำหรับการประมวลผลทั้งหมดได้อย่างไร
  • มีบางสิ่งที่ใช้เป็นตัวแทนใน vimscript หรือไม่?
  • หากผู้ร่วมประชุมไม่มีตัวตนฉันควรทำอย่างไรในสิ่งที่ "ดี"?

คำตอบ:


16

เพื่อตอบคำถามของคุณ: ต้นแบบของcall()ในคู่มือคือcall({func}, {arglist} [, {dict}]); {arglist}อาร์กิวเมนต์จะต้องมีตัวอักษรรายการวัตถุไม่ได้รายชื่อของการขัดแย้ง นั่นคือคุณต้องเขียนแบบนี้:

let @x = call(a:functionToExecute, [GetSelectedText()])

สิ่งนี้ถือว่าa:functionToExecuteเป็น Funcref (ดู:help Funcref) หรือชื่อของฟังก์ชัน (เช่นสตริงเช่น'Type1ProcessString')

ตอนนี้มันเป็นฟีเจอร์ที่ทรงพลังที่ให้ Vim มีคุณภาพเหมือน LISP แต่คุณอาจไม่ค่อยใช้มันตามที่กล่าวไว้ข้างต้น หากa:functionToExecuteเป็นสตริงชื่อของฟังก์ชันคุณสามารถทำสิ่งนี้ได้:

function! Wrapper(functionToExecute)
    " ...
    let s:processing = function(a:functionToExecute)
    let @x = s:processing(GetSelectedText())
    " ...
endfunction

และคุณจะเรียกเสื้อคลุมด้วยชื่อของฟังก์ชัน:

call Wrapper('Type1ProcessString')

หากในอีกทางa:functionToExecuteหนึ่งเป็น Funcref คุณสามารถโทรได้โดยตรง:

function! Wrapper(functionToExecute)
    " ...
    let @x = a:functionToExecute(GetSelectedText())
    " ...
endfunction

แต่คุณต้องเรียกเสื้อคลุมแบบนี้:

call Wrapper(function('Type1ProcessString'))

คุณสามารถตรวจสอบว่ามีฟังก์ชั่นอยู่ด้วยexists('*name')หรือไม่ ทำให้เป็นไปได้เคล็ดลับเล็กน้อยต่อไปนี้:

let s:width = function(exists('*strwidth') ? 'strwidth' : 'strlen')

เช่นฟังก์ชั่นที่ใช้บิวด์อินstrwidth()หาก Vim นั้นใหม่พอที่จะใช้งานได้และกลับไปเป็นstrlen()อย่างอื่น (ฉันไม่เถียงว่าทางเลือกดังกล่าวเหมาะสมแล้ว; ฉันแค่บอกว่ามันสามารถทำได้) :)

ด้วยฟังก์ชั่นพจนานุกรม (ดู:help Dictionary-function) คุณสามารถกำหนดบางสิ่งที่คล้ายคลาสได้:

let g:MyClass = {}

function! g:MyClass.New(...)
    let newObj = copy(self)

    if a:0 && type(a:1) == type({})
        let newObj._attributes = deepcopy(a:1)
    endif
    if exists('*MyClassProcess')
        let newObj._process = function('MyClassProcess')
    else
        let newObj._process = function('s:_process_default')
    endif

    return newObj
endfunction

function! g:MyClass.getFoo() dict
    return get(get(self, '_attributes', {}), 'foo')
endfunction

function! g:MyClass.setFoo(val) dict
    if !has_key(self, '_attributes')
        let self._attributes = {}
    endif
    let self._attributes['foo'] = a:val
endfunction

function! g:MyClass.process() dict
    call self._process()
endfunction

function! s:_process_default()
    echomsg 'nothing to see here, define MyClassProcess() to make me interesting'
endfunction

จากนั้นคุณจะยกตัวอย่างวัตถุเช่นนี้:

let little_object = g:MyClass.New({'foo': 'bar'})

และเรียกวิธีการ:

call little_object.setFoo('baz')
echomsg little_object.getFoo()
call little_object.process()

คุณยังสามารถมีคุณสมบัติและวิธีการเรียน:

let g:MyClass.__meaning_of_life = 42

function g:MyClass.GetMeaningOfLife()
    return get(g:MyClass, '__meaning_of_life')
endfunction

(แจ้งให้ทราบไม่จำเป็นต้องdictที่นี่)

แก้ไข: Subclassing มีลักษณะดังนี้:

let g:MySubclass = copy(g:MyClass)
call extend(g:MySubclass, subclass_attributes)

จุดที่ลึกซึ้งที่นี่คือการใช้แทนcopy() deepcopy()สาเหตุของการทำเช่นนี้คือสามารถเข้าถึงคุณลักษณะของคลาสพาเรนต์โดยอ้างอิง สิ่งนี้สามารถบรรลุผลได้ แต่มันมีความเปราะบางอย่างยิ่งและทำให้ถูกต้องอยู่ไกลจากเรื่องเล็กน้อย อีกปัญหาที่อาจเกิดขึ้นคือว่าชนิดของ conflates subclass นี้ด้วยis-a has-aด้วยเหตุนี้คุณสมบัติของชั้นเรียนจึงไม่คุ้มกับความเจ็บปวด

ตกลงนี่น่าจะเพียงพอแล้วที่จะให้อาหารแก่คุณ

กลับไปที่ข้อมูลโค้ดเริ่มต้นของคุณมีสองรายละเอียดซึ่งสามารถปรับปรุงได้:

  • คุณไม่จำเป็นต้องnormal gvdลบสิ่งที่เลือกเก่าออกnormal "xpจะแทนที่มันแม้ว่าคุณจะไม่ได้ฆ่ามันเสียก่อน
  • ใช้แทนcall setreg('x', [lines], type) let @x = [lines]นี่เป็นการกำหนดประเภทของการลงทะเบียนxอย่างชัดเจน มิฉะนั้นคุณจะต้องพึ่งพาxประเภทที่ถูกต้องอยู่แล้ว (เช่น characterwise, linewise หรือ blockwise)

เมื่อคุณสร้างฟังก์ชั่นในพจนานุกรมโดยตรง (เช่น "ฟังก์ชันที่มีหมายเลข") คุณไม่จำเป็นต้องใช้dictคำหลัก สิ่งนี้ใช้กับ "วิธีการเรียน" ของคุณ :h numbered-functionดู
Karl Yngve Lervåg

@ KarlYngveLervågในทางเทคนิคมันใช้กับทั้งคลาสและวิธีการของวัตถุ (เช่นไม่จำเป็นต้องdictมีMyClassฟังก์ชั่นใด ๆ) แต่ฉันพบว่าสับสนดังนั้นฉันมักจะเพิ่มdictอย่างชัดเจน
lcd047

ฉันเห็น. ดังนั้นคุณเพิ่มdictวิธีการวัตถุ แต่ไม่ใช่สำหรับวิธีการเรียนเพื่อช่วยชี้แจงเจตนาของคุณ?
Karl Yngve Lervåg

@ lcd047 ขอบคุณมากสำหรับคำตอบที่น่าทึ่งนี้! ฉันจะต้องทำงานกับมัน แต่นั่นคือสิ่งที่ฉันกำลังมองหา!
statox

1
@ KarlYngveLervågมีความฉลาดย่อยที่นี่ความหมายของselfมันแตกต่างกันไปสำหรับวิธีการเรียนและวิธีการวัตถุ - มันเป็นตัวเองในชั้นเรียนในกรณีที่อดีตและอินสแตนซ์ของวัตถุปัจจุบันในภายหลัง ด้วยเหตุนี้ฉันมักจะอ้างถึงคลาสของตัวเองว่าg:MyClassไม่เคยใช้selfและฉันส่วนใหญ่เห็นdictว่าเป็นตัวเตือนว่ามันโอเคที่จะใช้self(นั่นคือฟังก์ชั่นที่dictทำหน้าที่กับอินสแตนซ์ของวัตถุเสมอ) อีกครั้งฉันไม่ได้ใช้วิธีการเรียนมากและเมื่อฉันทำอย่างนั้นฉันก็มักจะละเว้นdictทุกที่ ใช่ความมั่นคงในตัวเองเป็นชื่อกลางของฉัน ;)
lcd047

1

สร้างคำสั่งในสตริงและใช้:exeเพื่อเรียกใช้ ดู:help executeรายละเอียดเพิ่มเติมได้ที่

ในกรณีexecuteนี้ใช้เพื่อเรียกใช้ฟังก์ชันและใส่ผลลัพธ์ในการลงทะเบียนองค์ประกอบต่าง ๆ ของคำสั่งจะต้องต่อกันกับตัว.ดำเนินการเป็นสตริงปกติ บรรทัดที่ 3 ควรเป็น:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.