Log Project

목표

  • Operator << 을 통해서 메세지의 Type을 보다 쉽게 사용할 수 있는 Log 생성
  • 해당 Log가 소멸자에서 메세지를 출력할 수 있도록

진행 관련

  • 기간 : 2025/01/24 ~ 2025/01/26
  • 스킬 : C++, CMake

구현 순서

  1. Operator << 를 이용해서, Type별로 내부 메세지에 보다 쉽게 저장할 수 있는 구현
  2. LogMessage가 소멸될 때, 해당 메세지를 처리하도록
  3. 메세지를 저장할 지, 출력할 지 Mode 및 Log Level을 설정할 수 있어야 함
  4. LogMessage에 해당 생성을 Macro 함수를 통해서 호출될 수 있도록

구현 객체

  • LogMessage : Operator <<을 통해서 내부 메세지에 넣을 수 있는 객체
  • LogHandler : LogMessage가 소멸될 때, 어떠한 방식으로 처리할 지 정하는 객체
  • LogManager : LogMessage의 실제 내부 메세지를 저장하는 객체

Log Message

  • 객체 설명
    Operator << 을 통해서 내부 메세지에 데이터를 넣을 수 있는 객체
    해당 객체가 소멸되면서, LogHandler를 통해서 어떠한 처리를 할 지 정할 수 있음

  • 객체 생명 주기
    사용자가 Log 메세지를 입력
    해당 함수 Scope 혹은 해당 줄에서 벗어날 시 소멸자 호출
    해당 소멸자 내부에서 LogHandler를 통해서 실제 내부 메세지 처리

#ifndef __LOG_CONSTANTS_H__
#define __LOG_CONSTANTS_H__

#include <cstdint>
#include <string>

namespace Log
{
    namespace Enum
    {
        enum eType : uint8_t
        {
            eType_None        = 0b0000001,
            eType_Info        = 0b0000010,
            eType_Error        = 0b0000100,
            eType_Warn        = 0b0001000,
            eType_Max        = 0b0001111,
        };

        enum eMode : uint8_t
        {
            eMode_None         = 0b00000000,
            eMode_Print     = 0b00000001,
            eMode_Save         = 0b00000010,
            eMode_Max         = 0b00000011,
        };

        enum eLevel : uint8_t
        {
            eLevel_None     = 0b00000000,
            eLevel_Type        = 0b00000001,
            eLevel_Time     = 0b00000010,
            eLevel_File     = 0b00000100,
            eLevel_Func        = 0b00001000,
            eLevel_Line     = 0b00010000,
            eLevel_Max         = 0b00011111,
        };
    };

    struct MetaData
    {
        Enum::eType eType;
        std::string strFileName;
        std::string strFuncName;
        std::string strLine;

        MetaData()
            : eType(Enum::eType_None)
            , strFileName()
            , strFuncName()
            , strLine()
        {}

        MetaData(const Enum::eType _eType, const char* _pFileName, const char* _pFuncName, const unsigned int _nLine)
            : eType(_eType)
        {
            strFileName = (_pFileName != nullptr) ? std::string(_pFileName) : std::string("");
            strFuncName = (_pFuncName != nullptr) ? std::string(_pFuncName) : std::string("");
            strLine = std::to_string(_nLine);
        }

        MetaData(const MetaData& _rhs)
            : eType(_rhs.eType)
            , strFileName(_rhs.strFileName)
            , strFuncName(_rhs.strFuncName)
            , strLine(_rhs.strLine)
        {}
    };

    ...

};

#endif // __LOG_CONSTANTS_H__

...

#ifndef __LOG_MESSAGE_H__
#define __LOG_MESSAGE_H__

#include <string>
#include <cstdint>
#include <memory>

#include "LogConstants.h"

namespace Log
{
    class Handler;

    class Message
    {
        public :
            Message();
            Message(const Message& _rhs);
            explicit Message(const MetaData& _MetaData, std::weak_ptr<Handler> _spHandler);
            Message(Message&& _rhs) noexcept;
            virtual ~Message();

            Message& operator=(const Message& _rhs);
            Message& operator=(Message&& _rhs) noexcept;

            Message& operator<<(const Message& _message);
            Message& operator<<(Message&& _message);
            Message& operator<<(const std::string& _string);
            Message& operator<<(const bool _boolean);

