Có thể sử dụng một đại biểu hoặc để truyền một hàm làm đối số trong Vimscript không?


11

Tôi đang cố gắng tạo một plugin nhỏ để học vimscript, mục tiêu của tôi là tạo ra một số chức năng xử lý văn bản đã chọn và thay thế nó bằng kết quả. Kịch bản chứa các mục sau đây:

  • Hai hàm xử lý văn bản: chúng lấy một chuỗi làm tham số trả về chuỗi nên được sử dụng để thay thế văn bản gốc. Bây giờ tôi chỉ có hai nhưng có thể nhiều hơn nữa trong một vài lần.

  • Một chức năng nhận văn bản đã chọn: chỉ đơn giản là kéo lựa chọn cuối cùng và trả lại nó.

  • Hàm bao bọc: gọi hàm xử lý, lấy kết quả của nó và thay thế lựa chọn cũ bằng kết quả này.

Bây giờ chức năng trình bao bọc của tôi trông như thế này:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

Và tôi phải tạo một trình bao bọc thứ hai thay thế dòng 3 bằng

let @x = Type2ProcessString(GetSelectedText())

Tôi muốn cung cấp cho hàm bao bọc của mình một tham số chứa hàm Process để thực thi và sử dụng lệnh gọi chung trong dòng 3. Hiện tại tôi đã thử sử dụng callcác cách khác nhau như, ví dụ:

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

nhưng tôi đã không thực sự thành công và :h callkhông thực sự hữu ích về chủ đề đại biểu.

Tóm lại đây là những câu hỏi của tôi:

  • Làm thế nào tôi có thể chỉ tạo một hàm bao cho tất cả các hàm xử lý?
  • Có cái gì đó làm việc như một đại biểu trong vimscript?
  • Nếu các đại biểu không tồn tại thì đâu là cách "tốt" để làm những gì tôi muốn?

Câu trả lời:


16

Để trả lời câu hỏi của bạn: nguyên mẫu call()trong hướng dẫn là call({func}, {arglist} [, {dict}]); các {arglist}tham số cần phải được theo nghĩa đen một đối tượng List, không phải là một danh sách các đối số. Đó là, bạn phải viết nó như thế này:

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

Giả định a:functionToExecutenày là Funcref (xem :help Funcref) hoặc tên của hàm (tức là một chuỗi, chẳng hạn như 'Type1ProcessString').

Bây giờ, đó là một tính năng mạnh mẽ mang lại cho Vim một loại chất lượng giống như LISP, nhưng có lẽ bạn hiếm khi sử dụng nó như trên. Nếu a:functionToExecutelà một chuỗi, tên của một hàm, thì bạn có thể làm điều này:

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

và bạn sẽ gọi trình bao bọc với tên của hàm:

call Wrapper('Type1ProcessString')

Nếu mặt khác a:functionToExecutelà Funcref, bạn có thể gọi trực tiếp:

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

nhưng bạn cần gọi trình bao bọc như thế này:

call Wrapper(function('Type1ProcessString'))

Bạn có thể kiểm tra sự tồn tại của các chức năng với exists('*name'). Điều này có thể thực hiện các mẹo nhỏ sau đây:

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

tức là một hàm sử dụng tích hợp strwidth()nếu Vim đủ mới để có nó và quay trở lại strlen()nếu không (tôi không cho rằng một dự phòng như vậy có ý nghĩa; tôi chỉ nói rằng nó có thể được thực hiện). :)

Với các hàm từ điển (xem :help Dictionary-function), bạn có thể định nghĩa một cái gì đó tương tự như các lớp:

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

Sau đó, bạn sẽ khởi tạo các đối tượng như thế này:

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

Và gọi các phương thức của nó:

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

Bạn cũng có thể có các thuộc tính và phương thức lớp:

let g:MyClass.__meaning_of_life = 42

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

(thông báo không cần dictở đây).

Chỉnh sửa: Phân lớp là một cái gì đó như thế này:

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

Điểm tinh tế ở đây là việc sử dụng copy()thay vì deepcopy(). Lý do cho điều này là để có thể truy cập các thuộc tính của lớp cha bằng cách tham chiếu. Điều này có thể đạt được, nhưng nó rất mong manh và làm cho nó trở nên xa vời. Một vấn đề tiềm năng khác là loại lớp con này liên kết is-avới has-a. Vì lý do này, các thuộc tính lớp thường không thực sự đáng giá.

Ok, điều này là đủ để cung cấp cho bạn một số thực phẩm cho suy nghĩ.

Quay lại đoạn mã ban đầu của bạn, có hai chi tiết có thể được cải thiện:

  • bạn không cần normal gvdxóa lựa chọn cũ, normal "xpsẽ thay thế ngay cả khi bạn không giết nó trước
  • sử dụng call setreg('x', [lines], type)thay vì let @x = [lines]. Điều này đặt rõ ràng các loại của thanh ghi x. xMặt khác, bạn đang dựa vào việc đã có loại chính xác (nghĩa là theo chiều dọc, theo chiều dọc hoặc theo chiều dọc).

Khi bạn tạo các hàm trong từ điển trực tiếp (tức là "hàm được đánh số"), bạn không cần dicttừ khóa. Điều này áp dụng cho "phương thức lớp" của bạn. Xem :h numbered-function.
Karl Yngve Lervåg

@ KarlYngveLervåg Về mặt kỹ thuật, nó áp dụng cho cả phương thức lớp và đối tượng (nghĩa là không cần dictbất kỳ MyClasshàm nào). Nhưng tôi thấy khó hiểu, vì vậy tôi có xu hướng thêm dictmột cách rõ ràng.
lcd047

Tôi hiểu rồi. Vì vậy, bạn thêm dictcho các phương thức đối tượng, nhưng không phải cho các phương thức lớp, để giúp làm rõ ý định của bạn?
Karl Yngve Lervåg

@ lcd047 Cảm ơn rất nhiều vì câu trả lời tuyệt vời này! Tôi sẽ phải làm việc với nó nhưng đó chính xác là những gì tôi đang tìm kiếm!
statox

1
@ KarlYngveLervåg Ở đây có một tính linh hoạt, ý nghĩa của selfnó là khác nhau đối với các phương thức lớp và đối với các phương thức đối tượng - đó là chính lớp trong trường hợp trước và thể hiện của đối tượng hiện tại trong trường hợp sau. Vì lý do này, tôi luôn đề cập đến chính lớp đó g:MyClass, không bao giờ sử dụng selfvà tôi chủ yếu xem đó dictlà một lời nhắc rằng sử dụng nó là ổn self(nghĩa là, một hàm dictluôn luôn hoạt động trên một thể hiện đối tượng). Hơn nữa, tôi không sử dụng các phương thức lớp nhiều và khi tôi làm điều đó tôi cũng có xu hướng bỏ qua dictmọi nơi. Vâng, tự thống nhất là tên đệm của tôi. ;)
lcd047

1

Xây dựng lệnh trong một chuỗi và sử dụng :exeđể chạy nó. Xem :help executeđể biết thêm chi tiết.

Trong trường hợp này, executeđược sử dụng để thực hiện cuộc gọi đến hàm và đưa kết quả vào thanh ghi, các phần tử khác nhau của lệnh phải được nối với .toán tử như một chuỗi thông thường. Dòng 3 sẽ trở thành:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.