├── .gopherci.yml ├── .travis.yml ├── README.md ├── caret ├── caret.go ├── caret_test.go └── whole_word.go ├── chanwriter.go ├── data └── fonts │ ├── Helvetica Neue.png │ ├── Menlo.png │ └── Tahoma.png ├── event ├── event.go ├── eventtype_string.go └── virtualcategory_string.go ├── font.go ├── go.mod ├── highlight.go ├── install-into-bundle.sh ├── main.go ├── page ├── events │ ├── index.html │ └── main.go ├── page1 │ └── main.go ├── page2 │ ├── font.go │ └── main.go └── terminal │ ├── font.go │ └── main.go ├── pkg ├── analysis │ ├── generated_detection.go │ ├── generated_detection_test.go │ └── testdata │ │ ├── generated_0.go.txt │ │ ├── handcrafted_0.go.txt │ │ └── handcrafted_1.go.txt ├── exp11 │ ├── main.go │ └── sortimports.go ├── exp12 │ └── main.go ├── exp13 │ └── main.go ├── exp14 │ └── main.go ├── gist4727543 │ ├── main.go │ └── main_test.go ├── gist5504644 │ ├── compat.go │ ├── go14.go │ └── main.go ├── gist5645828 │ └── main.go ├── gist6003701 │ ├── main.go │ └── main_test.go ├── gist7480523 │ ├── import_path_found.go │ ├── main.go │ └── main_test.go ├── gist7576154 │ └── main.go ├── gist7651991 │ └── main.go ├── gist7802150 │ ├── main.go │ ├── main_test.go │ ├── play_0.go │ └── play_1.go ├── gist8018045 │ ├── main.go │ └── main_test.go ├── httpstoppable │ └── httpstoppable.go ├── legacyvcs │ ├── git.go │ ├── hg.go │ └── vcs.go ├── markdown_http │ └── markdown.go ├── multilinecontent │ ├── multilinecontent.go │ └── reverse.go ├── u10 │ └── main.go └── u6 │ └── main.go ├── tail-log.sh └── text_test.go /.gopherci.yml: -------------------------------------------------------------------------------- 1 | apt_packages: 2 | - libgl1-mesa-dev 3 | - xorg-dev 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | addons: 3 | apt_packages: 4 | - libgl1-mesa-dev 5 | - xorg-dev 6 | language: go 7 | go: 8 | - 1.x 9 | - master 10 | env: 11 | - GO111MODULE=on 12 | matrix: 13 | allow_failures: 14 | - go: master 15 | fast_finish: true 16 | install: 17 | - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening. 18 | script: 19 | - git clone https://github.com/shurcooL/frontend "$GOPATH/src/github.com/shurcooL/frontend" || (cd "$GOPATH/src/github.com/shurcooL/frontend" && git pull --ff-only) # TODO: remove after golang.org/issue/31603 is resolved 20 | - diff -u <(echo -n) <(gofmt -d -s .) 21 | - go vet ./... 22 | - go test -v -race ./... 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Conception-go 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/shurcooL-legacy/Conception-go.svg?branch=master)](https://travis-ci.org/shurcooL-legacy/Conception-go) [![GoDoc](https://godoc.org/github.com/shurcooL-legacy/Conception-go?status.svg)](https://godoc.org/github.com/shurcooL-legacy/Conception-go) 5 | 6 | This is a work in progress Go implementation of [Conception](https://github.com/shurcooL/Conception#demonstration). 7 | 8 | Conception is an experimental project. It's a platform for researching software development tools and techniques. It is driven by a set of guiding principles. Conception-go targets Go development. 9 | 10 | My "short term" goal is to implement some of the ideas shown off in Bret Victor's [Learnable Programming](http://worrydream.com/LearnableProgramming/) article. Another goal is to try to beat my current Sublime Text + GoSublime development environment setup (which sets a really high bar). 11 | 12 | Watch this repo to follow the project's development. 13 | 14 | Screenshot 15 | ---------- 16 | 17 | A screenshot showing off a single aspect of the project that was recently added or improved. 18 | 19 | ![Early 2015 Overview](http://dmitri.shuralyov.com/projects/Conception/images/Go/early-2015-overview.png) 20 | 21 | Installation 22 | ------------ 23 | 24 | ### macOS 25 | 26 | ```bash 27 | # Install latest Go, git (if you don't already have them). 28 | ... 29 | 30 | # Step back and enjoy 2 commands that install Conception-go. 31 | git clone https://github.com/shurcooL/frontend "$GOPATH/src/github.com/shurcooL/frontend" || (cd "$GOPATH/src/github.com/shurcooL/frontend" && git pull --ff-only) # TODO: remove after golang.org/issue/31603 is resolved 32 | (cd; GO111MODULE=on go install github.com/shurcooL-legacy/Conception-go) 33 | 34 | # Run it. 35 | $(go env GOPATH)/bin/Conception-go 36 | ``` 37 | 38 | ### Linux 39 | 40 | ```bash 41 | # Install latest Go (if you don't already have it). 42 | sudo apt-get install --yes curl 43 | curl -L https://golang.org/dl/go1.13.7.linux-amd64.tar.gz | sudo tar zx -C /usr/local/ 44 | export PATH="$PATH:/usr/local/go/bin" 45 | 46 | # Install git, OpenGL headers (if you don't already have them). 47 | sudo apt-get install --yes git 48 | sudo apt-get install --yes libgl1-mesa-dev xorg-dev # OpenGL headers. 49 | 50 | # Step back and enjoy 2 commands that install Conception-go. 51 | git clone https://github.com/shurcooL/frontend "$GOPATH/src/github.com/shurcooL/frontend" || (cd "$GOPATH/src/github.com/shurcooL/frontend" && git pull --ff-only) # TODO: remove after golang.org/issue/31603 is resolved 52 | (cd; GO111MODULE=on go install github.com/shurcooL-legacy/Conception-go) 53 | 54 | # Run it. 55 | $(go env GOPATH)/bin/Conception-go 56 | ``` 57 | 58 | License 59 | ------- 60 | 61 | - [MIT License](https://opensource.org/licenses/mit-license.php) 62 | -------------------------------------------------------------------------------- /caret/caret.go: -------------------------------------------------------------------------------- 1 | // Package caret contains functionality related to text caret state and operations. 2 | package caret 3 | 4 | import ( 5 | "sort" 6 | "strings" 7 | 8 | "github.com/go-gl/mathgl/mgl64" 9 | intmath "github.com/pkg/math" 10 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 11 | ) 12 | 13 | type ContentLine interface { 14 | Start() uint32 15 | End() uint32 16 | Length() uint32 17 | } 18 | 19 | type MultilineContentI interface { 20 | Content() string 21 | LenContent() int 22 | LongestLine() uint32 // Line length 23 | 24 | Line(lineIndex int) ContentLine 25 | LenLines() int 26 | 27 | gist7802150.ViewGroupI 28 | } 29 | 30 | // ===== 31 | 32 | func ExpandedLength(s string, currentAdvance uint32) (expandedLineLength uint32) { 33 | segments := strings.Split(s, "\t") 34 | var advance uint32 = currentAdvance 35 | for segmentIndex, segment := range segments { 36 | advance += uint32(len(segment)) 37 | if segmentIndex != len(segments)-1 { 38 | advance += 4 - (advance % 4) 39 | } 40 | } 41 | return advance - currentAdvance 42 | } 43 | 44 | func ExpandedToLogical(s string, expanded uint32) uint32 { 45 | var logical uint32 46 | var advance uint32 47 | var smallestDifference int32 = int32(expanded) - 0 48 | for charIndex, char := range []byte(s) { 49 | if char == '\t' { 50 | advance += 4 - (advance % 4) 51 | } else { 52 | advance++ 53 | } 54 | 55 | difference := int32(advance) - int32(expanded) 56 | if difference < 0 { 57 | difference *= -1 58 | } 59 | if difference < smallestDifference { 60 | smallestDifference = difference 61 | logical = uint32(charIndex + 1) 62 | } 63 | } 64 | 65 | return logical 66 | } 67 | 68 | // --- 69 | 70 | // TODO: Rename. 71 | type caretPositionInternal struct { 72 | w MultilineContentI 73 | lineIndex int 74 | positionWithinLine uint32 75 | targetExpandedX uint32 76 | 77 | gist7802150.DepNode2Manual 78 | } 79 | 80 | func (cp *caretPositionInternal) NotifyContentChanged() { 81 | if cp.lineIndex > cp.w.LenLines()-1 { 82 | cp.Move(+3) 83 | } else if cp.positionWithinLine > cp.w.Line(cp.lineIndex).Length() { 84 | cp.Move(+2) 85 | } 86 | } 87 | 88 | func (cp *caretPositionInternal) Logical() uint32 { 89 | return cp.w.Line(cp.lineIndex).Start() + cp.positionWithinLine 90 | } 91 | 92 | func (cp *caretPositionInternal) tryMoveH(direction direction, jumpWords bool) { 93 | switch direction { 94 | case Backward: 95 | if cp.Logical() >= 1 { 96 | if jumpWords { 97 | // Skip spaces to the left 98 | LookAt := cp.Logical() 99 | for LookAt > 0 && !isCoreCharacter(cp.w.Content()[LookAt-1]) { 100 | LookAt-- 101 | } 102 | // Skip non-spaces to the left 103 | for LookAt > 0 && isCoreCharacter(cp.w.Content()[LookAt-1]) { 104 | LookAt-- 105 | } 106 | 107 | cp.willMoveH(int32(LookAt) - int32(cp.Logical())) 108 | } else { 109 | cp.willMoveH(-1) 110 | } 111 | } 112 | case Forward: 113 | if cp.Logical() < uint32(cp.w.LenContent()) { 114 | if jumpWords { 115 | // Skip spaces to the right 116 | LookAt := cp.Logical() 117 | for LookAt < uint32(cp.w.LenContent()) && !isCoreCharacter(cp.w.Content()[LookAt]) { 118 | LookAt++ 119 | } 120 | // Skip non-spaces to the right 121 | for LookAt < uint32(cp.w.LenContent()) && isCoreCharacter(cp.w.Content()[LookAt]) { 122 | LookAt++ 123 | } 124 | 125 | cp.willMoveH(int32(LookAt) - int32(cp.Logical())) 126 | } else { 127 | cp.willMoveH(+1) 128 | } 129 | } 130 | } 131 | } 132 | 133 | type direction int8 134 | 135 | const ( 136 | Backward direction = -1 137 | Forward direction = +1 138 | ) 139 | 140 | // expandSelection goes in direction over core characters only. 141 | func (cp *caretPositionInternal) expandSelection(direction direction) { 142 | switch direction { 143 | case Backward: 144 | if cp.Logical() > 0 { 145 | // Skip non-spaces to the left. 146 | lookAt := cp.Logical() 147 | for lookAt > 0 && isCoreCharacter(cp.w.Content()[lookAt-1]) { 148 | lookAt-- 149 | } 150 | 151 | cp.willMoveH(int32(lookAt) - int32(cp.Logical())) 152 | } 153 | case Forward: 154 | if cp.Logical() < uint32(cp.w.LenContent()) { 155 | // Skip non-spaces to the right. 156 | lookAt := cp.Logical() 157 | for lookAt < uint32(cp.w.LenContent()) && isCoreCharacter(cp.w.Content()[lookAt]) { 158 | lookAt++ 159 | } 160 | 161 | cp.willMoveH(int32(lookAt) - int32(cp.Logical())) 162 | } 163 | } 164 | } 165 | 166 | // Moves caret horizontally by amount. It doesn't do bounds checking, so it's 167 | // the caller's responsibility to ensure it's a legal amount to move by. 168 | // 169 | // Pre-conditions: 170 | // - Moving caret by amount should result in a valid position. 171 | func (cp *caretPositionInternal) willMoveH(amount int32) { 172 | switch { 173 | case amount < 0: 174 | absAmount := uint32(-amount) 175 | for absAmount != 0 { 176 | if cp.positionWithinLine >= absAmount { 177 | cp.positionWithinLine -= absAmount 178 | absAmount = 0 179 | } else { //if cp.lineIndex > 0 180 | absAmount -= 1 + cp.positionWithinLine 181 | cp.lineIndex-- 182 | cp.positionWithinLine = cp.w.Line(cp.lineIndex).Length() 183 | } 184 | } 185 | case amount > 0: 186 | absAmount := uint32(amount) 187 | for absAmount != 0 { 188 | if cp.positionWithinLine+absAmount <= cp.w.Line(cp.lineIndex).Length() { 189 | cp.positionWithinLine += absAmount 190 | absAmount = 0 191 | } else { //if cp.lineIndex < some_max 192 | absAmount -= 1 + cp.w.Line(cp.lineIndex).Length() - cp.positionWithinLine 193 | cp.lineIndex++ 194 | cp.positionWithinLine = 0 195 | } 196 | } 197 | default: 198 | // There's no change, so don't do anything else 199 | return 200 | } 201 | 202 | cp.targetExpandedX, _ = cp.expandedPosition() // TODO: More direct 203 | 204 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 205 | } 206 | 207 | func (cp *caretPositionInternal) tryMoveV(direction direction, jumpWords bool) { 208 | switch direction { 209 | case Backward: 210 | if cp.lineIndex > 0 { 211 | if jumpWords { 212 | for cp.lineIndex > 0 { 213 | cp.lineIndex-- 214 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 215 | if line == "" { 216 | break 217 | } 218 | } 219 | cp.positionWithinLine = 0 220 | } else { 221 | cp.lineIndex-- 222 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 223 | cp.positionWithinLine = ExpandedToLogical(line, cp.targetExpandedX) 224 | } 225 | 226 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 227 | } else { 228 | cp.Move(-2) 229 | } 230 | case Forward: 231 | if cp.lineIndex < cp.w.LenLines()-1 { 232 | if jumpWords { 233 | for cp.lineIndex < cp.w.LenLines()-1 { 234 | cp.lineIndex++ 235 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 236 | if line == "" { 237 | break 238 | } 239 | } 240 | cp.positionWithinLine = 0 241 | } else { 242 | cp.lineIndex++ 243 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 244 | cp.positionWithinLine = ExpandedToLogical(line, cp.targetExpandedX) 245 | } 246 | 247 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 248 | } else { 249 | cp.Move(+2) 250 | } 251 | } 252 | } 253 | 254 | func (cp *caretPositionInternal) MoveTo(other *caretPositionInternal) { 255 | cp.lineIndex = other.lineIndex 256 | cp.positionWithinLine = other.positionWithinLine 257 | cp.targetExpandedX = other.targetExpandedX 258 | 259 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 260 | } 261 | 262 | func (cp *caretPositionInternal) Compare(other *caretPositionInternal) int8 { 263 | if cp.lineIndex < other.lineIndex { 264 | return -1 265 | } else if cp.lineIndex > other.lineIndex { 266 | return +1 267 | } else { 268 | if cp.positionWithinLine < other.positionWithinLine { 269 | return -1 270 | } else if cp.positionWithinLine > other.positionWithinLine { 271 | return +1 272 | } else { 273 | return 0 274 | } 275 | } 276 | } 277 | 278 | // TODO: Change amount to a proper type with 4 values, etc. to avoid confusion with other funcs where amount can be an arbitrary number. 279 | // TODO: Rename to JumpTo or something to indicate it's a method that can never fail. 280 | // Move jumps the caret position to a new position. This operation never fails, but may not have any effect. 281 | // If amount is ±1, move by 1 character within same line if possible. 282 | // If amount is ±2, jump to start/end of line. 283 | // If amount is ±3, jump to start/end of content. 284 | func (cp *caretPositionInternal) Move(amount int8) { 285 | originalPosition := *cp 286 | 287 | switch amount { 288 | // Move by 1 character within same line, if possible. 289 | case -1: 290 | if cp.positionWithinLine > 0 { 291 | cp.positionWithinLine-- 292 | } 293 | case +1: 294 | if cp.positionWithinLine < cp.w.Line(cp.lineIndex).Length() { 295 | cp.positionWithinLine++ 296 | } 297 | case -2: 298 | cp.positionWithinLine = 0 299 | case +2: 300 | cp.positionWithinLine = cp.w.Line(cp.lineIndex).Length() 301 | case -3: 302 | cp.lineIndex = 0 303 | cp.positionWithinLine = 0 304 | case +3: 305 | cp.lineIndex = cp.w.LenLines() - 1 306 | cp.positionWithinLine = cp.w.Line(cp.lineIndex).Length() 307 | } 308 | 309 | if cp.Compare(&originalPosition) == 0 { 310 | // There's no change, so don't do anything else 311 | return 312 | } 313 | 314 | cp.targetExpandedX, _ = cp.expandedPosition() // TODO: More direct 315 | 316 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 317 | } 318 | 319 | func (cp *caretPositionInternal) SetPositionFromLogical(pos mgl64.Vec2) { 320 | if pos[1] < 0 { 321 | cp.lineIndex = 0 322 | } else if pos[1] >= float64(cp.w.LenLines()) { 323 | cp.lineIndex = cp.w.LenLines() - 1 324 | } else { 325 | cp.lineIndex = int(pos[1]) 326 | } 327 | 328 | if pos[0] < 0 { 329 | cp.targetExpandedX = 0 330 | } else { 331 | cp.targetExpandedX = uint32(pos[0] + 0.5) 332 | } 333 | 334 | line := cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).End()] 335 | cp.positionWithinLine = ExpandedToLogical(line, cp.targetExpandedX) 336 | 337 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 338 | } 339 | 340 | // TrySetPositionAtLineIndex places caret at beginning of lineIndex line. It accepts out of range line indicies. 341 | func (cp *caretPositionInternal) TrySetPositionAtLineIndex(lineIndex int) { 342 | if lineIndex < 0 { 343 | lineIndex = 0 344 | } else if lineIndex > cp.w.LenLines()-1 { 345 | lineIndex = cp.w.LenLines() - 1 346 | } 347 | 348 | cp.lineIndex = lineIndex 349 | cp.positionWithinLine = 0 350 | cp.targetExpandedX = 0 351 | 352 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 353 | } 354 | 355 | func (cp *caretPositionInternal) TrySet(position uint32) { 356 | if position > uint32(cp.w.LenContent()) { 357 | position = uint32(cp.w.LenContent()) 358 | } 359 | 360 | cp.lineIndex = sort.Search(cp.w.LenLines()-1, func(lineIndex int) bool { 361 | return cp.w.Line(lineIndex+1).Start() > position 362 | }) 363 | cp.positionWithinLine = position - cp.w.Line(cp.lineIndex).Start() 364 | 365 | cp.targetExpandedX, _ = cp.expandedPosition() // TODO: More direct 366 | 367 | gist7802150.ExternallyUpdated(&cp.DepNode2Manual) 368 | } 369 | 370 | // expandedPosition returns logical character units. 371 | // Multiply by (fontWidth, fontHeight) to get physical coords. 372 | func (cp *caretPositionInternal) expandedPosition() (x uint32, y uint32) { 373 | expandedCaretPosition := ExpandedLength(cp.w.Content()[cp.w.Line(cp.lineIndex).Start():cp.w.Line(cp.lineIndex).Start()+cp.positionWithinLine], 0) 374 | 375 | return expandedCaretPosition, uint32(cp.lineIndex) 376 | } 377 | 378 | // HACK 379 | func (cp *caretPositionInternal) SetHint(caretPosition uint32, beginLineIndex int) (x uint32, y uint32) { 380 | caretPosition -= cp.w.Line(beginLineIndex).Start() 381 | caretLine := beginLineIndex 382 | for caretPosition > cp.w.Line(caretLine).Length() { 383 | caretPosition -= cp.w.Line(caretLine).Length() + 1 384 | caretLine++ 385 | } 386 | expandedCaretPosition := ExpandedLength(cp.w.Content()[cp.w.Line(caretLine).Start():cp.w.Line(caretLine).Start()+caretPosition], 0) 387 | 388 | cp.targetExpandedX, y = expandedCaretPosition, uint32(caretLine) 389 | 390 | return cp.targetExpandedX, y 391 | } 392 | 393 | func (cp *caretPositionInternal) SaveState() *caretPositionInternal { 394 | return &caretPositionInternal{ 395 | lineIndex: cp.lineIndex, 396 | positionWithinLine: cp.positionWithinLine, 397 | targetExpandedX: cp.targetExpandedX, 398 | } 399 | } 400 | 401 | func isCoreCharacter(character byte) bool { 402 | return (('a' <= character && character <= 'z') || 403 | ('A' <= character && character <= 'Z') || 404 | ('0' <= character && character <= '9') || 405 | '_' == character) 406 | } 407 | 408 | // --- 409 | 410 | type CaretPosition struct { 411 | w MultilineContentI 412 | caretPosition *caretPositionInternal 413 | selectionPosition *caretPositionInternal 414 | 415 | gist7802150.DepNode2 416 | } 417 | 418 | func NewCaretPosition(mc MultilineContentI) *CaretPosition { 419 | cp := &CaretPosition{ 420 | w: mc, 421 | caretPosition: &caretPositionInternal{w: mc}, 422 | selectionPosition: &caretPositionInternal{w: mc}, 423 | } 424 | cp.AddSources(cp.caretPosition, cp.selectionPosition) 425 | return cp 426 | } 427 | 428 | func (cp *CaretPosition) Update() {} 429 | 430 | // TODO: Should DepNode2 be used instead of this custom func? 431 | func (cp *CaretPosition) NotifyContentChanged() { 432 | cp.caretPosition.NotifyContentChanged() 433 | cp.selectionPosition.NotifyContentChanged() 434 | } 435 | 436 | func (cp *CaretPosition) Logical() uint32 { 437 | return cp.caretPosition.Logical() 438 | } 439 | 440 | func (cp *CaretPosition) SelectionRange() (start, end uint32) { 441 | min := intmath.MinUint32(cp.caretPosition.Logical(), cp.selectionPosition.Logical()) 442 | max := intmath.MaxUint32(cp.caretPosition.Logical(), cp.selectionPosition.Logical()) 443 | return min, max 444 | } 445 | 446 | func (cp *CaretPosition) SelectionRange2() (start, end *caretPositionInternal) { 447 | if cp.caretPosition.Compare(cp.selectionPosition) <= 0 { 448 | return cp.caretPosition, cp.selectionPosition 449 | } else { 450 | return cp.selectionPosition, cp.caretPosition 451 | } 452 | } 453 | 454 | func (cp *CaretPosition) AnySelection() bool { 455 | return cp.caretPosition.Compare(cp.selectionPosition) != 0 456 | } 457 | 458 | func (cp *CaretPosition) TryMoveH(direction direction, leaveSelection, jumpWords bool) { 459 | min, max := cp.SelectionRange2() 460 | 461 | switch direction { 462 | case Backward: 463 | if cp.AnySelection() && !leaveSelection { 464 | max.MoveTo(min) 465 | } else { 466 | if cp.caretPosition.Logical() >= 1 { // TODO: Where should this check happen 467 | cp.caretPosition.tryMoveH(direction, jumpWords) 468 | 469 | if !leaveSelection { 470 | cp.selectionPosition.MoveTo(cp.caretPosition) 471 | } 472 | } 473 | } 474 | case Forward: 475 | if cp.AnySelection() && !leaveSelection { 476 | min.MoveTo(max) 477 | } else { 478 | if cp.caretPosition.Logical() < uint32(cp.w.LenContent()) { // TODO: Where should this check happen 479 | cp.caretPosition.tryMoveH(direction, jumpWords) 480 | 481 | if !leaveSelection { 482 | cp.selectionPosition.MoveTo(cp.caretPosition) 483 | } 484 | } 485 | } 486 | } 487 | } 488 | 489 | // HACK: leaveSelection is currently an optional bool parameter 490 | // TODO: Change amount to a proper type with 4 values, etc. to avoid confusion with other funcs where amount can be an arbitrary number. 491 | // TODO: Rename to JumpTo or something to indicate it's a method that can never fail. 492 | // Move jumps the caret position to a new position. This operation never fails, but may not have any effect. 493 | // If amount is ±1, move by 1 character within same line if possible. 494 | // If amount is ±2, jump to start/end of line. 495 | // If amount is ±3, jump to start/end of content. 496 | func (cp *CaretPosition) Move(amount int8, leaveSelectionOptional ...bool) { 497 | // HACK, TODO: Make leaveSelection a required parameter? 498 | leaveSelection := len(leaveSelectionOptional) != 0 && leaveSelectionOptional[0] 499 | 500 | cp.caretPosition.Move(amount) 501 | 502 | if !leaveSelection { 503 | cp.selectionPosition.MoveTo(cp.caretPosition) 504 | } 505 | } 506 | 507 | func (cp *CaretPosition) MoveTo(target *caretPositionInternal) { 508 | cp.caretPosition.MoveTo(target) 509 | cp.selectionPosition.MoveTo(target) 510 | } 511 | 512 | func (cp *CaretPosition) TryMoveV(direction direction, leaveSelection, jumpWords bool) { 513 | cp.caretPosition.tryMoveV(direction, jumpWords) 514 | 515 | if !leaveSelection { 516 | cp.selectionPosition.MoveTo(cp.caretPosition) 517 | } 518 | } 519 | 520 | func (cp *CaretPosition) SetPositionFromLogical(pos mgl64.Vec2, leaveSelectionOptional ...bool) { 521 | // HACK, TODO: Make leaveSelection a required parameter? 522 | leaveSelection := len(leaveSelectionOptional) != 0 && leaveSelectionOptional[0] 523 | 524 | cp.caretPosition.SetPositionFromLogical(pos) 525 | 526 | if !leaveSelection { 527 | cp.selectionPosition.MoveTo(cp.caretPosition) 528 | } 529 | } 530 | 531 | func (cp *CaretPosition) Backspace() { 532 | cp.CreateSelectionIfNone(-1) 533 | cp.ReplaceSelectionWith("") 534 | } 535 | 536 | func (cp *CaretPosition) SelectAll() { 537 | // TODO: Not move the view 538 | cp.Move(-3) 539 | cp.Move(+3, true) 540 | } 541 | 542 | func (cp *CaretPosition) CreateSelectionIfNone(amount int32) { 543 | if !cp.AnySelection() { 544 | if (amount < 0 && cp.caretPosition.Logical() >= uint32(-amount)) || 545 | (amount > 0 && cp.caretPosition.Logical()+uint32(amount) <= uint32(cp.w.LenContent())) { 546 | 547 | cp.caretPosition.willMoveH(amount) 548 | } 549 | } 550 | } 551 | 552 | func (cp *CaretPosition) CreateSelectionLineIfNone() { 553 | if !cp.AnySelection() { 554 | cp.selectionPosition.Move(-2) 555 | cp.caretPosition.Move(+2) 556 | cp.caretPosition.tryMoveH(+1, false) 557 | } 558 | } 559 | 560 | // Replaces selection with string s and moves caret to end of s. 561 | func (cp *CaretPosition) ReplaceSelectionWith(s string) { 562 | selStart, selEnd := cp.SelectionRange2() 563 | gist7802150.SetViewGroup(cp.w, cp.w.Content()[:selStart.Logical()]+s+cp.w.Content()[selEnd.Logical():]) 564 | selStart.willMoveH(int32(len(s))) 565 | selEnd.MoveTo(selStart) 566 | } 567 | 568 | // Gets the selection content. 569 | func (cp *CaretPosition) GetSelectionContent() (selectionContent string) { 570 | selStart, selEnd := cp.SelectionRange2() 571 | return cp.w.Content()[selStart.Logical():selEnd.Logical()] 572 | } 573 | 574 | // TrySetPositionAtLineIndex places caret at beginning of lineIndex line. It accepts out of range line indicies. 575 | func (cp *CaretPosition) TrySetPositionAtLineIndex(lineIndex int) { 576 | cp.caretPosition.TrySetPositionAtLineIndex(lineIndex) 577 | cp.selectionPosition.MoveTo(cp.caretPosition) 578 | } 579 | 580 | // TODO: Change api to ask for an caretPositionInternal instance? 581 | func (cp *CaretPosition) TrySet(position uint32, leaveSelectionOptional ...bool) { 582 | // HACK, TODO: Make leaveSelection a required parameter? 583 | leaveSelection := len(leaveSelectionOptional) != 0 && leaveSelectionOptional[0] 584 | 585 | cp.caretPosition.TrySet(position) 586 | 587 | if !leaveSelection { 588 | cp.selectionPosition.MoveTo(cp.caretPosition) 589 | } 590 | } 591 | 592 | // SetSelection sets a selection starting at start of length. Indices must be valid. 593 | // TODO: Optimise by not doing the "try" logic. 594 | func (cp *CaretPosition) SetSelection(start, length uint32) { 595 | cp.TrySet(start) 596 | cp.caretPosition.willMoveH(int32(length)) 597 | } 598 | 599 | func (cp *CaretPosition) ExpandSelectionToWord() { 600 | switch 1 { 601 | case 0: 602 | if cp.AnySelection() { 603 | return 604 | } 605 | case 1: 606 | // Try it out so that it always works, even if there's already a selection... 607 | cp.selectionPosition, cp.caretPosition = cp.SelectionRange2() 608 | } 609 | 610 | cp.selectionPosition.expandSelection(-1) 611 | cp.caretPosition.expandSelection(+1) 612 | } 613 | 614 | // ExpandedPosition returns logical character units of primary cursor position. 615 | // Multiply by (fontWidth, fontHeight) to get physical coords. 616 | func (cp *CaretPosition) ExpandedPosition() (x, y uint32) { 617 | return cp.caretPosition.expandedPosition() 618 | } 619 | 620 | // LineNumber returns line number of primary caret. Line number is line index + 1, so it starts at 1. 621 | func (cp *CaretPosition) LineNumber() int { 622 | return cp.caretPosition.lineIndex + 1 623 | } 624 | 625 | // lineIndexSpan returns line indicies that are spanned by the selection. 626 | func (cp *CaretPosition) lineIndexSpan() (first, last int) { 627 | min := intmath.MinInt(cp.caretPosition.lineIndex, cp.selectionPosition.lineIndex) 628 | max := intmath.MaxInt(cp.caretPosition.lineIndex, cp.selectionPosition.lineIndex) 629 | return min, max 630 | } 631 | 632 | // DecreaseIndent decreases indent of lines that fall within selection by 1 tab (for lines where it's possible). 633 | func (cp *CaretPosition) DecreaseIndent() { 634 | selStart, selEnd := cp.SelectionRange2() 635 | first, last := cp.lineIndexSpan() 636 | 637 | newContent := cp.w.Content()[:cp.w.Line(first).Start()] 638 | for lineIndex := first; lineIndex <= last; lineIndex++ { 639 | contentLine := cp.w.Line(lineIndex) 640 | 641 | if lineIndex != first { 642 | newContent += "\n" 643 | } 644 | line := cp.w.Content()[contentLine.Start():contentLine.End()] 645 | if strings.HasPrefix(line, "\t") { 646 | line = line[1:] // Trim leading tab. 647 | 648 | if lineIndex == first { 649 | selStart.Move(-1) 650 | } 651 | if lineIndex == last { 652 | selEnd.Move(-1) 653 | } 654 | } 655 | newContent += line 656 | } 657 | newContent += cp.w.Content()[cp.w.Line(last).End():] 658 | 659 | gist7802150.SetViewGroup(cp.w, newContent) 660 | } 661 | 662 | // IncreaseIndent increases indent of all lines that fall within selection by 1 tab. 663 | func (cp *CaretPosition) IncreaseIndent() { 664 | selStart, selEnd := cp.SelectionRange2() 665 | first, last := cp.lineIndexSpan() 666 | 667 | newContent := cp.w.Content()[:cp.w.Line(first).Start()] 668 | for lineIndex := first; lineIndex <= last; lineIndex++ { 669 | contentLine := cp.w.Line(lineIndex) 670 | 671 | if lineIndex != first { 672 | newContent += "\n" 673 | } 674 | newContent += "\t" + cp.w.Content()[contentLine.Start():contentLine.End()] 675 | } 676 | newContent += cp.w.Content()[cp.w.Line(last).End():] 677 | 678 | gist7802150.SetViewGroup(cp.w, newContent) 679 | 680 | // Safe to use willMoveH after SetViewGroup, since we know each line will always get a character longer. 681 | selStart.willMoveH(1) 682 | selEnd.willMoveH(1) 683 | } 684 | 685 | // LeadingTabCount returns number of leading tabs on current line. 686 | func (cp *CaretPosition) LeadingTabCount() int { 687 | tabCount := 0 688 | 689 | lineToCaret := cp.w.Content()[cp.w.Line(cp.caretPosition.lineIndex).Start():cp.Logical()] 690 | 691 | for _, c := range lineToCaret { 692 | if c == '\t' { 693 | tabCount++ 694 | } else { 695 | break 696 | } 697 | } 698 | 699 | return tabCount 700 | } 701 | 702 | func (cp *CaretPosition) SaveState() CaretPosition { 703 | return CaretPosition{ 704 | caretPosition: cp.caretPosition.SaveState(), 705 | selectionPosition: cp.selectionPosition.SaveState(), 706 | } 707 | } 708 | 709 | func (cp *CaretPosition) RestoreState(state CaretPosition) { 710 | // TODO: Add support for gracefully handling content changing since the time state was saved. 711 | cp.caretPosition.MoveTo(state.caretPosition) 712 | cp.selectionPosition.MoveTo(state.selectionPosition) 713 | } 714 | -------------------------------------------------------------------------------- /caret/caret_test.go: -------------------------------------------------------------------------------- 1 | package caret_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/shurcooL-legacy/Conception-go/caret" 7 | "github.com/shurcooL-legacy/Conception-go/pkg/multilinecontent" 8 | "github.com/shurcooL/go-goon" 9 | ) 10 | 11 | const sample = `package main 12 | 13 | import "fmt" 14 | 15 | func main() { 16 | fmt.Println("Hello world.") 17 | fmt.Println("How are you?") 18 | 19 | if 1 == 1 { 20 | fmt.Println("Good!") 21 | } 22 | } 23 | ` 24 | 25 | func ExampleCaretPosition() { 26 | mc := multilinecontent.NewString(sample) 27 | cp := caret.NewCaretPosition(mc) 28 | cp.SetSelection(43, 45) 29 | 30 | fmt.Println(cp.AnySelection()) 31 | goon.Dump(cp.GetSelectionContent()) 32 | 33 | // Output: 34 | // true 35 | // (string)("fmt.Println(\"Hello world.\")\n\tfmt.Println(\"How") 36 | } 37 | -------------------------------------------------------------------------------- /caret/whole_word.go: -------------------------------------------------------------------------------- 1 | package caret 2 | 3 | // TODO: Implement IsWholeWord more efficiently without relying on virtual CaretPosition, just use linear content access. 4 | // But reuse tryMoveH jump condition logic for word boundaries. 5 | 6 | func IsWholeWord(content MultilineContentI, caretPosition *CaretPosition) bool { 7 | if !caretPosition.AnySelection() { 8 | return false 9 | } 10 | 11 | start, end := caretPosition.SelectionRange2() 12 | 13 | // Figure out if the selection is a whole word. 14 | x := &caretPositionInternal{w: content} 15 | y := &caretPositionInternal{w: content} 16 | x.MoveTo(start) 17 | y.MoveTo(end) 18 | x.tryMoveH(+1, true) 19 | y.tryMoveH(-1, true) 20 | 21 | return (y.Compare(start) == 0 && x.Compare(end) == 0) 22 | } 23 | 24 | func IsWholeWord2(content MultilineContentI, start, length uint32) bool { 25 | cp := NewCaretPosition(content) 26 | cp.SetSelection(start, length) 27 | 28 | return IsWholeWord(content, cp) 29 | } 30 | -------------------------------------------------------------------------------- /chanwriter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type ChanWriter chan []byte 4 | 5 | func (cw ChanWriter) Write(p []byte) (n int, err error) { 6 | // Make a copy of p in order to avoid retaining it. 7 | b := make([]byte, len(p)) 8 | copy(b, p) 9 | cw <- b 10 | return len(b), nil 11 | } 12 | -------------------------------------------------------------------------------- /data/fonts/Helvetica Neue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shurcooL-legacy/Conception-go/7c2afaf2e798e042be848e0180316318c4c790c3/data/fonts/Helvetica Neue.png -------------------------------------------------------------------------------- /data/fonts/Menlo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shurcooL-legacy/Conception-go/7c2afaf2e798e042be848e0180316318c4c790c3/data/fonts/Menlo.png -------------------------------------------------------------------------------- /data/fonts/Tahoma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shurcooL-legacy/Conception-go/7c2afaf2e798e042be848e0180316318c4c790c3/data/fonts/Tahoma.png -------------------------------------------------------------------------------- /event/event.go: -------------------------------------------------------------------------------- 1 | // Package event is a package for common event handling. 2 | package event 3 | 4 | import ( 5 | "io" 6 | "time" 7 | 8 | "github.com/go-gl/mathgl/mgl64" 9 | ) 10 | 11 | type VirtualCategory uint8 12 | 13 | const ( 14 | Typing VirtualCategory = iota 15 | Pointing 16 | Windowing 17 | ) 18 | 19 | //go:generate stringer -type=VirtualCategory 20 | 21 | type Pointer struct { 22 | VirtualCategory VirtualCategory 23 | Mapping Widgeters // Always reflects current pointer state. 24 | OriginMapping Widgeters // Updated only when pointer is moved while not active (e.g., where mouse button was first pressed down). 25 | State PointerState 26 | } 27 | 28 | func (this *Pointer) Render() { 29 | /*switch { 30 | case this.VirtualCategory == POINTING && len(this.State.Axes) >= 2: 31 | // Prevent pointer from being drawn when the OS mouse pointer is visible. 32 | { 33 | // HACK 34 | var windowSize [2]int 35 | if globalWindow != nil { 36 | windowSize[0], windowSize[1], _ = globalWindow.GetSize() 37 | } 38 | 39 | // HACK: OS X specific. 40 | const border = 3 41 | if this.State.Axes[1] < 0 || this.State.Axes[0] < border || this.State.Axes[0] >= float64(windowSize[0])-border || this.State.Axes[1] >= float64(windowSize[1])-border { 42 | break 43 | } 44 | } 45 | 46 | gl.PushMatrix() 47 | defer gl.PopMatrix() 48 | gl.Translated(float64(nearInt64(this.State.Axes[0]))+0.5, float64(nearInt64(this.State.Axes[1]))+0.5, 0) 49 | 50 | const size float64 = 12 51 | gl.Color3d(1, 1, 1) 52 | gl.Begin(gl.TRIANGLE_FAN) 53 | gl.Vertex2d(0, 0) 54 | gl.Vertex2d(0, size) 55 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 56 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 57 | gl.End() 58 | 59 | gl.Begin(gl.LINE_LOOP) 60 | gl.Color3d(0, 0, 0) 61 | gl.Vertex2d(0, 0) 62 | gl.Vertex2d(0, size) 63 | gl.Color3d(0.75, 0.75, 0.75) 64 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 65 | gl.Color3d(0, 0, 0) 66 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 67 | gl.End() 68 | }*/ 69 | } 70 | 71 | type PointerState struct { 72 | Buttons []bool // True means pressed down. 73 | Axes []float64 74 | 75 | Time time.Time 76 | } 77 | 78 | // A pointer is defined to be active if any of its buttons are pressed down. 79 | func (ps *PointerState) IsActive() bool { 80 | for _, button := range ps.Buttons { 81 | if button { 82 | return true 83 | } 84 | } 85 | return false 86 | } 87 | 88 | func (ps *PointerState) Button(button int) bool { 89 | if button >= len(ps.Buttons) { 90 | return false 91 | } 92 | return ps.Buttons[button] 93 | } 94 | 95 | type EventType uint8 96 | 97 | const ( 98 | ButtonEvent EventType = iota 99 | CharacterEvent 100 | SliderEvent 101 | AxisEvent 102 | PointerActivation 103 | PointerDeactivation 104 | ) 105 | 106 | //go:generate stringer -type=EventType 107 | 108 | type eventTypeSet map[EventType]struct{} 109 | 110 | func (s eventTypeSet) Has(et EventType) bool { 111 | _, ok := s[et] 112 | return ok 113 | } 114 | 115 | type InputEvent struct { 116 | Pointer *Pointer 117 | EventTypes eventTypeSet 118 | InputID uint16 119 | // TODO: Add pointers to BeforeState and AfterState? 120 | 121 | Buttons []bool // First button has id InputID, second has id InputID+1, etc. 122 | // TODO: Characters? Split into distinct event types, bundle up in an event frame based on time? 123 | Sliders []float64 124 | Axes []float64 125 | ModifierKey uint8 // TODO: Think about the best solution here. 126 | } 127 | 128 | /*func (e InputEvent) String() string { 129 | return spew.Sdump(e) 130 | }*/ 131 | 132 | func ProcessInputEventQueue(inputEventQueue []InputEvent, widget Widgeter) []InputEvent { 133 | for len(inputEventQueue) > 0 { 134 | /*inputEvent := inputEventQueue[0] 135 | 136 | //fmt.Println(inputEvent) 137 | //spew.Dump(inputEvent)*/ 138 | 139 | inputEvent := inputEventQueue[0] 140 | 141 | // TODO: Calculate whether a pointing pointer moved relative to canvas in a better way... what if canvas is moved via keyboard, etc. 142 | pointingPointerMovedRelativeToCanvas := inputEvent.Pointer.VirtualCategory == Pointing && 143 | (inputEvent.EventTypes.Has(AxisEvent) && inputEvent.InputID == 0 || inputEvent.EventTypes.Has(SliderEvent) && inputEvent.InputID == 2) 144 | 145 | if pointingPointerMovedRelativeToCanvas { 146 | LocalPosition := mgl64.Vec2{inputEvent.Pointer.State.Axes[0], inputEvent.Pointer.State.Axes[1]} 147 | 148 | // Clear previously hit widgets. 149 | for _, widget := range inputEvent.Pointer.Mapping { 150 | delete(widget.HoverPointers(), inputEvent.Pointer) 151 | } 152 | inputEvent.Pointer.Mapping = []Widgeter{} 153 | 154 | // Recalculate currently hit widgets. 155 | inputEvent.Pointer.Mapping = append(inputEvent.Pointer.Mapping, widget.Hit(LocalPosition)...) 156 | for _, widget := range inputEvent.Pointer.Mapping { 157 | widget.HoverPointers()[inputEvent.Pointer] = true 158 | } 159 | } 160 | 161 | // Populate OriginMapping (but only when pointer is moved while not active, and this isn't a deactivation since that's handled below). 162 | if pointingPointerMovedRelativeToCanvas && 163 | !inputEvent.EventTypes.Has(PointerDeactivation) && !inputEvent.Pointer.State.IsActive() { 164 | 165 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 166 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 167 | } 168 | 169 | for _, widget := range inputEvent.Pointer.OriginMapping { 170 | widget.ProcessEvent(inputEvent) 171 | } 172 | 173 | // Populate OriginMapping (but only upon pointer deactivation event). 174 | if inputEvent.Pointer.VirtualCategory == Pointing && inputEvent.EventTypes.Has(PointerDeactivation) { 175 | 176 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 177 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 178 | } 179 | 180 | inputEventQueue = inputEventQueue[1:] 181 | } 182 | 183 | return inputEventQueue 184 | } 185 | 186 | func EnqueueInputEvent(inputEventQueue []InputEvent, inputEvent InputEvent) []InputEvent { 187 | preStateActive := inputEvent.Pointer.State.IsActive() 188 | 189 | { 190 | if inputEvent.EventTypes.Has(ButtonEvent) { 191 | // Extend slice if needed. 192 | neededSize := int(inputEvent.InputID) + len(inputEvent.Buttons) 193 | if neededSize > len(inputEvent.Pointer.State.Buttons) { 194 | inputEvent.Pointer.State.Buttons = append(inputEvent.Pointer.State.Buttons, make([]bool, neededSize-len(inputEvent.Pointer.State.Buttons))...) 195 | } 196 | 197 | copy(inputEvent.Pointer.State.Buttons[inputEvent.InputID:], inputEvent.Buttons) 198 | } 199 | 200 | if inputEvent.EventTypes.Has(AxisEvent) { 201 | // Extend slice if needed. 202 | neededSize := int(inputEvent.InputID) + len(inputEvent.Axes) 203 | if neededSize > len(inputEvent.Pointer.State.Axes) { 204 | inputEvent.Pointer.State.Axes = append(inputEvent.Pointer.State.Axes, make([]float64, neededSize-len(inputEvent.Pointer.State.Axes))...) 205 | } 206 | 207 | copy(inputEvent.Pointer.State.Axes[inputEvent.InputID:], inputEvent.Axes) 208 | } 209 | 210 | inputEvent.Pointer.State.Time = time.Now() 211 | } 212 | 213 | postStateActive := inputEvent.Pointer.State.IsActive() 214 | 215 | switch { 216 | case !preStateActive && postStateActive: 217 | inputEvent.EventTypes[PointerActivation] = struct{}{} 218 | case preStateActive && !postStateActive: 219 | inputEvent.EventTypes[PointerDeactivation] = struct{}{} 220 | } 221 | 222 | return append(inputEventQueue, inputEvent) 223 | } 224 | 225 | // --- 226 | 227 | func nearInt64(value float64) int64 { 228 | if value >= 0 { 229 | return int64(value + 0.5) 230 | } else { 231 | return int64(value - 0.5) 232 | } 233 | } 234 | 235 | // ===== 236 | 237 | // --- 238 | 239 | type ChangeListener interface { 240 | NotifyChange() 241 | } 242 | 243 | type ChangeListenerFunc func() 244 | 245 | func (f ChangeListenerFunc) NotifyChange() { f() } 246 | 247 | // --- 248 | 249 | type DepNodeI interface { 250 | AddChangeListener(l ChangeListener) 251 | } 252 | 253 | type DepNode struct { 254 | changeListeners []ChangeListener 255 | } 256 | 257 | func (this *DepNode) AddChangeListener(l ChangeListener) { 258 | this.changeListeners = append(this.changeListeners, l) 259 | 260 | l.NotifyChange() // TODO: In future, don't literally NotifyChange() right away, as this can lead to duplicate work; instead mark as "need to update" for next run. 261 | } 262 | 263 | // Pre-condition: l is a change listener that exists 264 | func (this *DepNode) RemoveChangeListener(l ChangeListener) { 265 | for i := range this.changeListeners { 266 | if this.changeListeners[i] == l { 267 | // Delete. 268 | copy(this.changeListeners[i:], this.changeListeners[i+1:]) 269 | this.changeListeners[len(this.changeListeners)-1] = nil 270 | this.changeListeners = this.changeListeners[:len(this.changeListeners)-1] 271 | //println("removed ith element of originally this many", i, len(this.changeListeners)+1) 272 | return 273 | } 274 | } 275 | panic("RemoveChangeListener: ChangeListener to be deleted wasn't found.") 276 | } 277 | 278 | func (this *DepNode) NotifyAllListeners() { 279 | // TODO: In future, don't literally NotifyChange() right away, as this can lead to duplicate work; instead mark as "need to update" for next run. 280 | for _, changeListener := range this.changeListeners { 281 | changeListener.NotifyChange() 282 | } 283 | } 284 | 285 | // --- 286 | 287 | type Widgeter interface { 288 | PollLogic() 289 | io.Closer 290 | Layout() 291 | LayoutNeeded() 292 | Render() 293 | Hit(mgl64.Vec2) []Widgeter 294 | ProcessEvent(InputEvent) // TODO: Upgrade to MatchEventQueue() or so. 295 | ContainsWidget(widget, target Widgeter) bool // Returns true if target is widget or within it. 296 | 297 | Pos() *mgl64.Vec2 298 | Size() *mgl64.Vec2 299 | HoverPointers() map[*Pointer]bool 300 | Parent() Widgeter 301 | SetParent(Widgeter) 302 | 303 | ParentToLocal(mgl64.Vec2) mgl64.Vec2 304 | 305 | //DepNodeI 306 | } 307 | 308 | type Widgeters []Widgeter 309 | 310 | func (widgets Widgeters) ContainsWidget(target Widgeter) bool { 311 | for _, widget := range widgets { 312 | if widget.ContainsWidget(widget, target) { 313 | return true 314 | } 315 | } 316 | return false 317 | } 318 | -------------------------------------------------------------------------------- /event/eventtype_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=EventType; DO NOT EDIT 2 | 3 | package event 4 | 5 | import "fmt" 6 | 7 | const _EventType_name = "BUTTON_EVENTCHARACTER_EVENTSLIDER_EVENTAXIS_EVENTPOINTER_ACTIVATIONPOINTER_DEACTIVATION" 8 | 9 | var _EventType_index = [...]uint8{0, 12, 27, 39, 49, 67, 87} 10 | 11 | func (i EventType) String() string { 12 | if i+1 >= EventType(len(_EventType_index)) { 13 | return fmt.Sprintf("EventType(%d)", i) 14 | } 15 | return _EventType_name[_EventType_index[i]:_EventType_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /event/virtualcategory_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type=VirtualCategory; DO NOT EDIT 2 | 3 | package event 4 | 5 | import "fmt" 6 | 7 | const _VirtualCategory_name = "TYPINGPOINTINGWINDOWING" 8 | 9 | var _VirtualCategory_index = [...]uint8{0, 6, 14, 23} 10 | 11 | func (i VirtualCategory) String() string { 12 | if i+1 >= VirtualCategory(len(_VirtualCategory_index)) { 13 | return fmt.Sprintf("VirtualCategory(%d)", i) 14 | } 15 | return _VirtualCategory_name[_VirtualCategory_index[i]:_VirtualCategory_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /font.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/png" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/go-gl/gl/v2.1/gl" 13 | "github.com/go-gl/mathgl/mgl64" 14 | 15 | "github.com/shurcooL-legacy/Conception-go/caret" 16 | ) 17 | 18 | var oFontBase, oFontBackground uint32 19 | var lodBias float64 = -66.67 20 | 21 | // FontOptions specifies the properties of the font. 22 | type FontOptions uint8 23 | 24 | const ( 25 | Regular FontOptions = iota 26 | Bold 27 | Italic 28 | BoldItalic 29 | ) 30 | 31 | // IsBold returns true if the font has the bold property set. 32 | func (fo FontOptions) IsBold() bool { return fo == Bold || fo == BoldItalic } 33 | 34 | // IsItalic returns true if the font has the italic property set. 35 | func (fo FontOptions) IsItalic() bool { return fo == Italic || fo == BoldItalic } 36 | 37 | type OpenGLStream struct { 38 | pos mgl64.Vec2 39 | lineStartX float64 40 | advance uint32 41 | 42 | FontOptions FontOptions 43 | BorderColor *mgl64.Vec3 // nil means no border color. 44 | BackgroundColor *mgl64.Vec3 // nil means no background color. 45 | ShowInvisibles bool 46 | } 47 | 48 | func NewOpenGLStream(pos mgl64.Vec2) *OpenGLStream { 49 | return &OpenGLStream{pos: pos, lineStartX: pos[0]} 50 | } 51 | 52 | func (o *OpenGLStream) SetPos(pos mgl64.Vec2) { 53 | o.pos = pos 54 | o.lineStartX = pos[0] 55 | o.advance = 0 56 | } 57 | 58 | func (o *OpenGLStream) SetPosWithExpandedPosition(pos mgl64.Vec2, x, y uint32) { 59 | o.pos = pos.Add(mgl64.Vec2{float64(x * fontWidth), float64(y * fontHeight)}) 60 | o.lineStartX = pos[0] 61 | o.advance = x 62 | } 63 | 64 | func (o *OpenGLStream) PrintText(s string) { 65 | for { 66 | end := strings.Index(s, "\n") 67 | 68 | length := len(s) 69 | if end != -1 { 70 | length = end 71 | } 72 | o.PrintLine(s[:length]) 73 | 74 | if end == -1 { 75 | break 76 | } else { 77 | //o.NewLine() 78 | o.PrintSegment(" ") // Newline 79 | o.pos[1] += fontHeight 80 | o.advanceReset() 81 | s = s[end+1:] 82 | } 83 | } 84 | } 85 | 86 | // Input shouldn't have newlines 87 | func (o *OpenGLStream) PrintLine(s string) { 88 | if o.BorderColor != nil { 89 | gl.PushAttrib(gl.CURRENT_BIT) 90 | 91 | expandedLineLength := caret.ExpandedLength(s, o.advance) 92 | 93 | backgroundColor := nearlyWhiteColor 94 | if o.BackgroundColor != nil { 95 | backgroundColor = *o.BackgroundColor 96 | } 97 | 98 | DrawInnerRoundedBox(o.pos, mgl64.Vec2{fontWidth * float64(expandedLineLength), fontHeight}, *o.BorderColor, backgroundColor) 99 | 100 | gl.PopAttrib() 101 | } 102 | 103 | segments := strings.Split(s, "\t") 104 | for index, segment := range segments { 105 | o.PrintSegment(segment) 106 | o.advanceBy(uint32(len(segment))) 107 | if index+1 < len(segments) { 108 | tabSpaces := 4 - (o.advance % 4) 109 | o.PrintSegment(strings.Repeat(" ", int(tabSpaces))) // Tab. 110 | if o.ShowInvisibles { 111 | gl.PushAttrib(gl.CURRENT_BIT) 112 | DrawBorderlessBox(o.pos.Add(mgl64.Vec2{1, fontHeight/2 - 1}), mgl64.Vec2{fontWidth*float64(tabSpaces) - 2, 2}, selectedTextDarkColor) 113 | gl.PopAttrib() 114 | } 115 | o.advanceBy(tabSpaces) 116 | } 117 | } 118 | } 119 | 120 | func (o *OpenGLStream) advanceBy(amount uint32) { 121 | o.advance += amount 122 | o.afterAdvance() 123 | } 124 | func (o *OpenGLStream) advanceReset() { 125 | o.advance = 0 126 | o.afterAdvance() 127 | } 128 | func (o *OpenGLStream) afterAdvance() { 129 | o.pos[0] = o.lineStartX + float64(fontWidth*o.advance) 130 | } 131 | 132 | // Shouldn't have tabs nor newlines 133 | func (o *OpenGLStream) PrintSegment(s string) { 134 | if s == "" { 135 | return 136 | } 137 | 138 | if o.BackgroundColor != nil && o.BorderColor == nil { 139 | gl.PushAttrib(gl.CURRENT_BIT) 140 | gl.Color3dv(&o.BackgroundColor[0]) 141 | gl.PushMatrix() 142 | gl.Translated(o.pos[0], o.pos[1], 0) 143 | for range s { 144 | gl.CallList(oFontBackground) 145 | } 146 | gl.PopMatrix() 147 | gl.PopAttrib() 148 | } 149 | 150 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, float32(lodBias*0.01)) 151 | 152 | gl.Enable(gl.BLEND) 153 | defer gl.Disable(gl.BLEND) 154 | gl.Enable(gl.TEXTURE_2D) 155 | defer gl.Disable(gl.TEXTURE_2D) 156 | 157 | gl.PushMatrix() 158 | gl.Translated(o.pos[0], o.pos[1], 0) 159 | gl.ListBase(oFontBase + uint32(o.FontOptions)*96) 160 | gl.CallLists(int32(len(s)), gl.UNSIGNED_BYTE, gl.Ptr(&[]byte(s)[0])) 161 | gl.PopMatrix() 162 | 163 | //checkGLError() 164 | } 165 | 166 | // --- 167 | 168 | const fontWidth, fontHeight = 6, 12 169 | 170 | func InitFont() { 171 | LoadTexture(filepath.Join("data", "fonts", "Menlo.png")) 172 | 173 | oFontBase = gl.GenLists(32 + 96*4) 174 | for i := 0; i < 96*4; i++ { 175 | const shiftX, shiftY = float64(1.0 / 16), float64(1.0 / 6 / 4) 176 | 177 | indexX, indexY := i%16, i/16 178 | 179 | gl.NewList(oFontBase+uint32(i+32), gl.COMPILE) 180 | gl.Begin(gl.QUADS) 181 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY)*shiftY) 182 | gl.Vertex2i(0, 0) 183 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY)*shiftY) 184 | gl.Vertex2i(fontWidth, 0) 185 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY+1)*shiftY) 186 | gl.Vertex2i(fontWidth, fontHeight) 187 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY+1)*shiftY) 188 | gl.Vertex2i(0, fontHeight) 189 | gl.End() 190 | gl.Translated(fontWidth, 0.0, 0.0) 191 | gl.EndList() 192 | } 193 | 194 | oFontBackground = gl.GenLists(1) 195 | gl.NewList(oFontBackground, gl.COMPILE) 196 | gl.Begin(gl.QUADS) 197 | gl.Vertex2i(0, 0) 198 | gl.Vertex2i(0, fontHeight) 199 | gl.Vertex2i(fontWidth, fontHeight) 200 | gl.Vertex2i(fontWidth, 0) 201 | gl.End() 202 | gl.Translated(fontWidth, 0.0, 0.0) 203 | gl.EndList() 204 | 205 | checkGLError() 206 | } 207 | 208 | func DeinitFont() { 209 | gl.DeleteLists(oFontBase, 32+96*4) 210 | gl.DeleteLists(oFontBackground, 1) 211 | } 212 | 213 | func LoadTexture(path string) { 214 | //fmt.Printf("Trying to load texture %q: ", path) 215 | 216 | // Open the file 217 | file, err := os.Open(path) 218 | if err != nil { 219 | fmt.Println(os.Getwd()) 220 | log.Fatal(err) 221 | } 222 | defer file.Close() 223 | 224 | // Decode the image 225 | img, _, err := image.Decode(file) 226 | if err != nil { 227 | log.Fatal(err) 228 | } 229 | 230 | bounds := img.Bounds() 231 | //fmt.Printf("Loaded %vx%v texture.\n", bounds.Dx(), bounds.Dy()) 232 | 233 | var format int 234 | var pixPointer *uint8 235 | switch img := img.(type) { 236 | case *image.RGBA: 237 | format = gl.RGBA 238 | pixPointer = &img.Pix[0] 239 | case *image.NRGBA: 240 | format = gl.RGBA 241 | pixPointer = &img.Pix[0] 242 | case *image.Gray: 243 | format = gl.ALPHA 244 | pixPointer = &img.Pix[0] 245 | default: 246 | log.Fatalf("LoadTexture: Unsupported type %T.\n", img) 247 | } 248 | 249 | var texture uint32 250 | gl.GenTextures(1, &texture) 251 | gl.BindTexture(gl.TEXTURE_2D, texture) 252 | gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE) 253 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 254 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 255 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, -0.5) 256 | gl.TexImage2D(gl.TEXTURE_2D, 0, int32(format), int32(bounds.Dx()), int32(bounds.Dy()), 0, uint32(format), gl.UNSIGNED_BYTE, gl.Ptr(pixPointer)) 257 | checkGLError() 258 | } 259 | 260 | func checkGLError() { 261 | err := gl.GetError() 262 | if err != 0 { 263 | panic(fmt.Errorf("GL error: %v", err)) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/shurcooL-legacy/Conception-go 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 7 | github.com/fsnotify/fsnotify v1.4.7 // indirect 8 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 9 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 10 | github.com/go-gl/mathgl v0.0.0-20190713194549-592312d8590a 11 | github.com/gopherjs/gopherjs v0.0.0-20200209144316-f9cef593def5 // indirect 12 | github.com/goxjs/gl v0.0.0-20171128034433-dc8f4a9a3c9c 13 | github.com/goxjs/glfw v0.0.0-20191126052801-d2efb5f20838 14 | github.com/kisielk/gotool v1.0.0 // indirect 15 | github.com/mattn/go-runewidth v0.0.8 16 | github.com/mb0/diff v0.0.0-20131118162322-d8d9a906c24d 17 | github.com/microcosm-cc/bluemonday v1.0.2 // indirect 18 | github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 // indirect 19 | github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab // indirect 20 | github.com/pkg/math v0.0.0-20141027224758-f2ed9e40e245 21 | github.com/sergi/go-diff v1.1.0 22 | github.com/shurcooL-legacy/octicons v0.0.0-20180602233653-10c8018fdc76 23 | github.com/shurcooL/frontend v0.0.0-20191223011957-441c9e127002 24 | github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470 25 | github.com/shurcooL/go v0.0.0-20191216061654-b114cc39af9f 26 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 27 | github.com/shurcooL/gopherjslib v0.0.0-20200209150458-30f639db26d4 // indirect 28 | github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect 29 | github.com/shurcooL/highlight_go v0.0.0-20191220051317-782971ddf21b 30 | github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 31 | github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 32 | github.com/shurcooL/markdownfmt v0.0.0-20191117054414-21fe95c248e9 33 | github.com/shurcooL/octicon v0.0.0-20191102190552-cbb32d6a785c // indirect 34 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 35 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect 36 | github.com/sourcegraph/go-diff v0.5.1 37 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e 38 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2 39 | golang.org/x/tools v0.0.0-20200207224406-61798d64f025 40 | gopkg.in/pipe.v2 v2.0.0-20140414041502-3c2ca4d52544 41 | honnef.co/go/js/dom v0.0.0-20190526011328-ebc4cf92d81f // indirect 42 | ) 43 | -------------------------------------------------------------------------------- /install-into-bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GO111MODULE=on GOPATH="$HOME/Library/Caches/go" GOBIN="$HOME/Dropbox/Applications/Conception.app/Contents/MacOS" go install -v 4 | -------------------------------------------------------------------------------- /page/events/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /page/events/main.go: -------------------------------------------------------------------------------- 1 | // events is a test program for printing basic events. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "runtime" 7 | 8 | "github.com/goxjs/gl" 9 | "github.com/goxjs/glfw" 10 | "github.com/shurcooL-legacy/Conception-go/event" 11 | ) 12 | 13 | var mousePointer = &event.Pointer{VirtualCategory: event.Pointing} 14 | var keyboardPointer = &event.Pointer{VirtualCategory: event.Typing} 15 | 16 | func init() { 17 | runtime.LockOSThread() 18 | } 19 | 20 | func main() { 21 | if err := glfw.Init(gl.ContextWatcher); err != nil { 22 | panic(err) 23 | } 24 | defer glfw.Terminate() 25 | 26 | window, err := glfw.CreateWindow(400, 400, "", nil, nil) 27 | if err != nil { 28 | panic(err) 29 | } 30 | window.MakeContextCurrent() 31 | 32 | glfw.SwapInterval(1) // Vsync. 33 | 34 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 35 | gl.Viewport(0, 0, framebufferSize0, framebufferSize1) 36 | 37 | var windowSize [2]int 38 | windowSize[0], windowSize[1] = w.GetSize() 39 | _, _ = windowSize[0], windowSize[1] 40 | 41 | /*inputEvent := InputEvent{ 42 | Pointer: windowPointer, 43 | EventTypes: map[EventType]struct{}{AXIS_EVENT: struct{}{}}, 44 | InputId: 0, 45 | Buttons: nil, 46 | Sliders: nil, 47 | Axes: []float64{float64(windowSize[0]), float64(windowSize[1])}, 48 | } 49 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent)*/ 50 | } 51 | { 52 | var framebufferSize [2]int 53 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 54 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 55 | } 56 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 57 | 58 | var inputEventQueue []event.InputEvent 59 | 60 | window.SetMouseMovementCallback(func(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) { 61 | inputEvent := event.InputEvent{ 62 | Pointer: mousePointer, 63 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 64 | InputID: 0, 65 | Buttons: nil, 66 | Sliders: []float64{xdelta, ydelta}, 67 | } 68 | if w.GetInputMode(glfw.CursorMode) != glfw.CursorDisabled { 69 | inputEvent.EventTypes[event.AxisEvent] = struct{}{} 70 | inputEvent.Axes = []float64{xpos, ypos} 71 | } 72 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 73 | }) 74 | 75 | window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { 76 | inputEvent := event.InputEvent{ 77 | Pointer: mousePointer, 78 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 79 | InputID: 2, 80 | Buttons: nil, 81 | Sliders: []float64{yoff, xoff}, 82 | Axes: nil, 83 | } 84 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 85 | }) 86 | 87 | window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 88 | inputEvent := event.InputEvent{ 89 | Pointer: mousePointer, 90 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 91 | InputID: uint16(button), 92 | Buttons: []bool{action != glfw.Release}, 93 | Sliders: nil, 94 | Axes: nil, 95 | ModifierKey: uint8(mods), 96 | } 97 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 98 | }) 99 | 100 | window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 101 | inputEvent := event.InputEvent{ 102 | Pointer: keyboardPointer, 103 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 104 | InputID: uint16(key), 105 | Buttons: []bool{action != glfw.Release}, 106 | Sliders: nil, 107 | Axes: nil, 108 | ModifierKey: uint8(mods), 109 | } 110 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 111 | 112 | // HACK. 113 | switch { 114 | case key == glfw.Key1 && action == glfw.Press: 115 | window.SetInputMode(glfw.CursorMode, glfw.CursorNormal) 116 | case key == glfw.Key2 && action == glfw.Press: 117 | window.SetInputMode(glfw.CursorMode, glfw.CursorHidden) 118 | case key == glfw.Key3 && action == glfw.Press: 119 | window.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) 120 | } 121 | }) 122 | 123 | window.SetCharCallback(func(w *glfw.Window, char rune) { 124 | inputEvent := event.InputEvent{ 125 | Pointer: keyboardPointer, 126 | EventTypes: map[event.EventType]struct{}{event.CharacterEvent: {}}, 127 | InputID: uint16(char), 128 | Buttons: nil, 129 | Sliders: nil, 130 | Axes: nil, 131 | } 132 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 133 | }) 134 | 135 | gl.ClearColor(0.85, 0.85, 0.85, 1) 136 | 137 | for !window.ShouldClose() { 138 | glfw.PollEvents() 139 | 140 | // Process Input. 141 | inputEventQueue = processInputEventQueue(inputEventQueue) 142 | 143 | gl.Clear(gl.COLOR_BUFFER_BIT) 144 | 145 | mousePointer.Render() 146 | 147 | window.SwapBuffers() 148 | runtime.Gosched() 149 | } 150 | } 151 | 152 | func processInputEventQueue(inputEventQueue []event.InputEvent) []event.InputEvent { 153 | for len(inputEventQueue) > 0 { 154 | inputEvent := inputEventQueue[0] 155 | 156 | fmt.Println(inputEvent) 157 | //spew.Dump(inputEvent) 158 | 159 | inputEventQueue = inputEventQueue[1:] 160 | } 161 | 162 | return inputEventQueue 163 | } 164 | -------------------------------------------------------------------------------- /page/page1/main.go: -------------------------------------------------------------------------------- 1 | // page1 is an experimental page for trying out a gesture recognition system. 2 | package main 3 | 4 | import ( 5 | "log" 6 | "math" 7 | "math/rand" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/go-gl/gl/v2.1/gl" 12 | "github.com/go-gl/mathgl/mgl64" 13 | "github.com/goxjs/glfw" 14 | ) 15 | 16 | // TODO: Remove these 17 | var globalWindow *glfw.Window 18 | 19 | var mousePointer *Pointer 20 | 21 | func init() { 22 | runtime.LockOSThread() 23 | } 24 | 25 | type nopContextWatcher struct{} 26 | 27 | func (nopContextWatcher) OnMakeCurrent(context interface{}) {} 28 | func (nopContextWatcher) OnDetach() {} 29 | 30 | func main() { 31 | if err := glfw.Init(nopContextWatcher{}); err != nil { 32 | panic(err) 33 | } 34 | defer glfw.Terminate() 35 | 36 | window, err := glfw.CreateWindow(1536, 960, "", nil, nil) 37 | if err != nil { 38 | panic(err) 39 | } 40 | globalWindow = window 41 | window.MakeContextCurrent() 42 | 43 | window.SetInputMode(glfw.CursorMode, glfw.CursorHidden) 44 | 45 | if err := gl.Init(); nil != err { 46 | panic(err) 47 | } 48 | 49 | glfw.SwapInterval(1) // Vsync. 50 | 51 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 52 | gl.Viewport(0, 0, int32(framebufferSize0), int32(framebufferSize1)) 53 | 54 | var windowSize [2]int 55 | windowSize[0], windowSize[1] = w.GetSize() 56 | 57 | // Update the projection matrix 58 | gl.MatrixMode(gl.PROJECTION) 59 | gl.LoadIdentity() 60 | gl.Ortho(0, float64(windowSize[0]), float64(windowSize[1]), 0, -1, 1) 61 | gl.MatrixMode(gl.MODELVIEW) 62 | 63 | /*inputEvent := InputEvent{ 64 | Pointer: windowPointer, 65 | EventTypes: map[EventType]bool{AXIS_EVENT: true}, 66 | InputId: 0, 67 | Buttons: nil, 68 | Sliders: nil, 69 | Axes: []float64{float64(windowSize[0]), float64(windowSize[1])}, 70 | } 71 | inputEventQueue = EnqueueInputEvent(inputEvent, inputEventQueue)*/ 72 | } 73 | { 74 | var framebufferSize [2]int 75 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 76 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 77 | } 78 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 79 | 80 | var inputEventQueue []InputEvent 81 | mousePointer = &Pointer{VirtualCategory: POINTING} 82 | 83 | var lastMousePos mgl64.Vec2 84 | lastMousePos[0], lastMousePos[1] = window.GetCursorPos() 85 | MousePos := func(w *glfw.Window, x, y float64) { 86 | //fmt.Println("MousePos:", x, y) 87 | 88 | inputEvent := InputEvent{ 89 | Pointer: mousePointer, 90 | EventTypes: map[EventType]bool{SLIDER_EVENT: true, AXIS_EVENT: true}, 91 | InputId: 0, 92 | Buttons: nil, 93 | Sliders: []float64{x - lastMousePos[0], y - lastMousePos[1]}, // TODO: Do this in a pointer general way? 94 | Axes: []float64{x, y}, 95 | } 96 | lastMousePos[0] = x 97 | lastMousePos[1] = y 98 | inputEventQueue = EnqueueInputEvent(inputEvent, inputEventQueue) 99 | } 100 | window.SetCursorPosCallback(MousePos) 101 | MousePos(window, lastMousePos[0], lastMousePos[1]) 102 | 103 | gl.ClearColor(0.85, 0.85, 0.85, 1) 104 | 105 | rand.Seed(4) 106 | var widget = newMultitouchTestBoxWidget(mgl64.Vec2{600, 300}, rand.Intn(6)) 107 | var widget2 = newMultitouchTestBoxWidget(mgl64.Vec2{600 + 210, 300 + 210}, rand.Intn(6)) 108 | var widget3 = newMultitouchTestBoxWidget(mgl64.Vec2{600 + 210, 300}, rand.Intn(6)) 109 | var widget4 = newMultitouchTestBoxWidget(mgl64.Vec2{600, 300 + 210}, rand.Intn(6)) 110 | 111 | go func() { 112 | <-time.After(5 * time.Second) 113 | log.Println("trigger!") 114 | widget.color++ // HACK: Racy. 115 | 116 | glfw.PostEmptyEvent() 117 | }() 118 | 119 | for !window.ShouldClose() { 120 | //glfw.PollEvents() 121 | glfw.WaitEvents() 122 | 123 | // Process Input. 124 | inputEventQueue = ProcessInputEventQueue(inputEventQueue) 125 | 126 | gl.Clear(gl.COLOR_BUFFER_BIT) 127 | 128 | widget.Render() 129 | widget2.Render() 130 | widget3.Render() 131 | widget4.Render() 132 | 133 | mousePointer.Render() 134 | 135 | window.SwapBuffers() 136 | log.Println("swapped buffers") 137 | runtime.Gosched() 138 | } 139 | } 140 | 141 | type MultitouchTestBoxWidget struct { 142 | pos mgl64.Vec2 143 | color int 144 | 145 | buffer uint32 146 | } 147 | 148 | func newMultitouchTestBoxWidget(pos mgl64.Vec2, color int) MultitouchTestBoxWidget { 149 | var buffer uint32 150 | gl.GenBuffers(1, &buffer) 151 | gl.BindBuffer(gl.ARRAY_BUFFER, buffer) 152 | vertices := []float32{ 153 | 0, 0, 154 | 0, 200, 155 | 200, 200, 156 | 200, 0, 157 | } 158 | gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW) 159 | 160 | return MultitouchTestBoxWidget{pos: pos, color: color, buffer: buffer} 161 | } 162 | 163 | func (w *MultitouchTestBoxWidget) Render() { 164 | colors := [...]mgl64.Vec3{ 165 | {0 / 255.0, 140 / 255.0, 0 / 255.0}, 166 | {0 / 255.0, 98 / 255.0, 140 / 255.0}, 167 | {194 / 255.0, 74 / 255.0, 0 / 255.0}, 168 | {89 / 255.0, 0 / 255.0, 140 / 255.0}, 169 | {191 / 255.0, 150 / 255.0, 0 / 255.0}, 170 | {140 / 255.0, 0 / 255.0, 0 / 255.0}, 171 | } 172 | 173 | backgroundColor := colors[w.color] 174 | 175 | //borderColor := backgroundColor 176 | 177 | switch 1 { 178 | case 0: 179 | DrawBorderlessBox(w.pos, mgl64.Vec2{200, 200}, backgroundColor) 180 | case 1: 181 | gl.PushMatrix() 182 | gl.Translated(w.pos[0], w.pos[1], 0) 183 | gl.Color3dv(&backgroundColor[0]) 184 | 185 | gl.EnableClientState(gl.VERTEX_ARRAY) 186 | gl.BindBuffer(gl.ARRAY_BUFFER, w.buffer) 187 | gl.VertexPointer(2, gl.FLOAT, 0, nil) 188 | 189 | gl.DrawArrays(gl.TRIANGLE_FAN, 0, 4) 190 | 191 | gl.PopMatrix() 192 | } 193 | } 194 | 195 | // --- 196 | 197 | func DrawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) { 198 | gl.Color3dv(&backgroundColor[0]) 199 | gl.Rectd(pos[0], pos[1], pos.Add(size)[0], pos.Add(size)[1]) 200 | } 201 | 202 | func DrawBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 203 | DrawBorderlessBox(pos.Add(mgl64.Vec2{-1, -1}), size.Add(mgl64.Vec2{2, 2}), borderColor) 204 | DrawBorderlessBox(pos, size, backgroundColor) 205 | } 206 | 207 | // ===== 208 | 209 | type VirtualCategory uint8 210 | 211 | const ( 212 | TYPING VirtualCategory = iota 213 | POINTING 214 | WINDOWING 215 | ) 216 | 217 | type Pointer struct { 218 | VirtualCategory VirtualCategory 219 | //Mapping Widgeters // Always reflects current pointer state. 220 | //OriginMapping Widgeters // Updated only when pointer is moved while not active (e.g., where mouse button was first pressed down). 221 | State PointerState 222 | } 223 | 224 | func (this *Pointer) Render() { 225 | switch { 226 | case this.VirtualCategory == POINTING && len(this.State.Axes) >= 2: 227 | // Prevent pointer from being drawn when the OS mouse pointer is visible. 228 | { 229 | // HACK 230 | var windowSize [2]int 231 | if globalWindow != nil { 232 | windowSize[0], windowSize[1] = globalWindow.GetSize() 233 | } 234 | 235 | // HACK: OS X specific. 236 | const border = 3 237 | if this.State.Axes[1] < 0 || this.State.Axes[0] < border || this.State.Axes[0] >= float64(windowSize[0])-border || this.State.Axes[1] >= float64(windowSize[1])-border { 238 | break 239 | } 240 | } 241 | 242 | gl.PushMatrix() 243 | defer gl.PopMatrix() 244 | gl.Translated(float64(NearInt64(this.State.Axes[0]))+0.5, float64(NearInt64(this.State.Axes[1]))+0.5, 0) 245 | 246 | const size float64 = 12 * 40 / 40 247 | gl.Color3d(1, 1, 1) 248 | gl.Begin(gl.TRIANGLE_FAN) 249 | gl.Vertex2d(0, 0) 250 | gl.Vertex2d(0, size) 251 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 252 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 253 | gl.End() 254 | 255 | gl.Begin(gl.LINE_LOOP) 256 | gl.Color3d(0, 0, 0) 257 | gl.Vertex2d(0, 0) 258 | gl.Vertex2d(0, size) 259 | gl.Color3d(0.75, 0.75, 0.75) 260 | gl.Vertex2d(size*0.85*math.Sin(math.Pi/8), size*0.85*math.Cos(math.Pi/8)) 261 | gl.Color3d(0, 0, 0) 262 | gl.Vertex2d(size/math.Sqrt2, size/math.Sqrt2) 263 | gl.End() 264 | } 265 | } 266 | 267 | type PointerState struct { 268 | Buttons []bool // True means pressed down 269 | Axes []float64 270 | 271 | Timestamp int64 272 | } 273 | 274 | // A pointer is defined to be active if any of its buttons are pressed down 275 | func (ps *PointerState) IsActive() bool { 276 | //IsAnyButtonsPressed() 277 | for _, button := range ps.Buttons { 278 | if button { 279 | return true 280 | } 281 | } 282 | 283 | return false 284 | } 285 | 286 | func (ps *PointerState) Button(button int) bool { 287 | if button < len(ps.Buttons) { 288 | return ps.Buttons[button] 289 | } else { 290 | return false 291 | } 292 | } 293 | 294 | type EventType uint8 295 | 296 | const ( 297 | BUTTON_EVENT EventType = iota 298 | CHARACTER_EVENT 299 | SLIDER_EVENT 300 | AXIS_EVENT 301 | POINTER_ACTIVATION 302 | POINTER_DEACTIVATION 303 | ) 304 | 305 | type InputEvent struct { 306 | Pointer *Pointer 307 | EventTypes map[EventType]bool 308 | InputId uint16 309 | // TODO: Add pointers to BeforeState and AfterState? 310 | 311 | Buttons []bool 312 | // TODO: Characters? Split into distinct event types, bundle up in an event frame based on time? 313 | Sliders []float64 314 | Axes []float64 315 | ModifierKey glfw.ModifierKey // HACK 316 | } 317 | 318 | func ProcessInputEventQueue(inputEventQueue []InputEvent) []InputEvent { 319 | for len(inputEventQueue) > 0 { 320 | /*inputEvent := inputEventQueue[0] 321 | 322 | // TODO: Calculate whether a pointing pointer moved relative to canvas in a better way... what if canvas is moved via keyboard, etc. 323 | pointingPointerMovedRelativeToCanvas := inputEvent.Pointer.VirtualCategory == POINTING && 324 | (inputEvent.EventTypes[AXIS_EVENT] && inputEvent.InputId == 0 || inputEvent.EventTypes[SLIDER_EVENT] && inputEvent.InputId == 2) 325 | 326 | if pointingPointerMovedRelativeToCanvas { 327 | LocalPosition := mgl64.Vec2{float64(inputEvent.Pointer.State.Axes[0]), float64(inputEvent.Pointer.State.Axes[1])} 328 | 329 | // Clear previously hit widgets 330 | for _, widget := range inputEvent.Pointer.Mapping { 331 | delete(widget.HoverPointers(), inputEvent.Pointer) 332 | } 333 | inputEvent.Pointer.Mapping = []Widgeter{} 334 | 335 | // Recalculate currently hit widgets 336 | inputEvent.Pointer.Mapping = append(inputEvent.Pointer.Mapping, widget.Hit(LocalPosition)...) 337 | for _, widget := range inputEvent.Pointer.Mapping { 338 | widget.HoverPointers()[inputEvent.Pointer] = true 339 | } 340 | } 341 | 342 | // Populate OriginMapping (but only when pointer is moved while not active, and this isn't a deactivation since that's handled below) 343 | if pointingPointerMovedRelativeToCanvas && 344 | !inputEvent.EventTypes[POINTER_DEACTIVATION] && !inputEvent.Pointer.State.IsActive() { 345 | 346 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 347 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 348 | } 349 | 350 | for _, widget := range inputEvent.Pointer.OriginMapping { 351 | widget.ProcessEvent(inputEvent) 352 | } 353 | 354 | // Populate OriginMapping (but only upon pointer deactivation event) 355 | if inputEvent.Pointer.VirtualCategory == POINTING && inputEvent.EventTypes[POINTER_DEACTIVATION] { 356 | 357 | inputEvent.Pointer.OriginMapping = make([]Widgeter, len(inputEvent.Pointer.Mapping)) 358 | copy(inputEvent.Pointer.OriginMapping, inputEvent.Pointer.Mapping) 359 | }*/ 360 | 361 | inputEventQueue = inputEventQueue[1:] 362 | } 363 | 364 | inputEventQueue = []InputEvent{} 365 | 366 | return inputEventQueue 367 | } 368 | 369 | func EnqueueInputEvent(inputEvent InputEvent, inputEventQueue []InputEvent) []InputEvent { 370 | //fmt.Printf("%#v\n", inputEvent) 371 | 372 | preStateActive := inputEvent.Pointer.State.IsActive() 373 | 374 | { 375 | if inputEvent.EventTypes[BUTTON_EVENT] { 376 | // Extend slice if needed 377 | neededSize := int(inputEvent.InputId) + len(inputEvent.Buttons) 378 | if neededSize > len(inputEvent.Pointer.State.Buttons) { 379 | inputEvent.Pointer.State.Buttons = append(inputEvent.Pointer.State.Buttons, make([]bool, neededSize-len(inputEvent.Pointer.State.Buttons))...) 380 | } 381 | 382 | copy(inputEvent.Pointer.State.Buttons[inputEvent.InputId:], inputEvent.Buttons) 383 | } 384 | 385 | if inputEvent.EventTypes[AXIS_EVENT] { 386 | // Extend slice if needed 387 | neededSize := int(inputEvent.InputId) + len(inputEvent.Axes) 388 | if neededSize > len(inputEvent.Pointer.State.Axes) { 389 | inputEvent.Pointer.State.Axes = append(inputEvent.Pointer.State.Axes, make([]float64, neededSize-len(inputEvent.Pointer.State.Axes))...) 390 | } 391 | 392 | copy(inputEvent.Pointer.State.Axes[inputEvent.InputId:], inputEvent.Axes) 393 | } 394 | 395 | inputEvent.Pointer.State.Timestamp = time.Now().UnixNano() 396 | } 397 | 398 | postStateActive := inputEvent.Pointer.State.IsActive() 399 | 400 | switch { 401 | case !preStateActive && postStateActive: 402 | inputEvent.EventTypes[POINTER_ACTIVATION] = true 403 | case preStateActive && !postStateActive: 404 | inputEvent.EventTypes[POINTER_DEACTIVATION] = true 405 | } 406 | 407 | return append(inputEventQueue, inputEvent) 408 | } 409 | 410 | // ===== 411 | 412 | func NearInt64(value float64) int64 { 413 | if value >= 0 { 414 | return int64(value + 0.5) 415 | } else { 416 | return int64(value - 0.5) 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /page/page2/font.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/png" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/go-gl/gl/v2.1/gl" 13 | "github.com/go-gl/mathgl/mgl64" 14 | 15 | "github.com/shurcooL-legacy/Conception-go/caret" 16 | ) 17 | 18 | var oFontBase, oFontBackground uint32 19 | var lodBias float64 = -66.67 20 | 21 | var selectedTextColor = mgl64.Vec3{195 / 255.0, 212 / 255.0, 242 / 255.0} 22 | var selectedTextDarkColor = selectedTextColor.Mul(0.75) 23 | 24 | // FontOptions specifies the properties of the font. 25 | type FontOptions uint8 26 | 27 | const ( 28 | Regular FontOptions = iota 29 | Bold 30 | Italic 31 | BoldItalic 32 | ) 33 | 34 | // IsBold returns true if the font has the bold property set. 35 | func (fo FontOptions) IsBold() bool { return fo == Bold || fo == BoldItalic } 36 | 37 | // IsItalic returns true if the font has the italic property set. 38 | func (fo FontOptions) IsItalic() bool { return fo == Italic || fo == BoldItalic } 39 | 40 | type OpenGLStream struct { 41 | pos mgl64.Vec2 42 | lineStartX float64 43 | advance uint32 44 | 45 | FontOptions FontOptions 46 | BorderColor *mgl64.Vec3 // nil means no border color. 47 | BackgroundColor *mgl64.Vec3 // nil means no background color. 48 | ShowInvisibles bool 49 | } 50 | 51 | func NewOpenGLStream(pos mgl64.Vec2) *OpenGLStream { 52 | return &OpenGLStream{pos: pos, lineStartX: pos[0]} 53 | } 54 | 55 | func (o *OpenGLStream) SetPos(pos mgl64.Vec2) { 56 | o.pos = pos 57 | o.lineStartX = pos[0] 58 | o.advance = 0 59 | } 60 | 61 | func (o *OpenGLStream) SetPosWithExpandedPosition(pos mgl64.Vec2, x, y uint32) { 62 | o.pos = pos.Add(mgl64.Vec2{float64(x) * fontWidth, float64(y) * fontHeight}) 63 | o.lineStartX = pos[0] 64 | o.advance = x 65 | } 66 | 67 | func (o *OpenGLStream) PrintText(s string) { 68 | for { 69 | end := strings.Index(s, "\n") 70 | 71 | length := len(s) 72 | if end != -1 { 73 | length = end 74 | } 75 | o.PrintLine(s[:length]) 76 | 77 | if end == -1 { 78 | break 79 | } else { 80 | //o.NewLine() 81 | o.PrintSegment(" ") // Newline 82 | o.pos[1] += fontHeight 83 | o.advanceReset() 84 | s = s[end+1:] 85 | } 86 | } 87 | } 88 | 89 | // Input shouldn't have newlines 90 | func (o *OpenGLStream) PrintLine(s string) { 91 | if o.BorderColor != nil { 92 | gl.PushAttrib(gl.CURRENT_BIT) 93 | 94 | expandedLineLength := caret.ExpandedLength(s, o.advance) 95 | 96 | backgroundColor := nearlyWhiteColor 97 | if o.BackgroundColor != nil { 98 | backgroundColor = *o.BackgroundColor 99 | } 100 | 101 | drawInnerSlicedBox(o.pos, mgl64.Vec2{fontWidth * float64(expandedLineLength), fontHeight}, *o.BorderColor, backgroundColor) 102 | 103 | gl.PopAttrib() 104 | } 105 | 106 | segments := strings.Split(s, "\t") 107 | for index, segment := range segments { 108 | o.PrintSegment(segment) 109 | o.advanceBy(uint32(len(segment))) 110 | if index+1 < len(segments) { 111 | tabSpaces := 4 - (o.advance % 4) 112 | o.PrintSegment(strings.Repeat(" ", int(tabSpaces))) // Tab. 113 | if o.ShowInvisibles { 114 | gl.PushAttrib(gl.CURRENT_BIT) 115 | drawBorderlessBox(o.pos.Add(mgl64.Vec2{1, fontHeight/2 - 1}), mgl64.Vec2{fontWidth*float64(tabSpaces) - 2, 2}, selectedTextDarkColor) 116 | gl.PopAttrib() 117 | } 118 | o.advanceBy(tabSpaces) 119 | } 120 | } 121 | } 122 | 123 | func (o *OpenGLStream) advanceBy(amount uint32) { 124 | o.advance += amount 125 | o.afterAdvance() 126 | } 127 | func (o *OpenGLStream) advanceReset() { 128 | o.advance = 0 129 | o.afterAdvance() 130 | } 131 | func (o *OpenGLStream) afterAdvance() { 132 | o.pos[0] = o.lineStartX + fontWidth*float64(o.advance) 133 | } 134 | 135 | // Shouldn't have tabs nor newlines 136 | func (o *OpenGLStream) PrintSegment(s string) { 137 | if s == "" { 138 | return 139 | } 140 | 141 | if o.BackgroundColor != nil && o.BorderColor == nil { 142 | gl.PushAttrib(gl.CURRENT_BIT) 143 | gl.Color3dv(&o.BackgroundColor[0]) 144 | gl.PushMatrix() 145 | gl.Translated(o.pos[0], o.pos[1], 0) 146 | for range s { 147 | gl.CallList(oFontBackground) 148 | } 149 | gl.PopMatrix() 150 | gl.PopAttrib() 151 | } 152 | 153 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, float32(lodBias*0.01)) 154 | 155 | gl.Enable(gl.BLEND) 156 | defer gl.Disable(gl.BLEND) 157 | gl.Enable(gl.TEXTURE_2D) 158 | defer gl.Disable(gl.TEXTURE_2D) 159 | 160 | gl.PushMatrix() 161 | gl.Translated(o.pos[0], o.pos[1], 0) 162 | gl.ListBase(oFontBase + uint32(o.FontOptions)*96) 163 | gl.CallLists(int32(len(s)), gl.UNSIGNED_BYTE, gl.Ptr(&[]byte(s)[0])) 164 | gl.PopMatrix() 165 | 166 | //checkGLError() 167 | } 168 | 169 | // --- 170 | 171 | //const fontHeight = 150 172 | const fontHeight = 13.1 173 | const fontWidth = fontHeight * (1312.0 / 900) * 6.0 / 16 174 | 175 | var shiftXs = [6][17]float64{{0, 0.025914634146341462, 0.051067073170731704, 0.09070121951219512, 0.14329268292682926, 0.19588414634146342, 0.29039634146341464, 0.34984756097560976, 0.37652439024390244, 0.4009146341463415, 0.4253048780487805, 0.45884146341463417, 0.5152439024390244, 0.541920731707317, 0.5785060975609756, 0.604420731707317, 0.6364329268292683}, {0, 0.052591463414634144, 0.10518292682926829, 0.15777439024390244, 0.21036585365853658, 0.2629573170731707, 0.3155487804878049, 0.36814024390243905, 0.42073170731707316, 0.4725609756097561, 0.5251524390243902, 0.551829268292683, 0.5777439024390244, 0.6349085365853658, 0.6913109756097561, 0.7484756097560976, 0.8010670731707317}, {0, 0.07545731707317073, 0.13719512195121952, 0.20121951219512196, 0.2698170731707317, 0.3361280487804878, 0.3940548780487805, 0.4481707317073171, 0.5198170731707317, 0.5884146341463414, 0.6128048780487805, 0.6615853658536586, 0.7248475609756098, 0.7774390243902439, 0.8597560975609756, 0.9283536585365854, 1}, {0, 0.06097560975609756, 0.13338414634146342, 0.19817073170731708, 0.25914634146341464, 0.3132621951219512, 0.38185975609756095, 0.43902439024390244, 0.5266768292682927, 0.5846036585365854, 0.645579268292683, 0.7035060975609756, 0.7278963414634146, 0.7591463414634146, 0.7842987804878049, 0.8407012195121951, 0.8879573170731707}, {0, 0.021341463414634148, 0.07164634146341463, 0.12804878048780488, 0.17835365853658536, 0.2347560975609756, 0.2850609756097561, 0.3132621951219512, 0.3673780487804878, 0.41996951219512196, 0.4413109756097561, 0.46189024390243905, 0.5114329268292683, 0.5320121951219512, 0.6128048780487805, 0.6653963414634146, 0.7195121951219512}, {0, 0.056402439024390245, 0.11204268292682927, 0.14329268292682926, 0.19054878048780488, 0.22027439024390244, 0.2728658536585366, 0.3201219512195122, 0.39176829268292684, 0.4413109756097561, 0.4885670731707317, 0.5335365853658537, 0.5647865853658537, 0.5861280487804879, 0.6173780487804879, 0.6745426829268293, 0.7004573170731707}} 176 | 177 | func InitFont() { 178 | LoadTexture(filepath.Join("data", "fonts", "Helvetica Neue.png")) 179 | 180 | oFontBase = gl.GenLists(32 + 96) 181 | for i := 0; i < 96; i++ { 182 | const shiftY = float64(1.0 / 6) 183 | 184 | indexX, indexY := i%16, i/16 185 | 186 | charWidth := shiftXs[indexY][indexX+1] - shiftXs[indexY][indexX] 187 | 188 | gl.NewList(oFontBase+uint32(i+32), gl.COMPILE) 189 | gl.Begin(gl.QUADS) 190 | gl.TexCoord2d(shiftXs[indexY][indexX], float64(indexY)*shiftY) 191 | gl.Vertex2d(0, 0) 192 | gl.TexCoord2d(shiftXs[indexY][indexX+1], float64(indexY)*shiftY) 193 | gl.Vertex2d(fontWidth*charWidth/float64(1.0/16), 0) 194 | gl.TexCoord2d(shiftXs[indexY][indexX+1], float64(indexY+1)*shiftY) 195 | gl.Vertex2d(fontWidth*charWidth/float64(1.0/16), fontHeight) 196 | gl.TexCoord2d(shiftXs[indexY][indexX], float64(indexY+1)*shiftY) 197 | gl.Vertex2d(0, fontHeight) 198 | gl.End() 199 | gl.Translated(fontWidth*charWidth/float64(1.0/16), 0.0, 0.0) 200 | gl.EndList() 201 | } 202 | 203 | oFontBackground = gl.GenLists(1) 204 | gl.NewList(oFontBackground, gl.COMPILE) 205 | gl.Begin(gl.QUADS) 206 | gl.Vertex2d(0, 0) 207 | gl.Vertex2d(0, fontHeight) 208 | gl.Vertex2d(fontWidth, fontHeight) 209 | gl.Vertex2d(fontWidth, 0) 210 | gl.End() 211 | gl.Translated(fontWidth, 0.0, 0.0) 212 | gl.EndList() 213 | 214 | checkGLError() 215 | } 216 | 217 | func DeinitFont() { 218 | gl.DeleteLists(oFontBase, 32+96) 219 | gl.DeleteLists(oFontBackground, 1) 220 | } 221 | 222 | func LoadTexture(path string) { 223 | //fmt.Printf("Trying to load texture %q: ", path) 224 | 225 | // Open the file 226 | file, err := os.Open(path) 227 | if err != nil { 228 | fmt.Println(os.Getwd()) 229 | log.Fatal(err) 230 | } 231 | defer file.Close() 232 | 233 | // Decode the image 234 | img, _, err := image.Decode(file) 235 | if err != nil { 236 | log.Fatal(err) 237 | } 238 | 239 | bounds := img.Bounds() 240 | //fmt.Printf("Loaded %vx%v texture.\n", bounds.Dx(), bounds.Dy()) 241 | 242 | var format int 243 | var pixPointer *uint8 244 | switch img := img.(type) { 245 | case *image.RGBA: 246 | format = gl.RGBA 247 | pixPointer = &img.Pix[0] 248 | case *image.NRGBA: 249 | format = gl.RGBA 250 | pixPointer = &img.Pix[0] 251 | case *image.Gray: 252 | format = gl.ALPHA 253 | pixPointer = &img.Pix[0] 254 | default: 255 | log.Fatalf("LoadTexture: Unsupported type %T.\n", img) 256 | } 257 | 258 | var texture uint32 259 | gl.GenTextures(1, &texture) 260 | gl.BindTexture(gl.TEXTURE_2D, texture) 261 | gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE) 262 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 263 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 264 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, -0.5) 265 | gl.TexImage2D(gl.TEXTURE_2D, 0, int32(format), int32(bounds.Dx()), int32(bounds.Dy()), 0, uint32(format), gl.UNSIGNED_BYTE, gl.Ptr(pixPointer)) 266 | checkGLError() 267 | } 268 | 269 | // ===== 270 | 271 | func drawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) { 272 | gl.Color3dv(&backgroundColor[0]) 273 | gl.Rectd(pos[0], pos[1], pos.Add(size)[0], pos.Add(size)[1]) 274 | } 275 | 276 | func checkGLError() { 277 | err := gl.GetError() 278 | if err != 0 { 279 | panic(fmt.Errorf("GL error: %v", err)) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /page/page2/main.go: -------------------------------------------------------------------------------- 1 | // page2 is an experimental page for trying out different fonts and small UI widgets. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "go/build" 7 | "log" 8 | "math" 9 | "os" 10 | "runtime" 11 | "time" 12 | 13 | "github.com/go-gl/gl/v2.1/gl" 14 | "github.com/go-gl/mathgl/mgl64" 15 | "github.com/goxjs/glfw" 16 | "github.com/shurcooL-legacy/Conception-go/event" 17 | "github.com/shurcooL/go-goon" 18 | ) 19 | 20 | // HACK: Play with various offsets for font rendering. 21 | var softwareUpdateTextOffset mgl64.Vec2 22 | 23 | var boxUpdated bool 24 | 25 | func drawBox() { 26 | /*gl.Translated(50, 100, 0) 27 | gl.Color3d(201.0/255, 201.0/255, 201.0/255) 28 | gl.Recti(0, 0, 106, 18) 29 | if !boxUpdated { 30 | gl.Color3d(1, 1, 1) 31 | } else { 32 | gl.Color3d(0, 1, 0) 33 | } 34 | gl.Recti(0+1, 0+1, 106-1, 18-1)*/ 35 | 36 | c := mgl64.Vec3{1, 1, 1} 37 | if boxUpdated { 38 | c = mgl64.Vec3{42.0 / 255, 154.0 / 255, 254.0 / 255} 39 | } 40 | 41 | drawInnerRoundedBox(mgl64.Vec2{50, 100}, 42 | mgl64.Vec2{106, 18}, 43 | c.Mul(201.0/255), 44 | c) 45 | } 46 | 47 | func drawInnerRoundedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 48 | if size[0] == 0 || size[1] == 0 { 49 | return 50 | } 51 | 52 | const totalSlices = 4 * 16 53 | const borderWidth = 1 54 | const radius = 2.5 + borderWidth 55 | var x = float64(totalSlices) 56 | 57 | gl.Color3dv(&backgroundColor[0]) 58 | gl.Begin(gl.TRIANGLE_STRIP) 59 | for i := 0; i <= totalSlices/4; i++ { 60 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 61 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(totalSlices-i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(totalSlices-i)/x)*(radius-borderWidth)) 62 | } 63 | for i := totalSlices / 4; i <= totalSlices/2; i++ { 64 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 65 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(totalSlices-i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(totalSlices-i)/x)*(radius-borderWidth)) 66 | } 67 | gl.End() 68 | 69 | gl.Color3dv(&borderColor[0]) 70 | gl.Begin(gl.TRIANGLE_STRIP) 71 | gl.Vertex2d(pos[0]+radius, pos[1]) 72 | gl.Vertex2d(pos[0]+radius, pos[1]+borderWidth) 73 | for i := 0; i <= totalSlices/4; i++ { 74 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+radius-math.Cos(Tau*float64(i)/x)*radius) 75 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 76 | } 77 | for i := totalSlices / 4; i <= totalSlices/2; i++ { 78 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*radius) 79 | gl.Vertex2d(pos[0]+size[0]-radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 80 | } 81 | for i := totalSlices / 2; i <= totalSlices*3/4; i++ { 82 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*radius) 83 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+size[1]-radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 84 | } 85 | for i := totalSlices * 3 / 4; i <= totalSlices; i++ { 86 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*radius, pos[1]+radius-math.Cos(Tau*float64(i)/x)*radius) 87 | gl.Vertex2d(pos[0]+radius+math.Sin(Tau*float64(i)/x)*(radius-borderWidth), pos[1]+radius-math.Cos(Tau*float64(i)/x)*(radius-borderWidth)) 88 | } 89 | gl.End() 90 | } 91 | 92 | func drawInnerSlicedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 93 | if size[0] == 0 || size[1] == 0 { 94 | return 95 | } 96 | 97 | const OuterDistance = 2.5 98 | gl.Begin(gl.POLYGON) 99 | gl.Color3dv(&borderColor[0]) 100 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]) 101 | gl.Vertex2d(pos[0], pos[1]+OuterDistance) 102 | gl.Vertex2d(pos[0], pos[1]-OuterDistance+size[1]) 103 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]+size[1]) 104 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]+size[1]) 105 | gl.Vertex2d(pos[0]+size[0], pos[1]-OuterDistance+size[1]) 106 | gl.Vertex2d(pos[0]+size[0], pos[1]+OuterDistance) 107 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]) 108 | gl.End() 109 | 110 | const InnerDistance = OuterDistance + (math.Sqrt2 - 1) 111 | gl.Begin(gl.POLYGON) 112 | gl.Color3dv(&backgroundColor[0]) 113 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]+1) 114 | gl.Vertex2d(pos[0]+1, pos[1]+InnerDistance) 115 | gl.Vertex2d(pos[0]+1, pos[1]-InnerDistance+size[1]) 116 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]-1+size[1]) 117 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]-1+size[1]) 118 | gl.Vertex2d(pos[0]-1+size[0], pos[1]-InnerDistance+size[1]) 119 | gl.Vertex2d(pos[0]-1+size[0], pos[1]+InnerDistance) 120 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]+1) 121 | gl.End() 122 | } 123 | 124 | // Tau is the constant τ, which equals to 6.283185... or 2π. 125 | // Reference: https://oeis.org/A019692 126 | const Tau = 2 * math.Pi 127 | 128 | func drawSpinner(spinner int) { 129 | gl.PushMatrix() 130 | gl.Translated(30.5, 30.5, 0) 131 | gl.Rotated(float64(spinner), 0, 0, 1) 132 | gl.Color3d(0, 0, 0) 133 | gl.Rectd(-0.5, -10.5, 0.5, 10.5) 134 | gl.PopMatrix() 135 | } 136 | 137 | func init() { 138 | runtime.LockOSThread() 139 | } 140 | 141 | // Set the working directory to the root of Conception-go package, so that its assets can be accessed. 142 | func init() { 143 | // importPathToDir resolves the absolute path from importPath. 144 | // There doesn't need to be a valid Go package inside that import path, 145 | // but the directory must exist. 146 | importPathToDir := func(importPath string) (string, error) { 147 | p, err := build.Import(importPath, "", build.FindOnly) 148 | if err != nil { 149 | return "", err 150 | } 151 | return p.Dir, nil 152 | } 153 | 154 | dir, err := importPathToDir("github.com/shurcooL-legacy/Conception-go") 155 | if err != nil { 156 | log.Fatalln("Unable to find github.com/shurcooL-legacy/Conception-go package in your GOPATH, it's needed to load assets:", err) 157 | } 158 | err = os.Chdir(dir) 159 | if err != nil { 160 | log.Fatalln("os.Chdir:", err) 161 | } 162 | } 163 | 164 | type nopContextWatcher struct{} 165 | 166 | func (nopContextWatcher) OnMakeCurrent(context interface{}) {} 167 | func (nopContextWatcher) OnDetach() {} 168 | 169 | func main() { 170 | if err := glfw.Init(nopContextWatcher{}); err != nil { 171 | panic(err) 172 | } 173 | defer glfw.Terminate() 174 | 175 | glfw.WindowHint(glfw.Samples, 8) // Anti-aliasing. 176 | 177 | window, err := glfw.CreateWindow(400, 400, "", nil, nil) 178 | if err != nil { 179 | panic(err) 180 | } 181 | window.MakeContextCurrent() 182 | 183 | if err := gl.Init(); err != nil { 184 | panic(err) 185 | } 186 | 187 | glfw.SwapInterval(1) // Vsync. 188 | 189 | InitFont() 190 | defer DeinitFont() 191 | 192 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 193 | gl.Viewport(0, 0, int32(framebufferSize0), int32(framebufferSize1)) 194 | 195 | var windowSize [2]int 196 | windowSize[0], windowSize[1] = w.GetSize() 197 | 198 | // Update the projection matrix. 199 | gl.MatrixMode(gl.PROJECTION) 200 | gl.LoadIdentity() 201 | gl.Ortho(0, float64(windowSize[0]), float64(windowSize[1]), 0, -1, 1) 202 | gl.MatrixMode(gl.MODELVIEW) 203 | } 204 | { 205 | var framebufferSize [2]int 206 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 207 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 208 | } 209 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 210 | 211 | var inputEventQueue []event.InputEvent 212 | mousePointer = &event.Pointer{VirtualCategory: event.Pointing} 213 | 214 | window.SetMouseMovementCallback(func(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) { 215 | inputEvent := event.InputEvent{ 216 | Pointer: mousePointer, 217 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 218 | InputID: 0, 219 | Buttons: nil, 220 | Sliders: []float64{xdelta, ydelta}, 221 | } 222 | if w.GetInputMode(glfw.CursorMode) != glfw.CursorDisabled { 223 | inputEvent.EventTypes[event.AxisEvent] = struct{}{} 224 | inputEvent.Axes = []float64{xpos, ypos} 225 | } 226 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 227 | }) 228 | 229 | window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 230 | inputEvent := event.InputEvent{ 231 | Pointer: mousePointer, 232 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 233 | InputID: uint16(button), 234 | Buttons: []bool{action != glfw.Release}, 235 | Sliders: nil, 236 | Axes: nil, 237 | ModifierKey: uint8(mods), 238 | } 239 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 240 | }) 241 | 242 | // HACK: Play with various offsets for font rendering. 243 | window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 244 | switch { 245 | case key == glfw.KeyLeft && action != glfw.Release: 246 | softwareUpdateTextOffset[0] -= 0.01 247 | case key == glfw.KeyRight && action != glfw.Release: 248 | softwareUpdateTextOffset[0] += 0.01 249 | case key == glfw.KeyUp && action != glfw.Release: 250 | softwareUpdateTextOffset[1] -= 0.01 251 | case key == glfw.KeyDown && action != glfw.Release: 252 | softwareUpdateTextOffset[1] += 0.01 253 | } 254 | }) 255 | defer func() { 256 | goon.DumpExpr(softwareUpdateTextOffset) 257 | }() 258 | 259 | go func() { 260 | <-time.After(5 * time.Second) 261 | log.Println("trigger!") 262 | boxUpdated = true 263 | 264 | glfw.PostEmptyEvent() 265 | }() 266 | 267 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) // For font. 268 | 269 | gl.ClearColor(247.0/255, 247.0/255, 247.0/255, 1) 270 | 271 | var spinner int 272 | 273 | var widgets []event.Widgeter 274 | 275 | widgets = append(widgets, NewButtonWidget(mgl64.Vec2{50, 200}, func() { fmt.Println("button triggered") })) 276 | 277 | for !window.ShouldClose() && glfw.Press != window.GetKey(glfw.KeyEscape) { 278 | glfw.WaitEvents() 279 | 280 | // Process Input. 281 | inputEventQueue = event.ProcessInputEventQueue(inputEventQueue, widgets[0]) 282 | 283 | gl.Clear(gl.COLOR_BUFFER_BIT) 284 | gl.LoadIdentity() 285 | 286 | for _, widget := range widgets { 287 | widget.Render() 288 | } 289 | 290 | drawSpinner(spinner) 291 | spinner++ 292 | 293 | drawBox() 294 | 295 | gl.Color3d(1, 0, 0) 296 | NewOpenGLStream(mgl64.Vec2{50, 300}).PrintText(` !"#$%&'()*+,-./ 297 | 0123456789:;<=>? 298 | @ABCDEFGHIJKLMNO 299 | PQRSTUVWXYZ[\]^_ 300 | ` + "`" + `abcdefghijklmno 301 | pqrstuvwxyz{|}~`) 302 | 303 | window.SwapBuffers() 304 | log.Println("swapped buffers") 305 | 306 | //runtime.Gosched() 307 | } 308 | } 309 | 310 | // ===== 311 | 312 | var mousePointer *event.Pointer 313 | 314 | // --- 315 | 316 | type Widget struct { 317 | pos mgl64.Vec2 318 | size mgl64.Vec2 319 | hoverPointers map[*event.Pointer]bool 320 | parent event.Widgeter 321 | 322 | //DepNode 323 | } 324 | 325 | func NewWidget(pos, size mgl64.Vec2) Widget { 326 | return Widget{pos: pos, size: size, hoverPointers: map[*event.Pointer]bool{}} 327 | } 328 | 329 | func (_ *Widget) PollLogic() {} 330 | func (_ *Widget) Close() error { return nil } 331 | func (w *Widget) Layout() { 332 | if w.parent != nil { 333 | w.parent.Layout() 334 | } 335 | } 336 | func (_ *Widget) LayoutNeeded() {} 337 | func (_ *Widget) Render() {} 338 | func (w *Widget) Hit(ParentPosition mgl64.Vec2) []event.Widgeter { 339 | LocalPosition := w.ParentToLocal(ParentPosition) 340 | 341 | Hit := (LocalPosition[0] >= 0 && 342 | LocalPosition[1] >= 0 && 343 | LocalPosition[0] <= w.size[0] && 344 | LocalPosition[1] <= w.size[1]) 345 | 346 | if Hit { 347 | return []event.Widgeter{w} 348 | } else { 349 | return nil 350 | } 351 | } 352 | func (w *Widget) ProcessEvent(inputEvent event.InputEvent) {} 353 | func (_ *Widget) ContainsWidget(widget, target event.Widgeter) bool { 354 | return widget == target 355 | } 356 | 357 | func (w *Widget) Pos() *mgl64.Vec2 { return &w.pos } 358 | func (w *Widget) Size() *mgl64.Vec2 { return &w.size } 359 | 360 | func (w *Widget) HoverPointers() map[*event.Pointer]bool { 361 | return w.hoverPointers 362 | } 363 | 364 | func (w *Widget) Parent() event.Widgeter { return w.parent } 365 | func (w *Widget) SetParent(p event.Widgeter) { w.parent = p } 366 | 367 | func (w *Widget) ParentToLocal(ParentPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) { 368 | return ParentPosition.Sub(w.pos) 369 | } 370 | 371 | type WidgeterS struct{ event.Widgeter } 372 | 373 | func (w WidgeterS) GlobalToParent(GlobalPosition mgl64.Vec2) (ParentPosition mgl64.Vec2) { 374 | switch w.Parent() { 375 | case nil: 376 | ParentPosition = GlobalPosition 377 | default: 378 | ParentPosition = WidgeterS{w.Parent()}.GlobalToLocal(GlobalPosition) 379 | } 380 | return ParentPosition 381 | } 382 | func (w WidgeterS) GlobalToLocal(GlobalPosition mgl64.Vec2) (LocalPosition mgl64.Vec2) { 383 | return w.ParentToLocal(WidgeterS{w}.GlobalToParent(GlobalPosition)) 384 | } 385 | 386 | // --- 387 | 388 | type ButtonWidget struct { 389 | Widget 390 | action func() 391 | } 392 | 393 | func NewButtonWidget(pos mgl64.Vec2, action func()) *ButtonWidget { 394 | w := &ButtonWidget{Widget: NewWidget(pos, mgl64.Vec2{106, 18})} 395 | //w := &ButtonWidget{Widget: NewWidget(pos, mgl64.Vec2{122, 18})} 396 | w.setAction(action) 397 | 398 | return w 399 | } 400 | 401 | func (w *ButtonWidget) setAction(action func()) { 402 | w.action = action 403 | } 404 | 405 | var ( 406 | nearlyWhiteColor = mgl64.Vec3{0.975, 0.975, 0.975} 407 | grayColor = mgl64.Vec3{0.75, 0.75, 0.75} 408 | highlightColor = mgl64.Vec3{0.898, 0.765, 0.396} // Yellowish on-hover border color. 409 | ) 410 | 411 | func (w *ButtonWidget) Render() { 412 | // HACK: Brute-force check the mouse pointer if it contains this widget 413 | isOriginHit := false 414 | for _, hit := range mousePointer.OriginMapping { 415 | if w == hit { 416 | isOriginHit = true 417 | break 418 | } 419 | } 420 | isHit := len(w.HoverPointers()) > 0 421 | 422 | // HACK: Assumes mousePointer rather than considering all connected pointing pointers 423 | if isOriginHit && mousePointer.State.IsActive() && isHit { 424 | //DrawGBox(w.pos, w.size) 425 | //drawInnerRoundedBox(w.pos, w.size, highlightColor, grayColor) 426 | c := mgl64.Vec3{42.0 / 255, 154.0 / 255, 254.0 / 255} 427 | drawInnerRoundedBox(w.pos, w.size, c.Mul(201.0/255), c) 428 | gl.Color3d(1, 1, 1) 429 | //} else if (isHit && !mousePointer.State.IsActive()) || isOriginHit { 430 | // //DrawYBox(w.pos, w.size) 431 | // drawInnerRoundedBox(w.pos, w.size, highlightColor, nearlyWhiteColor) 432 | } else { 433 | //DrawNBox(w.pos, w.size) 434 | //drawInnerRoundedBox(w.pos, w.size, mgl64.Vec3{0.3, 0.3, 0.3}, nearlyWhiteColor) 435 | c := mgl64.Vec3{1, 1, 1} 436 | drawInnerRoundedBox(w.pos, w.size, c.Mul(201.0/255), c) 437 | gl.Color3d(0, 0, 0) 438 | } 439 | 440 | NewOpenGLStream(w.pos.Add(mgl64.Vec2{8, 3}).Add(softwareUpdateTextOffset)).PrintText("Software Update...") 441 | } 442 | func (w *ButtonWidget) Hit(ParentPosition mgl64.Vec2) []event.Widgeter { 443 | if len(w.Widget.Hit(ParentPosition)) > 0 { 444 | return []event.Widgeter{w} 445 | } else { 446 | return nil 447 | } 448 | } 449 | 450 | func (w *ButtonWidget) ProcessEvent(inputEvent event.InputEvent) { 451 | //if _, buttonEvent := inputEvent.EventTypes[event.BUTTON_EVENT]; inputEvent.Pointer.VirtualCategory == event.POINTING && buttonEvent && inputEvent.InputId == 0 && inputEvent.Buttons[0] == false && 452 | if inputEvent.Pointer.VirtualCategory == event.Pointing && inputEvent.EventTypes.Has(event.ButtonEvent) && inputEvent.InputID == 0 && inputEvent.Buttons[0] == false && 453 | inputEvent.Pointer.Mapping.ContainsWidget(w) && /* TODO: GetHoverer() */ // IsHit(this button) should be true 454 | inputEvent.Pointer.OriginMapping.ContainsWidget(w) { /* TODO: GetHoverer() */ // Make sure we're releasing pointer over same button that it originally went active on, and nothing is in the way (i.e. button is hoverer) 455 | 456 | if w.action != nil { 457 | w.action() 458 | //println(GetSourceAsString(w.action)) 459 | 460 | w.Layout() 461 | //w.NotifyAllListeners() 462 | } 463 | } 464 | } 465 | -------------------------------------------------------------------------------- /page/terminal/font.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | _ "image/png" 7 | "log" 8 | "math" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/go-gl/gl/v2.1/gl" 14 | "github.com/go-gl/mathgl/mgl64" 15 | 16 | "github.com/shurcooL-legacy/Conception-go/caret" 17 | ) 18 | 19 | var oFontBase, oFontBackground uint32 20 | var lodBias = -66.67 21 | 22 | var selectedTextColor = mgl64.Vec3{195 / 255.0, 212 / 255.0, 242 / 255.0} 23 | var selectedTextDarkColor = selectedTextColor.Mul(0.75) 24 | 25 | // FontOptions specifies the properties of the font. 26 | type FontOptions uint8 27 | 28 | const ( 29 | Regular FontOptions = iota 30 | Bold 31 | Italic 32 | BoldItalic 33 | ) 34 | 35 | // IsBold returns true if the font has the bold property set. 36 | func (fo FontOptions) IsBold() bool { return fo == Bold || fo == BoldItalic } 37 | 38 | // IsItalic returns true if the font has the italic property set. 39 | func (fo FontOptions) IsItalic() bool { return fo == Italic || fo == BoldItalic } 40 | 41 | type OpenGLStream struct { 42 | pos mgl64.Vec2 43 | lineStartX float64 44 | advance uint32 45 | 46 | FontOptions FontOptions 47 | BorderColor *mgl64.Vec3 // nil means no border color. 48 | BackgroundColor *mgl64.Vec3 // nil means no background color. 49 | ShowInvisibles bool 50 | } 51 | 52 | func NewOpenGLStream(pos mgl64.Vec2) *OpenGLStream { 53 | return &OpenGLStream{pos: pos, lineStartX: pos[0]} 54 | } 55 | 56 | func (o *OpenGLStream) SetPos(pos mgl64.Vec2) { 57 | o.pos = pos 58 | o.lineStartX = pos[0] 59 | o.advance = 0 60 | } 61 | 62 | func (o *OpenGLStream) SetPosWithExpandedPosition(pos mgl64.Vec2, x, y uint32) { 63 | o.pos = pos.Add(mgl64.Vec2{float64(x) * fontWidth, float64(y) * fontHeight}) 64 | o.lineStartX = pos[0] 65 | o.advance = x 66 | } 67 | 68 | func (o *OpenGLStream) PrintText(s string) { 69 | for { 70 | end := strings.Index(s, "\n") 71 | 72 | length := len(s) 73 | if end != -1 { 74 | length = end 75 | } 76 | o.PrintLine(s[:length]) 77 | 78 | if end == -1 { 79 | break 80 | } else { 81 | //o.NewLine() 82 | o.PrintSegment(" ") // Newline 83 | o.pos[1] += fontHeight 84 | o.advanceReset() 85 | s = s[end+1:] 86 | } 87 | } 88 | } 89 | 90 | // Input shouldn't have newlines 91 | func (o *OpenGLStream) PrintLine(s string) { 92 | if o.BorderColor != nil { 93 | gl.PushAttrib(gl.CURRENT_BIT) 94 | 95 | expandedLineLength := caret.ExpandedLength(s, o.advance) 96 | 97 | backgroundColor := nearlyWhiteColor 98 | if o.BackgroundColor != nil { 99 | backgroundColor = *o.BackgroundColor 100 | } 101 | 102 | drawInnerSlicedBox(o.pos, mgl64.Vec2{fontWidth * float64(expandedLineLength), fontHeight}, *o.BorderColor, backgroundColor) 103 | 104 | gl.PopAttrib() 105 | } 106 | 107 | segments := strings.Split(s, "\t") 108 | for index, segment := range segments { 109 | o.PrintSegment(segment) 110 | o.advanceBy(uint32(len(segment))) 111 | if index+1 < len(segments) { 112 | tabSpaces := 4 - (o.advance % 4) 113 | o.PrintSegment(strings.Repeat(" ", int(tabSpaces))) // Tab. 114 | if o.ShowInvisibles { 115 | gl.PushAttrib(gl.CURRENT_BIT) 116 | drawBorderlessBox(o.pos.Add(mgl64.Vec2{1, fontHeight/2 - 1}), mgl64.Vec2{fontWidth*float64(tabSpaces) - 2, 2}, selectedTextDarkColor) 117 | gl.PopAttrib() 118 | } 119 | o.advanceBy(tabSpaces) 120 | } 121 | } 122 | } 123 | 124 | func (o *OpenGLStream) advanceBy(amount uint32) { 125 | o.advance += amount 126 | o.afterAdvance() 127 | } 128 | func (o *OpenGLStream) advanceReset() { 129 | o.advance = 0 130 | o.afterAdvance() 131 | } 132 | func (o *OpenGLStream) afterAdvance() { 133 | o.pos[0] = o.lineStartX + fontWidth*float64(o.advance) 134 | } 135 | 136 | // Shouldn't have tabs nor newlines 137 | func (o *OpenGLStream) PrintSegment(s string) { 138 | if s == "" { 139 | return 140 | } 141 | 142 | if o.BackgroundColor != nil && o.BorderColor == nil { 143 | gl.PushAttrib(gl.CURRENT_BIT) 144 | gl.Color3dv(&o.BackgroundColor[0]) 145 | gl.PushMatrix() 146 | gl.Translated(o.pos[0], o.pos[1], 0) 147 | for range s { 148 | gl.CallList(oFontBackground) 149 | } 150 | gl.PopMatrix() 151 | gl.PopAttrib() 152 | } 153 | 154 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, float32(lodBias*0.01)) 155 | 156 | gl.Enable(gl.BLEND) 157 | defer gl.Disable(gl.BLEND) 158 | gl.Enable(gl.TEXTURE_2D) 159 | defer gl.Disable(gl.TEXTURE_2D) 160 | 161 | gl.PushMatrix() 162 | gl.Translated(o.pos[0], o.pos[1], 0) 163 | gl.ListBase(oFontBase + uint32(o.FontOptions)*96) 164 | gl.CallLists(int32(len(s)), gl.UNSIGNED_BYTE, gl.Ptr(&[]byte(s)[0])) 165 | gl.PopMatrix() 166 | 167 | //checkGLError() 168 | } 169 | 170 | // --- 171 | 172 | const fontWidth, fontHeight = 6, 12 173 | 174 | func InitFont() { 175 | gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 176 | 177 | LoadTexture(filepath.Join("data", "fonts", "Menlo.png")) 178 | 179 | oFontBase = gl.GenLists(32 + 96*4) 180 | for i := 0; i < 96*4; i++ { 181 | const shiftX, shiftY = float64(1.0 / 16), float64(1.0 / 6 / 4) 182 | 183 | indexX, indexY := i%16, i/16 184 | 185 | gl.NewList(oFontBase+uint32(i+32), gl.COMPILE) 186 | gl.Begin(gl.QUADS) 187 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY)*shiftY) 188 | gl.Vertex2i(0, 0) 189 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY)*shiftY) 190 | gl.Vertex2i(fontWidth, 0) 191 | gl.TexCoord2d(float64(indexX+1)*shiftX, float64(indexY+1)*shiftY) 192 | gl.Vertex2i(fontWidth, fontHeight) 193 | gl.TexCoord2d(float64(indexX)*shiftX, float64(indexY+1)*shiftY) 194 | gl.Vertex2i(0, fontHeight) 195 | gl.End() 196 | gl.Translated(fontWidth, 0.0, 0.0) 197 | gl.EndList() 198 | } 199 | 200 | oFontBackground = gl.GenLists(1) 201 | gl.NewList(oFontBackground, gl.COMPILE) 202 | gl.Begin(gl.QUADS) 203 | gl.Vertex2i(0, 0) 204 | gl.Vertex2i(0, fontHeight) 205 | gl.Vertex2i(fontWidth, fontHeight) 206 | gl.Vertex2i(fontWidth, 0) 207 | gl.End() 208 | gl.Translated(fontWidth, 0.0, 0.0) 209 | gl.EndList() 210 | 211 | checkGLError() 212 | } 213 | 214 | func DeinitFont() { 215 | gl.DeleteLists(oFontBase, 32+96*4) 216 | gl.DeleteLists(oFontBackground, 1) 217 | } 218 | 219 | func LoadTexture(path string) { 220 | //fmt.Printf("Trying to load texture %q: ", path) 221 | 222 | // Open the file 223 | file, err := os.Open(path) 224 | if err != nil { 225 | fmt.Println(os.Getwd()) 226 | log.Fatal(err) 227 | } 228 | defer file.Close() 229 | 230 | // Decode the image 231 | img, _, err := image.Decode(file) 232 | if err != nil { 233 | log.Fatal(err) 234 | } 235 | 236 | bounds := img.Bounds() 237 | //fmt.Printf("Loaded %vx%v texture.\n", bounds.Dx(), bounds.Dy()) 238 | 239 | var format int 240 | var pixPointer *uint8 241 | switch img := img.(type) { 242 | case *image.RGBA: 243 | format = gl.RGBA 244 | pixPointer = &img.Pix[0] 245 | case *image.NRGBA: 246 | format = gl.RGBA 247 | pixPointer = &img.Pix[0] 248 | case *image.Gray: 249 | format = gl.ALPHA 250 | pixPointer = &img.Pix[0] 251 | default: 252 | log.Fatalf("LoadTexture: Unsupported type %T.\n", img) 253 | } 254 | 255 | var texture uint32 256 | gl.GenTextures(1, &texture) 257 | gl.BindTexture(gl.TEXTURE_2D, texture) 258 | gl.TexParameteri(gl.TEXTURE_2D, gl.GENERATE_MIPMAP, gl.TRUE) 259 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) 260 | gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) 261 | gl.TexParameterf(gl.TEXTURE_2D, gl.TEXTURE_LOD_BIAS, -0.5) 262 | gl.TexImage2D(gl.TEXTURE_2D, 0, int32(format), int32(bounds.Dx()), int32(bounds.Dy()), 0, uint32(format), gl.UNSIGNED_BYTE, gl.Ptr(pixPointer)) 263 | checkGLError() 264 | } 265 | 266 | var ( 267 | nearlyWhiteColor = mgl64.Vec3{0.975, 0.975, 0.975} 268 | ) 269 | 270 | func drawBorderlessBox(pos, size mgl64.Vec2, backgroundColor mgl64.Vec3) { 271 | gl.Color3dv(&backgroundColor[0]) 272 | gl.Rectd(pos[0], pos[1], pos.Add(size)[0], pos.Add(size)[1]) 273 | } 274 | 275 | func drawInnerSlicedBox(pos, size mgl64.Vec2, borderColor, backgroundColor mgl64.Vec3) { 276 | if size[0] == 0 || size[1] == 0 { 277 | return 278 | } 279 | 280 | const OuterDistance = 2.5 281 | gl.Begin(gl.POLYGON) 282 | gl.Color3dv(&borderColor[0]) 283 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]) 284 | gl.Vertex2d(pos[0], pos[1]+OuterDistance) 285 | gl.Vertex2d(pos[0], pos[1]-OuterDistance+size[1]) 286 | gl.Vertex2d(pos[0]+OuterDistance, pos[1]+size[1]) 287 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]+size[1]) 288 | gl.Vertex2d(pos[0]+size[0], pos[1]-OuterDistance+size[1]) 289 | gl.Vertex2d(pos[0]+size[0], pos[1]+OuterDistance) 290 | gl.Vertex2d(pos[0]-OuterDistance+size[0], pos[1]) 291 | gl.End() 292 | 293 | const InnerDistance = OuterDistance + (math.Sqrt2 - 1) 294 | gl.Begin(gl.POLYGON) 295 | gl.Color3dv(&backgroundColor[0]) 296 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]+1) 297 | gl.Vertex2d(pos[0]+1, pos[1]+InnerDistance) 298 | gl.Vertex2d(pos[0]+1, pos[1]-InnerDistance+size[1]) 299 | gl.Vertex2d(pos[0]+InnerDistance, pos[1]-1+size[1]) 300 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]-1+size[1]) 301 | gl.Vertex2d(pos[0]-1+size[0], pos[1]-InnerDistance+size[1]) 302 | gl.Vertex2d(pos[0]-1+size[0], pos[1]+InnerDistance) 303 | gl.Vertex2d(pos[0]-InnerDistance+size[0], pos[1]+1) 304 | gl.End() 305 | } 306 | 307 | func checkGLError() { 308 | err := gl.GetError() 309 | if err != 0 { 310 | panic(fmt.Errorf("GL error: %v", err)) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /page/terminal/main.go: -------------------------------------------------------------------------------- 1 | // terminal is a simple terminal app. 2 | package main 3 | 4 | import ( 5 | "go/build" 6 | "log" 7 | "os" 8 | "runtime" 9 | "strings" 10 | 11 | "github.com/go-gl/gl/v2.1/gl" 12 | "github.com/go-gl/mathgl/mgl64" 13 | "github.com/goxjs/glfw" 14 | "github.com/shurcooL-legacy/Conception-go/event" 15 | ) 16 | 17 | const ( 18 | borderX, borderY = 5, 4 19 | ) 20 | 21 | func init() { 22 | runtime.LockOSThread() 23 | } 24 | 25 | const prompt = "~ $ " 26 | 27 | var text = prompt 28 | 29 | func run() error { 30 | if err := glfw.Init(nopContextWatcher{}); err != nil { 31 | return err 32 | } 33 | defer glfw.Terminate() 34 | 35 | glfw.WindowHint(glfw.Resizable, gl.FALSE) 36 | window, err := glfw.CreateWindow(80*fontWidth+borderX*2, 25*fontHeight+borderY*2, "Terminal — 80×25", nil, nil) 37 | if err != nil { 38 | return err 39 | } 40 | window.MakeContextCurrent() 41 | 42 | if err := gl.Init(); err != nil { 43 | return err 44 | } 45 | 46 | // Clear screen right away. 47 | gl.ClearColor(1, 1, 1, 1) 48 | gl.Clear(gl.COLOR_BUFFER_BIT) 49 | window.SwapBuffers() 50 | 51 | glfw.SwapInterval(1) // Vsync. 52 | 53 | InitFont() 54 | defer DeinitFont() 55 | 56 | framebufferSizeCallback := func(w *glfw.Window, framebufferSize0, framebufferSize1 int) { 57 | gl.Viewport(0, 0, int32(framebufferSize0), int32(framebufferSize1)) 58 | 59 | var windowSize [2]int 60 | windowSize[0], windowSize[1] = w.GetSize() 61 | 62 | // Update the projection matrix. 63 | gl.MatrixMode(gl.PROJECTION) 64 | gl.LoadIdentity() 65 | gl.Ortho(0, float64(windowSize[0]), float64(windowSize[1]), 0, -1, 1) 66 | gl.MatrixMode(gl.MODELVIEW) 67 | 68 | /*inputEvent := InputEvent{ 69 | Pointer: windowPointer, 70 | EventTypes: map[EventType]struct{}{AxisEvent: struct{}{}}, 71 | InputID: 0, 72 | Buttons: nil, 73 | Sliders: nil, 74 | Axes: []float64{float64(windowSize[0]), float64(windowSize[1])}, 75 | } 76 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent)*/ 77 | } 78 | { 79 | var framebufferSize [2]int 80 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 81 | framebufferSizeCallback(window, framebufferSize[0], framebufferSize[1]) 82 | } 83 | window.SetFramebufferSizeCallback(framebufferSizeCallback) 84 | 85 | var mousePointer = &event.Pointer{VirtualCategory: event.Pointing} 86 | var keyboardPointer = &event.Pointer{VirtualCategory: event.Typing} 87 | var inputEventQueue []event.InputEvent 88 | 89 | window.SetMouseMovementCallback(func(w *glfw.Window, xpos, ypos, xdelta, ydelta float64) { 90 | inputEvent := event.InputEvent{ 91 | Pointer: mousePointer, 92 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 93 | InputID: 0, 94 | Buttons: nil, 95 | Sliders: []float64{xdelta, ydelta}, 96 | } 97 | if w.GetInputMode(glfw.CursorMode) != glfw.CursorDisabled { 98 | inputEvent.EventTypes[event.AxisEvent] = struct{}{} 99 | inputEvent.Axes = []float64{xpos, ypos} 100 | } 101 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 102 | }) 103 | 104 | window.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) { 105 | inputEvent := event.InputEvent{ 106 | Pointer: mousePointer, 107 | EventTypes: map[event.EventType]struct{}{event.SliderEvent: {}}, 108 | InputID: 2, 109 | Buttons: nil, 110 | Sliders: []float64{yoff, xoff}, 111 | Axes: nil, 112 | } 113 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 114 | }) 115 | 116 | window.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { 117 | inputEvent := event.InputEvent{ 118 | Pointer: mousePointer, 119 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 120 | InputID: uint16(button), 121 | Buttons: []bool{action != glfw.Release}, 122 | Sliders: nil, 123 | Axes: nil, 124 | ModifierKey: uint8(mods), 125 | } 126 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 127 | }) 128 | 129 | window.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { 130 | inputEvent := event.InputEvent{ 131 | Pointer: keyboardPointer, 132 | EventTypes: map[event.EventType]struct{}{event.ButtonEvent: {}}, 133 | InputID: uint16(key), 134 | Buttons: []bool{action != glfw.Release}, 135 | Sliders: nil, 136 | Axes: nil, 137 | ModifierKey: uint8(mods), 138 | } 139 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 140 | }) 141 | 142 | window.SetCharCallback(func(w *glfw.Window, char rune) { 143 | inputEvent := event.InputEvent{ 144 | Pointer: keyboardPointer, 145 | EventTypes: map[event.EventType]struct{}{event.CharacterEvent: {}}, 146 | InputID: uint16(char), 147 | Buttons: nil, 148 | Sliders: nil, 149 | Axes: nil, 150 | } 151 | inputEventQueue = event.EnqueueInputEvent(inputEventQueue, inputEvent) 152 | }) 153 | 154 | var framebufferSize [2]int 155 | framebufferSize[0], framebufferSize[1] = window.GetFramebufferSize() 156 | 157 | app := &app{window: window} 158 | 159 | for !window.ShouldClose() { 160 | glfw.PollEvents() 161 | 162 | // Process Input. 163 | inputEventQueue = app.processInputEventQueue(inputEventQueue) 164 | 165 | gl.Clear(gl.COLOR_BUFFER_BIT) 166 | 167 | /*gl.Color3d(0.5, 0.5, 0.5) 168 | gl.Begin(gl.TRIANGLE_FAN) 169 | gl.Vertex2i(0, 0) 170 | gl.Vertex2i(int32(framebufferSize[0]), 0) 171 | gl.Vertex2i(int32(framebufferSize[0]), 1) 172 | gl.Vertex2i(0, 1) 173 | gl.End()*/ 174 | 175 | gl.Color3d(0, 0, 0) 176 | NewOpenGLStream(mgl64.Vec2{borderX, borderY}).PrintText(text) 177 | 178 | // Draw caret. 179 | lines := strings.Split(text, "\n") 180 | lastLine := lines[len(lines)-1] 181 | expandedCaretPosition, caretLine := len(lastLine), len(lines)-1 182 | gl.PushMatrix() 183 | gl.Translated(borderX, borderY, 0) 184 | gl.Color3d(0, 0, 0) 185 | gl.Recti(int32(expandedCaretPosition*fontWidth-1), int32(caretLine*fontHeight), int32(expandedCaretPosition*fontWidth+1), int32(caretLine*fontHeight)+fontHeight) 186 | gl.PopMatrix() 187 | 188 | mousePointer.Render() 189 | 190 | window.SwapBuffers() 191 | runtime.Gosched() 192 | } 193 | 194 | return nil 195 | } 196 | 197 | func main() { 198 | err := run() 199 | if err != nil { 200 | log.Fatalln(err) 201 | } 202 | } 203 | 204 | type app struct { 205 | window *glfw.Window 206 | } 207 | 208 | func (a *app) processInputEventQueue(inputEventQueue []event.InputEvent) []event.InputEvent { 209 | for len(inputEventQueue) > 0 { 210 | inputEvent := inputEventQueue[0] 211 | 212 | //fmt.Println(inputEvent) 213 | _ = inputEvent 214 | //spew.Dump(inputEvent) 215 | 216 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && glfw.Key(inputEvent.InputID) == glfw.KeyW && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) == glfw.ModSuper { 217 | a.window.SetShouldClose(true) 218 | } 219 | 220 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) & ^glfw.ModShift == 0 { 221 | switch glfw.Key(inputEvent.InputID) { 222 | case glfw.KeyBackspace: 223 | if len(text) > 0 { 224 | text = text[:len(text)-1] 225 | } 226 | case glfw.KeyEnter: 227 | text += "\n-bash: command not found" 228 | text += "\n" + prompt 229 | } 230 | } 231 | 232 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && glfw.Key(inputEvent.InputID) == glfw.KeyL && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) == glfw.ModControl { 233 | text = prompt 234 | } 235 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.ButtonEvent) && glfw.Key(inputEvent.InputID) == glfw.KeyC && inputEvent.Buttons[0] && glfw.ModifierKey(inputEvent.ModifierKey) == glfw.ModControl { 236 | text += "^C\n" + prompt 237 | } 238 | 239 | if inputEvent.Pointer.VirtualCategory == event.Typing && inputEvent.EventTypes.Has(event.CharacterEvent) { 240 | text += string(inputEvent.InputID) 241 | } 242 | 243 | inputEventQueue = inputEventQueue[1:] 244 | } 245 | 246 | return inputEventQueue 247 | } 248 | 249 | type nopContextWatcher struct{} 250 | 251 | func (nopContextWatcher) OnMakeCurrent(context interface{}) {} 252 | func (nopContextWatcher) OnDetach() {} 253 | 254 | // Set the working directory to the root of Conception-go package, so that its assets can be accessed. 255 | func init() { 256 | // importPathToDir resolves the absolute path from importPath. 257 | // There doesn't need to be a valid Go package inside that import path, 258 | // but the directory must exist. 259 | importPathToDir := func(importPath string) (string, error) { 260 | p, err := build.Import(importPath, "", build.FindOnly) 261 | if err != nil { 262 | return "", err 263 | } 264 | return p.Dir, nil 265 | } 266 | 267 | dir, err := importPathToDir("github.com/shurcooL-legacy/Conception-go") 268 | if err != nil { 269 | log.Fatalln("Unable to find github.com/shurcooL-legacy/Conception-go package in your GOPATH, it's needed to load assets:", err) 270 | } 271 | err = os.Chdir(dir) 272 | if err != nil { 273 | log.Fatalln("os.Chdir:", err) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /pkg/analysis/generated_detection.go: -------------------------------------------------------------------------------- 1 | // Package analysis provides a routine that determines if a file is generated or handcrafted. 2 | // 3 | // Deprecated: Use github.com/shurcooL/go/generated package instead. This implementation 4 | // was done ad-hoc before a standard was proposed. 5 | package analysis 6 | 7 | import ( 8 | "bufio" 9 | "io" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | // IsFileGenerated returns true if the specified file is generated, or false if it's handcrafted. 16 | // rootDir is the filepath of root directory, but name is a '/'-separated path to file. 17 | // 18 | // It considers vendored files as "generated", in the sense that they are not the canonical 19 | // version of a file. This behavior would ideally be factored out into a higher level utility, 20 | // since it has nothing to do with generated comments. 21 | // 22 | // Deprecated: Use generated.ParseFile instead, which is more well defined because it 23 | // implements a specification. 24 | func IsFileGenerated(rootDir, name string) (bool, error) { 25 | // Detect from name. 26 | switch { 27 | case strings.HasPrefix(name, "vendor/") || strings.Contains(name, "/vendor/"): 28 | return true, nil 29 | case strings.HasPrefix(name, "Godeps/"): 30 | return true, nil 31 | } 32 | 33 | // Detect from file contents. 34 | f, err := os.Open(filepath.Join(rootDir, filepath.FromSlash(name))) 35 | if err != nil { 36 | return false, err 37 | } 38 | defer f.Close() 39 | r := bufio.NewReader(f) 40 | s, err := r.ReadString('\n') 41 | if err == io.EOF { 42 | // Empty file or exactly 1 line is not considered to be generated. 43 | return false, nil 44 | } else if err != nil { 45 | return false, err 46 | } 47 | if strings.Contains(s, "Code generated by") { // Consistent with https://golang.org/cl/15073. 48 | return true, nil 49 | } 50 | return (strings.Contains(s, "GENERATED") || strings.Contains(s, "generated")) && strings.Contains(s, "DO NOT EDIT"), nil 51 | } 52 | -------------------------------------------------------------------------------- /pkg/analysis/generated_detection_test.go: -------------------------------------------------------------------------------- 1 | package analysis_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/shurcooL-legacy/Conception-go/pkg/analysis" 8 | ) 9 | 10 | func ExampleIsFileGenerated() { 11 | cwd, err := os.Getwd() 12 | if err != nil { 13 | panic(err) 14 | } 15 | 16 | fmt.Println(analysis.IsFileGenerated(cwd, "testdata/generated_0.go.txt")) 17 | fmt.Println(analysis.IsFileGenerated(cwd, "testdata/handcrafted_0.go.txt")) 18 | fmt.Println(analysis.IsFileGenerated(cwd, "testdata/handcrafted_1.go.txt")) 19 | fmt.Println(analysis.IsFileGenerated(cwd, "vendor/github.com/shurcooL/go/analysis/file.go")) 20 | fmt.Println(analysis.IsFileGenerated(cwd, "subpkg/vendor/math/math.go")) 21 | 22 | // Output: 23 | // true 24 | // false 25 | // false 26 | // true 27 | // true 28 | } 29 | -------------------------------------------------------------------------------- /pkg/analysis/testdata/generated_0.go.txt: -------------------------------------------------------------------------------- 1 | // generated by vfsgen; DO NOT EDIT 2 | 3 | // +build !dev 4 | 5 | package issues 6 | 7 | import ( 8 | "bytes" 9 | "compress/gzip" 10 | "fmt" 11 | "io" 12 | "net/http" 13 | "os" 14 | pathpkg "path" 15 | "time" 16 | ) 17 | 18 | // Assets statically implements the virtual filesystem given to vfsgen as input. 19 | var Assets = func() http.FileSystem { 20 | mustUnmarshalTextTime := func(text string) time.Time { 21 | var t time.Time 22 | err := t.UnmarshalText([]byte(text)) 23 | if err != nil { 24 | panic(err) 25 | } 26 | return t 27 | } 28 | 29 | fs := _vfsgen_fs{ 30 | ... 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/analysis/testdata/handcrafted_0.go.txt: -------------------------------------------------------------------------------- 1 | // Package foo offers bar. 2 | package foo 3 | 4 | import "strings" 5 | 6 | // Bar is bar. 7 | func Bar() string { 8 | return strings.Title("bar") 9 | } 10 | -------------------------------------------------------------------------------- /pkg/analysis/testdata/handcrafted_1.go.txt: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. Actually it isn't, because it's only 1 line. -------------------------------------------------------------------------------- /pkg/exp11/main.go: -------------------------------------------------------------------------------- 1 | // Package exp11 allows displaying Go package source code with dot imports inlined, or merging entire Go package into a single file. 2 | package exp11 3 | 4 | import ( 5 | "fmt" 6 | "go/ast" 7 | "go/parser" 8 | "go/token" 9 | "io" 10 | "os" 11 | "strings" 12 | 13 | "github.com/shurcooL/go/printerutil" 14 | "golang.org/x/tools/go/loader" 15 | "golang.org/x/tools/imports" 16 | ) 17 | 18 | const parserMode = parser.ParseComments 19 | const astMergeMode = 0*ast.FilterFuncDuplicates | ast.FilterUnassociatedComments | ast.FilterImportDuplicates 20 | 21 | var dotImports []*loader.PackageInfo 22 | 23 | func findDotImports(prog *loader.Program, pi *loader.PackageInfo) { 24 | for _, file := range pi.Files { 25 | for _, importSpec := range file.Imports { 26 | if importSpec.Name != nil && importSpec.Name.Name == "." { 27 | dotImportImportPath := strings.Trim(importSpec.Path.Value, `"`) 28 | dotImportPi := prog.Package(dotImportImportPath) 29 | dotImports = append(dotImports, dotImportPi) 30 | findDotImports(prog, dotImportPi) 31 | } 32 | } 33 | } 34 | } 35 | 36 | // InlineDotImports displays Go package source code with dot imports inlined. 37 | func InlineDotImports(w io.Writer, importPath string) { 38 | /*imp2 := importer.New() 39 | imp2.Config.UseGcFallback = true 40 | cfg := types.Config{Import: imp2.Import} 41 | _ = cfg*/ 42 | 43 | conf := loader.Config{ 44 | //TypeChecker: cfg, 45 | } 46 | 47 | conf.Import(importPath) 48 | 49 | prog, err := conf.Load() 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | /*pi, err := imp.ImportPackage(importPath) 55 | if err != nil { 56 | panic(err) 57 | } 58 | _ = pi*/ 59 | 60 | pi := prog.Imported[importPath] 61 | 62 | findDotImports(prog, pi) 63 | 64 | files := make(map[string]*ast.File) 65 | { 66 | // This package 67 | for _, file := range pi.Files { 68 | filename := prog.Fset.File(file.Package).Name() 69 | files[filename] = file 70 | } 71 | 72 | // All dot imports 73 | for _, pi := range dotImports { 74 | for _, file := range pi.Files { 75 | filename := prog.Fset.File(file.Package).Name() 76 | files[filename] = file 77 | } 78 | } 79 | } 80 | 81 | apkg := &ast.Package{Name: pi.Pkg.Name(), Files: files} 82 | 83 | merged := ast.MergePackageFiles(apkg, astMergeMode) 84 | 85 | WriteMergedPackage(w, prog.Fset, merged) 86 | } 87 | 88 | // WriteMergedPackage writes a merged package, typically coming from ast.MergePackageFiles, to w. 89 | // It sorts and de-duplicates imports. 90 | // 91 | // TODO: Support comments. 92 | func WriteMergedPackage(w io.Writer, fset *token.FileSet, merged *ast.File) { 93 | switch 3 { 94 | case 1: 95 | fmt.Fprintln(w, "package "+printerutil.SprintAst(fset, merged.Name)) 96 | fmt.Fprintln(w) 97 | fmt.Fprintln(w, `import (`) 98 | // TODO: SortImports (ala goimports). 99 | for _, importSpec := range merged.Imports { 100 | if importSpec.Name != nil && importSpec.Name.Name == "." { 101 | continue 102 | } 103 | fmt.Fprintln(w, "\t"+printerutil.SprintAst(fset, importSpec)) 104 | } 105 | fmt.Fprintln(w, `)`) 106 | fmt.Fprintln(w) 107 | 108 | for _, decl := range merged.Decls { 109 | if x, ok := decl.(*ast.GenDecl); ok && x.Tok == token.IMPORT { 110 | continue 111 | } 112 | 113 | fmt.Fprintln(w, printerutil.SprintAst(fset, decl)) 114 | fmt.Fprintln(w) 115 | } 116 | case 2: 117 | sortDecls(merged) 118 | 119 | //fmt.Fprintln(w, gist5639599.SprintAst(token.NewFileSet(), merged)) 120 | 121 | //ast.SortImports(fset, merged) 122 | sortImports2(fset, merged) 123 | 124 | fmt.Fprintln(w, printerutil.SprintAst(fset, merged)) 125 | case 3: 126 | sortDecls(merged) 127 | 128 | // TODO: Clean up this mess... 129 | fset2, f2 := sortImports2(token.NewFileSet(), merged) 130 | 131 | fmt.Fprintln(w, "package "+printerutil.SprintAst(fset, merged.Name)) 132 | for _, decl := range f2.Decls { 133 | if x, ok := decl.(*ast.GenDecl); ok && x.Tok == token.IMPORT { 134 | fmt.Fprintln(w) 135 | fmt.Fprintln(w, printerutil.SprintAst(fset2, decl)) 136 | } 137 | } 138 | for _, decl := range merged.Decls { 139 | if x, ok := decl.(*ast.GenDecl); ok && (x.Tok == token.IMPORT || x.Tok == token.PACKAGE) { 140 | continue 141 | } 142 | 143 | fmt.Fprintln(w) 144 | fmt.Fprintln(w, printerutil.SprintAst(fset, decl)) 145 | } 146 | case 4: 147 | sortDecls(merged) 148 | 149 | src := []byte(printerutil.SprintAst(fset, merged)) 150 | 151 | out, err := imports.Process("", src, nil) 152 | if err != nil { 153 | panic(err) 154 | } 155 | os.Stdout.Write(out) 156 | fmt.Println() 157 | } 158 | } 159 | 160 | func sortDecls(merged *ast.File) { 161 | var sortedDecls []ast.Decl 162 | for _, decl := range merged.Decls { 163 | if x, ok := decl.(*ast.GenDecl); ok && x.Tok == token.PACKAGE { 164 | sortedDecls = append(sortedDecls, decl) 165 | } 166 | } 167 | /*for _, decl := range merged.Decls { 168 | if x, ok := decl.(*ast.GenDecl); ok && x.Tok == token.IMPORT { 169 | sortedDecls = append(sortedDecls, decl) 170 | goon.DumpExpr(decl) 171 | } 172 | }*/ 173 | var specs []ast.Spec 174 | for _, importSpec := range merged.Imports { 175 | if importSpec.Name != nil && importSpec.Name.Name == "." { 176 | continue 177 | } 178 | importSpec.EndPos = 0 179 | specs = append(specs, importSpec) 180 | } 181 | sortedDecls = append(sortedDecls, &ast.GenDecl{ 182 | Tok: token.IMPORT, 183 | Lparen: (token.Pos)(1), // Needs to be non-zero to be considered as a group. 184 | Specs: specs, 185 | }) 186 | //goon.DumpExpr(sortedDecls[len(sortedDecls)-1]) 187 | for _, decl := range merged.Decls { 188 | if x, ok := decl.(*ast.GenDecl); ok && (x.Tok == token.IMPORT || x.Tok == token.PACKAGE) { 189 | continue 190 | } 191 | sortedDecls = append(sortedDecls, decl) 192 | } 193 | merged.Decls = sortedDecls 194 | } 195 | -------------------------------------------------------------------------------- /pkg/exp11/sortimports.go: -------------------------------------------------------------------------------- 1 | package exp11 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "go/ast" 8 | "go/parser" 9 | "go/token" 10 | "io" 11 | "regexp" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | 16 | "github.com/shurcooL/go/printerutil" 17 | "golang.org/x/tools/go/ast/astutil" 18 | ) 19 | 20 | // Hacked up copy of golang.org/x/tools/imports/sortimports.go. 21 | 22 | // TODO: Replace with go/ast.SortImports or golang.org/x/tools/imports.SortImports whenever it's possible. 23 | 24 | func sortImports2(fset *token.FileSet, f *ast.File) (fset2 *token.FileSet, f2 *ast.File) { 25 | sortImports(fset, f) 26 | imps := astutil.Imports(fset, f) 27 | 28 | var spacesBefore []string // import paths we need spaces before 29 | for _, impSection := range imps { 30 | // Within each block of contiguous imports, see if any 31 | // import lines are in different group numbers. If so, 32 | // we'll need to put a space between them so it's 33 | // compatible with gofmt. 34 | lastGroup := -1 35 | for _, importSpec := range impSection { 36 | importPath, _ := strconv.Unquote(importSpec.Path.Value) 37 | groupNum := importGroup(importPath) 38 | if groupNum != lastGroup && lastGroup != -1 { 39 | spacesBefore = append(spacesBefore, importPath) 40 | } 41 | lastGroup = groupNum 42 | } 43 | } 44 | 45 | // So gross. Print out the entire AST, add a space by modifying the bytes, and reparse the whole thing. 46 | // Because I don't see an API that'd let me modify (insert a newline) the FileSet directly. 47 | // We really need a go/ast v2, which is as friendly to parsing/printing/formatting as current, but 48 | // also friendly towards direct AST modification... To avoid all these horrible hacks. 49 | out := []byte(printerutil.SprintAst(fset, f)) 50 | if len(spacesBefore) > 0 { 51 | out = addImportSpaces(bytes.NewReader(out), spacesBefore) 52 | } 53 | 54 | fset2 = token.NewFileSet() 55 | var err error 56 | f2, err = parser.ParseFile(fset2, "", out, parser.ParseComments) 57 | if err != nil { 58 | panic(err) 59 | } 60 | return 61 | } 62 | 63 | var impLine = regexp.MustCompile(`^\s+(?:\w+\s+)?"(.+)"`) 64 | 65 | func addImportSpaces(r io.Reader, breaks []string) []byte { 66 | var out bytes.Buffer 67 | sc := bufio.NewScanner(r) 68 | inImports := false 69 | done := false 70 | for sc.Scan() { 71 | s := sc.Text() 72 | 73 | if !inImports && !done && strings.HasPrefix(s, "import") { 74 | inImports = true 75 | } 76 | if inImports && (strings.HasPrefix(s, "var") || 77 | strings.HasPrefix(s, "func") || 78 | strings.HasPrefix(s, "const") || 79 | strings.HasPrefix(s, "type")) { 80 | done = true 81 | inImports = false 82 | } 83 | if inImports && len(breaks) > 0 { 84 | if m := impLine.FindStringSubmatch(s); m != nil { 85 | if m[1] == string(breaks[0]) { 86 | out.WriteByte('\n') 87 | breaks = breaks[1:] 88 | } 89 | } 90 | } 91 | 92 | fmt.Fprintln(&out, s) 93 | } 94 | return out.Bytes() 95 | } 96 | 97 | // sortImports sorts runs of consecutive import lines in import blocks in f. 98 | // It also removes duplicate imports when it is possible to do so without data loss. 99 | func sortImports(fset *token.FileSet, f *ast.File) { 100 | for i, d := range f.Decls { 101 | d, ok := d.(*ast.GenDecl) 102 | if !ok || d.Tok != token.IMPORT { 103 | // Not an import declaration, so we're done. 104 | // Imports are always first. 105 | break 106 | } 107 | 108 | if len(d.Specs) == 0 { 109 | // Empty import block, remove it. 110 | f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) 111 | } 112 | 113 | if !d.Lparen.IsValid() { 114 | // Not a block: sorted by default. 115 | continue 116 | } 117 | 118 | // Identify and sort runs of specs on successive lines. 119 | i := 0 120 | specs := d.Specs[:0] 121 | for j, s := range d.Specs { 122 | if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { 123 | // j begins a new run. End this one. 124 | specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) 125 | i = j 126 | } 127 | } 128 | specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) 129 | d.Specs = specs 130 | 131 | // Deduping can leave a blank line before the rparen; clean that up. 132 | if len(d.Specs) > 0 { 133 | lastSpec := d.Specs[len(d.Specs)-1] 134 | lastLine := fset.Position(lastSpec.Pos()).Line 135 | if rParenLine := fset.Position(d.Rparen).Line; rParenLine > lastLine+1 { 136 | fset.File(d.Rparen).MergeLine(rParenLine - 1) 137 | } 138 | } 139 | } 140 | } 141 | 142 | func importPath(s ast.Spec) string { 143 | t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) 144 | if err == nil { 145 | return t 146 | } 147 | return "" 148 | } 149 | 150 | func importName(s ast.Spec) string { 151 | n := s.(*ast.ImportSpec).Name 152 | if n == nil { 153 | return "" 154 | } 155 | return n.Name 156 | } 157 | 158 | func importComment(s ast.Spec) string { 159 | c := s.(*ast.ImportSpec).Comment 160 | if c == nil { 161 | return "" 162 | } 163 | return c.Text() 164 | } 165 | 166 | // importToGroup is a list of functions which map from an import path to 167 | // a group number. 168 | var importToGroup = []func(importPath string) (num int, ok bool){ 169 | func(importPath string) (num int, ok bool) { 170 | if strings.HasPrefix(importPath, "appengine") { 171 | return 2, true 172 | } 173 | return 174 | }, 175 | func(importPath string) (num int, ok bool) { 176 | if strings.Contains(importPath, ".") { 177 | return 1, true 178 | } 179 | return 180 | }, 181 | } 182 | 183 | func importGroup(importPath string) int { 184 | for _, fn := range importToGroup { 185 | if n, ok := fn(importPath); ok { 186 | return n 187 | } 188 | } 189 | return 0 190 | } 191 | 192 | // collapse indicates whether prev may be removed, leaving only next. 193 | func collapse(prev, next ast.Spec) bool { 194 | if importPath(next) != importPath(prev) || importName(next) != importName(prev) { 195 | return false 196 | } 197 | return prev.(*ast.ImportSpec).Comment == nil 198 | } 199 | 200 | type posSpan struct { 201 | Start token.Pos 202 | End token.Pos 203 | } 204 | 205 | func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { 206 | // Can't short-circuit here even if specs are already sorted, 207 | // since they might yet need deduplication. 208 | // A lone import, however, may be safely ignored. 209 | if len(specs) <= 1 { 210 | return specs 211 | } 212 | 213 | // Record positions for specs. 214 | pos := make([]posSpan, len(specs)) 215 | for i, s := range specs { 216 | pos[i] = posSpan{s.Pos(), s.End()} 217 | } 218 | 219 | // Identify comments in this range. 220 | // Any comment from pos[0].Start to the final line counts. 221 | lastLine := fset.Position(pos[len(pos)-1].End).Line 222 | cstart := len(f.Comments) 223 | cend := len(f.Comments) 224 | for i, g := range f.Comments { 225 | if g.Pos() < pos[0].Start { 226 | continue 227 | } 228 | if i < cstart { 229 | cstart = i 230 | } 231 | if fset.Position(g.End()).Line > lastLine { 232 | cend = i 233 | break 234 | } 235 | } 236 | comments := f.Comments[cstart:cend] 237 | 238 | // Assign each comment to the import spec preceding it. 239 | importComment := map[*ast.ImportSpec][]*ast.CommentGroup{} 240 | specIndex := 0 241 | for _, g := range comments { 242 | for specIndex+1 < len(specs) && pos[specIndex+1].Start <= g.Pos() { 243 | specIndex++ 244 | } 245 | s := specs[specIndex].(*ast.ImportSpec) 246 | importComment[s] = append(importComment[s], g) 247 | } 248 | 249 | // Sort the import specs by import path. 250 | // Remove duplicates, when possible without data loss. 251 | // Reassign the import paths to have the same position sequence. 252 | // Reassign each comment to abut the end of its spec. 253 | // Sort the comments by new position. 254 | sort.Sort(byImportSpec(specs)) 255 | 256 | // Dedup. Thanks to our sorting, we can just consider 257 | // adjacent pairs of imports. 258 | deduped := specs[:0] 259 | for i, s := range specs { 260 | if i == len(specs)-1 || !collapse(s, specs[i+1]) { 261 | deduped = append(deduped, s) 262 | } else { 263 | p := s.Pos() 264 | fset.File(p).MergeLine(fset.Position(p).Line) 265 | } 266 | } 267 | specs = deduped 268 | 269 | // Fix up comment positions 270 | for i, s := range specs { 271 | s := s.(*ast.ImportSpec) 272 | if s.Name != nil { 273 | s.Name.NamePos = pos[i].Start 274 | } 275 | s.Path.ValuePos = pos[i].Start 276 | s.EndPos = pos[i].End 277 | for _, g := range importComment[s] { 278 | for _, c := range g.List { 279 | c.Slash = pos[i].End 280 | } 281 | } 282 | } 283 | 284 | sort.Sort(byCommentPos(comments)) 285 | 286 | return specs 287 | } 288 | 289 | type byImportSpec []ast.Spec // slice of *ast.ImportSpec 290 | 291 | func (x byImportSpec) Len() int { return len(x) } 292 | func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 293 | func (x byImportSpec) Less(i, j int) bool { 294 | ipath := importPath(x[i]) 295 | jpath := importPath(x[j]) 296 | 297 | igroup := importGroup(ipath) 298 | jgroup := importGroup(jpath) 299 | if igroup != jgroup { 300 | return igroup < jgroup 301 | } 302 | 303 | if ipath != jpath { 304 | return ipath < jpath 305 | } 306 | iname := importName(x[i]) 307 | jname := importName(x[j]) 308 | 309 | if iname != jname { 310 | return iname < jname 311 | } 312 | return importComment(x[i]) < importComment(x[j]) 313 | } 314 | 315 | type byCommentPos []*ast.CommentGroup 316 | 317 | func (x byCommentPos) Len() int { return len(x) } 318 | func (x byCommentPos) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 319 | func (x byCommentPos) Less(i, j int) bool { return x[i].Pos() < x[j].Pos() } 320 | -------------------------------------------------------------------------------- /pkg/exp12/main.go: -------------------------------------------------------------------------------- 1 | // Package exp12 provides caching of vcs per directory. 2 | package exp12 3 | 4 | import ( 5 | "sync" 6 | 7 | "github.com/shurcooL-legacy/Conception-go/pkg/exp13" 8 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 9 | "github.com/shurcooL-legacy/Conception-go/pkg/legacyvcs" 10 | ) 11 | 12 | // rootPath -> *VcsState 13 | var repos = make(map[string]*exp13.VcsState) 14 | var reposLock sync.Mutex 15 | 16 | // path -> *Directory 17 | var directories = make(map[string]*Directory) 18 | var directoriesLock sync.Mutex 19 | 20 | type Directory struct { 21 | path string 22 | 23 | Repo *exp13.VcsState // nil if no Vcs. 24 | 25 | gist7802150.DepNode2 26 | } 27 | 28 | func (this *Directory) Update() { 29 | if vcs := legacyvcs.New(this.path); vcs != nil { 30 | reposLock.Lock() 31 | if repo, ok := repos[vcs.RootPath()]; ok { 32 | this.Repo = repo 33 | } else { 34 | this.Repo = exp13.NewVcsState(vcs) 35 | repos[vcs.RootPath()] = this.Repo 36 | } 37 | reposLock.Unlock() 38 | } 39 | } 40 | 41 | func newDirectory(path string) *Directory { 42 | this := &Directory{path: path} 43 | // No DepNode2I sources, so each instance can only be updated (i.e. initialized) once. 44 | return this 45 | } 46 | 47 | // LookupDirectory is safe to call concurrently. 48 | func LookupDirectory(path string) *Directory { 49 | directoriesLock.Lock() 50 | defer directoriesLock.Unlock() 51 | if dir := directories[path]; dir != nil { 52 | return dir 53 | } else { 54 | dir = newDirectory(path) 55 | directories[path] = dir 56 | return dir 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /pkg/exp13/main.go: -------------------------------------------------------------------------------- 1 | // Package exp13 offers caching of vcs state per repository. 2 | package exp13 3 | 4 | import ( 5 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 6 | "github.com/shurcooL-legacy/Conception-go/pkg/legacyvcs" 7 | go_vcs "golang.org/x/tools/go/vcs" 8 | ) 9 | 10 | type VcsState struct { 11 | Vcs legacyvcs.Vcs 12 | 13 | VcsLocal *VcsLocal 14 | VcsRemote *VcsRemote 15 | 16 | RepoRoot *go_vcs.RepoRoot 17 | 18 | // THINK: No need to add repo as a DepNode2I, just add it a plain variable. Maybe? 19 | // TODO: No need for this to have a DepNode2Manual, remove it. 20 | // Well, the idea is I don't foresee anyone invalidating the entire VcsState. 21 | gist7802150.DepNode2Manual 22 | } 23 | 24 | func NewVcsState(vcs legacyvcs.Vcs) *VcsState { 25 | this := &VcsState{ 26 | Vcs: vcs, 27 | } 28 | this.VcsLocal = NewVcsLocal(this) 29 | this.VcsRemote = NewVcsRemote(this) 30 | return this 31 | } 32 | 33 | // --- 34 | 35 | type VcsLocal struct { 36 | Status string 37 | Stash string 38 | Remote string 39 | LocalBranch string 40 | LocalRev string 41 | LocalRemoteRev string 42 | 43 | gist7802150.DepNode2 44 | } 45 | 46 | func NewVcsLocal(repo *VcsState) *VcsLocal { 47 | this := &VcsLocal{} 48 | // THINK: No need to add repo as a DepNode2I, just add it a plain variable. Maybe? 49 | this.AddSources(repo, &gist7802150.DepNode2Manual{}) 50 | return this 51 | } 52 | 53 | func (this *VcsLocal) Update() { 54 | // THINK: No need to add repo as a DepNode2I, just add it a plain variable. Maybe? 55 | vcs := this.GetSources()[0].(*VcsState).Vcs 56 | 57 | this.Status = vcs.GetStatus() 58 | this.Stash = vcs.GetStash() 59 | this.Remote = vcs.GetRemote() 60 | this.LocalBranch = vcs.GetLocalBranch() 61 | this.LocalRev = vcs.GetLocalRev() 62 | this.LocalRemoteRev = vcs.GetLocalRemoteRev() 63 | } 64 | 65 | // --- 66 | 67 | type VcsRemote struct { 68 | RemoteRev string 69 | IsContained bool // True if remote commit is contained in the default local branch. 70 | 71 | gist7802150.DepNode2 72 | } 73 | 74 | func NewVcsRemote(repo *VcsState) *VcsRemote { 75 | this := &VcsRemote{} 76 | this.AddSources(repo) 77 | return this 78 | } 79 | 80 | func (this *VcsRemote) Update() { 81 | vcs := this.GetSources()[0].(*VcsState).Vcs 82 | 83 | this.RemoteRev = vcs.GetRemoteRev() 84 | if this.RemoteRev != "" { 85 | this.IsContained = vcs.IsContained(this.RemoteRev) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pkg/exp14/main.go: -------------------------------------------------------------------------------- 1 | // Package exp14 provides caching of a list of Go packages. 2 | package exp14 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "time" 8 | 9 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7480523" 10 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7651991" 11 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 12 | "github.com/shurcooL-legacy/Conception-go/pkg/gist8018045" 13 | ) 14 | 15 | type GoPackageList interface { 16 | List() []*gist7480523.GoPackage 17 | 18 | gist7802150.DepNode2I 19 | } 20 | 21 | // GoPackages is a cached list of all Go packages in GOPATH including/excluding GOROOT. 22 | type GoPackages struct { 23 | SkipGoroot bool // Currently, works on initial run only; changing its value afterwards has no effect. 24 | 25 | Entries []*gist7480523.GoPackage 26 | 27 | gist7802150.DepNode2 28 | } 29 | 30 | func (this *GoPackages) Update() { 31 | // TODO: Have a source? 32 | 33 | started := time.Now() 34 | defer func() { 35 | fmt.Printf("GoPackages.Update: %v ms; %v packages.\n", time.Since(started).Seconds()*1000, len(this.Entries)) 36 | }() 37 | 38 | // TODO: Make it load in background, without blocking, etc. 39 | { 40 | goPackages := make(chan *gist7480523.GoPackage, 64) 41 | 42 | if this.SkipGoroot { 43 | go gist8018045.GetGopathGoPackages(goPackages) 44 | } else { 45 | go gist8018045.GetGoPackages(goPackages) 46 | } 47 | 48 | this.Entries = nil 49 | for { 50 | if goPackage, ok := <-goPackages; ok { 51 | this.Entries = append(this.Entries, goPackage) 52 | } else { 53 | break 54 | } 55 | } 56 | } 57 | } 58 | 59 | func (this *GoPackages) List() []*gist7480523.GoPackage { 60 | return this.Entries 61 | } 62 | 63 | // GoPackagesFromReader is a cached list of Go packages specified by newline separated import paths from Reader. 64 | type GoPackagesFromReader struct { 65 | Reader io.Reader 66 | 67 | Entries []*gist7480523.GoPackage 68 | 69 | gist7802150.DepNode2 70 | } 71 | 72 | func (this *GoPackagesFromReader) Update() { 73 | reduceFunc := func(importPath string) interface{} { 74 | if goPackage := gist7480523.GoPackageFromImportPath(importPath); goPackage != nil { 75 | return goPackage 76 | } 77 | return nil 78 | } 79 | 80 | goPackages := gist7651991.GoReduceLinesFromReader(this.Reader, 8, reduceFunc) 81 | 82 | this.Entries = nil 83 | for { 84 | if goPackage, ok := <-goPackages; ok { 85 | this.Entries = append(this.Entries, goPackage.(*gist7480523.GoPackage)) 86 | } else { 87 | break 88 | } 89 | } 90 | } 91 | 92 | func (this *GoPackagesFromReader) List() []*gist7480523.GoPackage { 93 | return this.Entries 94 | } 95 | -------------------------------------------------------------------------------- /pkg/gist4727543/main.go: -------------------------------------------------------------------------------- 1 | // Package gist4727543 generates an anonymous usage of the package for given import path 2 | // to avoid "imported and not used" errors. 3 | // 4 | // This is largely unneeded now that goimports exists. 5 | package gist4727543 6 | 7 | import ( 8 | "fmt" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/shurcooL-legacy/Conception-go/pkg/gist5504644" 13 | ) 14 | 15 | // tryUnquote returns the unquoted string, or the original string if unquoting fails. 16 | func tryUnquote(s string) string { 17 | if t, err := strconv.Unquote(s); err == nil { 18 | return t 19 | } 20 | return s 21 | } 22 | 23 | // GetForcedUseFromImport generates an anonymous usage for the given import spec to avoid "imported and not used" errors. 24 | // 25 | // E.g., `io/ioutil` -> `var _ = ioutil.NopCloser`, 26 | // 27 | // `renamed "io/ioutil"` -> `var _ = renamed.NopCloser`, 28 | // 29 | // `. "io/ioutil"` -> `var _ = NopCloser`. 30 | func GetForcedUseFromImport(importSpec string) (out string) { 31 | switch parts := strings.Split(importSpec, " "); len(parts) { 32 | case 1: 33 | return GetForcedUse(tryUnquote(parts[0])) 34 | case 2: 35 | return GetForcedUseRenamed(tryUnquote(parts[1]), parts[0]) 36 | default: 37 | return "Invalid import string." 38 | } 39 | } 40 | 41 | // GetForcedUse generates an anonymous usage of the package to avoid "imported and not used" errors 42 | // 43 | // E.g., `io/ioutil` -> `var _ = ioutil.NopCloser`. 44 | func GetForcedUse(importPath string) string { 45 | return GetForcedUseRenamed(importPath, "") 46 | } 47 | 48 | // GetForcedUseRenamed generates an anonymous usage of a renamed imported package. 49 | // 50 | // E.g., `io/ioutil`, `RenamedPkg` -> `var _ = RenamedPkg.NopCloser`. 51 | func GetForcedUseRenamed(importPath, localPackageName string) string { 52 | dpkg, err := gist5504644.GetDocPackage(gist5504644.BuildPackageFromImportPath(importPath)) 53 | if err != nil { 54 | return fmt.Sprintf("Package %q not valid (doesn't exist or can't be built).", importPath) 55 | } 56 | 57 | // Uncomment only for testing purposes. 58 | //dpkg.Funcs = dpkg.Funcs[0:0] 59 | //dpkg.Vars = dpkg.Vars[0:0] 60 | //dpkg.Consts = dpkg.Consts[0:0] 61 | //dpkg.Types = dpkg.Types[0:0] 62 | 63 | prefix := "var _ = " 64 | var usage string 65 | if len(dpkg.Funcs) > 0 { 66 | usage = dpkg.Funcs[0].Name 67 | } else if len(dpkg.Vars) > 0 { 68 | usage = dpkg.Vars[0].Names[0] 69 | } else if len(dpkg.Consts) > 0 { 70 | usage = dpkg.Consts[0].Names[0] 71 | } else if len(dpkg.Types) > 0 { 72 | usage = dpkg.Types[0].Name 73 | prefix = "var _ " 74 | } else { 75 | return "Package doesn't have a single public func, var, const or type." 76 | } 77 | 78 | switch { 79 | case localPackageName == "": 80 | return prefix + dpkg.Name + "." + usage 81 | case localPackageName == ".": 82 | return prefix + usage 83 | default: 84 | return prefix + localPackageName + "." + usage 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /pkg/gist4727543/main_test.go: -------------------------------------------------------------------------------- 1 | package gist4727543_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/shurcooL-legacy/Conception-go/pkg/gist4727543" 7 | ) 8 | 9 | func Example() { 10 | fmt.Println(gist4727543.GetForcedUse("io/ioutil")) 11 | fmt.Println(gist4727543.GetForcedUseRenamed("io/ioutil", "")) 12 | fmt.Println(gist4727543.GetForcedUseRenamed("io/ioutil", "RenamedPkg")) 13 | fmt.Println(gist4727543.GetForcedUseRenamed("io/ioutil", ".")) 14 | fmt.Println() 15 | fmt.Println(gist4727543.GetForcedUseFromImport(`github.com/shurcooL-legacy/Conception-go/pkg/gist4727543`)) 16 | fmt.Println(gist4727543.GetForcedUseFromImport(`"github.com/shurcooL-legacy/Conception-go/pkg/gist4727543"`)) 17 | fmt.Println(gist4727543.GetForcedUseFromImport("`github.com/shurcooL-legacy/Conception-go/pkg/gist4727543`")) 18 | fmt.Println(gist4727543.GetForcedUseFromImport(`. "github.com/shurcooL-legacy/Conception-go/pkg/gist4727543"`)) 19 | fmt.Println(gist4727543.GetForcedUseFromImport(`renamed "github.com/shurcooL-legacy/Conception-go/pkg/gist4727543"`)) 20 | fmt.Println(gist4727543.GetForcedUseFromImport(`bad`)) 21 | fmt.Println(gist4727543.GetForcedUseFromImport(`bad bad bad`)) 22 | 23 | // Output: 24 | // var _ = ioutil.NopCloser 25 | // var _ = ioutil.NopCloser 26 | // var _ = RenamedPkg.NopCloser 27 | // var _ = NopCloser 28 | // 29 | // var _ = gist4727543.GetForcedUse 30 | // var _ = gist4727543.GetForcedUse 31 | // var _ = gist4727543.GetForcedUse 32 | // var _ = GetForcedUse 33 | // var _ = renamed.GetForcedUse 34 | // Package "bad" not valid (doesn't exist or can't be built). 35 | // Invalid import string. 36 | } 37 | -------------------------------------------------------------------------------- /pkg/gist5504644/compat.go: -------------------------------------------------------------------------------- 1 | // +build !go1.4 2 | 3 | package gist5504644 4 | 5 | import "go/build" 6 | 7 | const importMode build.ImportMode = 0 8 | -------------------------------------------------------------------------------- /pkg/gist5504644/go14.go: -------------------------------------------------------------------------------- 1 | // +build go1.4 2 | 3 | package gist5504644 4 | 5 | import "go/build" 6 | 7 | const importMode build.ImportMode = build.ImportComment 8 | -------------------------------------------------------------------------------- /pkg/gist5504644/main.go: -------------------------------------------------------------------------------- 1 | // Package gist5504644 provides helpers for parsing Go AST, creating build and godoc packages. 2 | package gist5504644 3 | 4 | import ( 5 | "go/ast" 6 | "go/build" 7 | "go/doc" 8 | "go/parser" 9 | "go/token" 10 | "os" 11 | "path/filepath" 12 | "sync" 13 | ) 14 | 15 | // ParseFiles parses the Go source files files within directory dir 16 | // and returns their ASTs, or the first parse error if any. 17 | // TODO: This was made unexported in Go library, need to find a good replacement. 18 | func ParseFiles(fset *token.FileSet, dir string, files ...string) ([]*ast.File, error) { 19 | var wg sync.WaitGroup 20 | n := len(files) 21 | parsed := make([]*ast.File, n, n) 22 | errors := make([]error, n, n) 23 | for i, file := range files { 24 | if !filepath.IsAbs(file) { 25 | file = filepath.Join(dir, file) 26 | } 27 | wg.Add(1) 28 | go func(i int, file string) { 29 | parsed[i], errors[i] = parser.ParseFile(fset, file, nil, 0) 30 | wg.Done() 31 | }(i, file) 32 | } 33 | wg.Wait() 34 | 35 | for _, err := range errors { 36 | if err != nil { 37 | return nil, err 38 | } 39 | } 40 | return parsed, nil 41 | } 42 | 43 | // --- 44 | 45 | func AstPackageFromBuildPackage(bpkg *build.Package) (apkg *ast.Package, err error) { 46 | // TODO: Either find a way to use golang.org/x/tools/importer directly, or do file AST parsing in parallel like it does 47 | filenames := append(bpkg.GoFiles, bpkg.CgoFiles...) 48 | files := make(map[string]*ast.File, len(filenames)) 49 | fset := token.NewFileSet() 50 | for _, filename := range filenames { 51 | fileAst, err := parser.ParseFile(fset, filepath.Join(bpkg.Dir, filename), nil, parser.ParseComments) 52 | if err != nil { 53 | return nil, err 54 | } 55 | files[filename] = fileAst // TODO: Figure out if filename or full path are to be used (the key of this map doesn't seem to be used anywhere!) 56 | } 57 | return &ast.Package{Name: bpkg.Name, Files: files}, nil 58 | } 59 | 60 | func BuildPackageFromImportPathBuildTags(importPath string, buildTags []string) (bpkg *build.Package, err error) { 61 | c := build.Default 62 | c.BuildTags = buildTags 63 | return c.Import(importPath, "", importMode) 64 | } 65 | 66 | func BuildPackageFromImportPath(importPath string) (bpkg *build.Package, err error) { 67 | wd, err := os.Getwd() 68 | if err != nil { 69 | return nil, err 70 | } 71 | return build.Import(importPath, wd, importMode) 72 | } 73 | 74 | func BuildPackageFromSrcDir(srcDir string) (bpkg *build.Package, err error) { 75 | return build.ImportDir(srcDir, importMode) 76 | } 77 | 78 | func BuildPackageFromPath(path, srcDir string) (bpkg *build.Package, err error) { 79 | return build.Import(path, srcDir, importMode) 80 | } 81 | 82 | func getDocPackageMode(bpkg *build.Package, err error, mode doc.Mode) (dpkg *doc.Package, err2 error) { 83 | if err != nil { 84 | return nil, err 85 | } 86 | apkg, err := AstPackageFromBuildPackage(bpkg) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return doc.New(apkg, bpkg.ImportPath, mode), nil 91 | } 92 | 93 | func GetDocPackage(bpkg *build.Package, err error) (dpkg *doc.Package, err2 error) { 94 | return getDocPackageMode(bpkg, err, 0) 95 | } 96 | 97 | func GetDocPackageAll(bpkg *build.Package, err error) (dpkg *doc.Package, err2 error) { 98 | return getDocPackageMode(bpkg, err, doc.AllDecls|doc.AllMethods) 99 | } 100 | 101 | func main() { 102 | dpkg, err := GetDocPackage(BuildPackageFromImportPath("os")) 103 | if err != nil { 104 | panic(err) 105 | } 106 | println(dpkg.Consts[0].Names[0]) 107 | println(dpkg.Types[0].Name) 108 | println(dpkg.Vars[0].Names[0]) 109 | println(dpkg.Funcs[0].Name) 110 | } 111 | -------------------------------------------------------------------------------- /pkg/gist5645828/main.go: -------------------------------------------------------------------------------- 1 | // Package gist5645828 offers functionality to print higher-level summaries of Go packages. 2 | package gist5645828 3 | 4 | import ( 5 | "fmt" 6 | "go/ast" 7 | "go/build" 8 | "go/doc" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | 14 | "github.com/shurcooL-legacy/Conception-go/pkg/gist5504644" 15 | "github.com/shurcooL/go/printerutil" 16 | ) 17 | 18 | type sectionWriter struct { 19 | Writer io.Writer 20 | writtenSection bool 21 | } 22 | 23 | func (sw *sectionWriter) Write(p []byte) (n int, err error) { 24 | sw.writtenSection = true 25 | return sw.Writer.Write(p) 26 | } 27 | 28 | func (sw *sectionWriter) WriteBreak() { 29 | if sw.writtenSection { 30 | io.WriteString(sw.Writer, "\n") 31 | sw.writtenSection = false 32 | } 33 | } 34 | 35 | func PrintPackageFullSummary(dpkg *doc.Package) { 36 | FprintPackageFullSummary(os.Stdout, dpkg) 37 | } 38 | 39 | func FprintPackageFullSummary(wr io.Writer, dpkg *doc.Package) { 40 | w := §ionWriter{Writer: wr} 41 | for _, v := range dpkg.Vars { 42 | for _, spec := range v.Decl.Specs { 43 | spec.(*ast.ValueSpec).Doc = nil 44 | spec.(*ast.ValueSpec).Comment = nil 45 | } 46 | fmt.Fprintln(w, printerutil.SprintAstBare(v.Decl)) 47 | } 48 | for _, t := range dpkg.Types { 49 | for _, v := range t.Vars { 50 | for _, spec := range v.Decl.Specs { 51 | spec.(*ast.ValueSpec).Doc = nil 52 | spec.(*ast.ValueSpec).Comment = nil 53 | } 54 | fmt.Fprintln(w, printerutil.SprintAstBare(v.Decl)) 55 | } 56 | } 57 | w.WriteBreak() 58 | for _, f := range dpkg.Funcs { 59 | fmt.Fprintln(w, printerutil.SprintAstBare(f.Decl)) 60 | } 61 | for _, t := range dpkg.Types { 62 | for _, f := range t.Funcs { 63 | fmt.Fprintln(w, printerutil.SprintAstBare(f.Decl)) 64 | } 65 | for _, m := range t.Methods { 66 | fmt.Fprintln(w, printerutil.SprintAstBare(m.Decl)) 67 | } 68 | } 69 | w.WriteBreak() 70 | for _, c := range dpkg.Consts { 71 | for _, spec := range c.Decl.Specs { 72 | spec.(*ast.ValueSpec).Values = nil 73 | spec.(*ast.ValueSpec).Doc = nil 74 | spec.(*ast.ValueSpec).Comment = nil 75 | } 76 | fmt.Fprintln(w, printerutil.SprintAstBare(c.Decl)) 77 | //fmt.Fprintln(w, "const", strings.Join(c.Names, "\n")) 78 | } 79 | for _, t := range dpkg.Types { 80 | for _, c := range t.Consts { 81 | for _, spec := range c.Decl.Specs { 82 | spec.(*ast.ValueSpec).Values = nil 83 | spec.(*ast.ValueSpec).Doc = nil 84 | spec.(*ast.ValueSpec).Comment = nil 85 | } 86 | fmt.Fprintln(w, printerutil.SprintAstBare(c.Decl)) 87 | //fmt.Fprintln(w, "const", strings.Join(c.Names, "\n")) 88 | } 89 | } 90 | w.WriteBreak() 91 | for _, t := range dpkg.Types { 92 | //fmt.Fprintln(w, gist5639599.SprintAstBare(t.Decl)) 93 | fmt.Fprintln(w, "type", t.Name) 94 | } 95 | } 96 | 97 | func printPackageSummary(dpkg *doc.Package) { 98 | fmt.Println(`import "` + dpkg.ImportPath + `"`) 99 | for _, f := range dpkg.Funcs { 100 | fmt.Print("\t") 101 | printerutil.PrintlnAstBare(f.Decl) 102 | } 103 | for _, t := range dpkg.Types { 104 | for _, f := range t.Funcs { 105 | fmt.Print("\t") 106 | printerutil.PrintlnAstBare(f.Decl) 107 | } 108 | // THINK: Do I want to include methods? 109 | /*for _, m := range t.Methods { 110 | fmt.Print("\t") 111 | gist5639599.PrintlnAstBare(m.Decl) 112 | }*/ 113 | } 114 | fmt.Println() 115 | } 116 | 117 | // hasAnyFuncs returns true if the package has any funcs or methods. 118 | func hasAnyFuncs(dpkg *doc.Package) bool { 119 | if len(dpkg.Funcs) > 0 { 120 | return true 121 | } 122 | 123 | for _, t := range dpkg.Types { 124 | if len(t.Funcs) > 0 { 125 | return true 126 | } 127 | } 128 | 129 | return false 130 | } 131 | 132 | func PrintPackageSummaryBuildTags(importPath string, buildTags []string) { 133 | dpkg, err := gist5504644.GetDocPackage(gist5504644.BuildPackageFromImportPathBuildTags(importPath, buildTags)) 134 | if err != nil { 135 | return 136 | } 137 | if !hasAnyFuncs(dpkg) { 138 | return 139 | } 140 | printPackageSummary(dpkg) 141 | } 142 | 143 | func PrintPackageSummary(importPath string) { 144 | dpkg, err := gist5504644.GetDocPackage(gist5504644.BuildPackageFromImportPath(importPath)) 145 | if err != nil { 146 | return 147 | } 148 | if !hasAnyFuncs(dpkg) { 149 | return 150 | } 151 | printPackageSummary(dpkg) 152 | } 153 | 154 | func PrintPackageSummaryWithPath(importPath, fullPath string) { 155 | dpkg, err := gist5504644.GetDocPackage(gist5504644.BuildPackageFromImportPath(importPath)) 156 | if err != nil { 157 | return 158 | } 159 | if !hasAnyFuncs(dpkg) { 160 | return 161 | } 162 | fmt.Println(filepath.Join(fullPath, dpkg.Filenames[0])) 163 | printPackageSummary(dpkg) 164 | } 165 | 166 | func PrintPackageSummariesInDir(dirname string) { 167 | gopathEntries := filepath.SplitList(build.Default.GOPATH) 168 | for _, gopathEntry := range gopathEntries { 169 | path0 := filepath.Join(gopathEntry, "src") 170 | entries, err := ioutil.ReadDir(filepath.Join(path0, dirname)) 171 | //CheckError(err) 172 | if err != nil { 173 | continue 174 | } 175 | //for _, v := range entries { 176 | for i := len(entries) - 1; i >= 0; i-- { 177 | v := entries[i] 178 | if v.IsDir() { // TODO: Build a build.Package to figure out if this is a valid Go package; rather than assuming all dirs are 179 | //PrintPackageSummaryWithPath(filepath.Join(dirname, v.Name()), filepath.Join(path0, dirname, v.Name())) 180 | PrintPackageSummary(filepath.Join(dirname, v.Name())) 181 | } 182 | } 183 | } 184 | } 185 | 186 | func main() { 187 | //PrintPackageSummary("gist.github.com/5639599.git"); return 188 | //PrintPackageSummariesInDir("gist.github.com") 189 | dpkg, err := gist5504644.GetDocPackageAll(gist5504644.BuildPackageFromImportPath("github.com/microcosm-cc/bluemonday")) 190 | if err != nil { 191 | panic(err) 192 | } 193 | PrintPackageFullSummary(dpkg) 194 | } 195 | -------------------------------------------------------------------------------- /pkg/gist6003701/main.go: -------------------------------------------------------------------------------- 1 | // Package gist6003701 implements functions for converting between CamelCase, snake_case and MixedCaps forms for identifier names. 2 | package gist6003701 3 | 4 | import ( 5 | "strings" 6 | "unicode" 7 | ) 8 | 9 | // UnderscoreSepToCamelCase converts "string_URL_append" to "StringUrlAppend" form. 10 | func UnderscoreSepToCamelCase(s string) string { 11 | return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1) 12 | } 13 | 14 | func addSegment(inout, seg []rune) []rune { 15 | if len(seg) == 0 { 16 | return inout 17 | } 18 | if len(inout) != 0 { 19 | inout = append(inout, '_') 20 | } 21 | initialism := strings.ToUpper(string(seg)) 22 | if _, ok := initialisms[initialism]; ok { 23 | inout = append(inout, []rune(initialism)...) 24 | } else { 25 | inout = append(inout, seg...) 26 | } 27 | return inout 28 | } 29 | 30 | // CamelCaseToUnderscoreSep converts "StringUrlAppend" to "string_url_append" form. 31 | func CamelCaseToUnderscoreSep(s string) string { 32 | var out []rune 33 | var seg []rune 34 | for _, r := range s { 35 | if !unicode.IsLower(r) { 36 | out = addSegment(out, seg) 37 | seg = nil 38 | } 39 | seg = append(seg, unicode.ToLower(r)) 40 | } 41 | out = addSegment(out, seg) 42 | return string(out) 43 | } 44 | 45 | // UnderscoreSepToMixedCaps converts "string_URL_append" to "StringURLAppend" form. 46 | func UnderscoreSepToMixedCaps(in string) string { 47 | var out string 48 | ss := strings.Split(in, "_") 49 | for _, s := range ss { 50 | initialism := strings.ToUpper(s) 51 | if _, ok := initialisms[initialism]; ok { 52 | out += initialism 53 | } else { 54 | out += strings.Title(s) 55 | } 56 | } 57 | return out 58 | } 59 | 60 | // MixedCapsToUnderscoreSep converts "StringURLAppend" to "string_URL_append" form. 61 | func MixedCapsToUnderscoreSep(in string) string { 62 | var out []rune 63 | var seg []rune 64 | for _, r := range in { 65 | if !unicode.IsLower(r) { 66 | out = addSegment(out, seg) 67 | seg = nil 68 | } 69 | seg = append(seg, unicode.ToLower(r)) 70 | } 71 | out = addSegment(out, seg) 72 | // This is awful. You're welcome to make it better. 73 | // For each supported initialism, do a strings.Replace to fix up bad output like "_u_r_l_" with "_URL_". 74 | s := "_" + string(out) + "_" 75 | for initialism := range initialisms { 76 | ruinedInitialism := ruinedInitialism(initialism) 77 | s = strings.Replace(s, ruinedInitialism, "_"+initialism+"_", -1) 78 | } 79 | return s[1 : len(s)-1] 80 | } 81 | 82 | // ruinedInitialism returns "_u_r_l_" form that CamelCaseToUnderscoreSep generates for initialism "URL". 83 | func ruinedInitialism(initialism string) string { 84 | var out = []rune{'_'} 85 | for _, r := range initialism { 86 | out = append(out, unicode.ToLower(r)) 87 | out = append(out, '_') 88 | } 89 | return string(out) 90 | } 91 | 92 | // initialisms is the set of initialisms in Go-style Mixed Caps case. 93 | var initialisms = map[string]struct{}{ 94 | "API": {}, 95 | "ASCII": {}, 96 | "CPU": {}, 97 | "CSS": {}, 98 | "DNS": {}, 99 | "EOF": {}, 100 | "GUID": {}, 101 | "HTML": {}, 102 | "HTTP": {}, 103 | "HTTPS": {}, 104 | "ID": {}, 105 | "IP": {}, 106 | "JSON": {}, 107 | "LHS": {}, 108 | "QPS": {}, 109 | "RAM": {}, 110 | "RHS": {}, 111 | "RPC": {}, 112 | "SLA": {}, 113 | "SMTP": {}, 114 | "SQL": {}, 115 | "SSH": {}, 116 | "TCP": {}, 117 | "TLS": {}, 118 | "TTL": {}, 119 | "UDP": {}, 120 | "UI": {}, 121 | "UID": {}, 122 | "UUID": {}, 123 | "URI": {}, 124 | "URL": {}, 125 | "UTF8": {}, 126 | "VM": {}, 127 | "XML": {}, 128 | "XSRF": {}, 129 | "XSS": {}, 130 | } 131 | -------------------------------------------------------------------------------- /pkg/gist6003701/main_test.go: -------------------------------------------------------------------------------- 1 | package gist6003701 2 | 3 | import "fmt" 4 | 5 | func ExampleUnderscoreSepToCamelCase() { 6 | fmt.Println(UnderscoreSepToCamelCase("string_URL_append")) 7 | 8 | // Output: StringUrlAppend 9 | } 10 | 11 | func ExampleCamelCaseToUnderscoreSep() { 12 | fmt.Println(CamelCaseToUnderscoreSep("StringUrlAppend")) 13 | 14 | // Output: string_URL_append 15 | } 16 | 17 | func ExampleUnderscoreSepToMixedCaps() { 18 | fmt.Println(UnderscoreSepToMixedCaps("string_URL_append")) 19 | 20 | // Output: StringURLAppend 21 | } 22 | 23 | func ExampleMixedCapsToUnderscoreSep() { 24 | fmt.Println(MixedCapsToUnderscoreSep("StringURLAppend")) 25 | fmt.Println(MixedCapsToUnderscoreSep("URLFrom")) 26 | fmt.Println(MixedCapsToUnderscoreSep("SetURLHTML")) 27 | 28 | // Output: 29 | // string_URL_append 30 | // URL_from 31 | // set_URL_HTML 32 | } 33 | -------------------------------------------------------------------------------- /pkg/gist7480523/import_path_found.go: -------------------------------------------------------------------------------- 1 | package gist7480523 2 | 3 | import ( 4 | "path/filepath" 5 | ) 6 | 7 | // An ImportPathFound describes the Import Path found in a GOPATH workspace. 8 | type ImportPathFound struct { 9 | importPath string 10 | gopathEntry string 11 | } 12 | 13 | func NewImportPathFound(importPath, gopathEntry string) ImportPathFound { 14 | return ImportPathFound{ 15 | importPath: importPath, 16 | gopathEntry: gopathEntry, 17 | } 18 | } 19 | 20 | func (w *ImportPathFound) ImportPath() string { 21 | return w.importPath 22 | } 23 | 24 | func (w *ImportPathFound) GopathEntry() string { 25 | return w.gopathEntry 26 | } 27 | 28 | func (w *ImportPathFound) FullPath() string { 29 | return filepath.Join(w.gopathEntry, "src", w.importPath) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/gist7480523/main.go: -------------------------------------------------------------------------------- 1 | // Package gist7480523 contains types and funcs for dealing with instances of a Go package found in a directory, 2 | // including caching of its directory entry, vcs repository, and vcs state. 3 | package gist7480523 4 | 5 | import ( 6 | "fmt" 7 | "go/build" 8 | "os" 9 | "path/filepath" 10 | "sort" 11 | "strings" 12 | 13 | "github.com/shurcooL-legacy/Conception-go/pkg/exp12" 14 | "golang.org/x/tools/go/vcs" 15 | 16 | "github.com/shurcooL-legacy/Conception-go/pkg/gist5504644" 17 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 18 | ) 19 | 20 | type GoPackageStringer func(*GoPackage) string 21 | 22 | // A GoPackage describes a single package found in a directory. 23 | // This is partially a copy of "cmd/go".Package, except it can be imported and reused. 24 | // https://code.google.com/p/go/source/browse/src/cmd/go/pkg.go?name=release#24 25 | type GoPackage struct { 26 | Bpkg *build.Package // Bpkg is not nil. 27 | BpkgErr error 28 | 29 | Dir *exp12.Directory 30 | } 31 | 32 | func GoPackageFromImportPathFound(importPathFound ImportPathFound) *GoPackage { 33 | bpkg, err := gist5504644.BuildPackageFromSrcDir(importPathFound.FullPath()) 34 | return goPackageFromBuildPackage(bpkg, err) 35 | } 36 | 37 | func GoPackageFromImportPath(importPath string) *GoPackage { 38 | bpkg, err := gist5504644.BuildPackageFromImportPath(importPath) 39 | return goPackageFromBuildPackage(bpkg, err) 40 | } 41 | 42 | func GoPackageFromPath(path, srcDir string) (*GoPackage, error) { 43 | bpkg, err := gist5504644.BuildPackageFromPath(path, srcDir) 44 | if err != nil { 45 | if _, noGo := err.(*build.NoGoError); noGo || bpkg.Dir == "" { 46 | return nil, err 47 | } 48 | } 49 | return goPackageFromBuildPackage(bpkg, err), nil 50 | } 51 | 52 | func goPackageFromBuildPackage(bpkg *build.Package, bpkgErr error) *GoPackage { 53 | if bpkgErr != nil { 54 | if _, noGo := bpkgErr.(*build.NoGoError); noGo || bpkg.Dir == "" { 55 | return nil 56 | } 57 | } 58 | 59 | if bpkg.ConflictDir != "" { 60 | fmt.Fprintf(os.Stderr, "warning: ConflictDir=%q (Dir=%q)\n", bpkg.ConflictDir, bpkg.Dir) 61 | return nil 62 | } 63 | 64 | goPackage := &GoPackage{ 65 | Bpkg: bpkg, 66 | BpkgErr: bpkgErr, 67 | 68 | Dir: exp12.LookupDirectory(bpkg.Dir), 69 | } 70 | 71 | /*if goPackage.Bpkg.Goroot == false { // Optimization that assume packages under Goroot are not under vcs 72 | // TODO: markAsNotNeedToUpdate() because of external insight? 73 | }*/ 74 | 75 | return goPackage 76 | } 77 | 78 | // This is okay to call concurrently (a mutex is used internally). 79 | // Actually, not completely okay because MakeUpdated technology is not thread-safe. 80 | func (this *GoPackage) UpdateVcs() { 81 | if this.Bpkg.Goroot == false { // Optimization that assume packages under Goroot are not under vcs 82 | gist7802150.MakeUpdated(this.Dir) 83 | } 84 | } 85 | 86 | func (this *GoPackage) UpdateVcsFields() { 87 | if this.Dir.Repo == nil { 88 | return 89 | } 90 | 91 | gist7802150.MakeUpdated(this.Dir.Repo.VcsLocal) 92 | gist7802150.MakeUpdated(this.Dir.Repo.VcsRemote) 93 | 94 | repoImportPath := GetRepoImportPath(this.Dir.Repo.Vcs.RootPath(), this.Bpkg.SrcRoot) 95 | if repoRoot, err := vcs.RepoRootForImportPath(repoImportPath, false); err == nil { 96 | this.Dir.Repo.RepoRoot = repoRoot 97 | } 98 | } 99 | 100 | // GetRepoImportPath figures out the repo root import path given repoPath and srcRoot. 101 | // It handles symlinks that may be involved in the paths. 102 | // It also handles a possible case mismatch in the prefix, printing a warning to stderr if detected. 103 | func GetRepoImportPath(repoPath, srcRoot string) string { 104 | if s, err := filepath.EvalSymlinks(repoPath); err == nil { 105 | repoPath = s 106 | } else { 107 | fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: can't resolve symlink:", err) 108 | } 109 | if s, err := filepath.EvalSymlinks(srcRoot); err == nil { 110 | srcRoot = s 111 | } else { 112 | fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: can't resolve symlink:", err) 113 | } 114 | 115 | sep := string(filepath.Separator) 116 | 117 | // Detect and handle case mismatch in prefix. 118 | if prefixLen := len(srcRoot + sep); len(repoPath) >= prefixLen && srcRoot+sep != repoPath[:prefixLen] && strings.EqualFold(srcRoot+sep, repoPath[:prefixLen]) { 119 | fmt.Fprintln(os.Stderr, "warning: GetRepoImportPath: prefix case doesn't match:", srcRoot+sep, repoPath[:prefixLen]) 120 | return filepath.ToSlash(repoPath[prefixLen:]) 121 | } 122 | 123 | return filepath.ToSlash(strings.TrimPrefix(repoPath, srcRoot+sep)) 124 | } 125 | func GetRepoImportPathPattern(repoPath, srcRoot string) string { 126 | return GetRepoImportPath(repoPath, srcRoot) + "/..." 127 | } 128 | 129 | func (this *GoPackage) String() string { 130 | return this.Bpkg.ImportPath 131 | } 132 | 133 | // byImportPath implements sort.Interface for sorting Go packages by their import path. 134 | type byImportPath []*GoPackage 135 | 136 | func (v byImportPath) Len() int { return len(v) } 137 | func (v byImportPath) Less(i, j int) bool { return v[i].Bpkg.ImportPath < v[j].Bpkg.ImportPath } 138 | func (v byImportPath) Swap(i, j int) { v[i], v[j] = v[j], v[i] } 139 | 140 | // ===== 141 | 142 | // GoPackageRepo represents a collection of Go packages contained by one VCS repository. 143 | type GoPackageRepo struct { 144 | rootPath string 145 | goPackages []*GoPackage 146 | } 147 | 148 | // NewGoPackageRepo sorts goPackages by import path and returns a GoPackageRepo. 149 | func NewGoPackageRepo(rootPath string, goPackages []*GoPackage) GoPackageRepo { 150 | sort.Sort(byImportPath(goPackages)) 151 | return GoPackageRepo{rootPath, goPackages} 152 | } 153 | 154 | // RepoImportPath returns what would be the import path of the root folder of the repository. It may or may not 155 | // be an actual Go package. E.g., 156 | // 157 | // "github.com/owner/repo" 158 | func (repo GoPackageRepo) RepoImportPath() string { 159 | return GetRepoImportPath(repo.rootPath, repo.goPackages[0].Bpkg.SrcRoot) 160 | } 161 | 162 | // ImportPathPattern returns an import path pattern that matches all of the Go packages in this repo. 163 | // E.g., 164 | // 165 | // "github.com/owner/repo/..." 166 | func (repo GoPackageRepo) ImportPathPattern() string { 167 | return GetRepoImportPathPattern(repo.rootPath, repo.goPackages[0].Bpkg.SrcRoot) 168 | } 169 | 170 | // RootPath returns the path to the root workdir folder of the repository. 171 | func (repo GoPackageRepo) RootPath() string { return repo.rootPath } 172 | func (repo GoPackageRepo) GoPackages() []*GoPackage { return repo.goPackages } 173 | 174 | // ImportPaths returns a newline separated list of all import paths. 175 | func (repo GoPackageRepo) ImportPaths() string { 176 | var importPaths []string 177 | for _, goPackage := range repo.goPackages { 178 | importPaths = append(importPaths, goPackage.Bpkg.ImportPath) 179 | } 180 | return strings.Join(importPaths, "\n") 181 | } 182 | -------------------------------------------------------------------------------- /pkg/gist7480523/main_test.go: -------------------------------------------------------------------------------- 1 | package gist7480523_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7480523" 7 | ) 8 | 9 | func ExampleGetRepoImportPathPattern() { 10 | fmt.Println(gist7480523.GetRepoImportPathPattern("/home/User/Go/src/github.com/owner/repo", "/home/User/Go/src")) 11 | fmt.Println(gist7480523.GetRepoImportPathPattern("/home/user/go/src/github.com/owner/repo", "/home/User/Go/src")) 12 | 13 | // Output: 14 | // github.com/owner/repo/... 15 | // github.com/owner/repo/... 16 | } 17 | -------------------------------------------------------------------------------- /pkg/gist7576154/main.go: -------------------------------------------------------------------------------- 1 | // Package gist7576154 implements functionality to create commands and pipes using templates. 2 | package gist7576154 3 | 4 | import ( 5 | "io" 6 | "os/exec" 7 | 8 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 9 | "gopkg.in/pipe.v2" 10 | ) 11 | 12 | // CmdFactory is an interface to create new commands. 13 | type CmdFactory interface { 14 | NewCommand() *exec.Cmd 15 | } 16 | 17 | // CmdTemplate is a command template. 18 | type CmdTemplate struct { 19 | NameArgs []string 20 | Dir string 21 | Stdin func() io.Reader 22 | } 23 | 24 | // NewCmdTemplate returns a CmdTemplate. 25 | func NewCmdTemplate(name string, arg ...string) CmdTemplate { 26 | return CmdTemplate{ 27 | NameArgs: append([]string{name}, arg...), 28 | } 29 | } 30 | 31 | // NewCommand generates a new *exec.Cmd from the template. 32 | func (ct CmdTemplate) NewCommand() *exec.Cmd { 33 | cmd := exec.Command(ct.NameArgs[0], ct.NameArgs[1:]...) 34 | cmd.Dir = ct.Dir 35 | if ct.Stdin != nil { 36 | cmd.Stdin = ct.Stdin() 37 | } 38 | return cmd 39 | } 40 | 41 | // --- 42 | 43 | type String interface { 44 | Get() string 45 | } 46 | 47 | type StringFunc func() string 48 | 49 | func (this StringFunc) Get() string { 50 | return this() 51 | } 52 | 53 | type Strings interface { 54 | Get() []string 55 | } 56 | 57 | type StringsFunc func() []string 58 | 59 | func (this StringsFunc) Get() []string { 60 | return this() 61 | } 62 | 63 | type CmdTemplateDynamic struct { 64 | NameArgs Strings 65 | Dir String 66 | Stdin func() io.Reader 67 | } 68 | 69 | func NewCmdTemplateDynamic(nameArgs Strings) CmdTemplateDynamic { 70 | return CmdTemplateDynamic{ 71 | NameArgs: nameArgs, 72 | } 73 | } 74 | 75 | func (ct CmdTemplateDynamic) NewCommand() *exec.Cmd { 76 | nameArgs := ct.NameArgs.Get() 77 | cmd := exec.Command(nameArgs[0], nameArgs[1:]...) 78 | if ct.Dir != nil { 79 | cmd.Dir = ct.Dir.Get() 80 | } 81 | if ct.Stdin != nil { 82 | cmd.Stdin = ct.Stdin() 83 | } 84 | return cmd 85 | } 86 | 87 | // --- 88 | 89 | type CmdTemplateDynamic2 struct { 90 | Template CmdTemplate 91 | 92 | gist7802150.DepNode2Func 93 | } 94 | 95 | // TODO: See if there's some way to initialize DepNode2Func.UpdateFunc through NewCmdTemplateDynamic2(). 96 | func NewCmdTemplateDynamic2() *CmdTemplateDynamic2 { 97 | return &CmdTemplateDynamic2{} 98 | } 99 | 100 | func (this *CmdTemplateDynamic2) NewCommand() *exec.Cmd { 101 | gist7802150.MakeUpdated(this) 102 | return this.Template.NewCommand() 103 | } 104 | 105 | // ===== 106 | 107 | type PipeFactory interface { 108 | NewPipe(stdout, stderr io.Writer) (*pipe.State, pipe.Pipe) 109 | } 110 | 111 | // --- 112 | 113 | type PipeStatic pipe.Pipe 114 | 115 | func (this PipeStatic) NewPipe(stdout, stderr io.Writer) (*pipe.State, pipe.Pipe) { 116 | return pipe.NewState(stdout, stderr), (pipe.Pipe)(this) 117 | } 118 | 119 | // --- 120 | 121 | type pipeTemplate struct { 122 | Pipe pipe.Pipe 123 | Dir string 124 | Stdin func() io.Reader 125 | } 126 | 127 | func NewPipeTemplate(pipe pipe.Pipe) *pipeTemplate { 128 | return &pipeTemplate{Pipe: pipe} 129 | } 130 | 131 | func (this *pipeTemplate) NewPipe(stdout, stderr io.Writer) (*pipe.State, pipe.Pipe) { 132 | s := pipe.NewState(stdout, stderr) 133 | s.Dir = this.Dir 134 | if this.Stdin != nil { 135 | s.Stdin = this.Stdin() 136 | } 137 | return s, this.Pipe 138 | } 139 | 140 | // --- 141 | 142 | type pipeTemplateDynamic struct { 143 | Template *pipeTemplate 144 | 145 | gist7802150.DepNode2Func 146 | } 147 | 148 | func NewPipeTemplateDynamic() *pipeTemplateDynamic { 149 | return &pipeTemplateDynamic{} 150 | } 151 | 152 | func (this *pipeTemplateDynamic) NewPipe(stdout, stderr io.Writer) (*pipe.State, pipe.Pipe) { 153 | gist7802150.MakeUpdated(this) 154 | return this.Template.NewPipe(stdout, stderr) 155 | } 156 | -------------------------------------------------------------------------------- /pkg/gist7651991/main.go: -------------------------------------------------------------------------------- 1 | // Package gist7651991 provides functionality for performing processing in parallel. 2 | package gist7651991 3 | 4 | import ( 5 | "bufio" 6 | "io" 7 | "sync" 8 | ) 9 | 10 | func ProcessLinesFromReader(r io.Reader, processFunc func(string)) { 11 | br := bufio.NewReader(r) 12 | for line, err := br.ReadString('\n'); err == nil; line, err = br.ReadString('\n') { 13 | processFunc(line[:len(line)-1]) // Trim last newline. 14 | } 15 | } 16 | 17 | // GoReduceLinesFromReader spawns numWorkers goroutines and reduces each line from reader with reduceFunc. 18 | // 19 | // source := strings.NewReader(`1 20 | // 2 21 | // three 22 | // four 23 | // etc. 24 | // `) 25 | // 26 | // reduceFunc := func(in string) interface{} { 27 | // time.Sleep(2 * time.Second) 28 | // return "Hello: " + in 29 | // } 30 | // 31 | // outChan := gist7651991.GoReduceLinesFromReader(source, 4, reduceFunc) 32 | // 33 | // for out := range outChan { 34 | // fmt.Println(out) 35 | // } 36 | // 37 | func GoReduceLinesFromReader(r io.Reader, numWorkers int, reduceFunc func(string) interface{}) <-chan interface{} { 38 | outChan := make(chan interface{}) 39 | 40 | go func() { 41 | inChan := make(chan string) 42 | var wg sync.WaitGroup 43 | 44 | // TODO: See if I can create goroutines alongside with the work, up to a max number, rather than all in advance 45 | // Create numWorkers goroutines 46 | for worker := 0; worker < numWorkers; worker++ { 47 | wg.Add(1) 48 | go func() { 49 | defer wg.Done() 50 | for { 51 | if in, ok := <-inChan; ok { 52 | if out := reduceFunc(in); out != nil { 53 | outChan <- out 54 | } 55 | } else { 56 | return 57 | } 58 | } 59 | }() 60 | } 61 | 62 | ProcessLinesFromReader(r, func(in string) { inChan <- in }) 63 | close(inChan) 64 | wg.Wait() 65 | close(outChan) 66 | }() 67 | 68 | return outChan 69 | } 70 | 71 | func GoReduceLinesFromSlice(inSlice []string, numWorkers int, reduceFunc func(string) interface{}) <-chan interface{} { 72 | inChan := make(chan interface{}) 73 | go func() { // This needs to happen in the background because sending input will be blocked on reading output. 74 | for _, in := range inSlice { 75 | inChan <- in 76 | } 77 | close(inChan) 78 | }() 79 | reduceFuncWrapper := func(in interface{}) interface{} { return reduceFunc(in.(string)) } 80 | outChan := GoReduce(inChan, numWorkers, reduceFuncWrapper) 81 | 82 | return outChan 83 | } 84 | 85 | // Caller is expected to close inChan after sending all input to it. Sending input should be done in a background goroutine, 86 | // because sending input will be blocked on reading output. 87 | func GoReduce(inChan <-chan interface{}, numWorkers int, reduceFunc func(interface{}) interface{}) <-chan interface{} { 88 | outChan := make(chan interface{}) 89 | 90 | go func() { 91 | var wg sync.WaitGroup 92 | 93 | // TODO: See if I can create goroutines alongside with the work, up to a max number, rather than all in advance 94 | // Create numWorkers goroutines 95 | for worker := 0; worker < numWorkers; worker++ { 96 | wg.Add(1) 97 | go func() { 98 | defer wg.Done() 99 | for { 100 | in, ok := <-inChan 101 | if !ok { 102 | return 103 | } 104 | 105 | out := reduceFunc(in) 106 | if out != nil { 107 | outChan <- out 108 | } 109 | } 110 | }() 111 | } 112 | 113 | wg.Wait() 114 | close(outChan) 115 | }() 116 | 117 | return outChan 118 | } 119 | 120 | // Caller is expected to close inChan after sending all input to it. Sending input should be done in a background goroutine, 121 | // because sending input will be blocked on reading output. 122 | // Order is guaranteed to be preserved. 123 | // 124 | // TODO: Implement this. 125 | /*func GoReducePreservingOrder(inChan <-chan interface{}, numWorkers int, reduceFunc func(interface{}) interface{}) <-chan interface{} { 126 | panic("not implemented") 127 | }*/ 128 | -------------------------------------------------------------------------------- /pkg/gist7802150/main.go: -------------------------------------------------------------------------------- 1 | // Package gist7802150 implements an experimental approach for caching and cache invalidation with dependency tracking and lazy evaluation. 2 | package gist7802150 3 | 4 | import ( 5 | "strings" 6 | "sync" 7 | ) 8 | 9 | type DepNode2I interface { 10 | Update() 11 | 12 | GetSources() []DepNode2I 13 | 14 | addSink(*DepNode2) 15 | getNeedToUpdate() bool 16 | markAllAsNeedToUpdate() 17 | markAsNotNeedToUpdate() 18 | } 19 | 20 | type DepNode2ManualI interface { 21 | DepNode2I 22 | manual() // Noop, just to separate it from automatic DepNode2I 23 | } 24 | 25 | // Used to make MakeUpdated resilient to concurrent access. Not really a good long term solution, 26 | // but needed for now to prevent race conditions. Especially when called form http handler funcs. 27 | var MakeUpdatedLock sync.Mutex 28 | 29 | // Updates dependencies and itself, only if its dependencies have changed. 30 | func MakeUpdated(this DepNode2I) { 31 | MakeUpdatedLock.Lock() 32 | makeUpdated(this) 33 | MakeUpdatedLock.Unlock() 34 | } 35 | 36 | func makeUpdated(this DepNode2I) { 37 | if !this.getNeedToUpdate() { 38 | return 39 | } 40 | for _, source := range this.GetSources() { 41 | makeUpdated(source) 42 | } 43 | this.Update() 44 | this.markAsNotNeedToUpdate() 45 | } 46 | 47 | // Updates dependencies and itself, regardless. 48 | /*func ForceUpdated(this DepNode2I) { 49 | this.markAllAsNeedToUpdate() 50 | MakeUpdated(this) 51 | }*/ 52 | 53 | // Updates only itself, regardless (skipping Update()). 54 | func ExternallyUpdated(this DepNode2ManualI) { 55 | this.markAllAsNeedToUpdate() 56 | //this.markAsNotNeedToUpdate() 57 | } 58 | 59 | // --- 60 | 61 | type DepNode2 struct { 62 | updated bool 63 | sources []DepNode2I 64 | sinks []*DepNode2 65 | } 66 | 67 | func (this *DepNode2) GetSources() []DepNode2I { 68 | return this.sources 69 | } 70 | 71 | func (this *DepNode2) AddSources(sources ...DepNode2I) { 72 | this.updated = false 73 | this.sources = append(this.sources, sources...) 74 | for _, source := range sources { 75 | source.addSink(this) 76 | } 77 | } 78 | 79 | func (this *DepNode2) addSink(sink *DepNode2) { 80 | this.sinks = append(this.sinks, sink) 81 | } 82 | 83 | func (this *DepNode2) getNeedToUpdate() bool { 84 | return !this.updated 85 | } 86 | 87 | func (this *DepNode2) markAllAsNeedToUpdate() { 88 | this.updated = false 89 | for _, sink := range this.sinks { 90 | // TODO: See if this can be optimized away... 91 | sink.markAllAsNeedToUpdate() 92 | } 93 | } 94 | 95 | func (this *DepNode2) markAsNotNeedToUpdate() { 96 | this.updated = true 97 | } 98 | 99 | // --- 100 | 101 | type DepNode2Manual struct { 102 | sinks []*DepNode2 103 | } 104 | 105 | func (this *DepNode2Manual) Update() { panic("") } 106 | func (this *DepNode2Manual) GetSources() []DepNode2I { panic("") } 107 | func (this *DepNode2Manual) addSink(sink *DepNode2) { 108 | this.sinks = append(this.sinks, sink) 109 | } 110 | func (this *DepNode2Manual) getNeedToUpdate() bool { return false } 111 | func (this *DepNode2Manual) markAllAsNeedToUpdate() { 112 | for _, sink := range this.sinks { 113 | // TODO: See if this can be optimized away... 114 | sink.markAllAsNeedToUpdate() 115 | } 116 | } 117 | func (this *DepNode2Manual) markAsNotNeedToUpdate() { panic("") } 118 | func (this *DepNode2Manual) manual() { panic("") } 119 | 120 | // Given there are two distinct DepNode2Manual structs, each having a pointer, 121 | // merge takes other and merges it (along with its current sinks) into this. 122 | // Afterwards, both pointers point to a single unified DepNode2Manual struct. 123 | func (this *DepNode2Manual) merge(other **DepNode2Manual) { 124 | presentSinks := make(map[*DepNode2]struct{}) 125 | for _, sink := range this.sinks { 126 | presentSinks[sink] = struct{}{} 127 | } 128 | 129 | for _, sink := range (*other).sinks { 130 | if _, present := presentSinks[sink]; !present { 131 | this.sinks = append(this.sinks, sink) 132 | } 133 | } 134 | 135 | *other = this 136 | } 137 | 138 | // --- 139 | 140 | type DepNode2Func struct { 141 | UpdateFunc func(DepNode2I) 142 | DepNode2 143 | } 144 | 145 | func (this *DepNode2Func) Update() { 146 | this.UpdateFunc(this) 147 | } 148 | 149 | // ===== 150 | 151 | type ViewGroupI interface { 152 | SetSelf(string) 153 | 154 | AddAndSetViewGroup(ViewGroupI, string) 155 | RemoveView(ViewGroupI) 156 | 157 | GetUri() FileUri 158 | GetAllUris() []FileUri 159 | GetUriForProtocol(protocol string) (uri FileUri, ok bool) 160 | ContainsUri(FileUri) bool 161 | 162 | getViewGroup() *ViewGroup 163 | 164 | DepNode2ManualI 165 | } 166 | 167 | // FileUri represents a URI with a protocol notation. 168 | // 169 | // For example, "file:///tmp/foo" or "memory://???". 170 | type FileUri string 171 | 172 | // Path returns the path of URI, without the protocol notation. 173 | func (u FileUri) Path() string { 174 | i := strings.Index(string(u), "://") + len("://") 175 | return string(u)[i:] 176 | } 177 | 178 | type ViewGroup struct { 179 | all *map[ViewGroupI]struct{} 180 | uri FileUri 181 | 182 | *DepNode2Manual 183 | } 184 | 185 | func (this *ViewGroup) getViewGroup() *ViewGroup { 186 | return this 187 | } 188 | 189 | // InitViewGroup must be called after creating a new ViewGroupI, 190 | // before any other ViewGroup method or ViewGroupI func. 191 | func (this *ViewGroup) InitViewGroup(self ViewGroupI, uri FileUri) { 192 | this.all = &map[ViewGroupI]struct{}{self: {}} 193 | this.uri = uri 194 | this.DepNode2Manual = &DepNode2Manual{} 195 | } 196 | 197 | // AddAndSetViewGroup adds another ViewGroupI and sets it to thisCurrent value, the current state of this ViewGroup. 198 | func (this *ViewGroup) AddAndSetViewGroup(other ViewGroupI, thisCurrent string) { 199 | // Set other ViewGroup to thisCurrent 200 | for v := range *other.getViewGroup().all { 201 | v.SetSelf(thisCurrent) 202 | } 203 | ExternallyUpdated(other.getViewGroup().DepNode2Manual) // Notify whatever depended on the other ViewGroupI that it's been updated 204 | 205 | (*this.all)[other] = struct{}{} 206 | other.getViewGroup().all = this.all 207 | this.DepNode2Manual.merge(&other.getViewGroup().DepNode2Manual) 208 | } 209 | 210 | // RemoveView removes a single view from the ViewGroup. 211 | func (this *ViewGroup) RemoveView(other ViewGroupI) { 212 | delete(*this.all, other) 213 | other.getViewGroup().InitViewGroup(other, other.GetUri()) 214 | } 215 | 216 | func (this *ViewGroup) GetUri() FileUri { 217 | return this.uri 218 | } 219 | func (this *ViewGroup) GetAllUris() (uris []FileUri) { 220 | for v := range *this.all { 221 | uris = append(uris, v.GetUri()) 222 | } 223 | return uris 224 | } 225 | func (this *ViewGroup) GetUriForProtocol(protocol string) (uri FileUri, ok bool) { 226 | for v := range *this.all { 227 | if strings.HasPrefix(string(v.GetUri()), protocol) { 228 | return v.GetUri(), true 229 | } 230 | } 231 | return "", false 232 | } 233 | func (this *ViewGroup) ContainsUri(uri FileUri) bool { 234 | for v := range *this.all { 235 | if uri == v.GetUri() { 236 | return true 237 | } 238 | } 239 | return false 240 | } 241 | 242 | func SetViewGroup(this ViewGroupI, s string) { 243 | for v := range *this.getViewGroup().all { 244 | v.SetSelf(s) 245 | } 246 | 247 | ExternallyUpdated(this) 248 | } 249 | 250 | func SetViewGroupOther(this ViewGroupI, s string) { 251 | for v := range *this.getViewGroup().all { 252 | if v != this { 253 | v.SetSelf(s) 254 | } 255 | } 256 | 257 | ExternallyUpdated(this) 258 | } 259 | -------------------------------------------------------------------------------- /pkg/gist7802150/main_test.go: -------------------------------------------------------------------------------- 1 | package gist7802150_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 7 | ) 8 | 9 | func ExampleFileUri_Path() { 10 | u := gist7802150.FileUri("file:///usr/local/go/bin/go") 11 | 12 | fmt.Println(u.Path()) 13 | 14 | // Output: 15 | ///usr/local/go/bin/go 16 | } 17 | -------------------------------------------------------------------------------- /pkg/gist7802150/play_0.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | . "github.com/shurcooL/go/gists/gist7802150" 7 | 8 | "fmt" 9 | "os" 10 | "time" 11 | ) 12 | 13 | type node struct { 14 | Value int 15 | DepNode2Manual 16 | name byte // Debug 17 | } 18 | 19 | // Debug 20 | func (n *node) String() string { 21 | return fmt.Sprintf("%s -> %d", string(n.name), n.Value) 22 | } 23 | 24 | type nodeAdder struct { 25 | Value int 26 | DepNode2 27 | name byte // Debug 28 | } 29 | 30 | // Debug 31 | func (n *nodeAdder) String() string { 32 | return fmt.Sprintf("%s -> %d", string(n.name), n.Value) 33 | } 34 | 35 | func (n *nodeAdder) Update() { 36 | n.Value = 0 37 | for _, source := range n.GetSources() { 38 | n.Value += source.(*node).Value 39 | } 40 | fmt.Println("Auto Updated", n) // Debug 41 | } 42 | 43 | type nodeMultiplier struct { 44 | Value int 45 | DepNode2 46 | name byte // Debug 47 | } 48 | 49 | // Debug 50 | func (n *nodeMultiplier) String() string { 51 | return fmt.Sprintf("%s -> %d", string(n.name), n.Value) 52 | } 53 | 54 | func (n *nodeMultiplier) Update() { 55 | n.Value = 1 56 | for _, source := range n.GetSources() { 57 | n.Value *= source.(*nodeAdder).Value 58 | } 59 | fmt.Println("Auto Updated", n) // Debug 60 | } 61 | 62 | /* 63 | 64 | X = A + B 65 | Y = B + T 66 | Z = X * Y 67 | 68 | A 69 | ↘ 70 | X 71 | ↗ ↘ 72 | B Z 73 | ↘ ↗ 74 | Y 75 | ↗ 76 | T 77 | 78 | */ 79 | var nodeA = &node{name: 'A'} 80 | var nodeB = &node{name: 'B'} 81 | var nodeT = &node{name: 'T'} 82 | var nodeX = &nodeAdder{name: 'X'} 83 | var nodeY = &nodeAdder{name: 'Y'} 84 | var nodeZ = &nodeMultiplier{name: 'Z'} 85 | var zLive = false 86 | 87 | func main() { 88 | nodeX.AddSources(nodeA, nodeB) 89 | nodeY.AddSources(nodeB, nodeT) 90 | nodeZ.AddSources(nodeX, nodeY) 91 | 92 | user := make(chan byte) 93 | go func() { 94 | b := make([]byte, 1024) 95 | for { 96 | os.Stdin.Read(b) 97 | user <- b[0] 98 | } 99 | }() 100 | 101 | tick := time.Tick(10 * time.Second) 102 | 103 | for { 104 | select { 105 | case c := <-user: 106 | switch c { 107 | case 'a': 108 | nodeA.Value++ 109 | ExternallyUpdated(&nodeA.DepNode2Manual) 110 | fmt.Println("User Updated", nodeA) // Debug 111 | case 'b': 112 | nodeB.Value++ 113 | ExternallyUpdated(&nodeB.DepNode2Manual) 114 | fmt.Println("User Updated", nodeB) // Debug 115 | case 'z': 116 | zLive = !zLive 117 | fmt.Println("Zlive changed to", zLive) // Debug 118 | } 119 | case <-tick: 120 | nodeT.Value++ 121 | ExternallyUpdated(&nodeT.DepNode2Manual) 122 | fmt.Println("Timer Updated", nodeT) // Debug 123 | default: 124 | } 125 | 126 | if zLive { 127 | MakeUpdated(nodeZ) 128 | } 129 | 130 | time.Sleep(5 * time.Millisecond) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /pkg/gist7802150/play_1.go: -------------------------------------------------------------------------------- 1 | // +build ignore 2 | 3 | package main 4 | 5 | import ( 6 | . "github.com/shurcooL/go/gists/gist7802150" 7 | 8 | "fmt" 9 | 10 | "github.com/shurcooL/go/gists/gist6418290" 11 | ) 12 | 13 | type file struct { 14 | ViewGroup 15 | } 16 | 17 | func (*file) SetSelf(s string) { 18 | fmt.Println(gist6418290.GetParentFuncAsString()) 19 | } 20 | 21 | type websocket struct { 22 | ViewGroup 23 | } 24 | 25 | func (*websocket) SetSelf(s string) { 26 | fmt.Println(gist6418290.GetParentFuncAsString()) 27 | } 28 | 29 | type memory struct { 30 | ViewGroup 31 | } 32 | 33 | func (*memory) SetSelf(s string) { 34 | fmt.Println(gist6418290.GetParentFuncAsString()) 35 | } 36 | 37 | func main() { 38 | f := file{} 39 | f.InitViewGroup(&f, "memory://???") 40 | w := websocket{} 41 | w.InitViewGroup(&w, "memory://???") 42 | m := memory{} 43 | m.InitViewGroup(&m, "memory://???") 44 | 45 | f.AddAndSetViewGroup(&w, "") 46 | f.AddAndSetViewGroup(&m, "") 47 | 48 | fmt.Println("---") 49 | 50 | SetViewGroupOther(&m, "hey") 51 | 52 | fmt.Println("---") 53 | 54 | SetViewGroupOther(&w, "hey from websocket") 55 | 56 | fmt.Println("---") 57 | 58 | m.RemoveView(&w) 59 | 60 | SetViewGroupOther(&m, "hey 2") 61 | } 62 | -------------------------------------------------------------------------------- /pkg/gist8018045/main.go: -------------------------------------------------------------------------------- 1 | // Package gist8018045 provides funcs to get a list of all local Go packages in GOPATH workspaces and GOROOT. 2 | package gist8018045 3 | 4 | import ( 5 | "go/build" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | 12 | "github.com/shurcooL-legacy/Conception-go/pkg/gist5504644" 13 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7480523" 14 | ) 15 | 16 | // GetGoPackages gets all local Go packages (from GOROOT and all GOPATH workspaces). 17 | func GetGoPackages(out chan<- *gist7480523.GoPackage) { 18 | for _, root := range build.Default.SrcDirs() { 19 | _ = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 20 | if err != nil { 21 | log.Printf("can't stat file %s: %v\n", path, err) 22 | return nil 23 | } 24 | switch { 25 | case !fi.IsDir(): 26 | return nil 27 | case path == root: 28 | return nil 29 | case strings.HasPrefix(fi.Name(), ".") || strings.HasPrefix(fi.Name(), "_") || fi.Name() == "testdata": 30 | return filepath.SkipDir 31 | default: 32 | importPath, err := filepath.Rel(root, path) 33 | if err != nil { 34 | return nil 35 | } 36 | // Prune search if we encounter any of these import paths. 37 | switch importPath { 38 | case "builtin": 39 | return nil 40 | } 41 | if goPackage := gist7480523.GoPackageFromImportPath(importPath); goPackage != nil { 42 | out <- goPackage 43 | } 44 | return nil 45 | } 46 | }) 47 | } 48 | close(out) 49 | } 50 | 51 | // GetGopathGoPackages gets Go packages in all GOPATH workspaces. 52 | func GetGopathGoPackages(out chan<- *gist7480523.GoPackage) { 53 | gopathEntries := filepath.SplitList(build.Default.GOPATH) 54 | for _, gopathEntry := range gopathEntries { 55 | root := filepath.Join(gopathEntry, "src") 56 | if !isDir(root) { 57 | continue 58 | } 59 | _ = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 60 | if err != nil { 61 | log.Printf("can't stat file %s: %v\n", path, err) 62 | return nil 63 | } 64 | if !fi.IsDir() { 65 | return nil 66 | } 67 | if strings.HasPrefix(fi.Name(), ".") || strings.HasPrefix(fi.Name(), "_") || fi.Name() == "testdata" { 68 | return filepath.SkipDir 69 | } 70 | importPath, err := filepath.Rel(root, path) 71 | if err != nil { 72 | return nil 73 | } 74 | importPathFound := gist7480523.NewImportPathFound(importPath, gopathEntry) 75 | if goPackage := gist7480523.GoPackageFromImportPathFound(importPathFound); goPackage != nil { 76 | out <- goPackage 77 | } 78 | return nil 79 | }) 80 | } 81 | close(out) 82 | } 83 | 84 | func isDir(path string) bool { 85 | fi, err := os.Stat(path) 86 | return err == nil && fi.IsDir() 87 | } 88 | 89 | // rec is the recursive body of getGoPackagesA. 90 | func rec(out chan<- gist7480523.ImportPathFound, importPathFound gist7480523.ImportPathFound) { 91 | if goPackage := gist7480523.GoPackageFromImportPathFound(importPathFound); goPackage != nil { 92 | out <- importPathFound 93 | } 94 | 95 | entries, err := ioutil.ReadDir(importPathFound.FullPath()) 96 | if err == nil { 97 | for _, v := range entries { 98 | if v.IsDir() && !strings.HasPrefix(v.Name(), ".") && !strings.HasPrefix(v.Name(), "_") || v.Name() == "testdata" { 99 | rec(out, gist7480523.NewImportPathFound(filepath.Join(importPathFound.ImportPath(), v.Name()), importPathFound.GopathEntry())) 100 | } 101 | } 102 | } 103 | } 104 | 105 | func getGoPackagesA(out chan<- gist7480523.ImportPathFound) { 106 | gopathEntries := filepath.SplitList(build.Default.GOPATH) 107 | for _, gopathEntry := range gopathEntries { 108 | rec(out, gist7480523.NewImportPathFound(".", gopathEntry)) 109 | } 110 | close(out) 111 | } 112 | 113 | func getGoPackagesB(out chan<- gist7480523.ImportPathFound) { 114 | gopathEntries := filepath.SplitList(build.Default.GOPATH) 115 | for _, gopathEntry := range gopathEntries { 116 | root := filepath.Join(gopathEntry, "src") 117 | if !isDir(root) { 118 | continue 119 | } 120 | _ = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 121 | if err != nil { 122 | log.Printf("can't stat file %s: %v\n", path, err) 123 | return nil 124 | } 125 | if !fi.IsDir() { 126 | return nil 127 | } 128 | if strings.HasPrefix(fi.Name(), ".") || strings.HasPrefix(fi.Name(), "_") || fi.Name() == "testdata" { 129 | return filepath.SkipDir 130 | } 131 | importPath, err := filepath.Rel(root, path) 132 | if err != nil { 133 | return nil 134 | } 135 | importPathFound := gist7480523.NewImportPathFound(importPath, gopathEntry) 136 | if goPackage := gist7480523.GoPackageFromImportPathFound(importPathFound); goPackage != nil { 137 | out <- importPathFound 138 | } 139 | return nil 140 | }) 141 | } 142 | close(out) 143 | } 144 | 145 | func getGoPackagesC(out chan<- gist7480523.ImportPathFound) { 146 | gopathEntries := filepath.SplitList(build.Default.GOPATH) 147 | for _, gopathEntry := range gopathEntries { 148 | root := filepath.Join(gopathEntry, "src") 149 | if !isDir(root) { 150 | continue 151 | } 152 | _ = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { 153 | if err != nil { 154 | log.Printf("can't stat file %s: %v\n", path, err) 155 | return nil 156 | } 157 | if !fi.IsDir() { 158 | return nil 159 | } 160 | if strings.HasPrefix(fi.Name(), ".") || strings.HasPrefix(fi.Name(), "_") || fi.Name() == "testdata" { 161 | return filepath.SkipDir 162 | } 163 | bpkg, err := gist5504644.BuildPackageFromSrcDir(path) 164 | if err != nil { 165 | return nil 166 | } 167 | /*if bpkg.Goroot { 168 | return nil 169 | }*/ 170 | out <- gist7480523.NewImportPathFound(bpkg.ImportPath, bpkg.Root) 171 | return nil 172 | }) 173 | } 174 | close(out) 175 | } 176 | -------------------------------------------------------------------------------- /pkg/gist8018045/main_test.go: -------------------------------------------------------------------------------- 1 | package gist8018045 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7480523" 8 | ) 9 | 10 | func ExampleGetGoPackages() { 11 | started := time.Now() 12 | 13 | out := make(chan *gist7480523.GoPackage) 14 | go GetGoPackages(out) 15 | for goPackage := range out { 16 | fmt.Println(goPackage.Bpkg.ImportPath) 17 | } 18 | 19 | fmt.Println("time taken:", time.Since(started).Seconds()*1000, "ms") 20 | } 21 | -------------------------------------------------------------------------------- /pkg/httpstoppable/httpstoppable.go: -------------------------------------------------------------------------------- 1 | // Package httpstoppable provides ListenAndServe like http.ListenAndServe, 2 | // but with ability to stop it. 3 | // 4 | // Deprecated: Go 1.8 adds native support for stopping a server in net/http. 5 | // Once 1.8 is out, net/http should be used instead. This package will be 6 | // removed shortly thereafter. 7 | package httpstoppable 8 | 9 | import ( 10 | "log" 11 | "net" 12 | "net/http" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // ListenAndServe listens on the TCP network address addr 18 | // and then calls Serve with handler to handle requests 19 | // on incoming connections. 20 | // Accepted connections are configured to enable TCP keep-alives. 21 | // Handler is typically nil, in which case the http.DefaultServeMux is 22 | // used. 23 | // 24 | // When receiving from stop unblocks (because it's closed or a value is sent), 25 | // listener is closed and ListenAndServe returns with nil error. 26 | // Otherise, it always returns a non-nil error. 27 | func ListenAndServe(addr string, handler http.Handler, stop <-chan struct{}) error { 28 | srv := &http.Server{Addr: addr, Handler: handler} 29 | if addr == "" { 30 | addr = ":http" 31 | } 32 | ln, err := net.Listen("tcp", addr) 33 | if err != nil { 34 | return err 35 | } 36 | go func() { 37 | <-stop 38 | err := ln.Close() 39 | if err != nil { 40 | log.Println("httpstoppable.ListenAndServe: error closing listener:", err) 41 | } 42 | }() 43 | err = srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) 44 | switch { // Serve always returns a non-nil error. 45 | case strings.Contains(err.Error(), "use of closed network connection"): 46 | return nil 47 | default: 48 | return err 49 | } 50 | } 51 | 52 | // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 53 | // connections. It's used by ListenAndServe so dead TCP connections 54 | // (e.g. closing laptop mid-download) eventually go away. 55 | type tcpKeepAliveListener struct { 56 | *net.TCPListener 57 | } 58 | 59 | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 60 | tc, err := ln.AcceptTCP() 61 | if err != nil { 62 | return 63 | } 64 | tc.SetKeepAlive(true) 65 | tc.SetKeepAlivePeriod(3 * time.Minute) 66 | return tc, nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/legacyvcs/git.go: -------------------------------------------------------------------------------- 1 | package legacyvcs 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | "strings" 7 | 8 | "github.com/shurcooL/go/osutil" 9 | ) 10 | 11 | type gitVcs struct { 12 | commonVcs 13 | } 14 | 15 | func (this *gitVcs) Type() Type { return Git } 16 | 17 | func (this *gitVcs) GetStatus() string { 18 | cmd := exec.Command("git", "status", "--porcelain") 19 | cmd.Dir = this.rootPath 20 | 21 | if out, err := cmd.Output(); err == nil { 22 | return string(out) 23 | } else { 24 | return "" 25 | } 26 | } 27 | 28 | func (this *gitVcs) GetStash() string { 29 | cmd := exec.Command("git", "stash", "list") 30 | cmd.Dir = this.rootPath 31 | 32 | if out, err := cmd.Output(); err == nil { 33 | return string(out) 34 | } else { 35 | return "" 36 | } 37 | } 38 | 39 | func (this *gitVcs) GetRemote() string { 40 | cmd := exec.Command("git", "ls-remote", "--get-url") 41 | cmd.Dir = this.rootPath 42 | 43 | if out, err := cmd.Output(); err == nil { 44 | return strings.TrimSuffix(string(out), "\n") 45 | } else { 46 | return "" 47 | } 48 | } 49 | 50 | func (this *gitVcs) GetDefaultBranch() string { 51 | return "master" 52 | } 53 | 54 | func (this *gitVcs) GetLocalBranch() string { 55 | cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") 56 | cmd.Dir = this.rootPath 57 | 58 | if out, err := cmd.Output(); err == nil { 59 | // Since rev-parse is considered porcelain and may change, need to error-check its output. 60 | return strings.TrimSuffix(string(out), "\n") 61 | } else { 62 | return "" 63 | } 64 | } 65 | 66 | // Length of a git revision hash. 67 | const gitRevisionLength = 40 68 | 69 | func (this *gitVcs) GetLocalRev() string { 70 | cmd := exec.Command("git", "rev-parse", this.GetDefaultBranch()) 71 | cmd.Dir = this.rootPath 72 | 73 | if out, err := cmd.Output(); err == nil && len(out) >= gitRevisionLength { 74 | return string(out[:gitRevisionLength]) 75 | } else { 76 | return "" 77 | } 78 | } 79 | 80 | func (this *gitVcs) GetLocalRemoteRev() string { 81 | cmd := exec.Command("git", "rev-parse", "origin/"+this.GetDefaultBranch()) 82 | cmd.Dir = this.rootPath 83 | 84 | if out, err := cmd.Output(); err == nil && len(out) >= gitRevisionLength { 85 | return string(out[:gitRevisionLength]) 86 | } else { 87 | return "" 88 | } 89 | } 90 | 91 | func (this *gitVcs) GetRemoteRev() string { 92 | // true here is not a boolean value, but a command /bin/true that will make git think it asked for a password, 93 | // and prevent potential interactive password prompts (opting to return failure exit code instead). 94 | cmd := exec.Command("git", "-c", "core.askpass=true", "ls-remote", "--heads", "origin", this.GetDefaultBranch()) 95 | cmd.Dir = this.rootPath 96 | env := osutil.Environ(os.Environ()) 97 | env.Set("GIT_SSH_COMMAND", "ssh -o StrictHostKeyChecking=yes") // Default for StrictHostKeyChecking is "ask", which we don't want since this is non-interactive and we prefer to fail than block asking for user input. 98 | cmd.Env = env 99 | 100 | if out, err := cmd.Output(); err == nil && len(out) >= gitRevisionLength { 101 | return string(out[:gitRevisionLength]) 102 | } else { 103 | return "" 104 | } 105 | } 106 | 107 | func (this *gitVcs) IsContained(rev string) bool { 108 | cmd := exec.Command("git", "branch", "--list", "--contains", rev, this.GetDefaultBranch()) 109 | cmd.Dir = this.rootPath 110 | 111 | if out, err := cmd.Output(); err == nil { 112 | if len(out) >= 2 && strings.TrimSuffix(string(out[2:]), "\n") == this.GetDefaultBranch() { 113 | return true 114 | } 115 | } 116 | return false 117 | } 118 | 119 | // --- 120 | 121 | func getGitRepoRoot(path string) (isGitRepo bool, rootPath string) { 122 | // TODO: Consider `git rev-parse --show-cdup`? 123 | cmd := exec.Command("git", "rev-parse", "--show-toplevel") 124 | cmd.Dir = path 125 | 126 | if out, err := cmd.Output(); err == nil { 127 | // Since rev-parse is considered porcelain and may change, need to error-check its output 128 | return true, strings.TrimSuffix(string(out), "\n") 129 | } else { 130 | return false, "" 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /pkg/legacyvcs/hg.go: -------------------------------------------------------------------------------- 1 | package legacyvcs 2 | 3 | import ( 4 | "log" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | func getHgRepoRoot(path string) (isHgRepo bool, rootPath string) { 10 | cmd := exec.Command("hg", "root") 11 | cmd.Dir = path 12 | 13 | if out, err := cmd.Output(); err == nil { 14 | return true, strings.TrimSuffix(string(out), "\n") 15 | } else { 16 | return false, "" 17 | } 18 | } 19 | 20 | type hgVcs struct { 21 | commonVcs 22 | } 23 | 24 | func (this *hgVcs) Type() Type { return Hg } 25 | 26 | func (this *hgVcs) GetStatus() string { 27 | cmd := exec.Command("hg", "status") 28 | cmd.Dir = this.rootPath 29 | 30 | if out, err := cmd.Output(); err == nil { 31 | return string(out) 32 | } else { 33 | return "" 34 | } 35 | } 36 | 37 | func (this *hgVcs) GetStash() string { 38 | // TODO: Does Mercurial have stashes? Figure it out, add support, etc. 39 | return "" 40 | } 41 | 42 | func (this *hgVcs) GetRemote() string { 43 | cmd := exec.Command("hg", "paths", "default") 44 | cmd.Dir = this.rootPath 45 | 46 | if out, err := cmd.Output(); err == nil { 47 | return strings.TrimSuffix(string(out), "\n") 48 | } else { 49 | return "" 50 | } 51 | } 52 | 53 | func (this *hgVcs) GetDefaultBranch() string { 54 | return "default" 55 | } 56 | 57 | func (this *hgVcs) GetLocalBranch() string { 58 | cmd := exec.Command("hg", "branch") 59 | cmd.Dir = this.rootPath 60 | 61 | if out, err := cmd.Output(); err == nil { 62 | return strings.TrimSuffix(string(out), "\n") 63 | } else { 64 | return "" 65 | } 66 | } 67 | 68 | // Length of a Mercurial revision hash. 69 | const hgRevisionLength = 40 70 | 71 | func (this *hgVcs) GetLocalRev() string { 72 | // Alternative: hg parent --template '{node}' 73 | cmd := exec.Command("hg", "--debug", "identify", "-i", "--rev", this.GetDefaultBranch()) 74 | cmd.Dir = this.rootPath 75 | 76 | if out, err := cmd.Output(); err == nil && len(out) >= hgRevisionLength { 77 | return string(out[:hgRevisionLength]) 78 | } else { 79 | return "" 80 | } 81 | } 82 | 83 | func (this *hgVcs) GetLocalRemoteRev() string { 84 | log.Println("hgVcs.GetLocalRemoteRev: not implemented") 85 | return "" 86 | } 87 | 88 | func (this *hgVcs) GetRemoteRev() string { 89 | // TODO: Make this more robust and proper, etc. 90 | cmd := exec.Command("hg", "--debug", "identify", "-i", "--rev", this.GetDefaultBranch(), "default") 91 | cmd.Dir = this.rootPath 92 | 93 | if out, err := cmd.Output(); err == nil { 94 | // Get the last line of output. 95 | lines := strings.Split(strings.TrimSuffix(string(out), "\n"), "\n") // Always returns at least 1 element. 96 | return lines[len(lines)-1] 97 | } 98 | return "" 99 | } 100 | 101 | func (this *hgVcs) IsContained(rev string) bool { 102 | // TODO. 103 | return false 104 | } 105 | -------------------------------------------------------------------------------- /pkg/legacyvcs/vcs.go: -------------------------------------------------------------------------------- 1 | // Package vcs allows getting status of a repo under vcs. 2 | // 3 | // This package has been superseded by a better version at 4 | // github.com/shurcooL/vcsstate. That package should be used. 5 | // This legacy version is copied into Conception-go repo so that 6 | // it can continue to build without doing the work of refactoring. 7 | package legacyvcs 8 | 9 | import "os/exec" 10 | 11 | type Type uint8 12 | 13 | const ( 14 | Git Type = iota 15 | Hg 16 | ) 17 | 18 | // VcsType returns a vcsType string compatible with sourcegraph.com/sourcegraph/go-vcs notation. 19 | func (t Type) VcsType() (vcsType string) { 20 | switch t { 21 | case Git: 22 | return "git" 23 | case Hg: 24 | return "hg" 25 | default: 26 | panic("bad vcs.Type") 27 | } 28 | } 29 | 30 | type Vcs interface { 31 | RootPath() string // Returns the full path to the root of the repo. 32 | Type() Type // Returns the type of vcs implementation. 33 | 34 | GetStatus() string // Returns empty string if no outstanding status. 35 | GetStash() string // Returns empty string if no stash. 36 | 37 | GetRemote() string // Get primary remote repository url. 38 | 39 | GetDefaultBranch() string // Get default branch name for this vcs. 40 | GetLocalBranch() string // Get currently checked out local branch name. 41 | 42 | GetLocalRev() string // Get current local revision of default branch. 43 | GetLocalRemoteRev() string // Get current locally-known remote revision of default branch. 44 | GetRemoteRev() string // Get latest remote revision of default branch. 45 | 46 | // Returns true if given commit is contained in the default local branch. 47 | IsContained(rev string) bool 48 | } 49 | 50 | type commonVcs struct { 51 | rootPath string 52 | } 53 | 54 | func (this *commonVcs) RootPath() string { 55 | return this.rootPath 56 | } 57 | 58 | // New returns a new Vcs if path is under version control, otherwise nil. 59 | // It should be a valid path. 60 | func New(path string) Vcs { 61 | // TODO: Try to figure out vcs provider with a more constant-time operation. 62 | // TODO: Potentially check in parallel. 63 | for _, vcsProvider := range vcsProviders { 64 | if vcs := vcsProvider(path); vcs != nil { 65 | return vcs 66 | } 67 | } 68 | 69 | return nil 70 | } 71 | 72 | // Experimental, NewFromType returns a Vcs repository of the specified type without a local representation. 73 | // Operations that require a local repository will fail. 74 | func NewFromType(t Type) Vcs { 75 | switch t { 76 | case Git: 77 | return &gitVcs{} 78 | case Hg: 79 | return &hgVcs{} 80 | default: 81 | panic("bad vcs.Type") 82 | } 83 | } 84 | 85 | type vcsProvider func(path string) Vcs 86 | 87 | var vcsProviders []vcsProvider 88 | 89 | func addVcsProvider(s vcsProvider) { 90 | vcsProviders = append(vcsProviders, s) 91 | } 92 | 93 | func init() { 94 | // As an optimization, add Vcs providers sorted by the most likely first. 95 | 96 | // git. 97 | if _, err := exec.LookPath("git"); err == nil { 98 | addVcsProvider(func(path string) Vcs { 99 | if isRepo, rootPath := getGitRepoRoot(path); isRepo { 100 | return &gitVcs{commonVcs{rootPath: rootPath}} 101 | } 102 | return nil 103 | }) 104 | } 105 | 106 | // hg. 107 | if _, err := exec.LookPath("hg"); err == nil { 108 | addVcsProvider(func(path string) Vcs { 109 | if isRepo, rootPath := getHgRepoRoot(path); isRepo { 110 | return &hgVcs{commonVcs{rootPath: rootPath}} 111 | } 112 | return nil 113 | }) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /pkg/markdown_http/markdown.go: -------------------------------------------------------------------------------- 1 | // Package markdown_http provides an http.Handler for serving Markdown over http. 2 | package markdown_http 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/shurcooL/go/gfmutil" 10 | ) 11 | 12 | // MarkdownHandlerFunc is an http.Handler that serves rendered Markdown. 13 | type MarkdownHandlerFunc func(req *http.Request) (markdown []byte, err error) 14 | 15 | func (this MarkdownHandlerFunc) ServeHTTP(w http.ResponseWriter, req *http.Request) { 16 | if req.Method != "GET" { 17 | w.Header().Set("Allow", "GET") 18 | http.Error(w, "method should be GET", http.StatusMethodNotAllowed) 19 | return 20 | } 21 | 22 | markdown, err := this(req) 23 | if err != nil { 24 | http.Error(w, err.Error(), http.StatusInternalServerError) 25 | return 26 | } 27 | 28 | if _, plain := req.URL.Query()["plain"]; plain { 29 | w.Header().Set("Content-Type", "text/plain") 30 | w.Write(markdown) 31 | } else if _, github := req.URL.Query()["github"]; github { 32 | w.Header().Set("Content-Type", "text/html") 33 | started := time.Now() 34 | gfmutil.WriteGitHubFlavoredMarkdownViaGitHub(w, markdown) 35 | fmt.Println("rendered GFM via GitHub, took", time.Since(started)) 36 | } else { 37 | w.Header().Set("Content-Type", "text/html") 38 | started := time.Now() 39 | gfmutil.WriteGitHubFlavoredMarkdownViaLocal(w, markdown) 40 | fmt.Println("rendered GFM locally, took", time.Since(started)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/multilinecontent/multilinecontent.go: -------------------------------------------------------------------------------- 1 | package multilinecontent 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/shurcooL-legacy/Conception-go/caret" 7 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7802150" 8 | ) 9 | 10 | // New creates an empty memory-backed MultilineContent. 11 | func New() *MultilineContent { 12 | mc := &MultilineContent{} 13 | mc.InitViewGroup(mc, "memory://???") 14 | mc.updateLines() 15 | return mc 16 | } 17 | 18 | // New creates a memory-backed MultilineContent, 19 | // setting its initial value to content. 20 | func NewString(content string) *MultilineContent { 21 | mc := &MultilineContent{} 22 | mc.InitViewGroup(mc, "memory://???") 23 | gist7802150.SetViewGroup(mc, content) 24 | return mc 25 | } 26 | 27 | type MultilineContent struct { 28 | content string 29 | lines []contentLine // TODO: Can be replaced by line starts only, calculate length in Line() 30 | longestLine uint32 // Line length 31 | 32 | gist7802150.ViewGroup 33 | } 34 | 35 | func (c *MultilineContent) Content() string { return c.content } 36 | func (c *MultilineContent) LongestLine() uint32 { return c.longestLine } 37 | 38 | func (c *MultilineContent) LenContent() int { return len(c.content) } 39 | 40 | func (c *MultilineContent) Line(lineIndex int) caret.ContentLine { 41 | if lineIndex < 0 { 42 | return contentLine{0, 0} 43 | } else if lineIndex >= len(c.lines) { 44 | return contentLine{uint32(len(c.content)), 0} 45 | } else { 46 | return c.lines[lineIndex] 47 | } 48 | } 49 | func (c *MultilineContent) LenLines() int { 50 | return len(c.lines) 51 | } 52 | 53 | func (mc *MultilineContent) SetSelf(content string) { 54 | mc.content = content 55 | mc.updateLines() 56 | } 57 | 58 | func (w *MultilineContent) updateLines() { 59 | lines := strings.Split(w.content, "\n") 60 | w.lines = make([]contentLine, len(lines)) 61 | w.longestLine = 0 62 | for lineIndex, line := range lines { 63 | expandedLineLength := caret.ExpandedLength(line, 0) 64 | if expandedLineLength > w.longestLine { 65 | w.longestLine = expandedLineLength 66 | } 67 | if lineIndex >= 1 { 68 | w.lines[lineIndex].start = w.lines[lineIndex-1].End() + 1 69 | } 70 | w.lines[lineIndex].length = uint32(len(line)) 71 | } 72 | } 73 | 74 | // contentLine represents a content line. 75 | type contentLine struct { 76 | start uint32 77 | length uint32 78 | } 79 | 80 | func (this contentLine) Start() uint32 { 81 | return this.start 82 | } 83 | 84 | func (this contentLine) End() uint32 { 85 | return this.start + this.length 86 | } 87 | 88 | func (this contentLine) Length() uint32 { 89 | return this.length 90 | } 91 | -------------------------------------------------------------------------------- /pkg/multilinecontent/reverse.go: -------------------------------------------------------------------------------- 1 | package multilinecontent 2 | 3 | func NewReverse() *ReverseMultilineContent { 4 | rmc := &ReverseMultilineContent{&MultilineContent{}} 5 | rmc.InitViewGroup(rmc, "memory://???(reverse)") 6 | rmc.updateLines() 7 | return rmc 8 | } 9 | 10 | // TEST: An unfinished experiment. 11 | type ReverseMultilineContent struct { 12 | *MultilineContent 13 | } 14 | 15 | func (c *ReverseMultilineContent) Content() string { return reverse(c.content) } 16 | 17 | /*func (c *ReverseMultilineContent) SetSelf(content string) { 18 | c.content = reverse(content) 19 | c.updateLines() 20 | }*/ 21 | 22 | // reverse returns a reversed s. 23 | func reverse(s string) string { 24 | r := []rune(s) 25 | for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { 26 | r[i], r[j] = r[j], r[i] 27 | } 28 | return string(r) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/u10/main.go: -------------------------------------------------------------------------------- 1 | // Package u10 provides an http.Handler with options that serves rendered Markdown. 2 | package u10 3 | 4 | import ( 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/shurcooL/github_flavored_markdown" 11 | "github.com/shurcooL/go/gfmutil" 12 | 13 | // An experiment in making the frontend resources available. 14 | // This registers on default mux paths "/table-of-contents.js" and "/table-of-contents.css". 15 | // TODO: This is not quite done and requires figuring out a good way to solve the challenge... 16 | // Relative paths do not work at all when it's a library rather than package main. 17 | // TODO: Perhaps the strings `` and 18 | // `` should be coming 19 | // from the TOC handler package? 20 | // TODO: Perhaps I could use "/go/import/path" notation to ensure no path collisions? 21 | _ "github.com/shurcooL/frontend/table-of-contents/handler" 22 | ) 23 | 24 | type Options struct { 25 | TableOfContents bool 26 | } 27 | 28 | // MarkdownOptionsHandlerFunc is an http.Handler that serves rendered Markdown. 29 | type MarkdownOptionsHandlerFunc func(req *http.Request) (markdown []byte, opt *Options, err error) 30 | 31 | func (f MarkdownOptionsHandlerFunc) ServeHTTP(w http.ResponseWriter, req *http.Request) { 32 | if req.Method != "GET" { 33 | w.Header().Set("Allow", "GET") 34 | http.Error(w, "method should be GET", http.StatusMethodNotAllowed) 35 | return 36 | } 37 | 38 | markdown, opt, err := f(req) 39 | if err != nil { 40 | http.Error(w, err.Error(), http.StatusInternalServerError) 41 | return 42 | } 43 | 44 | if opt == nil { 45 | opt = &Options{} 46 | } 47 | 48 | if _, plain := req.URL.Query()["plain"]; plain { 49 | w.Header().Set("Content-Type", "text/plain") 50 | w.Write(markdown) 51 | } else if _, github := req.URL.Query()["github"]; github { 52 | w.Header().Set("Content-Type", "text/html") 53 | started := time.Now() 54 | switch opt.TableOfContents { 55 | case false: 56 | gfmutil.WriteGitHubFlavoredMarkdownViaGitHub(w, markdown) 57 | case true: 58 | http.Error(w, "not implemented", http.StatusInternalServerError) 59 | panic("not implemented") 60 | } 61 | fmt.Println("rendered GFM via GitHub, took", time.Since(started)) 62 | } else { 63 | w.Header().Set("Content-Type", "text/html") 64 | started := time.Now() 65 | switch opt.TableOfContents { 66 | case false: 67 | gfmutil.WriteGitHubFlavoredMarkdownViaLocal(w, markdown) 68 | case true: 69 | writeGitHubFlavoredMarkdownViaLocalWithToc(w, markdown) 70 | } 71 | fmt.Println("rendered GFM locally, took", time.Since(started)) 72 | } 73 | } 74 | 75 | // writeGitHubFlavoredMarkdownViaLocalWithToc renders GFM as a full HTML page with table of contents and writes to w. 76 | // It assumes that GFM CSS is available at /assets/gfm/gfm.css. 77 | func writeGitHubFlavoredMarkdownViaLocalWithToc(w io.Writer, markdown []byte) { 78 | io.WriteString(w, `
`) 79 | w.Write(github_flavored_markdown.Markdown(markdown)) 80 | io.WriteString(w, `
`) 81 | } 82 | -------------------------------------------------------------------------------- /pkg/u6/main.go: -------------------------------------------------------------------------------- 1 | // Package u6 implements funcs for comparing working directories and branches in vcs repositories. 2 | package u6 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "os/exec" 8 | "strings" 9 | 10 | "github.com/shurcooL-legacy/Conception-go/pkg/exp13" 11 | "github.com/shurcooL-legacy/Conception-go/pkg/gist7480523" 12 | "github.com/shurcooL-legacy/Conception-go/pkg/legacyvcs" 13 | "github.com/shurcooL/go/pipeutil" 14 | "gopkg.in/pipe.v2" 15 | ) 16 | 17 | // GoPackageWorkingDiff shows the difference between the working directory and the most recent commit. 18 | // Precondition is that goPackage.Dir.Repo is not nil, and VcsLocal is updated. 19 | func GoPackageWorkingDiff(goPackage *gist7480523.GoPackage) string { 20 | if goPackage.Dir.Repo.VcsLocal.Status == "" { 21 | return "" 22 | } 23 | 24 | switch goPackage.Dir.Repo.Vcs.Type() { 25 | case legacyvcs.Git: 26 | newFileDiff := func(line []byte) []byte { 27 | cmd := exec.Command("git", "diff", "--no-ext-diff", "--", "/dev/null", strings.TrimSuffix(string(line), "\n")) 28 | cmd.Dir = goPackage.Dir.Repo.Vcs.RootPath() 29 | out, err := cmd.Output() 30 | if len(out) > 0 { 31 | // diff exits with a non-zero status when the files don't match. 32 | // Ignore that failure as long as we get output. 33 | err = nil 34 | } 35 | if err != nil { 36 | return []byte(err.Error()) 37 | } 38 | return out 39 | } 40 | 41 | p := pipe.Script( 42 | pipe.Exec("git", "diff", "--no-ext-diff", "--find-renames", "HEAD"), 43 | pipe.Line( 44 | pipe.Exec("git", "ls-files", "--others", "--exclude-standard"), 45 | pipe.Replace(newFileDiff), 46 | ), 47 | ) 48 | 49 | out, err := pipeutil.OutputDir(p, goPackage.Dir.Repo.Vcs.RootPath()) 50 | if err != nil { 51 | return err.Error() 52 | } 53 | return string(out) 54 | default: 55 | return "" 56 | } 57 | } 58 | 59 | // GoPackageWorkingDiffMaster shows the difference between the working directory and master branch. 60 | // Precondition is that goPackage.Dir.Repo is not nil, and VcsLocal is updated. 61 | func GoPackageWorkingDiffMaster(goPackage *gist7480523.GoPackage) string { 62 | if goPackage.Dir.Repo.VcsLocal.Status == "" && goPackage.Dir.Repo.VcsLocal.LocalBranch == goPackage.Dir.Repo.Vcs.GetDefaultBranch() { 63 | return "" 64 | } 65 | 66 | switch goPackage.Dir.Repo.Vcs.Type() { 67 | case legacyvcs.Git: 68 | newFileDiff := func(line []byte) []byte { 69 | cmd := exec.Command("git", "diff", "--no-ext-diff", "--", "/dev/null", strings.TrimSuffix(string(line), "\n")) 70 | cmd.Dir = goPackage.Dir.Repo.Vcs.RootPath() 71 | out, err := cmd.Output() 72 | if len(out) > 0 { 73 | // diff exits with a non-zero status when the files don't match. 74 | // Ignore that failure as long as we get output. 75 | err = nil 76 | } 77 | if err != nil { 78 | return []byte(err.Error()) 79 | } 80 | return out 81 | } 82 | 83 | p := pipe.Script( 84 | pipe.Exec("git", "diff", "--no-ext-diff", "--find-renames", "master"), 85 | pipe.Line( 86 | pipe.Exec("git", "ls-files", "--others", "--exclude-standard"), 87 | pipe.Replace(newFileDiff), 88 | ), 89 | ) 90 | 91 | out, err := pipeutil.OutputDir(p, goPackage.Dir.Repo.Vcs.RootPath()) 92 | if err != nil { 93 | return err.Error() 94 | } 95 | return string(out) 96 | default: 97 | return "" 98 | } 99 | } 100 | 101 | // GoPackageWorkingDiffOriginMaster shows the difference between the working directory and origin/master branch. 102 | // Precondition is that goPackage.Dir.Repo is not nil, and VcsLocal is updated. 103 | func GoPackageWorkingDiffOriginMaster(goPackage *gist7480523.GoPackage) string { 104 | if goPackage.Dir.Repo.VcsLocal.Status == "" && (goPackage.Dir.Repo.VcsLocal.Remote == "" || goPackage.Dir.Repo.VcsLocal.LocalRev == goPackage.Dir.Repo.VcsLocal.LocalRemoteRev) { 105 | return "" 106 | } 107 | 108 | switch goPackage.Dir.Repo.Vcs.Type() { 109 | case legacyvcs.Git: 110 | newFileDiff := func(line []byte) []byte { 111 | cmd := exec.Command("git", "diff", "--no-ext-diff", "--", "/dev/null", strings.TrimSuffix(string(line), "\n")) 112 | cmd.Dir = goPackage.Dir.Repo.Vcs.RootPath() 113 | out, err := cmd.Output() 114 | if len(out) > 0 { 115 | // diff exits with a non-zero status when the files don't match. 116 | // Ignore that failure as long as we get output. 117 | err = nil 118 | } 119 | if err != nil { 120 | return []byte(err.Error()) 121 | } 122 | return out 123 | } 124 | 125 | p := pipe.Script( 126 | pipe.Exec("git", "diff", "--no-ext-diff", "--find-renames", "origin/master"), 127 | pipe.Line( 128 | pipe.Exec("git", "ls-files", "--others", "--exclude-standard"), 129 | pipe.Replace(newFileDiff), 130 | ), 131 | ) 132 | 133 | out, err := pipeutil.OutputDir(p, goPackage.Dir.Repo.Vcs.RootPath()) 134 | if err != nil { 135 | return err.Error() 136 | } 137 | return string(out) 138 | default: 139 | return "" 140 | } 141 | } 142 | 143 | // BranchesOptions are options for Branches. 144 | type BranchesOptions struct { 145 | Base string // Base branch to compare against (if blank, defaults to "master"). 146 | } 147 | 148 | func (bo *BranchesOptions) defaults() { 149 | if bo.Base == "" { 150 | bo.Base = "master" 151 | } 152 | } 153 | 154 | // Branches returns a Markdown table of branches with ahead/behind information relative to master branch. 155 | func Branches(repo *exp13.VcsState, opt BranchesOptions) string { 156 | opt.defaults() 157 | switch repo.Vcs.Type() { 158 | case legacyvcs.Git: 159 | branchInfo := func(line []byte) []byte { 160 | branch := strings.TrimSuffix(string(line), "\n") 161 | branchDisplay := branch 162 | if branch == repo.VcsLocal.LocalBranch { 163 | branchDisplay = "**" + branch + "**" 164 | } 165 | 166 | cmd := exec.Command("git", "rev-list", "--count", "--left-right", opt.Base+"..."+branch) 167 | cmd.Dir = repo.Vcs.RootPath() 168 | out, err := cmd.Output() 169 | if err != nil { 170 | log.Printf("error running %v: %v\n", cmd.Args, err) 171 | return []byte(fmt.Sprintf("%s | ? | ?\n", branchDisplay)) 172 | } 173 | 174 | behindAhead := strings.Split(strings.TrimSuffix(string(out), "\n"), "\t") 175 | return []byte(fmt.Sprintf("%s | %s | %s\n", branchDisplay, behindAhead[0], behindAhead[1])) 176 | } 177 | 178 | p := pipe.Script( 179 | pipe.Println("Branch | Behind | Ahead"), 180 | pipe.Println("-------|-------:|:-----"), 181 | pipe.Line( 182 | pipe.Exec("git", "for-each-ref", "--format=%(refname:short)", "refs/heads"), 183 | pipe.Replace(branchInfo), 184 | ), 185 | ) 186 | 187 | out, err := pipeutil.OutputDir(p, repo.Vcs.RootPath()) 188 | if err != nil { 189 | return err.Error() 190 | } 191 | return string(out) 192 | default: 193 | return "" 194 | } 195 | } 196 | 197 | // Input is a line containing tab-separated local branch and remote branch. 198 | // For example, "master\torigin/master". 199 | func branchRemoteInfo(repo *exp13.VcsState) func(line []byte) []byte { 200 | return func(line []byte) []byte { 201 | branchRemote := strings.Split(strings.TrimSuffix(string(line), "\n"), "\t") 202 | if len(branchRemote) != 2 { 203 | return []byte("error: len(branchRemote) != 2") 204 | } 205 | 206 | branch := branchRemote[0] 207 | branchDisplay := branch 208 | if branch == repo.VcsLocal.LocalBranch { 209 | branchDisplay = "**" + branch + "**" 210 | } 211 | 212 | remote := branchRemote[1] 213 | if remote == "" { 214 | return []byte(fmt.Sprintf("%s | | | \n", branchDisplay)) 215 | } 216 | 217 | cmd := exec.Command("git", "rev-list", "--count", "--left-right", remote+"..."+branch) 218 | cmd.Dir = repo.Vcs.RootPath() 219 | out, err := cmd.Output() 220 | if err != nil { 221 | // This usually happens when the remote branch is gone. 222 | remoteDisplay := "~~" + remote + "~~" 223 | return []byte(fmt.Sprintf("%s | %s | | \n", branchDisplay, remoteDisplay)) 224 | } 225 | 226 | behindAhead := strings.Split(strings.TrimSuffix(string(out), "\n"), "\t") 227 | return []byte(fmt.Sprintf("%s | %s | %s | %s\n", branchDisplay, remote, behindAhead[0], behindAhead[1])) 228 | } 229 | } 230 | 231 | // BranchesRemote returns a Markdown table of branches with ahead/behind information relative to remote. 232 | func BranchesRemote(repo *exp13.VcsState) string { 233 | switch repo.Vcs.Type() { 234 | case legacyvcs.Git: 235 | p := pipe.Script( 236 | pipe.Println("Branch | Remote | Behind | Ahead"), 237 | pipe.Println("-------|--------|-------:|:-----"), 238 | pipe.Line( 239 | pipe.Exec("git", "for-each-ref", "--format=%(refname:short)\t%(upstream:short)", "refs/heads"), 240 | pipe.Replace(branchRemoteInfo(repo)), 241 | ), 242 | ) 243 | 244 | out, err := pipeutil.OutputDir(p, repo.Vcs.RootPath()) 245 | if err != nil { 246 | return err.Error() 247 | } 248 | return string(out) 249 | default: 250 | return "" 251 | } 252 | } 253 | 254 | // BranchesRemoteCustom returns a Markdown table of branches with ahead/behind information relative to the specified remote. 255 | func BranchesRemoteCustom(repo *exp13.VcsState, remote string) string { 256 | switch repo.Vcs.Type() { 257 | case legacyvcs.Git: 258 | p := pipe.Script( 259 | pipe.Println("Branch | Remote | Behind | Ahead"), 260 | pipe.Println("-------|--------|-------:|:-----"), 261 | pipe.Line( 262 | pipe.Exec("git", "for-each-ref", "--format=%(refname:short)\t"+remote+"/%(refname:short)", "refs/heads"), 263 | pipe.Replace(branchRemoteInfo(repo)), 264 | ), 265 | ) 266 | 267 | out, err := pipeutil.OutputDir(p, repo.Vcs.RootPath()) 268 | if err != nil { 269 | return err.Error() 270 | } 271 | return string(out) 272 | default: 273 | return "" 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /tail-log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | tail -f "$HOME/Dropbox/Applications/Conception.app/Contents/Resources/log.txt" 4 | -------------------------------------------------------------------------------- /text_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestReverse(t *testing.T) { 9 | if got, want := reverse("Hello."), ".olleH"; got != want { 10 | t.Errorf("got:\n%v\nwant:\n%v\n", got, want) 11 | } 12 | } 13 | 14 | func Example_reverse() { 15 | fmt.Println(reverse("Hello.")) 16 | fmt.Printf("%q\n", reverse("")) 17 | fmt.Printf("%q\n", reverse("1")) 18 | fmt.Printf("%q\n", reverse("12")) 19 | fmt.Printf("%q\n", reverse("123")) 20 | fmt.Printf("%q\n", reverse("Hello, 世界")) 21 | 22 | // Output: 23 | // .olleH 24 | // "" 25 | // "1" 26 | // "21" 27 | // "321" 28 | // "界世 ,olleH" 29 | } 30 | 31 | func Example_underline() { 32 | fmt.Print(underline("Underline Test") + "\nstuff that goes here") 33 | 34 | // Output: 35 | // Underline Test 36 | // -------------- 37 | // 38 | // stuff that goes here 39 | } 40 | 41 | // reverse returns a reversed s. 42 | func reverse(s string) string { 43 | r := []rune(s) 44 | for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { 45 | r[i], r[j] = r[j], r[i] 46 | } 47 | return string(r) 48 | } 49 | --------------------------------------------------------------------------------