            Message& operator<<(const uint8_t _uint8);
            Message& operator<<(const uint16_t _uint16);
            Message& operator<<(const uint32_t _uint32);
            Message& operator<<(const uint64_t _uint64);

            Message& operator<<(const int8_t _sint8);
            Message& operator<<(const int16_t _sint16);
            Message& operator<<(const int32_t _sint32);
            Message& operator<<(const int64_t _sin64);

            Message& operator<<(const float _float);
            Message& operator<<(const double _double);

            Message& operator<<(const void* _pVoid);
            Message& operator<<(const char* _pChar);

        public :
            const std::string Get();

        private :
            MetaData m_tMetaData;
            std::string m_strMessage;
            std::weak_ptr<Handler> m_wpHandler;
    };
};

#endif // __LOG_MESSAGE_H__
  • 코드 설명
  • m_tMetaData* Log Type, File, Function, Line 들에 대한 정보
  • m_strMessage* 실제 내부 메세지를 지니는 객체
  • m_wpHandler* LogMessage를 어떠한 방식으로 처리할 지에 대한 방식을 지닌 객체

LogHandler

  • 객체 설명

  • LogMessage*를 어떠한 방식으로 다룰 지에 대한 정보를 지닌 객체

  • 객체 생명 주기

  • Log* namespace가 초기화될 때, LogDefaultHandler : LogHandler가 생성됨


#ifndef __LOG_CONSTANTS_H__
#define __LOG_CONSTANTS_H__

#include <cstdint>
#include <string>

namespace Log
{

...

    struct Entry
    {    
        MetaData tMetaData;
        std::string strMessage;

        Entry()
            : tMetaData()
            , strMessage()
        {}

        Entry(const Entry& _rhs)
            : tMetaData(_rhs.tMetaData)
            , strMessage(_rhs.strMessage)
        {}

        Entry(const MetaData& _MetaData, const std::string& _strMessage)
            : tMetaData(_MetaData)
            , strMessage(_strMessage)
        {}
    };
};

#endif // __LOG_CONSTANTS_H__

...

#ifndef __LOG_HANDLER_H__
#define __LOG_HANDLER_H__

#include <memory>

namespace Log
{
    struct Entry;
    class Manager;

    class Handler
    {
        public :
            Handler() = delete;
            Handler(std::weak_ptr<Manager> _wpManager);
            virtual ~Handler();

        public :
            virtual void Send(const Entry& _rEntry) = 0;
            void Init(const uint8_t _eMode, const uint8_t _eLevel);
            const uint8_t GetMode();
            const uint8_t GetLevel();

        protected :
            uint8_t m_eMode;
            uint8_t m_eLevel;
            std::weak_ptr<Manager> m_wpManager;
    };
};

#endif // __LOG_EVENT_QUEUE_H__
  • 코드 설명
  • Send()* : Entry 정보를 처리하는 것에 대한 가상 함수
  • Init()* : eMode(Print, Save), Level(None, Info, Warn, Error, Verbose)로 초기화하는 객체

겪은 문제점

  1. LogMessagem_strMessage에 대한 생명주기 관리

  2. 1 LogMessage가 소멸될 때 처리할 지, 아니면 살아있을 때 해당 m_strMessage를 Get해서 사용할 지에 대한 정리가 완전하지 않았음

  3. MetaData에 대한 개념 확립 필요

  4. 1 Type, Mode, Level에 대한 소유 주최는 누구인가?

  5. 1.1 누가 Log에 대한 사용 및 관리 자체를 다루는 지에 대한 역할이 명확히 안 정해졌음

  6. LogMessageLogManager와의 연결성

  7. 1 모든 Log가 LogManager를 가서, 저장할 지 말지를 정해야 하는 지에 대한 의문이 안 풀림

해결 방법

  1. LogMessagem_strMessage에 대한 생명주기 관리

  2. 1 LogMessage가 소멸될 때, m_wpHandler를 통해서 m_strMessage에 대한 처리를 전적으로 맡김

    // LogMessage.cpp //
    ...
      Message::~Message()
      {
          if (m_strMessage.empty())
          {
              return;
          }
    
          if (std::shared_ptr<Handler> spHandler = m_wpHandler.lock())
          {
              spHandler->Send(Entry(m_tMetaData, Get()));
          }
      }
    

...


2. **MetaData**에 대한 개념 확립 필요
2.1 **Type** : **LogMessage**에 대한 종류 선별이 필요함
2.2 **Mode** : **LogHandler**에서 어떠한 방식으로 처리할 지에 대한 선별이 필요함
2.3 **Level** : **LogMessage** 자체에서는 전체 **Level**에 대한 정보가 있지만, 그것들 다루는 **LogHandler**와 **LogUtils**(Log MetaData에서 데이터를 추출하는) 특정 부분만 있으면 됨
2.4 **LogMessage**에선 **Type**과 전체 **Level**에 대한 정보들만 지니고 있게 수정
```C++
// LogConstants.h
...
    struct MetaData
    {
        Enum::eType eType;
        std::string strFileName;
        std::string strFuncName;
        std::string strLine;

        MetaData()
            : eType(Enum::eType_None)
            , strFileName()
            , strFuncName()
            , strLine()
        {}

        MetaData(const Enum::eType _eType, const char* _pFileName, const char* _pFuncName, const unsigned int _nLine)
            : eType(_eType)
        {
            strFileName = (_pFileName != nullptr) ? std::string(_pFileName) : std::string("");
            strFuncName = (_pFuncName != nullptr) ? std::string(_pFuncName) : std::string("");
            strLine = std::to_string(_nLine);
        }

        MetaData(const MetaData& _rhs)
            : eType(_rhs.eType)
            , strFileName(_rhs.strFileName)
            , strFuncName(_rhs.strFuncName)
            , strLine(_rhs.strLine)
        {}
    };

...

// LogUtils.cpp
...
        std::string GetDetails(const uint8_t _eLevel, const MetaData& _rMetaData)
        {
            std::string strDetail;

            if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Type))
            {
                strDetail += "[Type] " + GetType(_rMetaData.eType);
                strDetail += " ";
            }

            if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Time))
            {
                strDetail += "[Time] " + GetTime();
                strDetail += " ";
            }

            if (Enum::eLevel_None != (_eLevel & Enum::eLevel_File))
            {
                strDetail += "[File] " + _rMetaData.strFileName;
                strDetail += " ";
            }

            if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Func))
            {
                strDetail += "[Func] " + _rMetaData.strFuncName;
                strDetail += " ";
            }

            if (Enum::eLevel_None != (_eLevel & Enum::eLevel_Line))
            {
                strDetail += "[Line] " + _rMetaData.strLine;
                strDetail += " ";
            }

            return strDetail;
        }
...
  1. LogMessageLogManager와의 연결성

  2. 1 모든 Log들은 LogManager를 거쳐서 갈 필요가 없음

  3. 2 둘 사이에 LogMessage를 처리하는 객체가 필요, 그리고 그 객체가 LogManager를 호출해서 저장하는 방식으로 처리하는 것이 유형 중 하나

  4. 3 LogHandler를 만들어서, LogManager에 접근하도록 수정

  5. 4 서로 다른 방식의 Log 처리 방식이 가능하도록 열려놓은 상태로 구현

    // LogDefaultHandler.cpp
    ...
      void DefaultHandler::Send(const Entry& _rEntry)
      {
          const uint8_t eMode = GetMode();
          const uint8_t eLevel = GetLevel();
    
          std::string strLog = Utils::GetDetails(eLevel, _rEntry.tMetaData);
          strLog += "[Log] " + _rEntry.strMessage;
    
          if (Enum::eMode_None != (eMode & Enum::eMode_Print))
          {
              std::cout << strLog << std::endl;
          }
    
          if (Enum::eMode_None != (eMode & Enum::eMode_Save))
          {
              if (std::shared_ptr<Manager> spManager = m_wpManager.lock())
              {
                  spManager->SetLog(_rEntry.tMetaData.eType, strLog);
              }
          }
      }
    ...
    
    

```

@echo off
doskey ls = dir $*
doskey cp = copy $*
doskey rm = del $*
doskey mv = move $*
doskey cwd = cd
doskey cd = pushd $*
doskey cmake_clean = del /s /f /q build
doskey cmake_allcl = rmdir /s /q build $T mkdir build
doskey cmake32 = cmake $* -G "Visual Studio 17 2022" -S ./ -B ./build -A Win32
doskey cmake64 = cmake $* -G "Visual Studio 17 2022" -S ./ -B ./build -A x64
doskey cmake_Release = cmake --build ./build --config Release
doskey cmake_Debug = cmake --build ./build --config Debug
doskey ccd = cd .. $T cd ..
doskey st = start build\Debug\$*
doskey gt = g++ -std=c++20 $*
doskey vim = nvim $*
doskey init = nvim %USERPROFILE%\AppData\Local\nvim\init.vim


doskey all = doskey /macros

'개인 프로젝트 > Vim' 카테고리의 다른 글

[Vim] vimrc 파일  (0) 2022.04.17

Memory 관리 방법

보다 효율적인 Memory 관리 방법이 필요하기에 진행함

연속 메모리 할당

  • 가장 일반적인 방법으로, 동일한 타입의 데이터들을 연속된 메모리 상에 할당시키는 방법
  • Vector와 Array가 대표적인 예이다.
  • 다른 타입 간의 메모리 관계를 구축할 수 없다는 단점이 존재
  • 내부 단편화 : 없음
  • 외부 단편화 : 있음

페이징 기법 ( Paging )

  • 고정된 크기의 메모리 블락을 할당시키는 방법
  • 일정한 크기의 메모리 블락을 제공해주고, 해당 메모리 블락 안에서 객체들을 생성하는 방법
  • 내부 단편화 : 해당 메모리 블락을 전부 사용하지 못할 경우, 내부 단편화 발생 가능
  • 외부 단편화 : 없음

세그먼테이션 기법 ( Segmentation )

  • 서로 다른 크기의 메모리 블락을 할당하는 방법
  • 요구되거나 혹은 필요로 되어지는 메모리 블락만큼 할당시키는 방법
  • 내부 단편화 : 요구되어지는 전체 블락을 사용하기에 내부 단편화 발생하지 않음
  • 외부 단편화 : 해당 메모리 사용이 끝나고 해제되었을 때, 해당 메모리 블락을 다시 알맞은 크기로 사용 못할 가능성이 다분함

현재 Memory Project 내부 메모리 관리 방법

페이징 기법 ( Paging )

  • 해당 타입에 대해서 일정한 크기의 메모리 블락을 제공함
  • 해당 메모리 블락이 가득 찼을 시, 새로운 메모리 블락을 생성
    • 이때, 연속된 메모리 블락의 위치가 아닌 임의의 위치로 메모리 블락이 생성
  • 또한, 서로 다른 타입의 메모리 블락 크기가 일정치 않을 수 있음
    • 이로 인한, 외부 단편화 발생 가능

개선 방안

L1 캐시 메모리만큼 할당

  • 결국엔 메모리 블락만큼 할당해서 사용한 이유가 캐시 히트 확률을 높이기 위함임
  • 제공 되어질 메모리 블락의 최대치에 한계를 두어야 함
  • L1 캐시 메모리 사이즈를 넘어갈 시에는, 랜덤된 위치에 할당되어질 수 있음

메모리 주소 직접 관리

  • 자체적으로 메모리 주소를 관리해줘야 함
  • 서로 다른 객체들의 할당이 이루어졌을 때, 해당 타입의 메모리 블락을 할당하려고 하는데 이미 그곳에 다른 객체의 메모리 블락이 위치할 수도 있음
  • 그럴 경우, 직접 주소들을 확인할 수 있는 내부 구성이 필요함
  • 또한 이 과정에서 오버헤드가 많이 발생할 것으로 보임
  • 직접 주소를 관리하게 된다면
    • 해당 메모리 블락이 가득 찼을 시, 다음을 검사함
      • 현재 해당 연속 메모리 블락의 사이즈가 L1 캐시 사이즈를 넘었는지 확인
      • 다음 연속 메모리 블락을 생성할 위치가 다른 메모리가 사용 중이지 않거나 유효한지 확인
    • 위의 검사를 통과한 후에 해당 타입의 메모리 블락을 생성하여서 사용

Memory Pool과 연속 Memory Pool 객체가 필요

  • 메모리 풀과 그러한 메모리 풀들을 묶어서 타입별로 관리하는 객체가 필요함
  • 현재 Memory Pool 객체는 존재하지만, 해당 Memory Pool 내에서 연장할 수 있는 기능을 추가해야 함
  • 또한, 해당 Memory Pool들을 묶어서 타입 별로 관리할 수 있는 객체가 필요
  • 그러한 객체를 Memory Manager에서 최종 관리하는 방식으로 진행 필요

최종안

graph TB
A(Send Message for Creating Instance) --> B[MemoryManager]
B --> C{ Check the MemoryAddress existed }
C -- Yes --> D{ Check the MemoryPool }
C -- No --> E[ Create MemoryAddress for Instance ]
E --> D
D -- No Full --> F[ Create Instance ] --> DONE( DONE )
D -- Full --> G{ Check the adding MemoryPool' Size  }
G -- Bigger than L1 Cache --> H{ Check the adding address is valid }
G -- Smaller than L1 Cache --> I
H -- Alreay used --> J( Find new address ) 
J ----> H
H -- No used --> K( Create new MemoryPool ) --> I
I( Input address for MemoryPool ) --> F
  • MemoryManager : MemoryAddress를 관리, 인스턴스 생성 및 소멸 명령을 유저에게 받는 객체
  • MemoryAddress : 각 타입별로 MemoryPool들을 관리하는 역할
  • MemoryPool : 각 타입별로 지정된 크기만큼 메모리 블락을 할당 받고 연장 및 축소가 가능한 객체

[ vimrc 파일 ]

.vimrc
0.00MB

[ zshrc 파일 ]

.zshrc
0.00MB

 

[ vimrc 파일 코드 ]

  1 " Basic Settings
  2 
  3 map <F3> <ESC>:NERDTreeToggle<CR>
  4 map <F5> :tabnew<CR>
  5 map <F4> :bn<CR>
  6 
  7 colorscheme jellybeans
  8 set termguicolors
  9 syntax on
 10 set number
 11 set nu
 12 set hlsearch
 13 set ignorecase
 14 set tabstop=4
 15 set softtabstop=4
 16 set shiftwidth=4
 17 set expandtab
 18 autocmd FileType make setlocal noexpandtab
 19 
 20 " Key Settings
 21 nnoremap <F2> :set invpaste paste?<CR>
 22 set pastetoggle=<F2>
 23 let mapleader = ","
 24 nnoremap <leader>q :bp<CR>
 25 nnoremap <leader>w :bn<CR>
 26 
 27 " Key Setting - resize windows
 28 nnoremap <silent> <Leader>= :exe "resize +3"<CR>
 29 nnoremap <silent> <Leader>- :exe "resize -3"<CR>
 30 nnoremap <silent> <Leader>] :exe "vertical resize +8"<CR>
 31 nnoremap <silent> <Leader>[ :exe "vertical resize -8"<CR>
 32 
 33 nnoremap <silent> <Leader>+ :exe "resize " . (winheight(0) * 3/2)<CR>
 34 nnoremap <silent> <Leader>_ :exe "resize " . (winheight(0) * 2/3)<CR>
 35 nnoremap <silent> <Leader>} :exe "vertical resize " . (winheight(0) * 3/2)<CR>
 36 nnoremap <silent> <Leader>{ :exe "vertical resize " . (winheight(0) * 2/3)<CR>
 37 
 38 " Vundle
 39 set nocompatible              " be iMproved, required
 40 filetype off                  " required
 41 
 42 " set the runtime path to include Vundle and initialize
 43 set rtp+=~/.vim/bundle/Vundle.vim
 44 call vundle#begin()
 45 " alternatively, pass a path where Vundle should install plugins
 46 "call vundle#begin('~/some/path/here')
 47 
 48 " let Vundle manage Vundle, required
 49 Plugin 'VundleVim/Vundle.vim'
 50 
 51 " Keep Plugin commands between vundle#begin/end.
 52 
 53 Plugin 'vim-airline/vim-airline'
 54 Plugin 'vim-airline/vim-airline-themes'
 55 Plugin 'The-NERD-Tree'
 56 Plugin 'terryma/vim-multiple-cursors'
 57 Plugin 'terryma/vim-smooth-scroll'
 58 Plugin 'Raimondi/delimitMate'
 59 Plugin 'SirVer/ultisnips'
 60 Plugin 'honza/vim-snippets'
 61 Plugin 'Syntastic'
 62 Plugin 'Shougo/deoplete.nvim'
 63 Plugin 'Rip-Rip/clang_complete'
 64 Plugin 'roxma/nvim-yarp'
 65 Plugin 'roxma/vim-hug-neovim-rpc'
 66 Plugin 'morhetz/gruvbox'
 67 
 68 " All of your Plugins must be added before the following line
 69 call vundle#end()            " required
 70 filetype plugin indent on    " required
 71 " To ignore plugin indent changes, instead use:
 72 "filetype plugin on
 73 "
 74 " Brief help
 75 " :PluginList       - lists configured plugins
 76 " :PluginInstall    - installs plugins; append `!` to update or just :PluginUpdate
 77 " :PluginSearch foo - searches for foo; append `!` to refresh local cache
 78 " :PluginClean      - confirms removal of unused plugins; append `!` to auto-approve removal
 79 "
 80 " see :h vundle for more details or wiki for FAQ
 81 " Put your non-Plugin stuff after this line
 82 
 83 " for vim-airline
 84 let g:airline#extensions#tabline#enabled = 1 " turn on buffer list
 85 " Smarter tab line 활성화: 모든 파일 버퍼 출력
 86 let g:airline#extensions#tabline#enabled = 1
 87 
 88 " Tab line 구분자 '|' 로 설정
 89 let g:airline#extensions#tabline#left_sep = ' '
 90 let g:airline#extensions#tabline#left_alt_sep = '|'
 91 
 92 " Tab line 에 파일명만 출력되도록 설정
 93 let g:airline#extensions#tabline#formatter = 'unique_tail'
 94 
 95 " Powerline-font 활성화
 96 let g:airline_powerline_fonts = 1
 97 let g:airline_theme='hybrid'
 98 set laststatus=2 " turn on bottom bar
 99 
100 " The-NERD-Tree
101 autocmd BufEnter * lcd %:p:h
102 autocmd VimEnter * if !argc() | NERDTree | endif
103 nmap <leader>ne :NERDTreeToggle<cr>
104 let NERDTreeShowLineNumbers=1
105 let g:NERDTreeWinPos = "right"
106 
107 " gruvbox
108 let g:gruvbox_contrast_dark="hard"
109 set background=dark
110 autocmd vimenter * colorscheme gruvbox
111 
112 " vim-multiple-cursor
113 let g:multi_cursor_use_default_mapping=0
114 " Default mapping
115 let g:multi_cursor_next_key='<C-n>'
116 let g:multi_cursor_prev_key='<C-p>'
117 let g:multi_cursor_skip_key='<C-x>'
118 let g:multi_cursor_quit_key='<Esc>'
119 
120 " vim-smooth-scroll
121 noremap <silent> <c-b> :call smooth_scroll#up(&scroll*2, 10, 5)<CR>
122 noremap <silent> <c-f> :call smooth_scroll#down(&scroll*2, 10, 5)<CR>
123 noremap <silent> <c-u> :call smooth_scroll#up(&scroll, 10, 3)<CR>
124 noremap <silent> <c-d> :call smooth_scroll#down(&scroll, 10, 3)<CR>
125 
126 " delimitMate
127 let delimitMate_expand_cr=1
128 
129 " UltiSnips
130 let g:UltiSnipsExpandTrigger="<tab>"
131 let g:UltiSnipsJumpForwardTrigger="<tab>"
132 let g:UltiSnipsJumpBackwardTrigger="<s-tab>"
133 let g:UltiSnipsEditSplit="vertical"
134 
135 " Syntastic
136 set statusline+=%#warningmsg#
137 set statusline+=%{SyntasticStatuslineFlag()}
138 set statusline+=%*
139 
140 let g:syntastic_always_populate_loc_list = 1
141 let g:syntastic_auto_loc_list = 1
142 let g:syntastic_check_on_open = 1
143 let g:syntastic_check_on_wq = 0
144 
145 let g:syntastic_cpp_compiler = 'g++'
146 let g:syntastic_cpp_compiler_options = "-std=c++11 -Wall -Wextra -Wpedantic"
147 let g:syntastic_c_compiler_options = "-std=c11 -Wall -Wextra -Wpedantic"
148 
149 " Deoplete.
150 let g:deoplete#enable_at_startup = 1
151 
152 " clang_complete
153 set completeopt-=preview

'개인 프로젝트 > Vim' 카테고리의 다른 글

Alias.cmd  (1) 2024.02.19

+ Recent posts